coil
coil copied to clipboard
Use software bitmap if target size exceeds max texture size
Is your feature request related to a problem? Please describe. It may fail if the bitmap's dimension exceeds the maximum texture size when creating a hardware bitmap. https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/hwui/jni/ImageDecoder.cpp;l=492;drc=fff1a293e380ed87258f741475574eb37f77fe4f
Describe the solution you'd like Use software bitmap instead if the target size exceeds the maximum texture size, the limit could be an option or automatically calculated.
Do you know if we can get the maximum texture size? I think it might be a hidden implementation detail.
I believe it can be retrieved from getMaximumBitmapHeight() on a hardware canvas or EGL configurations. I think it's fine to leave it to the user.
This will be fixed in the next alpha release via https://github.com/coil-kt/coil/pull/2398. Coil now places a global cap on an allocated bitmap's size (default to 4096x4096). In practice this prevents allocating a hardware bitmap larger than the max size for all modern devices.
I opted to use a fixed max size value as it's more predictable and avoids an expensive EGL configuration lookup.
Thanks, but would it be possible to have a separate maxHardwareBitmapSize config?
In our case, some images (e.g. long screenshots) should not be scaled, and we want to use software bitmaps (which generally support way larger size than hardware ones) for them.
I think I'd want to avoid adding a separate maxHardwareBitmapSize API since it's bit niche. That said, this should be easy to enforce with a custom Interceptor. You can do something like:
// Set `ImageLoader.Builder.maxBitmapSize` to `Size.ORIGINAL` to disable global enforcement.
class HardwareBitmapSizeInterceptor : Interceptor {
fun intercept(chain: Interceptor.Chain): ImageResult {
if (chain.request.bitmapConfig == Bitmap.Config.HARDWARE) {
val newRequest = chain.request.newBuilder()
.maxBitmapSize(Size(4096, 4096))
.build()
return chain.withRequest(newRequest).proceed()
} else {
return chain.proceed()
}
}
}
Won't that scale the images? That will make long screenshots unreadable.
We do have a custom Interceptor like this as a workaround now, but it takes additional allocations.
if (maxOf(bitmap.width, bitmap.height) <= request.maxHardwareBitmapSize) {
bitmap.copy(Bitmap.Config.HARDWARE, false)?.let { hardwareBitmap ->
return result.copy(
image = hardwareBitmap.asImage(),
request = request,
dataSource = result.dataSource,
)
}
}
If maxHardwareBitmapSize is considered niche, would something like a callback that allows changing the bitmap config after the output image's size has been calculated be better?
I think that will fullfill more generic use cases.
@Foolbar Ah gotcha - sorry you're right maxBitmapSize fulfill a different use case. maxBitmapSize guards against allocating a bitmap that could OOM or exceed the max hardware bitmap size (especially if using Size.ORIGINAL) - to guard against cases like this.
For automatically swapping to a software bitmap config when a very large image is loaded, have you considered using something like Telephoto instead? Coil isn't really built to load very large media since decoding very large software bitmaps on lower end devices could still OOM. Telephoto will handle viewing large images much more efficiently.
We don't worry about OOM much as our users are mainly Android 8+. We haven't tested Telephoto yet, but back in the days when we were using SSIV (which I believe is what Telephoto inspired from and they use more or less the same mechanism under the hood), we encountered some performance issues, like https://github.com/davemorrissey/subsampling-scale-image-view/issues/366, when tiling was enabled.
This will be fixed in the next alpha release via #2398. Coil now places a global cap on an allocated bitmap's size (default to 4096x4096). In practice this prevents allocating a hardware bitmap larger than the max size for all modern devices.
I opted to use a fixed max size value as it's more predictable and avoids an expensive EGL configuration lookup.
Colin, do you intend to back port this feature to 2.x?