Skip to content

Commit

Permalink
Only do the Androud U gainmap workaround on bitmaps that have a gainmap
Browse files Browse the repository at this point in the history
  • Loading branch information
falhassen committed Feb 4, 2025
1 parent 9c9f56f commit aa84904
Showing 1 changed file with 141 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Gainmap;
Expand All @@ -18,6 +19,7 @@
import com.bumptech.glide.util.GlideSuppliers.GlideSupplier;
import com.bumptech.glide.util.Preconditions;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;

/**
Expand All @@ -37,6 +39,8 @@
*/
final class GlideBitmapFactory {

private static final String TAG = "GlideBitmapFactory";

private GlideBitmapFactory() {}

/** Wrapper for {@link BitmapFactory#decodeStream}. */
Expand Down Expand Up @@ -72,7 +76,10 @@ public static Bitmap decodeFileDescriptor(

/**
* Returns a decoded bitmap for the input stream, ensuring that any associated gainmap is decoded
* without errors on Android U if it is a valid gainamp.
* without being silently dropped on Android U.
*
* <p>If the input stream does not reference an image with a gainmap, then this method simply
* returns a hardware bitmap.
*
* <p>This method safely wraps BitmapFactory#decodeStream(InputStream, Rect, Options)} on Android
* U.
Expand All @@ -85,8 +92,33 @@ public static Bitmap decodeFileDescriptor(
private static Bitmap safeDecodeHardwareBitmapWithGainmap(
InputStream inputStream, Options options) {
Preconditions.checkArgument(options.inPreferredConfig == Config.HARDWARE);
Bitmap softwareBitmap = null;
// Check to make sure that the inputStream has an associated bitmap using BitmapRegionDecoder,
// which does not require the workaround.
BitmapRegionDecoder bitmapRegionDecoder = null;
try {
bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream);
if (bitmapRegionDecoder == null) {
return null;
}
boolean bitmapNeedsWorkaround = efficientHasGainmap(bitmapRegionDecoder, options);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "calculateNeedsGainmapDecodeWorkaround(inputStream)=" + bitmapNeedsWorkaround);
}
if (!bitmapNeedsWorkaround) {
return BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "safeDecodeHardwareBitmapWithGainmap(inputStream)", e);
}
return null;
} finally {
if (bitmapRegionDecoder != null) {
bitmapRegionDecoder.recycle();
}
}
options.inPreferredConfig = Config.ARGB_8888;
Bitmap softwareBitmap = null;
try {
softwareBitmap = BitmapFactory.decodeStream(inputStream, /* outPadding= */ null, options);
if (softwareBitmap == null) {
Expand All @@ -103,7 +135,10 @@ private static Bitmap safeDecodeHardwareBitmapWithGainmap(

/**
* Returns a decoded bitmap for the input byte array, ensuring that any associated gainmap is
* decoded without errors on Android U if it is a valid gainmap.
* decoded without being silently dropped on Android U.
*
* <p>If the input bytes do not reference an image with a gainmap, then this method simply returns
* a hardware bitmap.
*
* <p>This method safely wraps BitmapFactory#decodeByteArray(byte[], int, int)} on Android U.
*
Expand All @@ -117,8 +152,30 @@ private static Bitmap safeDecodeHardwareBitmapWithGainmap(
@Nullable
private static Bitmap safeDecodeHardwareBitmapWithGainmap(byte[] bytes, Options options) {
Preconditions.checkArgument(options.inPreferredConfig == Config.HARDWARE);
Bitmap softwareBitmap = null;
// Check to make sure that the inputStream has an associated bitmap using BitmapRegionDecoder,
// which does not require the workaround.
BitmapRegionDecoder bitmapRegionDecoder = null;
try {
bitmapRegionDecoder = BitmapRegionDecoder.newInstance(bytes, /* offset= */ 0, bytes.length);
boolean bitmapNeedsWorkaround = efficientHasGainmap(bitmapRegionDecoder, options);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "calculateNeedsGainmapDecodeWorkaround(bytes)=" + bitmapNeedsWorkaround);
}
if (!bitmapNeedsWorkaround) {
return BitmapFactory.decodeByteArray(bytes, /* offset= */ 0, bytes.length, options);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "safeDecodeHardwareBitmapWithGainmap(bytes)", e);
}
return null;
} finally {
if (bitmapRegionDecoder != null) {
bitmapRegionDecoder.recycle();
}
}
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap softwareBitmap = null;
try {
softwareBitmap = BitmapFactory.decodeByteArray(bytes, /* offset= */ 0, bytes.length, options);
if (softwareBitmap == null) {
Expand All @@ -135,7 +192,10 @@ private static Bitmap safeDecodeHardwareBitmapWithGainmap(byte[] bytes, Options

/**
* Returns a decoded bitmap for the input file descriptor, ensuring that any associated gainmap is
* decoded without errors on Android U if it is a valid gainmap.
* decoded without being silently dropped on Android U.
*
* <p>If the input file descriptor does not reference an image with a gainmap, then this method
* simply returns a hardware bitmap.
*
* <p>This method safely wraps {@link BitmapFactory#decodeFileDescriptor(FileDescriptor, Rect,
* Options)} on Android U.
Expand All @@ -151,8 +211,30 @@ private static Bitmap safeDecodeHardwareBitmapWithGainmap(byte[] bytes, Options
private static Bitmap safeDecodeHardwareBitmapWithGainmap(
FileDescriptor fileDescriptor, Options options) {
Preconditions.checkArgument(options.inPreferredConfig == Config.HARDWARE);
Bitmap softwareBitmap = null;
// Check to make sure that the inputStream has an associated bitmap using BitmapRegionDecoder,
// which does not require the workaround.
BitmapRegionDecoder bitmapRegionDecoder = null;
try {
bitmapRegionDecoder = BitmapRegionDecoder.newInstance(fileDescriptor, /* isShareable= */ false);
boolean bitmapNeedsWorkaround = efficientHasGainmap(bitmapRegionDecoder, options);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "calculateNeedsGainmapDecodeWorkaround(fd)=" + bitmapNeedsWorkaround);
}
if (!bitmapNeedsWorkaround) {
return BitmapFactory.decodeFileDescriptor(fileDescriptor, /* outPadding= */ null, options);
}
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "safeDecodeHardwareBitmapWithGainmap(fd)", e);
}
return null;
} finally {
if (bitmapRegionDecoder != null) {
bitmapRegionDecoder.recycle();
}
}
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap softwareBitmap = null;
try {
softwareBitmap =
BitmapFactory.decodeFileDescriptor(fileDescriptor, /* outPadding= */ null, options);
Expand All @@ -168,30 +250,65 @@ private static Bitmap safeDecodeHardwareBitmapWithGainmap(
}
}

