Skip to content

Commit

Permalink
Add a property to detect ultra HDR content
Browse files Browse the repository at this point in the history
  • Loading branch information
saket committed Nov 19, 2024
1 parent edd9974 commit e9967f9
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.painter.ColorPainter
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.ScaleFactor
import androidx.compose.ui.platform.LocalContext
Expand Down Expand Up @@ -256,7 +255,7 @@ class SubSamplingImageTest {
val fakeRegionDecoderFactory = ImageRegionDecoder.Factory { params ->
val real = AndroidImageRegionDecoder.Factory.create(params)
object : ImageRegionDecoder by real {
override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): ImageRegionDecoder.DecodeResult {
return if (region.sampleSize == ImageSampleSize(1) && region.bounds.left == 3648) {
delay(Long.MAX_VALUE)
error("shouldn't reach here")
Expand Down Expand Up @@ -316,7 +315,7 @@ class SubSamplingImageTest {
val fakeRegionDecoderFactory = ImageRegionDecoder.Factory { params ->
val real = AndroidImageRegionDecoder.Factory.create(params)
object : ImageRegionDecoder by real {
override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): ImageRegionDecoder.DecodeResult {
val isBaseTile = region.sampleSize.size == 8
val isCentroidTile = region.sampleSize.size == 1 && region.bounds == IntRect(0, 1200, 1216, 3265)
return if (isBaseTile || (isCentroidTile && !firstNonBaseTileReceived.getAndSet(true))) {
Expand Down Expand Up @@ -574,7 +573,7 @@ class SubSamplingImageTest {
val gatedDecoderFactory = ImageRegionDecoder.Factory { params ->
val real = AndroidImageRegionDecoder.Factory.create(params)
object : ImageRegionDecoder by real {
override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): ImageRegionDecoder.DecodeResult {
return previewBitmapMutex.withLock {
real.decodeRegion(region)
}.also {
Expand Down Expand Up @@ -668,12 +667,15 @@ class SubSamplingImageTest {
val fakeRegionDecoderFactory = ImageRegionDecoder.Factory { params ->
val real = AndroidImageRegionDecoder.Factory.create(params)
object : ImageRegionDecoder by real {
override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): ImageRegionDecoder.DecodeResult {
return if (region.sampleSize == ImageSampleSize(1)) {
if (region.bounds.topLeft == IntOffset(4864, 1200)) {
mutexForDecodingLastTile.lock()
}
ColorPainter(Color.Yellow.copy(alpha = 0.5f))
ImageRegionDecoder.DecodeResult(
painter = ColorPainter(Color.Yellow.copy(alpha = 0.5f)),
hasUltraHdrContent = false,
)
} else {
real.decodeRegion(region)
}
Expand Down Expand Up @@ -728,7 +730,7 @@ class SubSamplingImageTest {
val recordingDecoderFactory = ImageRegionDecoder.Factory { params ->
val real = AndroidImageRegionDecoder.Factory.create(params)
object : ImageRegionDecoder by real {
override suspend fun decodeRegion(region: ImageRegionTile) =
override suspend fun decodeRegion(region: ImageRegionTile): ImageRegionDecoder.DecodeResult =
real.decodeRegion(region).also {
if (region.sampleSize == ImageSampleSize(1)) {
decodedRegionCount.incrementAndGet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import me.saket.telephoto.subsamplingimage.internal.maxScale
import me.saket.telephoto.subsamplingimage.internal.overlaps
import me.saket.telephoto.subsamplingimage.internal.scaledAndOffsetBy
import me.saket.telephoto.zoomable.ZoomableContentTransformation
import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder.DecodeResult as ImageDecodeResult

/** State for [SubSamplingImage]. Created using [rememberSubSamplingImageState]. */
@Stable
Expand Down Expand Up @@ -74,7 +75,7 @@ internal class RealSubSamplingImageState(
* versions, layout changes caused image flickering because tile updates were asynchronous
* and lagged by one frame.
*/
private var loadedImages: ImmutableMap<ImageRegionTile, Painter> by mutableStateOf(persistentMapOf())
private var loadedImages: ImmutableMap<ImageRegionTile, ImageDecodeResult> by mutableStateOf(persistentMapOf())

private val isReadyToBeDisplayed: Boolean by derivedStateOf {
val viewportSize = viewportSize
Expand Down Expand Up @@ -135,7 +136,7 @@ internal class RealSubSamplingImageState(
if (tile.isVisible && (!tile.isBase || canDrawBaseTile)) {
ViewportImageTile(
tile = tile,
painter = loadedImages[tile.region] ?: if (tile.isBase) imagePreview else null,
painter = loadedImages[tile.region]?.painter ?: if (tile.isBase) imagePreview else null,
)
} else null
}.toImmutableList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package me.saket.telephoto.subsamplingimage.internal
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.os.Build
import androidx.compose.ui.graphics.painter.Painter
import android.os.Build.VERSION.SDK_INT
import androidx.compose.ui.graphics.toAndroidRect
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntRect
Expand All @@ -16,6 +16,7 @@ import kotlinx.coroutines.withContext
import me.saket.telephoto.subsamplingimage.ImageBitmapOptions
import me.saket.telephoto.subsamplingimage.SubSamplingImageSource
import me.saket.telephoto.subsamplingimage.internal.ExifMetadata.ImageOrientation
import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder.DecodeResult
import me.saket.telephoto.subsamplingimage.toAndroidConfig

/** Bitmap decoder backed by Android's [BitmapRegionDecoder]. */
Expand All @@ -29,7 +30,7 @@ internal class AndroidImageRegionDecoder private constructor(

override val imageSize: IntSize get() = decoder.size()

override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): DecodeResult {
val options = BitmapFactory.Options().apply {
inSampleSize = region.sampleSize.size
inPreferredConfig = imageOptions.config.toAndroidConfig()
Expand All @@ -53,9 +54,12 @@ internal class AndroidImageRegionDecoder private constructor(
}
}
if (bitmap != null) {
return RotatedBitmapPainter(
image = bitmap,
orientation = exif.orientation,
return DecodeResult(
painter = RotatedBitmapPainter(
image = bitmap,
orientation = exif.orientation,
),
hasUltraHdrContent = if (SDK_INT >= 34) bitmap.hasGainmap() else false,
)
} else {
error("BitmapRegionDecoder returned a null bitmap. Image format may not be supported: $imageSource.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal class ImageCache(
private val cachedImages = MutableStateFlow(emptyMap<ImageRegionTile, LoadingState>())

private sealed interface LoadingState {
data class Loaded(val painter: Painter) : LoadingState
data class Loaded(val painter: ImageRegionDecoder.DecodeResult) : LoadingState
data class InFlight(val job: Job) : LoadingState
}

Expand Down Expand Up @@ -72,7 +72,7 @@ internal class ImageCache(
}
}

fun observeCachedImages(): Flow<ImmutableMap<ImageRegionTile, Painter>> {
fun observeCachedImages(): Flow<ImmutableMap<ImageRegionTile, ImageRegionDecoder.DecodeResult>> {
return cachedImages.map { map ->
buildMap(capacity = map.size) {
map.forEach { (region, state) ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.IntSize
import dev.drewhamilton.poko.Poko
import me.saket.telephoto.subsamplingimage.ImageBitmapOptions
import me.saket.telephoto.subsamplingimage.SubSamplingImageSource

Expand All @@ -17,14 +18,20 @@ import me.saket.telephoto.subsamplingimage.SubSamplingImageSource
internal interface ImageRegionDecoder {
val imageSize: IntSize

suspend fun decodeRegion(region: ImageRegionTile): Painter
suspend fun decodeRegion(region: ImageRegionTile): DecodeResult

fun close()

fun interface Factory {
suspend fun create(params: FactoryParams): ImageRegionDecoder
}

@Poko
class DecodeResult(
val painter: Painter,
val hasUltraHdrContent: Boolean,
)

class FactoryParams(
val context: Context,
val imageSource: SubSamplingImageSource,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package me.saket.telephoto.subsamplingimage.internal
import android.app.ActivityManager
import android.graphics.BitmapRegionDecoder
import androidx.annotation.VisibleForTesting
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.unit.IntSize
import androidx.core.content.getSystemService
import kotlinx.coroutines.channels.Channel
import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder.DecodeResult
import me.saket.telephoto.subsamplingimage.internal.ImageRegionDecoder.FactoryParams

/**
Expand All @@ -19,7 +19,7 @@ internal class PooledImageRegionDecoder private constructor(
private val decoders: ResourcePool<ImageRegionDecoder>,
) : ImageRegionDecoder {

override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): DecodeResult {
return decoders.borrow { decoder ->
decoder.decodeRegion(region)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ private class FakeImageRegionDecoder : ImageRegionDecoder {
val requestedRegions = MutableSharedFlow<ImageRegionTile>()
val decodedRegions = Channel<Painter>()

override suspend fun decodeRegion(region: ImageRegionTile): Painter {
override suspend fun decodeRegion(region: ImageRegionTile): ImageRegionDecoder.DecodeResult {
requestedRegions.emit(region)
return decodedRegions.receive()
return ImageRegionDecoder.DecodeResult(decodedRegions.receive(), hasUltraHdrContent = false)
}

override fun close() = Unit
Expand Down

0 comments on commit e9967f9

Please sign in to comment.