Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.systemui.glwallpaper; |
| 18 | |
Ahan Wu | 3222d3f | 2020-03-11 20:24:00 +0800 | [diff] [blame] | 19 | import static com.android.systemui.glwallpaper.ImageWallpaperRenderer.WallpaperTexture; |
| 20 | |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 21 | import android.graphics.Bitmap; |
| 22 | import android.graphics.Canvas; |
| 23 | import android.graphics.Color; |
| 24 | import android.graphics.ColorMatrix; |
| 25 | import android.graphics.ColorMatrixColorFilter; |
| 26 | import android.graphics.Matrix; |
| 27 | import android.graphics.Paint; |
| 28 | import android.os.AsyncTask; |
| 29 | import android.os.Handler; |
| 30 | import android.os.Handler.Callback; |
| 31 | import android.os.Message; |
| 32 | import android.util.Log; |
| 33 | |
| 34 | /** |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 35 | * A helper class that computes threshold from a bitmap. |
| 36 | * Threshold will be computed each time the user picks a new image wallpaper. |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 37 | */ |
| 38 | class ImageProcessHelper { |
| 39 | private static final String TAG = ImageProcessHelper.class.getSimpleName(); |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 40 | private static final float DEFAULT_THRESHOLD = 0.8f; |
| 41 | private static final float DEFAULT_OTSU_THRESHOLD = 0f; |
| 42 | private static final float MAX_THRESHOLD = 0.89f; |
| 43 | private static final int MSG_UPDATE_THRESHOLD = 1; |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 44 | |
| 45 | /** |
| 46 | * This color matrix will be applied to each pixel to get luminance from rgb by below formula: |
| 47 | * Luminance = .2126f * r + .7152f * g + .0722f * b. |
| 48 | */ |
| 49 | private static final float[] LUMINOSITY_MATRIX = new float[] { |
| 50 | .2126f, .0000f, .0000f, .0000f, .0000f, |
| 51 | .0000f, .7152f, .0000f, .0000f, .0000f, |
| 52 | .0000f, .0000f, .0722f, .0000f, .0000f, |
| 53 | .0000f, .0000f, .0000f, 1.000f, .0000f |
| 54 | }; |
| 55 | |
| 56 | private final Handler mHandler = new Handler(new Callback() { |
| 57 | @Override |
| 58 | public boolean handleMessage(Message msg) { |
| 59 | switch (msg.what) { |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 60 | case MSG_UPDATE_THRESHOLD: |
| 61 | mThreshold = (float) msg.obj; |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 62 | return true; |
| 63 | default: |
| 64 | return false; |
| 65 | } |
| 66 | } |
| 67 | }); |
| 68 | |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 69 | private float mThreshold = DEFAULT_THRESHOLD; |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 70 | |
Ahan Wu | 3222d3f | 2020-03-11 20:24:00 +0800 | [diff] [blame] | 71 | void start(WallpaperTexture texture) { |
| 72 | new ThresholdComputeTask(mHandler).execute(texture); |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 73 | } |
| 74 | |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 75 | float getThreshold() { |
| 76 | return Math.min(mThreshold, MAX_THRESHOLD); |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 77 | } |
| 78 | |
Ahan Wu | 3222d3f | 2020-03-11 20:24:00 +0800 | [diff] [blame] | 79 | private static class ThresholdComputeTask extends AsyncTask<WallpaperTexture, Void, Float> { |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 80 | private Handler mUpdateHandler; |
| 81 | |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 82 | ThresholdComputeTask(Handler handler) { |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 83 | super(handler); |
| 84 | mUpdateHandler = handler; |
| 85 | } |
| 86 | |
| 87 | @Override |
Ahan Wu | 3222d3f | 2020-03-11 20:24:00 +0800 | [diff] [blame] | 88 | protected Float doInBackground(WallpaperTexture... textures) { |
| 89 | WallpaperTexture texture = textures[0]; |
| 90 | final float[] threshold = new float[] {DEFAULT_THRESHOLD}; |
| 91 | if (texture == null) { |
| 92 | Log.e(TAG, "ThresholdComputeTask: WallpaperTexture not initialized"); |
| 93 | return threshold[0]; |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 94 | } |
Ahan Wu | 3222d3f | 2020-03-11 20:24:00 +0800 | [diff] [blame] | 95 | |
| 96 | texture.use(bitmap -> { |
| 97 | if (bitmap != null) { |
| 98 | threshold[0] = new Threshold().compute(bitmap); |
| 99 | } else { |
| 100 | Log.e(TAG, "ThresholdComputeTask: Can't get bitmap"); |
| 101 | } |
| 102 | }); |
| 103 | return threshold[0]; |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 104 | } |
| 105 | |
| 106 | @Override |
| 107 | protected void onPostExecute(Float result) { |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 108 | Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_THRESHOLD, result); |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 109 | mUpdateHandler.sendMessage(msg); |
| 110 | } |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 111 | } |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 112 | |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 113 | private static class Threshold { |
| 114 | public float compute(Bitmap bitmap) { |
| 115 | Bitmap grayscale = toGrayscale(bitmap); |
| 116 | int[] histogram = getHistogram(grayscale); |
| 117 | boolean isSolidColor = isSolidColor(grayscale, histogram); |
| 118 | |
| 119 | // We will see gray wallpaper during the transition if solid color wallpaper is set, |
| 120 | // please refer to b/130360362#comment16. |
| 121 | // As a result, we use Percentile85 rather than Otsus if a solid color wallpaper is set. |
| 122 | ThresholdAlgorithm algorithm = isSolidColor ? new Percentile85() : new Otsus(); |
| 123 | return algorithm.compute(grayscale, histogram); |
| 124 | } |
| 125 | |
| 126 | private Bitmap toGrayscale(Bitmap bitmap) { |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 127 | int width = bitmap.getWidth(); |
| 128 | int height = bitmap.getHeight(); |
| 129 | |
Ahan Wu | 287d828 | 2019-12-17 16:23:17 +0800 | [diff] [blame] | 130 | Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig(), |
| 131 | false /* hasAlpha */, bitmap.getColorSpace()); |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 132 | Canvas canvas = new Canvas(grayscale); |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 133 | ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX); |
| 134 | Paint paint = new Paint(); |
| 135 | paint.setColorFilter(new ColorMatrixColorFilter(cm)); |
| 136 | canvas.drawBitmap(bitmap, new Matrix(), paint); |
| 137 | |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 138 | return grayscale; |
| 139 | } |
| 140 | |
| 141 | private int[] getHistogram(Bitmap grayscale) { |
| 142 | int width = grayscale.getWidth(); |
| 143 | int height = grayscale.getHeight(); |
| 144 | |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 145 | // TODO: Fine tune the performance here, tracking on b/123615079. |
| 146 | int[] histogram = new int[256]; |
| 147 | for (int row = 0; row < height; row++) { |
| 148 | for (int col = 0; col < width; col++) { |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 149 | int pixel = grayscale.getPixel(col, row); |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 150 | int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel); |
| 151 | histogram[y]++; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | return histogram; |
| 156 | } |
| 157 | |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 158 | private boolean isSolidColor(Bitmap bitmap, int[] histogram) { |
| 159 | boolean solidColor = false; |
| 160 | int pixels = bitmap.getWidth() * bitmap.getHeight(); |
| 161 | |
| 162 | // In solid color case, only one element of histogram has value, |
| 163 | // which is pixel counts and the value of other elements should be 0. |
| 164 | for (int value : histogram) { |
| 165 | if (value != 0 && value != pixels) { |
| 166 | break; |
| 167 | } |
| 168 | if (value == pixels) { |
| 169 | solidColor = true; |
| 170 | break; |
| 171 | } |
| 172 | } |
| 173 | return solidColor; |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | private static class Percentile85 implements ThresholdAlgorithm { |
| 178 | @Override |
| 179 | public float compute(Bitmap bitmap, int[] histogram) { |
| 180 | float per85 = DEFAULT_THRESHOLD; |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 181 | int pixelCount = bitmap.getWidth() * bitmap.getHeight(); |
| 182 | float[] acc = new float[256]; |
| 183 | for (int i = 0; i < acc.length; i++) { |
| 184 | acc[i] = (float) histogram[i] / pixelCount; |
| 185 | float prev = i == 0 ? 0f : acc[i - 1]; |
| 186 | float next = acc[i]; |
| 187 | float idx = (float) (i + 1) / 255; |
| 188 | float sum = prev + next; |
| 189 | if (prev < 0.85f && sum >= 0.85f) { |
| 190 | per85 = idx; |
| 191 | } |
| 192 | if (i > 0) { |
| 193 | acc[i] += acc[i - 1]; |
| 194 | } |
| 195 | } |
| 196 | return per85; |
| 197 | } |
| 198 | } |
Ahan Wu | 330bc60 | 2019-04-25 15:04:39 +0800 | [diff] [blame] | 199 | |
| 200 | private static class Otsus implements ThresholdAlgorithm { |
| 201 | @Override |
| 202 | public float compute(Bitmap bitmap, int[] histogram) { |
| 203 | float threshold = DEFAULT_OTSU_THRESHOLD; |
| 204 | float maxVariance = 0; |
| 205 | float pixelCount = bitmap.getWidth() * bitmap.getHeight(); |
| 206 | float[] w = new float[2]; |
| 207 | float[] m = new float[2]; |
| 208 | float[] u = new float[2]; |
| 209 | |
| 210 | for (int i = 0; i < histogram.length; i++) { |
| 211 | m[1] += i * histogram[i]; |
| 212 | } |
| 213 | |
| 214 | w[1] = pixelCount; |
| 215 | for (int tonalValue = 0; tonalValue < histogram.length; tonalValue++) { |
| 216 | float dU; |
| 217 | float variance; |
| 218 | float numPixels = histogram[tonalValue]; |
| 219 | float tmp = numPixels * tonalValue; |
| 220 | w[0] += numPixels; |
| 221 | w[1] -= numPixels; |
| 222 | |
| 223 | if (w[0] == 0 || w[1] == 0) { |
| 224 | continue; |
| 225 | } |
| 226 | |
| 227 | m[0] += tmp; |
| 228 | m[1] -= tmp; |
| 229 | u[0] = m[0] / w[0]; |
| 230 | u[1] = m[1] / w[1]; |
| 231 | dU = u[0] - u[1]; |
| 232 | variance = w[0] * w[1] * dU * dU; |
| 233 | |
| 234 | if (variance > maxVariance) { |
| 235 | threshold = (tonalValue + 1f) / histogram.length; |
| 236 | maxVariance = variance; |
| 237 | } |
| 238 | } |
| 239 | return threshold; |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | private interface ThresholdAlgorithm { |
| 244 | float compute(Bitmap bitmap, int[] histogram); |
| 245 | } |
Ahan Wu | 67e7f10 | 2019-01-14 20:38:14 +0800 | [diff] [blame] | 246 | } |