diff --git a/zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/subsampling/SubsamplingState.kt b/zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/subsampling/SubsamplingState.kt index 81060aa56..be677e44a 100644 --- a/zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/subsampling/SubsamplingState.kt +++ b/zoomimage-compose/src/commonMain/kotlin/com/github/panpf/zoomimage/compose/subsampling/SubsamplingState.kt @@ -49,6 +49,7 @@ import com.github.panpf.zoomimage.subsampling.internal.TileDecoder import com.github.panpf.zoomimage.subsampling.internal.TileManager import com.github.panpf.zoomimage.subsampling.internal.TileManager.Companion.DefaultPausedContinuousTransformTypes import com.github.panpf.zoomimage.subsampling.internal.calculatePreferredTileSize +import com.github.panpf.zoomimage.subsampling.internal.checkNewPreferredTileSize import com.github.panpf.zoomimage.subsampling.internal.decodeAndCreateTileDecoder import com.github.panpf.zoomimage.subsampling.internal.toIntroString import com.github.panpf.zoomimage.util.IntSizeCompat @@ -64,7 +65,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlin.math.abs import kotlin.math.roundToInt @@ -289,25 +289,21 @@ class SubsamplingState( // Size animations cause frequent changes in containerSize, so a delayed reset avoids this problem @Suppress("OPT_IN_USAGE") snapshotFlow { zoomableState.containerSize }.debounce(80).collect { + val oldPreferredTileSize = this@SubsamplingState.preferredTileSize.toCompat() val newPreferredTileSize = calculatePreferredTileSize(it.toCompat()) - val finalPreferredTileSize = if (preferredTileSize.isEmpty()) { - newPreferredTileSize.toPlatform() - } else if (abs(newPreferredTileSize.width - preferredTileSize.width) >= - // When the width changes by more than 1x, the preferredTileSize is recalculated to reduce the need to reset the TileManager - preferredTileSize.width * (if (newPreferredTileSize.width > preferredTileSize.width) 1f else 0.5f) - ) { - newPreferredTileSize.toPlatform() - } else if (abs(newPreferredTileSize.height - preferredTileSize.height) >= - preferredTileSize.height * (if (newPreferredTileSize.height > preferredTileSize.height) 1f else 0.5f) - ) { - // When the height changes by more than 1x, the preferredTileSize is recalculated to reduce the need to reset the TileManager - newPreferredTileSize.toPlatform() - } else { - null + val checkPassed = checkNewPreferredTileSize( + oldPreferredTileSize = oldPreferredTileSize, + newPreferredTileSize = newPreferredTileSize + ) + logger.d { + "SubsamplingState. reset preferredTileSize. " + + "oldPreferredTileSize=$oldPreferredTileSize, " + + "newPreferredTileSize=$newPreferredTileSize, " + + "checkPassed=$checkPassed. " + + "'${imageKey}'" } - logger.d { "SubsamplingState. reset preferredTileSize. finalPreferredTileSize=$finalPreferredTileSize. '${imageKey}" } - if (finalPreferredTileSize != null) { - preferredTileSize = finalPreferredTileSize + if (checkPassed) { + this@SubsamplingState.preferredTileSize = newPreferredTileSize.toPlatform() } } } @@ -390,7 +386,6 @@ class SubsamplingState( } private fun resetTileDecoder(caller: String) { - // TODO Unexpectedly executed twice in a row cleanTileManager("resetTileDecoder:$caller") cleanTileDecoder("resetTileDecoder:$caller") diff --git a/zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/internal/subsamplings.kt b/zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/internal/subsamplings.kt index 9d5ba7e7c..017378b2e 100644 --- a/zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/internal/subsamplings.kt +++ b/zoomimage-core/src/commonMain/kotlin/com/github/panpf/zoomimage/subsampling/internal/subsamplings.kt @@ -109,4 +109,39 @@ fun List.toIntroString(): String { */ fun calculatePreferredTileSize(containerSize: IntSizeCompat): IntSizeCompat { return containerSize / 2 +} + +/** + * Returns true if the new preferred tile size is doubled in width or height or reduced by half, which can significantly reduce the number of times the TileManager is reset when the container size changes frequently (window resizing) + * + * @see com.github.panpf.zoomimage.core.common.test.subsampling.internal.SubsamplingsCommonTest.testCheckNewPreferredTileSize + */ +fun checkNewPreferredTileSize( + oldPreferredTileSize: IntSizeCompat, + newPreferredTileSize: IntSizeCompat +): Boolean { + if (newPreferredTileSize.isEmpty()) { + return false + } + if (oldPreferredTileSize.isEmpty()) { + return true + } + + val widthDifferent = abs(newPreferredTileSize.width - oldPreferredTileSize.width) + val widthTargetMultiple = + if (newPreferredTileSize.width > oldPreferredTileSize.width) 1f else 0.5f + val widthTarget = oldPreferredTileSize.width * widthTargetMultiple + if (widthDifferent >= widthTarget) { + return true + } + + val heightDifferent = abs(newPreferredTileSize.height - oldPreferredTileSize.height) + val heightTargetMultiple = + if (newPreferredTileSize.height > oldPreferredTileSize.height) 1f else 0.5f + val heightTarget = oldPreferredTileSize.height * heightTargetMultiple + if (heightDifferent >= heightTarget) { + return true + } + + return false } \ No newline at end of file diff --git a/zoomimage-core/src/commonTest/kotlin/com/github/panpf/zoomimage/core/common/test/subsampling/internal/SubsamplingsCommonTest.kt b/zoomimage-core/src/commonTest/kotlin/com/github/panpf/zoomimage/core/common/test/subsampling/internal/SubsamplingsCommonTest.kt index 8ad7a898e..49aa376e0 100644 --- a/zoomimage-core/src/commonTest/kotlin/com/github/panpf/zoomimage/core/common/test/subsampling/internal/SubsamplingsCommonTest.kt +++ b/zoomimage-core/src/commonTest/kotlin/com/github/panpf/zoomimage/core/common/test/subsampling/internal/SubsamplingsCommonTest.kt @@ -3,6 +3,7 @@ package com.github.panpf.zoomimage.core.common.test.subsampling.internal import com.github.panpf.zoomimage.subsampling.internal.calculatePreferredTileSize import com.github.panpf.zoomimage.subsampling.internal.calculateTileGridMap import com.github.panpf.zoomimage.subsampling.internal.canUseSubsamplingByAspectRatio +import com.github.panpf.zoomimage.subsampling.internal.checkNewPreferredTileSize import com.github.panpf.zoomimage.subsampling.internal.toIntroString import com.github.panpf.zoomimage.util.IntSizeCompat import com.github.panpf.zoomimage.util.ScaleFactorCompat @@ -116,4 +117,179 @@ class SubsamplingsCommonTest { /* actual = */ calculatePreferredTileSize(IntSizeCompat(1000, 2000)) ) } + + @Test + fun testCheckNewPreferredTileSize() { + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(200, 100) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(100, 200) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(200, 200) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(50, 100) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(100, 50) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(50, 50) + ) + ) + + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(0, 0) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(0, 0) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(0, 0) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(0, 0) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(0, 0) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(0, 0) + ) + ) + + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(199, 100) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(100, 199) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(199, 199) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(51, 100) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(100, 51) + ) + ) + assertEquals( + expected = false, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(100, 100), + newPreferredTileSize = IntSizeCompat(51, 51) + ) + ) + + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(0, 0), + newPreferredTileSize = IntSizeCompat(199, 100) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(0, 0), + newPreferredTileSize = IntSizeCompat(100, 199) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(0, 0), + newPreferredTileSize = IntSizeCompat(199, 199) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(0, 0), + newPreferredTileSize = IntSizeCompat(51, 100) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(0, 0), + newPreferredTileSize = IntSizeCompat(100, 51) + ) + ) + assertEquals( + expected = true, + checkNewPreferredTileSize( + oldPreferredTileSize = IntSizeCompat(0, 0), + newPreferredTileSize = IntSizeCompat(51, 51) + ) + ) + } } \ No newline at end of file diff --git a/zoomimage-view/src/main/kotlin/com/github/panpf/zoomimage/view/subsampling/SubsamplingEngine.kt b/zoomimage-view/src/main/kotlin/com/github/panpf/zoomimage/view/subsampling/SubsamplingEngine.kt index 1f7a8c191..9e0b05af3 100644 --- a/zoomimage-view/src/main/kotlin/com/github/panpf/zoomimage/view/subsampling/SubsamplingEngine.kt +++ b/zoomimage-view/src/main/kotlin/com/github/panpf/zoomimage/view/subsampling/SubsamplingEngine.kt @@ -32,6 +32,7 @@ import com.github.panpf.zoomimage.subsampling.internal.TileDecoder import com.github.panpf.zoomimage.subsampling.internal.TileManager import com.github.panpf.zoomimage.subsampling.internal.TileManager.Companion.DefaultPausedContinuousTransformTypes import com.github.panpf.zoomimage.subsampling.internal.calculatePreferredTileSize +import com.github.panpf.zoomimage.subsampling.internal.checkNewPreferredTileSize import com.github.panpf.zoomimage.subsampling.internal.decodeAndCreateTileDecoder import com.github.panpf.zoomimage.subsampling.internal.toIntroString import com.github.panpf.zoomimage.util.IntOffsetCompat @@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlin.math.abs import kotlin.math.roundToInt /** @@ -280,26 +280,21 @@ class SubsamplingEngine(val zoomableEngine: ZoomableEngine) { // View size animations cause frequent changes in viewSize, so a delayed reset avoids this problem @Suppress("OPT_IN_USAGE") zoomableEngine.containerSizeState.debounce(80).collect { + val oldPreferredTileSize = preferredTileSizeState.value val newPreferredTileSize = calculatePreferredTileSize(it) - val preferredTileSize = preferredTileSizeState.value - val finalPreferredTileSize = if (preferredTileSize.isEmpty()) { - newPreferredTileSize - } else if (abs(newPreferredTileSize.width - preferredTileSize.width) >= - // When the width changes by more than 1x, the preferredTileSize is recalculated to reduce the need to reset the TileManager - preferredTileSizeState.value.width * (if (newPreferredTileSize.width > preferredTileSize.width) 1f else 0.5f) - ) { - newPreferredTileSize - } else if (abs(newPreferredTileSize.height - preferredTileSize.height) >= - preferredTileSize.height * (if (newPreferredTileSize.height > preferredTileSize.height) 1f else 0.5f) - ) { - // When the height changes by more than 1x, the preferredTileSize is recalculated to reduce the need to reset the TileManager - newPreferredTileSize - } else { - null + val checkPassed = checkNewPreferredTileSize( + oldPreferredTileSize = oldPreferredTileSize, + newPreferredTileSize = newPreferredTileSize + ) + logger.d { + "SubsamplingEngine. reset preferredTileSize. " + + "oldPreferredTileSize=$oldPreferredTileSize, " + + "newPreferredTileSize=$newPreferredTileSize, " + + "checkPassed=$checkPassed. " + + "'${imageKey}'" } - logger.d { "SubsamplingEngine. reset preferredTileSize. finalPreferredTileSize=$finalPreferredTileSize. '${imageKey}" } - if (finalPreferredTileSize != null) { - preferredTileSizeState.value = finalPreferredTileSize + if (checkPassed) { + preferredTileSizeState.value = newPreferredTileSize } } }