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.
94 lines
3.1 KiB
94 lines
3.1 KiB
package org.jetbrains.compose.animatedimage |
|
|
|
import androidx.compose.animation.core.* |
|
import androidx.compose.runtime.* |
|
import androidx.compose.ui.graphics.ImageBitmap |
|
import androidx.compose.ui.graphics.asComposeImageBitmap |
|
import org.jetbrains.skia.AnimationFrameInfo |
|
import org.jetbrains.skia.Bitmap |
|
import org.jetbrains.skia.Codec |
|
import java.net.MalformedURLException |
|
import java.net.URL |
|
|
|
private const val DEFAULT_FRAME_DURATION = 100 |
|
|
|
actual class AnimatedImage(val codec: Codec) |
|
|
|
actual suspend fun loadAnimatedImage(path: String): AnimatedImage { |
|
val loader = getAnimatedImageLoaderByPath(path) |
|
return loader.loadAnimatedImage() |
|
} |
|
|
|
actual suspend fun loadResourceAnimatedImage(path: String): AnimatedImage { |
|
val loader = ResourceAnimatedImageLoader(path) |
|
return loader.loadAnimatedImage() |
|
} |
|
|
|
@Composable |
|
actual fun AnimatedImage.animate(): ImageBitmap { |
|
when (codec.frameCount) { |
|
0 -> return ImageBitmap.Blank // No frames at all |
|
1 -> { |
|
// Just one frame, no animation |
|
val bitmap = remember(codec) { Bitmap().apply { allocPixels(codec.imageInfo) } } |
|
remember(bitmap) { |
|
codec.readPixels(bitmap, 0) |
|
} |
|
return bitmap.asComposeImageBitmap() |
|
} |
|
else -> { |
|
val transition = rememberInfiniteTransition() |
|
val frameIndex by transition.animateValue( |
|
initialValue = 0, |
|
targetValue = codec.frameCount - 1, |
|
Int.VectorConverter, |
|
animationSpec = infiniteRepeatable( |
|
animation = keyframes { |
|
durationMillis = 0 |
|
for ((index, frame) in codec.framesInfo.withIndex()) { |
|
index at durationMillis |
|
val frameDuration = calcFrameDuration(frame) |
|
|
|
durationMillis += frameDuration |
|
} |
|
} |
|
) |
|
) |
|
|
|
val bitmap = remember(codec) { Bitmap().apply { allocPixels(codec.imageInfo) } } |
|
|
|
remember(bitmap, frameIndex) { |
|
codec.readPixels(bitmap, frameIndex) |
|
} |
|
|
|
return bitmap.asComposeImageBitmap() |
|
} |
|
} |
|
} |
|
|
|
private fun calcFrameDuration(frame: AnimationFrameInfo): Int { |
|
// If the frame does not contain information about a duration, set a reasonable constant duration |
|
val frameDuration = frame.duration |
|
return if (frameDuration == 0) DEFAULT_FRAME_DURATION else frameDuration |
|
} |
|
|
|
/** |
|
* Depending on the [path], provide a specific implementation of [AnimatedImageLoader] |
|
* @return [NetworkAnimatedImageLoader] if it is a network URL, [LocalAnimatedImageLoader] otherwise |
|
*/ |
|
private fun getAnimatedImageLoaderByPath(path: String): AnimatedImageLoader { |
|
return if (isNetworkPath(path)) { |
|
NetworkAnimatedImageLoader(path) |
|
} else { |
|
LocalAnimatedImageLoader(path) |
|
} |
|
} |
|
|
|
private fun isNetworkPath(path: String): Boolean { |
|
return try { |
|
URL(path) |
|
true |
|
} catch (e: MalformedURLException) { |
|
false |
|
} |
|
} |