/**
* Returns whether the bitmap associated with the input {@link BitmapRegionDecoder} has a gainmap.
*
* <p>This method is "efficient" in the sense that it only samples a small region of the image to
* make this determination.
*
* @param bitmapRegionDecoder Must be initialized and not recycled. The caller of this method is
* responsible for recycling the decoder.
*/
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
private static boolean efficientHasGainmap(BitmapRegionDecoder bitmapRegionDecoder, Options options) {
// Check to make sure that the inputStream has an associated bitmap using BitmapRegionDecoder,
// which does not require the workaround.
Bitmap onePercentBitmap = null;
try {
// We are using a 1% Rect instead of 1x1 Rect because BitmapRegionDecoder will drop some
// gainmaps if a 1x1 region is requested, whether or not the bitmap config is hardware or
// software.
Rect onePercentRect =
getOnePercentRect(bitmapRegionDecoder.getWidth(), bitmapRegionDecoder.getHeight());
onePercentBitmap = bitmapRegionDecoder.decodeRegion(onePercentRect, options);
return onePercentBitmap != null && onePercentBitmap.hasGainmap();
} finally {
if (onePercentBitmap != null) {
onePercentBitmap.recycle();
}
}
}

/**
* Returns a decoded bitmap for the input software bitmap, ensuring that any associated gainmap is
* decoded without errors on Android U if it is a valid gainmap.
*
* @param softwareBitmap The bitmap to be decoded. Must not be a hardware bitmap.
* @param softwareBitmap The bitmap to be decoded. Must not be a hardware bitmap. The caller of
* this method is responsible for recycling this bitmap.
* @throws IllegalArgumentException if {@link Options#inPreferredConfig} is set to any state other
* than {@link Config#HARDWARE}.
*/
@RequiresApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
@Nullable
private static Bitmap safeDecodeBitmapWithGainmap(Bitmap softwareBitmap) {
try {
Gainmap gainmap = softwareBitmap.getGainmap();
if (gainmap != null) {
Bitmap gainmapContents = gainmap.getGainmapContents();
if (gainmapContents.getConfig() == Config.ALPHA_8) {
softwareBitmap.setGainmap(
GainmapCopier.convertSingleChannelGainmapToTripleChannelGainmap(gainmap));
}
Gainmap gainmap = softwareBitmap.getGainmap();
if (gainmap != null) {
Bitmap gainmapContents = gainmap.getGainmapContents();
if (gainmapContents.getConfig() == Config.ALPHA_8) {
softwareBitmap.setGainmap(
GainmapCopier.convertSingleChannelGainmapToTripleChannelGainmap(gainmap));
}
return softwareBitmap.copy(Config.HARDWARE, /* isMutable= */ false);
} finally {
softwareBitmap.recycle();
}
return softwareBitmap.copy(Config.HARDWARE, /* isMutable= */ false);
}

/**
* Returns a {@link Rect} with the input width and height scaled at 1%.
*
* <p>The minimum returned size is {@code 1x1}.
*/
private static Rect getOnePercentRect(int width, int height) {
return new Rect(0, 0, Math.max((width - 1) / 100, 1), Math.max((height - 1) / 100, 1));
}

/** Utils to copy gainmaps. */
Expand All @@ -202,10 +319,10 @@ private static final class GainmapCopier {
private static final ColorMatrixColorFilter OPAQUE_FILTER =
new ColorMatrixColorFilter(
new float[] {
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 0f, 255f
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 1f, 0f,
0f, 0f, 0f, 0f, 255f
});

private GainmapCopier() {}
Expand Down Expand Up @@ -315,8 +432,8 @@ private static boolean calculateNeedsGainmapDecodeWorkaround() {
Bitmap a8HardwareBitmap = a8Source.copy(Config.HARDWARE, /* isMutable= */ false);
a8Source.recycle();
boolean needsGainmapDecodeWorkaround = a8HardwareBitmap == null;
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "calculateNeedsGainmapDecodeWorkaround=" + needsGainmapDecodeWorkaround);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "calculateNeedsGainmapDecodeWorkaround=" + needsGainmapDecodeWorkaround);
}
if (a8HardwareBitmap != null) {
a8HardwareBitmap.recycle();
Expand Down

0 comments on commit aa84904

Please sign in to comment.