Round-trip bitmap to PNG and back before requesting preview
Quantizers require the same input in order to generate the same output,
and we need the quantizer to generate the same output to guarantee the
same colors are showed in wallpaper picker.
A previous set of CLs helped convert the WallpaperPicker and SysUI
pipeline to guarantee the same quantizer input - i.e. the same bitmap,
mostly by converting and caching in a lossless image format, PNG, on
both sides of the pipeline.
However, applying 260 wallpapers and noting which ones generated a
different bitmap for preview, as opposed to cached in framework, showed
that bitmaps with ICC profiles (read: Bitmaps in a more exotic color
space than RGB) would generate inconsistent results when converted to
PNG and relayed to SysUI by WallpaperPicker.
Noting the root cause of the bug, WallpaperPicker must convert a Bitmap
to PNG for caching by framework, and that Bitmap will differ from the
raw Bitmap from the network/file, converting the Bitmap that will be
used for preview to PNG before using it for preview guarantees the same
framework will have the same Bitmap as WallpaperPicker when it caches
it, guaranteeing we get the same results for the same colors.
Bug: 185019924
Test: Add logs in framework + wallpaper picker with the hash of the
Bitmap as they see it, right before WallpaperColors extracts colors from
the bitmap. Apply a bunch of wallpapers, note which ones have differing
hashes in framework and WallpaperPicker. Look up the wallpapers that
differ in /googledata/.../chromecast/home. Use ImageMagick's `identify`
CLI command to look up EXIF atoms, note that all the wallpapers that
differ have EXIF atoms for an ICC profile. Apply this code, note that
the hashes now remain the same. Revert this fix, note the hashes now
differ.
Change-Id: Iaab1c5501b856395a7ec8b778eb491ce5e507c1b
diff --git a/src/com/android/wallpaper/picker/ImagePreviewFragment.java b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
index b0a5a4f..fd4465f 100755
--- a/src/com/android/wallpaper/picker/ImagePreviewFragment.java
+++ b/src/com/android/wallpaper/picker/ImagePreviewFragment.java
@@ -31,7 +31,9 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Color;
+import android.graphics.ColorSpace;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -77,6 +79,7 @@
import com.davemorrissey.labs.subscaleview.ImageSource;
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
+import java.io.ByteArrayOutputStream;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
@@ -378,6 +381,15 @@
@Override
public void onBitmapCropped(Bitmap croppedBitmap) {
boolean shouldRecycle = false;
+ ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();
+ if (croppedBitmap.compress(Bitmap.CompressFormat.PNG, 100, tmpOut)) {
+ byte[] outByteArray = tmpOut.toByteArray();
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ Bitmap decodedPng = BitmapFactory.decodeByteArray(outByteArray, 0,
+ outByteArray.length);
+ croppedBitmap = decodedPng;
+ }
if (croppedBitmap.getConfig() == Bitmap.Config.HARDWARE) {
croppedBitmap = croppedBitmap.copy(Bitmap.Config.ARGB_8888, false);
shouldRecycle = true;