Skip to content

Commit

Permalink
Hide StaticImageDecoderDecoder from public API. Minor refactoring. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
colinrtwhite authored Jan 1, 2024
1 parent 8a3a5c8 commit ca0b34d
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 89 deletions.
13 changes: 0 additions & 13 deletions coil-core/api/android/coil-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -335,19 +335,6 @@ public final class coil3/decode/ResourceMetadata : coil3/decode/ImageSource$Meta
public final fun getResId ()I
}

public final class coil3/decode/StaticImageDecoderDecoder : coil3/decode/Decoder {
public fun <init> (Landroid/graphics/ImageDecoder$Source;Lcoil3/decode/ImageSource;Lcoil3/request/Options;Lkotlinx/coroutines/sync/Semaphore;)V
public synthetic fun <init> (Landroid/graphics/ImageDecoder$Source;Lcoil3/decode/ImageSource;Lcoil3/request/Options;Lkotlinx/coroutines/sync/Semaphore;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class coil3/decode/StaticImageDecoderDecoder$Factory : coil3/decode/Decoder$Factory {
public fun <init> ()V
public fun <init> (I)V
public synthetic fun <init> (IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/Decoder;
}

public abstract interface class coil3/disk/DiskCache {
public abstract fun clear ()V
public abstract fun getDirectory ()Lokio/Path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.core.graphics.decodeBitmap
import androidx.core.util.component1
import androidx.core.util.component2
import coil3.ImageLoader
import coil3.annotation.ExperimentalCoilApi
import coil3.asCoilImage
import coil3.decode.BitmapFactoryDecoder.Companion.DEFAULT_MAX_PARALLELISM
import coil3.fetch.SourceFetchResult
Expand All @@ -22,61 +21,60 @@ import coil3.util.widthPx
import kotlin.math.roundToInt
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import okio.Closeable
import okio.FileSystem

@ExperimentalCoilApi
@RequiresApi(28)
class StaticImageDecoderDecoder(
internal class StaticImageDecoderDecoder(
private val source: ImageDecoder.Source,
private val toClose: ImageSource,
private val closeable: Closeable,
private val options: Options,
private val parallelismLock: Semaphore = Semaphore(Int.MAX_VALUE),
) : Decoder {

override suspend fun decode() = parallelismLock.withPermit {
var isSampled = false
val image = run {
var imageDecoder: ImageDecoder? = null
try {
source.decodeBitmap { info, _ ->
// Capture the image decoder to manually close it later.
imageDecoder = this
var imageDecoder: ImageDecoder? = null
try {
val bitmap = source.decodeBitmap { info, _ ->
// Capture the image decoder to manually close it later.
imageDecoder = this

// Configure the output image's size.
val (srcWidth, srcHeight) = info.size
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
if (srcWidth > 0 && srcHeight > 0 &&
(srcWidth != dstWidth || srcHeight != dstHeight)) {
val multiplier = DecodeUtils.computeSizeMultiplier(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale,
)
// Configure the output image's size.
val (srcWidth, srcHeight) = info.size
val dstWidth = options.size.widthPx(options.scale) { srcWidth }
val dstHeight = options.size.heightPx(options.scale) { srcHeight }
if (srcWidth > 0 && srcHeight > 0 &&
(srcWidth != dstWidth || srcHeight != dstHeight)) {
val multiplier = DecodeUtils.computeSizeMultiplier(
srcWidth = srcWidth,
srcHeight = srcHeight,
dstWidth = dstWidth,
dstHeight = dstHeight,
scale = options.scale,
)

// Set the target size if the image is larger than the requested dimensions
// or the request requires exact dimensions.
isSampled = multiplier < 1
if (isSampled || !options.allowInexactSize) {
val targetWidth = (multiplier * srcWidth).roundToInt()
val targetHeight = (multiplier * srcHeight).roundToInt()
setTargetSize(targetWidth, targetHeight)
}
// Set the target size if the image is larger than the requested dimensions
// or the request requires exact dimensions.
isSampled = multiplier < 1
if (isSampled || !options.allowInexactSize) {
val targetWidth = (multiplier * srcWidth).roundToInt()
val targetHeight = (multiplier * srcHeight).roundToInt()
setTargetSize(targetWidth, targetHeight)
}

// Configure any other attributes.
configureImageDecoderProperties()
}
} finally {
imageDecoder?.close()
toClose.close()

// Configure any other attributes.
configureImageDecoderProperties()
}
DecodeResult(
image = bitmap.asCoilImage(),
isSampled = isSampled,
)
} finally {
imageDecoder?.close()
closeable.close()
}
DecodeResult(
image = image.asCoilImage(),
isSampled = isSampled,
)
}

private fun ImageDecoder.configureImageDecoderProperties() {
Expand Down Expand Up @@ -114,9 +112,11 @@ class StaticImageDecoderDecoder(

@RequiresApi(28)
private fun ImageSource.imageDecoderSourceOrNull(options: Options): ImageDecoder.Source? {
val file = fileOrNull()
if (file != null) {
return ImageDecoder.createSource(file.toFile())
if (fileSystem == FileSystem.SYSTEM) {
val file = fileOrNull()
if (file != null && fileSystem == FileSystem.SYSTEM) {
return ImageDecoder.createSource(file.toFile())
}
}

val metadata = metadata
Expand Down
31 changes: 9 additions & 22 deletions coil-gif/src/main/java/coil3/decode/AnimatedImageDecoderDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import okio.BufferedSource
import okio.buffer
import okio.FileSystem

/**
* A [Decoder] that uses [ImageDecoder] to decode GIFs, animated WebPs, and animated HEIFs.
Expand All @@ -54,9 +54,9 @@ class AnimatedImageDecoderDecoder @JvmOverloads constructor(
var isSampled = false
val drawable = runInterruptible {
var imageDecoder: ImageDecoder? = null
val wrappedSource = wrapImageSource(source)
val source = maybeWrapImageSourceToRewriteFrameDelay(source, enforceMinimumFrameDelay)
try {
wrappedSource.toImageDecoderSource().decodeDrawable { info, _ ->
source.toImageDecoderSource().decodeDrawable { info, _ ->
// Capture the image decoder to manually close it later.
imageDecoder = this

Expand Down Expand Up @@ -89,7 +89,7 @@ class AnimatedImageDecoderDecoder @JvmOverloads constructor(
}
} finally {
imageDecoder?.close()
wrappedSource.close()
source.close()
}
}
return DecodeResult(
Expand All @@ -98,22 +98,12 @@ class AnimatedImageDecoderDecoder @JvmOverloads constructor(
)
}

private fun wrapImageSource(source: ImageSource): ImageSource {
return if (NeedRewriteGifSource && enforceMinimumFrameDelay && DecodeUtils.isGif(source.source())) {
// Wrap the source to rewrite its frame delay as it's read.
ImageSource(
source = FrameDelayRewritingSource(source.source()).buffer(),
fileSystem = options.fileSystem,
)
} else {
source
}
}

private fun ImageSource.toImageDecoderSource(): ImageDecoder.Source {
val file = fileOrNull()
if (file != null) {
return ImageDecoder.createSource(file.toFile())
if (fileSystem == FileSystem.SYSTEM) {
val file = fileOrNull()
if (file != null && fileSystem == FileSystem.SYSTEM) {
return ImageDecoder.createSource(file.toFile())
}
}

val metadata = metadata
Expand Down Expand Up @@ -194,6 +184,3 @@ class AnimatedImageDecoderDecoder @JvmOverloads constructor(
}
}
}

// https://android.googlesource.com/platform/frameworks/base/+/2be87bb707e2c6d75f668c4aff6697b85fbf5b15
private val NeedRewriteGifSource = SDK_INT < 34
19 changes: 19 additions & 0 deletions coil-gif/src/main/java/coil3/decode/FrameDelayRewritingSource.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

package coil3.decode

import android.os.Build.VERSION.SDK_INT
import okio.Buffer
import okio.ByteString
import okio.ByteString.Companion.decodeHex
import okio.ForwardingSource
import okio.Source
import okio.buffer

/**
* A [ForwardingSource] that rewrites the GIF frame delay in every graphics control block if it's
Expand Down Expand Up @@ -86,3 +88,20 @@ internal class FrameDelayRewritingSource(delegate: Source) : ForwardingSource(de
private const val DEFAULT_FRAME_DELAY = 10
}
}

internal fun maybeWrapImageSourceToRewriteFrameDelay(
source: ImageSource,
enforceMinimumFrameDelay: Boolean,
): ImageSource {
// https://android.googlesource.com/platform/frameworks/base/+/2be87bb707e2c6d75f668c4aff6697b85fbf5b15
if (enforceMinimumFrameDelay && SDK_INT < 34 && DecodeUtils.isGif(source.source())) {
// Wrap the source to rewrite its frame delay as it's read.
return ImageSource(
source = FrameDelayRewritingSource(source.source()).buffer(),
fileSystem = source.fileSystem,
// Intentionally don't copy any metadata.
)
} else {
return source
}
}
15 changes: 5 additions & 10 deletions coil-gif/src/main/java/coil3/decode/GifDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import coil3.request.repeatCount
import coil3.util.animatable2CompatCallbackOf
import coil3.util.isHardware
import kotlinx.coroutines.runInterruptible
import okio.buffer

/**
* A [Decoder] that uses [Movie] to decode GIFs.
Expand All @@ -31,16 +30,12 @@ import okio.buffer
class GifDecoder @JvmOverloads constructor(
private val source: ImageSource,
private val options: Options,
private val enforceMinimumFrameDelay: Boolean = true
private val enforceMinimumFrameDelay: Boolean = true,
) : Decoder {

override suspend fun decode() = runInterruptible {
val bufferedSource = if (enforceMinimumFrameDelay) {
FrameDelayRewritingSource(source.source()).buffer()
} else {
source.source()
}
val movie: Movie? = bufferedSource.use { Movie.decodeStream(it.inputStream()) }
val source = maybeWrapImageSourceToRewriteFrameDelay(source, enforceMinimumFrameDelay)
val movie: Movie? = source.use { Movie.decodeStream(it.source().inputStream()) }

check(movie != null && movie.width() > 0 && movie.height() > 0) { "Failed to decode GIF." }

Expand All @@ -51,7 +46,7 @@ class GifDecoder @JvmOverloads constructor(
options.bitmapConfig.isHardware -> Bitmap.Config.ARGB_8888
else -> options.bitmapConfig
},
scale = options.scale
scale = options.scale,
)

drawable.setRepeatCount(options.repeatCount)
Expand All @@ -68,7 +63,7 @@ class GifDecoder @JvmOverloads constructor(

DecodeResult(
image = drawable.asCoilImage(),
isSampled = false
isSampled = false,
)
}

Expand Down

0 comments on commit ca0b34d

Please sign in to comment.