From 330bc60cdc7e902107095fdf927b30a7b54c9a43 Mon Sep 17 00:00:00 2001 From: Ahan Wu Date: Thu, 25 Apr 2019 15:04:39 +0800 Subject: [PATCH] Use Otsu's thresholding get better transition effect. We can gt more appreciate threshold for the image wallpaper transition by Otsu's thresholding algorithm. In addition to Otsu's algorithm, we also limit the max threshold. Bug: 130360362 Test: Set wallpaper in "Art collection" category. Test: Press power to suspend / resume device. Test: Observe the transition, it is nice now. Test: Observe wallpaper in "Solid Color" category. Test: Observe wallpapers in any other categories. Change-Id: I2d6a4e9d19b8a8f1636bd2233e66bdba6e92909c --- .../glwallpaper/ImageProcessHelper.java | 138 +++++++++++++++--- .../glwallpaper/ImageWallpaperRenderer.java | 8 +- 2 files changed, 118 insertions(+), 28 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java index 477e7d7ebf72f..24a4b9e3052b0 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java @@ -30,13 +30,15 @@ import android.os.Message; import android.util.Log; /** - * A helper class that computes histogram and percentile 85 from a bitmap. - * Percentile 85 will be computed each time the user picks a new image wallpaper. + * A helper class that computes threshold from a bitmap. + * Threshold will be computed each time the user picks a new image wallpaper. */ class ImageProcessHelper { private static final String TAG = ImageProcessHelper.class.getSimpleName(); - private static final float DEFAULT_PER85 = 0.8f; - private static final int MSG_UPDATE_PER85 = 1; + private static final float DEFAULT_THRESHOLD = 0.8f; + private static final float DEFAULT_OTSU_THRESHOLD = 0f; + private static final float MAX_THRESHOLD = 0.89f; + private static final int MSG_UPDATE_THRESHOLD = 1; /** * This color matrix will be applied to each pixel to get luminance from rgb by below formula: @@ -53,8 +55,8 @@ class ImageProcessHelper { @Override public boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_UPDATE_PER85: - mPer85 = (float) msg.obj; + case MSG_UPDATE_THRESHOLD: + mThreshold = (float) msg.obj; return true; default: return false; @@ -62,20 +64,20 @@ class ImageProcessHelper { } }); - private float mPer85 = DEFAULT_PER85; + private float mThreshold = DEFAULT_THRESHOLD; - void startComputingPercentile85(Bitmap bitmap) { - new Per85ComputeTask(mHandler).execute(bitmap); + void start(Bitmap bitmap) { + new ThresholdComputeTask(mHandler).execute(bitmap); } - float getPercentile85() { - return mPer85; + float getThreshold() { + return Math.min(mThreshold, MAX_THRESHOLD); } - private static class Per85ComputeTask extends AsyncTask { + private static class ThresholdComputeTask extends AsyncTask { private Handler mUpdateHandler; - Per85ComputeTask(Handler handler) { + ThresholdComputeTask(Handler handler) { super(handler); mUpdateHandler = handler; } @@ -84,35 +86,55 @@ class ImageProcessHelper { protected Float doInBackground(Bitmap... bitmaps) { Bitmap bitmap = bitmaps[0]; if (bitmap != null) { - int[] histogram = processHistogram(bitmap); - return computePercentile85(bitmap, histogram); + return new Threshold().compute(bitmap); } - Log.e(TAG, "Per85ComputeTask: Can't get bitmap"); - return DEFAULT_PER85; + Log.e(TAG, "ThresholdComputeTask: Can't get bitmap"); + return DEFAULT_THRESHOLD; } @Override protected void onPostExecute(Float result) { - Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_PER85, result); + Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_THRESHOLD, result); mUpdateHandler.sendMessage(msg); } + } - private int[] processHistogram(Bitmap bitmap) { + private static class Threshold { + public float compute(Bitmap bitmap) { + Bitmap grayscale = toGrayscale(bitmap); + int[] histogram = getHistogram(grayscale); + boolean isSolidColor = isSolidColor(grayscale, histogram); + + // We will see gray wallpaper during the transition if solid color wallpaper is set, + // please refer to b/130360362#comment16. + // As a result, we use Percentile85 rather than Otsus if a solid color wallpaper is set. + ThresholdAlgorithm algorithm = isSolidColor ? new Percentile85() : new Otsus(); + return algorithm.compute(grayscale, histogram); + } + + private Bitmap toGrayscale(Bitmap bitmap) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); - Bitmap target = Bitmap.createBitmap(width, height, bitmap.getConfig()); - Canvas canvas = new Canvas(target); + Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig()); + Canvas canvas = new Canvas(grayscale); ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX); Paint paint = new Paint(); paint.setColorFilter(new ColorMatrixColorFilter(cm)); canvas.drawBitmap(bitmap, new Matrix(), paint); + return grayscale; + } + + private int[] getHistogram(Bitmap grayscale) { + int width = grayscale.getWidth(); + int height = grayscale.getHeight(); + // TODO: Fine tune the performance here, tracking on b/123615079. int[] histogram = new int[256]; for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { - int pixel = target.getPixel(col, row); + int pixel = grayscale.getPixel(col, row); int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel); histogram[y]++; } @@ -121,8 +143,29 @@ class ImageProcessHelper { return histogram; } - private float computePercentile85(Bitmap bitmap, int[] histogram) { - float per85 = DEFAULT_PER85; + private boolean isSolidColor(Bitmap bitmap, int[] histogram) { + boolean solidColor = false; + int pixels = bitmap.getWidth() * bitmap.getHeight(); + + // In solid color case, only one element of histogram has value, + // which is pixel counts and the value of other elements should be 0. + for (int value : histogram) { + if (value != 0 && value != pixels) { + break; + } + if (value == pixels) { + solidColor = true; + break; + } + } + return solidColor; + } + } + + private static class Percentile85 implements ThresholdAlgorithm { + @Override + public float compute(Bitmap bitmap, int[] histogram) { + float per85 = DEFAULT_THRESHOLD; int pixelCount = bitmap.getWidth() * bitmap.getHeight(); float[] acc = new float[256]; for (int i = 0; i < acc.length; i++) { @@ -141,4 +184,51 @@ class ImageProcessHelper { return per85; } } + + private static class Otsus implements ThresholdAlgorithm { + @Override + public float compute(Bitmap bitmap, int[] histogram) { + float threshold = DEFAULT_OTSU_THRESHOLD; + float maxVariance = 0; + float pixelCount = bitmap.getWidth() * bitmap.getHeight(); + float[] w = new float[2]; + float[] m = new float[2]; + float[] u = new float[2]; + + for (int i = 0; i < histogram.length; i++) { + m[1] += i * histogram[i]; + } + + w[1] = pixelCount; + for (int tonalValue = 0; tonalValue < histogram.length; tonalValue++) { + float dU; + float variance; + float numPixels = histogram[tonalValue]; + float tmp = numPixels * tonalValue; + w[0] += numPixels; + w[1] -= numPixels; + + if (w[0] == 0 || w[1] == 0) { + continue; + } + + m[0] += tmp; + m[1] -= tmp; + u[0] = m[0] / w[0]; + u[1] = m[1] / w[1]; + dU = u[0] - u[1]; + variance = w[0] * w[1] * dU * dU; + + if (variance > maxVariance) { + threshold = (tonalValue + 1f) / histogram.length; + maxVariance = variance; + } + } + return threshold; + } + } + + private interface ThresholdAlgorithm { + float compute(Bitmap bitmap, int[] histogram); + } } diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java index 464cbe33c5f0e..5bbfe84b63197 100644 --- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java +++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java @@ -78,8 +78,8 @@ public class ImageWallpaperRenderer implements GLSurfaceView.Renderer, mBitmap = mWallpaperManager.getBitmap(); mBitmapWidth = mBitmap.getWidth(); mBitmapHeight = mBitmap.getHeight(); - // Compute per85 as transition threshold, this is an async work. - mImageProcessHelper.startComputingPercentile85(mBitmap); + // Compute threshold of the image, this is an async work. + mImageProcessHelper.start(mBitmap); mWallpaperManager.forgetLoadedWallpaper(); } } @@ -108,13 +108,13 @@ public class ImageWallpaperRenderer implements GLSurfaceView.Renderer, @Override public void onDrawFrame(GL10 gl) { - float per85 = mImageProcessHelper.getPercentile85(); + float threshold = mImageProcessHelper.getThreshold(); float reveal = mImageRevealHelper.getReveal(); glClear(GL_COLOR_BUFFER_BIT); glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1); - glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), per85); + glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), threshold); glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal); scaleViewport(reveal);