Do not enlarge images on open
This commit is contained in:
1 parent
6d765ff6e4
commit
e9a0f1b9da
3 files changed
+26
-15
No files matched your search
@@ -1048,7 +1048,8 @@ dev-updater (Kotlin 2.4.x, CMP 1.11.x, JDK 21).
|
|||||||
system-bar regions** (2026-09-12). It hides the status and navigation bars
|
system-bar regions** (2026-09-12). It hides the status and navigation bars
|
||||||
independently when the fitted, zoomed or panned image reaches them, and
|
independently when the fitted, zoomed or panned image reaches them, and
|
||||||
restores either one when it does not. `100%` recenters at one bitmap pixel
|
restores either one when it does not. `100%` recenters at one bitmap pixel
|
||||||
per screen pixel; opening still fits the whole image first.
|
per screen pixel; opening only shrinks an image that needs it to fit and
|
||||||
|
never enlarges a smaller one.
|
||||||
- **All transcript text is selectable, from one `SelectionContainer`
|
- **All transcript text is selectable, from one `SelectionContainer`
|
||||||
around the whole list.** Not per row: a transcript is one body of text,
|
around the whole list.** Not per row: a transcript is one body of text,
|
||||||
so a selection has to run from a reply into the tool output under it —
|
so a selection has to run from a reply into the tool output under it —
|
||||||
|
|||||||
@@ -292,7 +292,8 @@ private fun enlargingFilter(sourceHeight: Int, drawnHeight: Int): FilterQuality
|
|||||||
*
|
*
|
||||||
* Inside a dialog rather than a screen -- see [SessionImageViewer] -- so the platform's back
|
* Inside a dialog rather than a screen -- see [SessionImageViewer] -- so the platform's back
|
||||||
* gesture returns to the transcript instead of leaving the app. It opens fitted, with the whole
|
* gesture returns to the transcript instead of leaving the app. It opens fitted, with the whole
|
||||||
* image visible; the 100% control changes to one bitmap pixel per screen pixel and recenters it.
|
* image visible without enlarging a smaller one; the 100% control changes to one bitmap pixel per
|
||||||
|
* screen pixel and recenters it.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun ZoomableImage(
|
private fun ZoomableImage(
|
||||||
@@ -328,7 +329,7 @@ private fun ZoomableImage(
|
|||||||
Image(
|
Image(
|
||||||
bitmap = image,
|
bitmap = image,
|
||||||
contentDescription = "Attached image",
|
contentDescription = "Attached image",
|
||||||
contentScale = ContentScale.Fit,
|
contentScale = ContentScale.Inside,
|
||||||
// Zoomed in, the reader is looking at pixels on purpose.
|
// Zoomed in, the reader is looking at pixels on purpose.
|
||||||
filterQuality = FilterQuality.None,
|
filterQuality = FilterQuality.None,
|
||||||
modifier =
|
modifier =
|
||||||
@@ -337,9 +338,8 @@ private fun ZoomableImage(
|
|||||||
.pointerInput(nativeScale) {
|
.pointerInput(nativeScale) {
|
||||||
detectTransformGestures { centroid, pan, zoom, _ ->
|
detectTransformGestures { centroid, pan, zoom, _ ->
|
||||||
val oldScale = scale
|
val oldScale = scale
|
||||||
val minimumScale = minOf(1f, nativeScale)
|
|
||||||
val maximumScale = maxOf(8f, nativeScale)
|
val maximumScale = maxOf(8f, nativeScale)
|
||||||
val newScale = (oldScale * zoom).coerceIn(minimumScale, maximumScale)
|
val newScale = (oldScale * zoom).coerceIn(1f, maximumScale)
|
||||||
if (newScale > 1f) {
|
if (newScale > 1f) {
|
||||||
val offset =
|
val offset =
|
||||||
zoomOffset(
|
zoomOffset(
|
||||||
@@ -414,8 +414,7 @@ internal fun viewerBars(
|
|||||||
if (imageWidth <= 0 || imageHeight <= 0 || viewportWidth <= 0 || viewportHeight <= 0) {
|
if (imageWidth <= 0 || imageHeight <= 0 || viewportWidth <= 0 || viewportHeight <= 0) {
|
||||||
return ViewerBars()
|
return ViewerBars()
|
||||||
}
|
}
|
||||||
val fittedScale =
|
val fittedScale = insideScale(imageWidth, imageHeight, viewportWidth, viewportHeight)
|
||||||
minOf(viewportWidth.toFloat() / imageWidth, viewportHeight.toFloat() / imageHeight)
|
|
||||||
val width = imageWidth * fittedScale * scale
|
val width = imageWidth * fittedScale * scale
|
||||||
val height = imageHeight * fittedScale * scale
|
val height = imageHeight * fittedScale * scale
|
||||||
val left = viewportWidth / 2f + offset.x - width / 2f
|
val left = viewportWidth / 2f + offset.x - width / 2f
|
||||||
@@ -433,7 +432,7 @@ internal fun viewerBars(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Scale relative to [ContentScale.Fit] at which bitmap and screen pixels are one-to-one. */
|
/** Scale relative to [ContentScale.Inside] at which bitmap and screen pixels are one-to-one. */
|
||||||
internal fun nativeScale(
|
internal fun nativeScale(
|
||||||
imageWidth: Int,
|
imageWidth: Int,
|
||||||
imageHeight: Int,
|
imageHeight: Int,
|
||||||
@@ -441,11 +440,22 @@ internal fun nativeScale(
|
|||||||
viewportHeight: Int,
|
viewportHeight: Int,
|
||||||
): Float {
|
): Float {
|
||||||
if (imageWidth <= 0 || imageHeight <= 0 || viewportWidth <= 0 || viewportHeight <= 0) return 1f
|
if (imageWidth <= 0 || imageHeight <= 0 || viewportWidth <= 0 || viewportHeight <= 0) return 1f
|
||||||
val fittedScale =
|
return 1f / insideScale(imageWidth, imageHeight, viewportWidth, viewportHeight)
|
||||||
minOf(viewportWidth.toFloat() / imageWidth, viewportHeight.toFloat() / imageHeight)
|
|
||||||
return 1f / fittedScale
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The downscale-only factor used by [ContentScale.Inside]. */
|
||||||
|
private fun insideScale(
|
||||||
|
imageWidth: Int,
|
||||||
|
imageHeight: Int,
|
||||||
|
viewportWidth: Int,
|
||||||
|
viewportHeight: Int,
|
||||||
|
): Float =
|
||||||
|
minOf(
|
||||||
|
1f,
|
||||||
|
viewportWidth.toFloat() / imageWidth,
|
||||||
|
viewportHeight.toFloat() / imageHeight,
|
||||||
|
)
|
||||||
|
|
||||||
/** Keeps the image point beneath [centroid] beneath the fingers as its scale changes. */
|
/** Keeps the image point beneath [centroid] beneath the fingers as its scale changes. */
|
||||||
internal fun zoomOffset(
|
internal fun zoomOffset(
|
||||||
offset: Offset,
|
offset: Offset,
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ class SessionImageTest {
|
|||||||
assertEquals(
|
assertEquals(
|
||||||
ViewerBars(status = true, navigation = true),
|
ViewerBars(status = true, navigation = true),
|
||||||
viewerBars(
|
viewerBars(
|
||||||
imageWidth = 500,
|
imageWidth = 1000,
|
||||||
imageHeight = 1000,
|
imageHeight = 2000,
|
||||||
viewportWidth = 1000,
|
viewportWidth = 1000,
|
||||||
viewportHeight = 2000,
|
viewportHeight = 2000,
|
||||||
scale = 1f,
|
scale = 1f,
|
||||||
@@ -67,9 +67,9 @@ class SessionImageTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `native scale reverses enlarging a small image`() {
|
fun `native scale leaves a small image alone`() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
0.5f,
|
1f,
|
||||||
nativeScale(
|
nativeScale(
|
||||||
imageWidth = 500,
|
imageWidth = 500,
|
||||||
imageHeight = 500,
|
imageHeight = 500,
|
||||||
|
|||||||
Reference in new issue
Block a user