You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.4 KiB
139 lines
4.4 KiB
package org.jetbrains.compose.resources |
|
|
|
import androidx.compose.runtime.Composable |
|
import androidx.compose.runtime.Immutable |
|
import androidx.compose.runtime.getValue |
|
import androidx.compose.runtime.remember |
|
import androidx.compose.ui.graphics.ImageBitmap |
|
import androidx.compose.ui.graphics.painter.BitmapPainter |
|
import androidx.compose.ui.graphics.painter.Painter |
|
import androidx.compose.ui.graphics.vector.ImageVector |
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter |
|
import androidx.compose.ui.platform.LocalDensity |
|
import androidx.compose.ui.unit.dp |
|
import kotlinx.coroutines.CoroutineStart |
|
import kotlinx.coroutines.Deferred |
|
import kotlinx.coroutines.async |
|
import kotlinx.coroutines.coroutineScope |
|
import kotlinx.coroutines.sync.Mutex |
|
import kotlinx.coroutines.sync.withLock |
|
import org.jetbrains.compose.resources.vector.toImageVector |
|
import org.jetbrains.compose.resources.vector.xmldom.Element |
|
|
|
/** |
|
* Represents an image resource. |
|
* |
|
* @param id The unique identifier of the image resource. |
|
* @param items The set of resource items associated with the image resource. |
|
*/ |
|
@Immutable |
|
class ImageResource(id: String, items: Set<ResourceItem>) : Resource(id, items) |
|
|
|
/** |
|
* Creates an [ImageResource] object with the specified path. |
|
* |
|
* @param path The path of the image resource. |
|
* @return An [ImageResource] object. |
|
*/ |
|
fun ImageResource(path: String): ImageResource = ImageResource( |
|
id = "ImageResource:$path", |
|
items = setOf(ResourceItem(emptySet(), path)) |
|
) |
|
|
|
/** |
|
* Retrieves a [Painter] using the specified image resource. |
|
* Automatically select a type of the Painter depending on the file extension. |
|
* |
|
* @param resource The image resource to be used. |
|
* @return The [Painter] loaded from the resource. |
|
*/ |
|
@ExperimentalResourceApi |
|
@Composable |
|
fun painterResource(resource: ImageResource): Painter { |
|
val filePath = remember(resource) { resource.getPathByEnvironment() } |
|
val isXml = filePath.endsWith(".xml", true) |
|
if (isXml) { |
|
return rememberVectorPainter(vectorResource(resource)) |
|
} else { |
|
return BitmapPainter(imageResource(resource)) |
|
} |
|
} |
|
|
|
private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) } |
|
|
|
/** |
|
* Retrieves an ImageBitmap using the specified image resource. |
|
* |
|
* @param resource The image resource to be used. |
|
* @return The ImageBitmap loaded from the resource. |
|
*/ |
|
@ExperimentalResourceApi |
|
@Composable |
|
fun imageResource(resource: ImageResource): ImageBitmap { |
|
val resourceReader = LocalResourceReader.current |
|
val imageBitmap by rememberState(resource, { emptyImageBitmap }) { |
|
val path = resource.getPathByEnvironment() |
|
val cached = loadImage(path, resourceReader) { |
|
ImageCache.Bitmap(it.toImageBitmap()) |
|
} as ImageCache.Bitmap |
|
cached.bitmap |
|
} |
|
return imageBitmap |
|
} |
|
|
|
private val emptyImageVector: ImageVector by lazy { |
|
ImageVector.Builder("emptyImageVector", 1.dp, 1.dp, 1f, 1f).build() |
|
} |
|
|
|
/** |
|
* Retrieves an ImageVector using the specified image resource. |
|
* |
|
* @param resource The image resource to be used. |
|
* @return The ImageVector loaded from the resource. |
|
*/ |
|
@ExperimentalResourceApi |
|
@Composable |
|
fun vectorResource(resource: ImageResource): ImageVector { |
|
val resourceReader = LocalResourceReader.current |
|
val density = LocalDensity.current |
|
val imageVector by rememberState(resource, { emptyImageVector }) { |
|
val path = resource.getPathByEnvironment() |
|
val cached = loadImage(path, resourceReader) { |
|
ImageCache.Vector(it.toXmlElement().toImageVector(density)) |
|
} as ImageCache.Vector |
|
cached.vector |
|
} |
|
return imageVector |
|
} |
|
|
|
internal expect fun ByteArray.toImageBitmap(): ImageBitmap |
|
internal expect fun ByteArray.toXmlElement(): Element |
|
|
|
private sealed interface ImageCache { |
|
class Bitmap(val bitmap: ImageBitmap) : ImageCache |
|
class Vector(val vector: ImageVector) : ImageCache |
|
} |
|
|
|
private val imageCacheMutex = Mutex() |
|
private val imageCache = mutableMapOf<String, Deferred<ImageCache>>() |
|
|
|
//@TestOnly |
|
internal fun dropImageCache() { |
|
imageCache.clear() |
|
} |
|
|
|
private suspend fun loadImage( |
|
path: String, |
|
resourceReader: ResourceReader, |
|
decode: (ByteArray) -> ImageCache |
|
): ImageCache = coroutineScope { |
|
val deferred = imageCacheMutex.withLock { |
|
imageCache.getOrPut(path) { |
|
//LAZY - to free the mutex lock as fast as possible |
|
async(start = CoroutineStart.LAZY) { |
|
decode(resourceReader.read(path)) |
|
} |
|
} |
|
} |
|
deferred.await() |
|
}
|
|
|