diff --git a/library/src/main/java/com/bumptech/glide/load/PreferredColorSpace.java b/library/src/main/java/com/bumptech/glide/load/PreferredColorSpace.java new file mode 100644 index 0000000000..ff346ef6c9 --- /dev/null +++ b/library/src/main/java/com/bumptech/glide/load/PreferredColorSpace.java @@ -0,0 +1,36 @@ +package com.bumptech.glide.load; + +/** + * Glide's supported handling of color spaces on Android O+, defaults to {@link #SRGB}. + * + *

On Android O, Glide will always request SRGB and will ignore this option if set. A bug on + * Android O prevents P3 images from being compressed correctly and can result in color distortion. + * We may eventually work around this in Glide if sufficient demand arises, but doing so will + * require a memory intensive conversion to SRGB prior to compressing Bitmaps to Glide's disk cache. + * This work around would also work only for Glide's compression, not for any compression that a + * caller performs on a Bitmap returned by Glide. + * + *

On Android P+, Glide supports SRGB and display P3. However, if display p3 is requested, we + * will still decode to SRGB unless {@link android.graphics.BitmapFactory.Options#outColorSpace} is + * also {@link android.graphics.ColorSpace.Named#DISPLAY_P3}. Preferring P3 for SRGB images adds + * unnecessary CPU work to convert back and forth between the color spaces at decode time. + * + *

Using {@link #DISPLAY_P3} is wasteful if either the screen or the renderer do not support P3. + * Currently Glide does not attempt to detect whether or not this support is present. Do not use + * {@link #DISPLAY_P3} thinking that you're going to get higher quality by default. Only use {@link + * #DISPLAY_P3} if you're confident you understand color spaces, your application is working with a + * display that supports wide gamut and you've set the appropriate options to render wide gamut + * colors. If you've missed one or more of these steps, {@link #DISPLAY_P3} can lead to poor color + * quality and washed out looking images. When in doubt, always use {@link #SRGB}, which is Glide's + * default. + * + *

As with {@link DecodeFormat} we cannot directly set color spaces, we can only suggest to the + * framework which one we want. Setting one of these values is not a guarantee that any returned + * Bitmap will actually use the requested color space. + */ +public enum PreferredColorSpace { + /** Prefers to decode images using {@link android.graphics.ColorSpace.Named#SRGB}. */ + SRGB, + /** Prefers to decode images using {@link android.graphics.ColorSpace.Named#DISPLAY_P3}. */ + DISPLAY_P3, +} diff --git a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java index 109322257e..55e33566da 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/bitmap/Downsampler.java @@ -4,6 +4,7 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; +import android.graphics.ColorSpace; import android.os.Build; import android.util.DisplayMetrics; import android.util.Log; @@ -14,6 +15,7 @@ import com.bumptech.glide.load.ImageHeaderParserUtils; import com.bumptech.glide.load.Option; import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.PreferredColorSpace; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; @@ -48,6 +50,18 @@ public final class Downsampler { public static final Option DECODE_FORMAT = Option.memory( "com.bumptech.glide.load.resource.bitmap.Downsampler.DecodeFormat", DecodeFormat.DEFAULT); + + /** + * Sets the {@link PreferredColorSpace} that will be used along with the version of Android and + * color space of the requested image to determine the final color space used to decode the image. + * + *

Refer to {@link PreferredColorSpace} for details on how this option works and its various + * limitations. + */ + public static final Option PREFERRED_COLOR_SPACE = + Option.memory( + "com.bumptech.glide.load.resource.bitmap.Downsampler.PreferredColorSpace", + PreferredColorSpace.SRGB); /** * Indicates the {@link com.bumptech.glide.load.resource.bitmap.DownsampleStrategy} option that * will be used to calculate the sample size to use to downsample an image given the original and @@ -199,6 +213,7 @@ public Resource decode( bitmapFactoryOptions.inTempStorage = bytesForOptions; DecodeFormat decodeFormat = options.get(DECODE_FORMAT); + PreferredColorSpace preferredColorSpace = options.get(PREFERRED_COLOR_SPACE); DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION); boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS); boolean isHardwareConfigAllowed = @@ -211,6 +226,7 @@ public Resource decode( bitmapFactoryOptions, downsampleStrategy, decodeFormat, + preferredColorSpace, isHardwareConfigAllowed, requestedWidth, requestedHeight, @@ -228,6 +244,7 @@ private Bitmap decodeFromWrappedStreams( BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, DecodeFormat decodeFormat, + PreferredColorSpace preferredColorSpace, boolean isHardwareConfigAllowed, int requestedWidth, int requestedHeight, @@ -328,6 +345,18 @@ private Bitmap decodeFromWrappedStreams( setInBitmap(options, bitmapPool, expectedWidth, expectedHeight); } } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + boolean isP3Eligible = + preferredColorSpace == PreferredColorSpace.DISPLAY_P3 + && options.outColorSpace != null + && options.outColorSpace.isWideGamut(); + options.inPreferredColorSpace = + ColorSpace.get(isP3Eligible ? ColorSpace.Named.DISPLAY_P3 : ColorSpace.Named.SRGB); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB); + } + Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool); callbacks.onDecodeComplete(bitmapPool, downsampled); @@ -860,6 +889,7 @@ private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) { decodeBitmapOptions.inDensity = 0; decodeBitmapOptions.inTargetDensity = 0; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + decodeBitmapOptions.outColorSpace = null; decodeBitmapOptions.outConfig = null; } decodeBitmapOptions.outWidth = 0;