Retain zoom when high resolution image is loaded.
Opened this issue · 14 comments
This is a common use case in photo viewer apps: first, a low-resolution image is downloaded and displayed, followed by the original high-resolution image.
Currently, in this library, if the user zooms in on the low-resolution image, the zoom position is reset when the high-resolution image is loaded later on.
CoilZoomAsyncImage(
model = request,
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
onSuccess = {
zoomState.zoomable.contentSize = it.painter.intrinsicSize.roundToIntSize() // how about locate?
}
)
I've tried like this, it doesn't seem to work. Is there any other way?
Yes, this is a known issue.
This is because ZoomImage relies on the original size of the image to calculate the step zoom ratio, and Coil's ImageResult does not include the original size of the image, so only ZoomImage can parse it. When you generate zoom during the parsing process, the zoom will be reset after the parsing is completed due to setting the original size of the image.
In addition, currently only the sketch image loader includes the original size of the image in the result, so the SketchZoomAsyncImage component of the zoomimage-compose-sketch4 module can avoid this problem.
[!TIP]
The step zoom ratios of other similar libraries are fixed, which is the difference between ZoomImage and them
Thanks for this amazing library.
I just tried with Sketch image loader.
// Mock loading images
var imagePaths by rememberSaveable {
mutableStateOf(
// First = high-res, second = low-res
Pair<String, String?>(
"file:///android_asset/thumbnail_cropped.jpeg",
null
)
)
}
LaunchedEffect(Unit) {
delay(1000)
imagePaths = Pair(
"file:///android_asset/smallSize.jpeg",
"file:///android_asset/thumbnail.jpeg"
)
delay(2000)
imagePaths = Pair(
"file:///android_asset/fullSize.jpeg",
"file:///android_asset/smallSize.jpeg"
)
}
val imageState = rememberAsyncImageState(
options = ImageOptions {
// placeholder(MemoryCacheStateImage(imagePaths.second)) // Required to retain zoom level
}
)
SketchZoomAsyncImage(
uri = imagePaths.first,
state = imageState,
zoomState = rememberSketchZoomState(),
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
)
Here the zoom is only retained if placeholder
is used, but zoom level and pan is not correct, and causes a jump. Do I need to set other image options?
Here are the images in case you want to test
Jumping.issue.webm
Sorry, I misunderstood you.
I thought you meant to load only full-size images, double-click to zoom immediately after seeing the image, and wait for a while before the zoom is automatically reset.
If you use multiple images of different resolutions to allow users to see the image faster as in your example, and you also want to keep the zoom unchanged, I haven't considered this scenario. I think it may require multiple components to cooperate. I need to study this issue carefully before I can give you an answer.
Thanks. For your information, here's our scenario:
- First, thumbnail is loaded from local file (150x150, 1:1 aspect ratio)
- Then preview image is loaded, which is same aspect ratio of the original image but lower resolution, max 750px width
- Finally the full size original image is loaded
Downloading and saving the image in app's local cache is handled by our SDK, and we use a Flow to observe when each file is ready to show to image view. Each step takes a few seconds depending on file size.
Our problem:
- Preview image is loaded
- User zooms on it
- Full size image is loaded
- Zoom level and pan is reset.
I also tried using ThumbnailMemoryCacheStateImage, it didn't work perfectly.
Please let me know if you need more information or any support.
The existing logic of ZoomImage is that any change in the image uri, painterSize, or contentOriginSize will reset the image, because painterSize and contentOriginSize need to be involved in calculating the step zoom factor and the maximum and minimum boundary zoom factors. A simple and rough reset when the zoom factor changes can avoid many bugs.
The currently available methods are:
// 1. Make sure that the cache of "file:///android_asset/thumbnail.jpeg" is already in the memory cache
// 1.1 If you are redirected from the list page, and the list page has already loaded "file:///android_asset/thumbnail.jpeg"
// 1.2 Otherwise, you need to actively load it into the memory cache, refer to the document:https://github.com/panpf/sketch/blob/main/docs/wiki/preload.md
// 2.
SketchZoomAsyncImage(
request = ComposableImageRequest("file:///android_asset/fullSize.jpeg") {
placeholder(ThumbnailMemoryCacheStateImage("file:///android_asset/thumbnail.jpeg"))
crossfade(fadeStart = false)
},
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
)
When I set the placeholder like this, it does a complete zoom reset.
ComposableImageRequest(imagePath) {
placeholder(ThumbnailMemoryCacheStateImage(previousImagePath))
crossfade(fadeStart = false)
},
However, if I set like this inside ImageOptions, it tries to retain the zoom but not accurately works
val imageState = rememberAsyncImageState(
options = ImageOptions {
placeholder(ThumbnailMemoryCacheStateImage(previousImagePath))
}
)
Yes, it will be reset. Let me work on how to keep the zoom information as much as possible.
Hi, this issue seems to also apply when switching between images of exact same size. Is there a way to work around it in this case?
Hi, this issue seems to also apply when switching between images of exact same size. Is there a way to work around it in this case?
Yes, it will reset as long as the image is switched, the logic is like this.
Imagine that when switching images in ViewPager, although the image size is the same, it also needs to be reset
So what is your demand scenario? Why do you need to keep the zoom unchanged when switching images?
Hi, this issue seems to also apply when switching between images of exact same size. Is there a way to work around it in this case?
Yes, it will reset as long as the image is switched, the logic is like this.
Imagine that when switching images in ViewPager, although the image size is the same, it also needs to be reset
So what is your demand scenario? Why do you need to keep the zoom unchanged when switching images?
I am trying to display a metro map on wearos, and I wish to switch to an ambient version when the watch goes into ambient mode, without the zoom and panning resetting.
As a library, we should try to ensure that only common functions are added to the library. For the same size switching of pictures of the same size may only be a rare case. I have to consider more about how to handle the transition of zoom and pan information when switching to pictures of different sizes. I don’t have a particularly good solution for this part, so there has been no progress. If you have a suitable solution, we can discuss it together.
Let’s solve your problem first. You can refer to the usage of ZoomImage in BasicZoomImageSample, manually load the picture and set contentSize and setSubsamplingImage, and then only modify painter and setSubsamplingImage when switching pictures.
As a library, we should try to ensure that only common functions are added to the library. For the same size switching of pictures of the same size may only be a rare case. I have to consider more about how to handle the transition of zoom and pan information when switching to pictures of different sizes. I don’t have a particularly good solution for this part, so there has been no progress. If you have a suitable solution, we can discuss it together.
Let’s solve your problem first. You can refer to the usage of ZoomImage in BasicZoomImageSample, manually load the picture and set contentSize and setSubsamplingImage, and then only modify painter and setSubsamplingImage when switching pictures.
Is it possible to call setSubsamplingImage
without causing the image zoom and pan to reset?
Yes, you just need to make sure that the image size is consistent, or the size in ImageInfo is consistent, because contentOriginSize comes from them, and it will not be reset as long as contentOriginSize does not change