Home | History | Annotate | Download | only in glwallpaper
      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 
     19 import android.graphics.Bitmap;
     20 import android.graphics.Canvas;
     21 import android.graphics.Color;
     22 import android.graphics.ColorMatrix;
     23 import android.graphics.ColorMatrixColorFilter;
     24 import android.graphics.Matrix;
     25 import android.graphics.Paint;
     26 import android.os.AsyncTask;
     27 import android.os.Handler;
     28 import android.os.Handler.Callback;
     29 import android.os.Message;
     30 import android.util.Log;
     31 
     32 /**
     33  * A helper class that computes threshold from a bitmap.
     34  * Threshold will be computed each time the user picks a new image wallpaper.
     35  */
     36 class ImageProcessHelper {
     37     private static final String TAG = ImageProcessHelper.class.getSimpleName();
     38     private static final float DEFAULT_THRESHOLD = 0.8f;
     39     private static final float DEFAULT_OTSU_THRESHOLD = 0f;
     40     private static final float MAX_THRESHOLD = 0.89f;
     41     private static final int MSG_UPDATE_THRESHOLD = 1;
     42 
     43     /**
     44      * This color matrix will be applied to each pixel to get luminance from rgb by below formula:
     45      * Luminance = .2126f * r + .7152f * g + .0722f * b.
     46      */
     47     private static final float[] LUMINOSITY_MATRIX = new float[] {
     48             .2126f,     .0000f,     .0000f,     .0000f,     .0000f,
     49             .0000f,     .7152f,     .0000f,     .0000f,     .0000f,
     50             .0000f,     .0000f,     .0722f,     .0000f,     .0000f,
     51             .0000f,     .0000f,     .0000f,     1.000f,     .0000f
     52     };
     53 
     54     private final Handler mHandler = new Handler(new Callback() {
     55         @Override
     56         public boolean handleMessage(Message msg) {
     57             switch (msg.what) {
     58                 case MSG_UPDATE_THRESHOLD:
     59                     mThreshold = (float) msg.obj;
     60                     return true;
     61                 default:
     62                     return false;
     63             }
     64         }
     65     });
     66 
     67     private float mThreshold = DEFAULT_THRESHOLD;
     68 
     69     void start(Bitmap bitmap) {
     70         new ThresholdComputeTask(mHandler).execute(bitmap);
     71     }
     72 
     73     float getThreshold() {
     74         return Math.min(mThreshold, MAX_THRESHOLD);
     75     }
     76 
     77     private static class ThresholdComputeTask extends AsyncTask<Bitmap, Void, Float> {
     78         private Handler mUpdateHandler;
     79 
     80         ThresholdComputeTask(Handler handler) {
     81             super(handler);
     82             mUpdateHandler = handler;
     83         }
     84 
     85         @Override
     86         protected Float doInBackground(Bitmap... bitmaps) {
     87             Bitmap bitmap = bitmaps[0];
     88             if (bitmap != null) {
     89                 return new Threshold().compute(bitmap);
     90             }
     91             Log.e(TAG, "ThresholdComputeTask: Can't get bitmap");
     92             return DEFAULT_THRESHOLD;
     93         }
     94 
     95         @Override
     96         protected void onPostExecute(Float result) {
     97             Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_THRESHOLD, result);
     98             mUpdateHandler.sendMessage(msg);
     99         }
    100     }
    101 
    102     private static class Threshold {
    103         public float compute(Bitmap bitmap) {
    104             Bitmap grayscale = toGrayscale(bitmap);
    105             int[] histogram = getHistogram(grayscale);
    106             boolean isSolidColor = isSolidColor(grayscale, histogram);
    107 
    108             // We will see gray wallpaper during the transition if solid color wallpaper is set,
    109             // please refer to b/130360362#comment16.
    110             // As a result, we use Percentile85 rather than Otsus if a solid color wallpaper is set.
    111             ThresholdAlgorithm algorithm = isSolidColor ? new Percentile85() : new Otsus();
    112             return algorithm.compute(grayscale, histogram);
    113         }
    114 
    115         private Bitmap toGrayscale(Bitmap bitmap) {
    116             int width = bitmap.getWidth();
    117             int height = bitmap.getHeight();
    118 
    119             Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig());
    120             Canvas canvas = new Canvas(grayscale);
    121             ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX);
    122             Paint paint = new Paint();
    123             paint.setColorFilter(new ColorMatrixColorFilter(cm));
    124             canvas.drawBitmap(bitmap, new Matrix(), paint);
    125 
    126             return grayscale;
    127         }
    128 
    129         private int[] getHistogram(Bitmap grayscale) {
    130             int width = grayscale.getWidth();
    131             int height = grayscale.getHeight();
    132 
    133             // TODO: Fine tune the performance here, tracking on b/123615079.
    134             int[] histogram = new int[256];
    135             for (int row = 0; row < height; row++) {
    136                 for (int col = 0; col < width; col++) {
    137                     int pixel = grayscale.getPixel(col, row);
    138                     int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel);
    139                     histogram[y]++;
    140                 }
    141             }
    142 
    143             return histogram;
    144         }
    145 
    146         private boolean isSolidColor(Bitmap bitmap, int[] histogram) {
    147             boolean solidColor = false;
    148             int pixels = bitmap.getWidth() * bitmap.getHeight();
    149 
    150             // In solid color case, only one element of histogram has value,
    151             // which is pixel counts and the value of other elements should be 0.
    152             for (int value : histogram) {
    153                 if (value != 0 && value != pixels) {
    154                     break;
    155                 }
    156                 if (value == pixels) {
    157                     solidColor = true;
    158                     break;
    159                 }
    160             }
    161             return solidColor;
    162         }
    163     }
    164 
    165     private static class Percentile85 implements ThresholdAlgorithm {
    166         @Override
    167         public float compute(Bitmap bitmap, int[] histogram) {
    168             float per85 = DEFAULT_THRESHOLD;
    169             int pixelCount = bitmap.getWidth() * bitmap.getHeight();
    170             float[] acc = new float[256];
    171             for (int i = 0; i < acc.length; i++) {
    172                 acc[i] = (float) histogram[i] / pixelCount;
    173                 float prev = i == 0 ? 0f : acc[i - 1];
    174                 float next = acc[i];
    175                 float idx = (float) (i + 1) / 255;
    176                 float sum = prev + next;
    177                 if (prev < 0.85f && sum >= 0.85f) {
    178                     per85 = idx;
    179                 }
    180                 if (i > 0) {
    181                     acc[i] += acc[i - 1];
    182                 }
    183             }
    184             return per85;
    185         }
    186     }
    187 
    188     private static class Otsus implements ThresholdAlgorithm {
    189         @Override
    190         public float compute(Bitmap bitmap, int[] histogram) {
    191             float threshold = DEFAULT_OTSU_THRESHOLD;
    192             float maxVariance = 0;
    193             float pixelCount = bitmap.getWidth() * bitmap.getHeight();
    194             float[] w = new float[2];
    195             float[] m = new float[2];
    196             float[] u = new float[2];
    197 
    198             for (int i = 0; i < histogram.length; i++) {
    199                 m[1] += i * histogram[i];
    200             }
    201 
    202             w[1] = pixelCount;
    203             for (int tonalValue = 0; tonalValue < histogram.length; tonalValue++) {
    204                 float dU;
    205                 float variance;
    206                 float numPixels = histogram[tonalValue];
    207                 float tmp = numPixels * tonalValue;
    208                 w[0] += numPixels;
    209                 w[1] -= numPixels;
    210 
    211                 if (w[0] == 0 || w[1] == 0) {
    212                     continue;
    213                 }
    214 
    215                 m[0] += tmp;
    216                 m[1] -= tmp;
    217                 u[0] = m[0] / w[0];
    218                 u[1] = m[1] / w[1];
    219                 dU = u[0] - u[1];
    220                 variance = w[0] * w[1] * dU * dU;
    221 
    222                 if (variance > maxVariance) {
    223                     threshold = (tonalValue + 1f) / histogram.length;
    224                     maxVariance = variance;
    225                 }
    226             }
    227             return threshold;
    228         }
    229     }
    230 
    231     private interface ThresholdAlgorithm {
    232         float compute(Bitmap bitmap, int[] histogram);
    233     }
    234 }
    235