coil icon indicating copy to clipboard operation
coil copied to clipboard

Use software bitmap if target size exceeds max texture size

Open FooIbar opened this issue 1 year ago • 2 comments

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.

FooIbar avatar Apr 18 '24 11:04 FooIbar

Do you know if we can get the maximum texture size? I think it might be a hidden implementation detail.

colinrtwhite avatar Apr 22 '24 15:04 colinrtwhite

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.

FooIbar avatar Apr 22 '24 16:04 FooIbar

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.

colinrtwhite avatar Aug 01 '24 02:08 colinrtwhite

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.

FooIbar avatar Aug 01 '24 03:08 FooIbar

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()
        }
    }
}

colinrtwhite avatar Aug 01 '24 18:08 colinrtwhite

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.

FooIbar avatar Aug 02 '24 07:08 FooIbar

@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.

colinrtwhite avatar Aug 02 '24 17:08 colinrtwhite

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.

FooIbar avatar Aug 02 '24 18:08 FooIbar

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?

guuilp avatar Aug 19 '24 13:08 guuilp