diff --git a/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt b/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt index 0146e9be..9f8d1061 100644 --- a/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt +++ b/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt @@ -55,7 +55,7 @@ import me.saket.telephoto.subsamplingimage.SubSamplingImage import me.saket.telephoto.subsamplingimage.SubSamplingImageSource import me.saket.telephoto.subsamplingimage.SubSamplingImageState import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder -import me.saket.telephoto.subsamplingimage.internal.PooledImageRegionDecoder +import me.saket.telephoto.subsamplingimage.internal.PooledAndroidImageRegionDecoder import me.saket.telephoto.subsamplingimage.rememberSubSamplingImageState import me.saket.telephoto.subsamplingimage.test.R import me.saket.telephoto.util.CiScreenshotValidator @@ -98,7 +98,7 @@ class SubSamplingImageTest { @After fun tearDown() { - PooledImageRegionDecoder.overriddenPoolCount = null + PooledAndroidImageRegionDecoder.overriddenPoolCount = null LeakAssertions.assertNoLeaks() } @@ -243,7 +243,7 @@ class SubSamplingImageTest { @Test fun draw_base_tile_to_fill_gaps_in_foreground_tiles() { // This test blocks 2 decoders indefinitely so at least 3 decoders are needed. - PooledImageRegionDecoder.overriddenPoolCount = 3 + PooledAndroidImageRegionDecoder.overriddenPoolCount = 3 // This fake image decoder will only decode the base tile. val imageSource = SubSamplingImageSource.asset("pahade.jpg") @@ -294,7 +294,7 @@ class SubSamplingImageTest { @Test fun draw_tile_under_centroid_first() { // This test only allows 1 decoder to work so at least 2 decoders are needed. - PooledImageRegionDecoder.overriddenPoolCount = 2 + PooledAndroidImageRegionDecoder.overriddenPoolCount = 2 // This fake decoder will ignore decoding of all but the first tiles. val firstNonBaseTileReceived = AtomicBoolean(false) @@ -630,7 +630,7 @@ class SubSamplingImageTest { @Test fun do_not_draw_base_tile_after_foreground_tiles_images_are_loaded() { // This test blocks 1 decoders so at least 2 decoders are needed. - PooledImageRegionDecoder.overriddenPoolCount = 2 + PooledAndroidImageRegionDecoder.overriddenPoolCount = 2 val mutexForDecodingLastTile = Mutex(locked = true) val imageSource = SubSamplingImageSource.asset("pahade.jpg") @@ -758,7 +758,7 @@ class SubSamplingImageTest { } @Test fun raw_stream_works_with_multiple_decoders() { - PooledImageRegionDecoder.overriddenPoolCount = 2 + PooledAndroidImageRegionDecoder.overriddenPoolCount = 2 rule.setContent { val zoomableState = rememberZoomableState() diff --git a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/SubSamplingImageSource.kt b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/SubSamplingImageSource.kt index 25437968..cd8fe334 100755 --- a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/SubSamplingImageSource.kt +++ b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/SubSamplingImageSource.kt @@ -12,7 +12,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.ui.graphics.ImageBitmap import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder -import me.saket.telephoto.subsamplingimage.internal.PooledAndroidImageRegionDecoderFactory +import me.saket.telephoto.subsamplingimage.internal.PooledAndroidImageRegionDecoder import okio.BufferedSource import okio.Closeable import okio.FileSystem @@ -164,7 +164,7 @@ internal data class FileImageSource( } override suspend fun decoder(): ImageRegionDecoder.Factory { - return PooledAndroidImageRegionDecoderFactory(this) { + return PooledAndroidImageRegionDecoder.Factory(this) { ParcelFileDescriptor.open(path.toFile(), ParcelFileDescriptor.MODE_READ_ONLY).use { fd -> @Suppress("DEPRECATION") BitmapRegionDecoder.newInstance(fd.fileDescriptor, /* ignored */ false) @@ -188,7 +188,7 @@ internal data class AssetImageSource( } override suspend fun decoder(): ImageRegionDecoder.Factory { - return PooledAndroidImageRegionDecoderFactory(this) { context -> + return PooledAndroidImageRegionDecoder.Factory(this) { context -> inputStream(context).use { stream -> check(stream is AssetManager.AssetInputStream) { error("BitmapRegionDecoder won't be able to optimize reading of this asset") @@ -215,7 +215,7 @@ internal data class ResourceImageSource( } override suspend fun decoder(): ImageRegionDecoder.Factory { - return PooledAndroidImageRegionDecoderFactory(this) { context -> + return PooledAndroidImageRegionDecoder.Factory(this) { context -> inputStream(context).use { stream -> @Suppress("DEPRECATION") BitmapRegionDecoder.newInstance(stream, /* ignored */ false)!! @@ -240,7 +240,7 @@ internal data class UriImageSource( } override suspend fun decoder(): ImageRegionDecoder.Factory { - return PooledAndroidImageRegionDecoderFactory(this) { context -> + return PooledAndroidImageRegionDecoder.Factory(this) { context -> inputStream(context).use { stream -> @Suppress("DEPRECATION") BitmapRegionDecoder.newInstance(stream, /* ignored */ false)!! @@ -272,7 +272,7 @@ internal data class RawImageSource( // This uses a peeking source because the image source can be decoded // by multiple decoders in parallel. A downside of this is that the // upstream source will never be consumed, which is probably okay? - return PooledAndroidImageRegionDecoderFactory(this) { context -> + return PooledAndroidImageRegionDecoder.Factory(this) { context -> peek(context).inputStream().use { stream -> @Suppress("DEPRECATION") BitmapRegionDecoder.newInstance(stream, /* ignored */ false)!! diff --git a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/ImageRegionDecoder.kt b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/ImageRegionDecoder.kt index 678b4456..356f7a57 100644 --- a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/ImageRegionDecoder.kt +++ b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/ImageRegionDecoder.kt @@ -1,7 +1,6 @@ package me.saket.telephoto.subsamplingimage.internal import android.content.Context -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.IntSize @@ -11,9 +10,10 @@ import kotlin.reflect.KClass import kotlin.reflect.cast /** - * [ImageBitmap] decoder, responsible for loading regions of an image for [SubSamplingImage]'s tiles. + * An image decoder, responsible for loading partial regions for + * [SubSamplingImage][me.saket.telephoto.subsamplingimage.SubSamplingImage]'s tiles. * - * Also see: [AndroidImageRegionDecoder] and [PooledImageRegionDecoder]. + * Also see: [AndroidImageRegionDecoder] and [PooledAndroidImageRegionDecoder]. */ interface ImageRegionDecoder { /** Raw size of the image, without any scaling applied. */ diff --git a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/PooledImageRegionDecoder.kt b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/PooledAndroidImageRegionDecoder.kt similarity index 87% rename from zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/PooledImageRegionDecoder.kt rename to zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/PooledAndroidImageRegionDecoder.kt index b55ca07a..c4a66d37 100644 --- a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/PooledImageRegionDecoder.kt +++ b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/PooledAndroidImageRegionDecoder.kt @@ -14,20 +14,12 @@ import me.saket.telephoto.subsamplingimage.SubSamplingImageSource import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder.DecodeResult import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder.FactoryParams -internal fun PooledAndroidImageRegionDecoderFactory( - imageSource: SubSamplingImageSource, - createDecoder: (context: Context) -> BitmapRegionDecoder -): ImageRegionDecoder.Factory = PooledImageRegionDecoder.Factory( - imageSource = imageSource, - delegate = AndroidImageRegionDecoder.Factory(imageSource, createDecoder), -) - /** * Maintains a pool of decoders to load multiple bitmap regions in parallel. Without this, * a single [BitmapRegionDecoder] can only be used for one region at a time because it * synchronizes its APIs internally. * */ -internal class PooledImageRegionDecoder private constructor( +internal class PooledAndroidImageRegionDecoder private constructor( override val imageSize: IntSize, private val decoders: ResourcePool, ) : ImageRegionDecoder { @@ -48,7 +40,7 @@ internal class PooledImageRegionDecoder private constructor( fun Factory( imageSource: SubSamplingImageSource, - delegate: ImageRegionDecoder.Factory + createDecoder: (context: Context) -> BitmapRegionDecoder, ) = ImageRegionDecoder.Factory { params -> val exif = ExifMetadata.read(params.context, imageSource) val params = FactoryParams( @@ -57,13 +49,18 @@ internal class PooledImageRegionDecoder private constructor( extras = params.extras.plus(ExifMetadata::class to exif), ) + val delegate = AndroidImageRegionDecoder.Factory( + imageSource = imageSource, + createDecoder = createDecoder, + ) + val decoders = buildList { add(delegate.create(params)) repeat(calculatePoolCount(params, first().imageSize) - 1) { add(delegate.create(params)) } } - PooledImageRegionDecoder( + PooledAndroidImageRegionDecoder( imageSize = decoders.first().imageSize, decoders = ResourcePool(decoders), )