Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c6a2b22 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[DO NOT MERGE] legacy icon treatment / circle detection Bug: 37357483"...

Merge "[DO NOT MERGE] legacy icon treatment / circle detection Bug: 37357483" into ub-launcher3-dorval
parents 846d7e12 c1cf7571
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -15,7 +15,7 @@
     limitations under the License.
     limitations under the License.
-->
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:color="#FFE0E0E0"/>
    <background android:drawable="@color/legacy_icon_background"/>
    <foreground>
    <foreground>
        <com.android.launcher3.graphics.FixedScaleDrawable />
        <com.android.launcher3.graphics.FixedScaleDrawable />
    </foreground>
    </foreground>
+1 −0
Original line number Original line Diff line number Diff line
@@ -45,5 +45,6 @@
    <!-- System shortcuts -->
    <!-- System shortcuts -->
    <color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color>
    <color name="system_shortcuts_icon_color">@android:color/tertiary_text_light</color>


    <color name="legacy_icon_background">#FFFFFF</color>
    <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
    <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
</resources>
</resources>
+1 −1
Original line number Original line Diff line number Diff line
@@ -767,7 +767,7 @@ public class IconCache {
    }
    }


    private static final class IconDB extends SQLiteCacheHelper {
    private static final class IconDB extends SQLiteCacheHelper {
        private final static int DB_VERSION = 11;
        private final static int DB_VERSION = 12;


        private final static int RELEASE_VERSION = DB_VERSION +
        private final static int RELEASE_VERSION = DB_VERSION +
                (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
                (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
+7 −1
Original line number Original line Diff line number Diff line
@@ -19,15 +19,17 @@ public class FixedScaleDrawable extends DrawableWrapper {


    // TODO b/33553066 use the constant defined in MaskableIconDrawable
    // TODO b/33553066 use the constant defined in MaskableIconDrawable
    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
    private float mScale;


    public FixedScaleDrawable() {
    public FixedScaleDrawable() {
        super(new ColorDrawable());
        super(new ColorDrawable());
        mScale = LEGACY_ICON_SCALE;
    }
    }


    @Override
    @Override
    public void draw(Canvas canvas) {
    public void draw(Canvas canvas) {
        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
        int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
        canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE,
        canvas.scale(mScale, mScale,
                getBounds().exactCenterX(), getBounds().exactCenterY());
                getBounds().exactCenterX(), getBounds().exactCenterY());
        super.draw(canvas);
        super.draw(canvas);
        canvas.restoreToCount(saveCount);
        canvas.restoreToCount(saveCount);
@@ -38,4 +40,8 @@ public class FixedScaleDrawable extends DrawableWrapper {


    @Override
    @Override
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }

    public void setScale(float scale) {
        mScale = scale * LEGACY_ICON_SCALE;
    }
}
}
+173 −8
Original line number Original line Diff line number Diff line
@@ -20,15 +20,31 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;


import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;


import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteBuffer;
import java.util.Random;


public class IconNormalizer {
public class IconNormalizer {


    private static final String TAG = "IconNormalizer";
    private static final boolean DEBUG = false;
    // Ratio of icon visible area to full icon size for a square shaped icon
    // Ratio of icon visible area to full icon size for a square shaped icon
    private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
    private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
    // Ratio of icon visible area to full icon size for a circular shaped icon
    // Ratio of icon visible area to full icon size for a circular shaped icon
@@ -42,17 +58,36 @@ public class IconNormalizer {


    private static final int MIN_VISIBLE_ALPHA = 40;
    private static final int MIN_VISIBLE_ALPHA = 40;


    // Shape detection related constants
    private static final float BOUND_RATIO_MARGIN = .05f;
    private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
    private static final float SCALE_NOT_INITIALIZED = 0;

    private static final Object LOCK = new Object();
    private static final Object LOCK = new Object();
    private static IconNormalizer sIconNormalizer;
    private static IconNormalizer sIconNormalizer;


    private final int mMaxSize;
    private final int mMaxSize;
    private final Bitmap mBitmap;
    private final Bitmap mBitmap;
    private final Bitmap mBitmapARGB;
    private final Canvas mCanvas;
    private final Canvas mCanvas;
    private final Paint mPaintMaskShape;
    private final Paint mPaintMaskShapeOutline;
    private final byte[] mPixels;
    private final byte[] mPixels;
    private final int[] mPixelsARGB;
    private float mAdaptiveIconScale;


    // for each y, stores the position of the leftmost x and the rightmost x
    // for each y, stores the position of the leftmost x and the rightmost x
    private final float[] mLeftBorder;
    private final float[] mLeftBorder;
    private final float[] mRightBorder;
    private final float[] mRightBorder;
    private final Rect mBounds;
    private final Matrix mMatrix;

    private Paint mPaintIcon;
    private Canvas mCanvasARGB;

    private File mDir;
    private int mFileId;
    private Random mRandom;


    private IconNormalizer(Context context) {
    private IconNormalizer(Context context) {
        // Use twice the icon size as maximum size to avoid scaling down twice.
        // Use twice the icon size as maximum size to avoid scaling down twice.
@@ -60,9 +95,121 @@ public class IconNormalizer {
        mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
        mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
        mCanvas = new Canvas(mBitmap);
        mCanvas = new Canvas(mBitmap);
        mPixels = new byte[mMaxSize * mMaxSize];
        mPixels = new byte[mMaxSize * mMaxSize];

        mPixelsARGB = new int[mMaxSize * mMaxSize];
        mLeftBorder = new float[mMaxSize];
        mLeftBorder = new float[mMaxSize];
        mRightBorder = new float[mMaxSize];
        mRightBorder = new float[mMaxSize];
        mBounds = new Rect();

        // Needed for isShape() method
        mBitmapARGB = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ARGB_8888);
        mCanvasARGB = new Canvas(mBitmapARGB);

        mPaintIcon = new Paint();
        mPaintIcon.setColor(Color.WHITE);

        mPaintMaskShape = new Paint();
        mPaintMaskShape.setColor(Color.RED);
        mPaintMaskShape.setStyle(Paint.Style.FILL);
        mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));

        mPaintMaskShapeOutline = new Paint();
        mPaintMaskShapeOutline.setStrokeWidth(2 * context.getResources().getDisplayMetrics().density);
        mPaintMaskShapeOutline.setStyle(Paint.Style.STROKE);
        mPaintMaskShapeOutline.setColor(Color.BLACK);
        mPaintMaskShapeOutline.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));

        mMatrix = new Matrix();
        int[] mPixels = new int[mMaxSize * mMaxSize];
        mAdaptiveIconScale = SCALE_NOT_INITIALIZED;

        mDir = context.getExternalFilesDir(null);
        mRandom = new Random();
    }

    /**
     * Returns if the shape of the icon is same as the path.
     * For this method to work, the shape path bounds should be in [0,1]x[0,1] bounds.
     */
    private boolean isShape(Path maskPath) {
        // Condition1:
        // If width and height of the path not close to a square, then the icon shape is
        // not same as the mask shape.
        float iconRatio = ((float) mBounds.width()) / mBounds.height();
        if (Math.abs(iconRatio - 1) > BOUND_RATIO_MARGIN) {
            if (DEBUG) {
                Log.d(TAG, "Not same as mask shape because width != height. " + iconRatio);
            }
            return false;
        }

        // Condition 2:
        // Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
        // should generate transparent image, if the actual icon is equivalent to the shape.
        mFileId = mRandom.nextInt();
        mBitmapARGB.eraseColor(Color.TRANSPARENT);
        mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon);

        if (DEBUG) {
            final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png");
            try {
                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
                        new FileOutputStream(beforeFile));
            } catch (Exception e) {}
        }

        // Fit the shape within the icon's bounding box
        mMatrix.reset();
        mMatrix.setScale(mBounds.width(), mBounds.height());
        mMatrix.postTranslate(mBounds.left, mBounds.top);
        maskPath.transform(mMatrix);

        // XOR operation
        mCanvasARGB.drawPath(maskPath, mPaintMaskShape);

        // DST_OUT operation around the mask path outline
        mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);

        boolean isTrans = isTransparentBitmap(mBitmapARGB);
        if (DEBUG) {
            final File afterFile = new File(mDir, "isShape" + mFileId + "_after_" + isTrans + ".png");
            try {
                mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
                        new FileOutputStream(afterFile));
            } catch (Exception e) {}
        }

        // Check if the result is almost transparent
        if (!isTrans) {
            if (DEBUG) {
                Log.d(TAG, "Not same as mask shape");
            }
            return false;
        }
        return true;
    }

    /**
     * Used to determine if certain the bitmap is transparent.
     */
    private boolean isTransparentBitmap(Bitmap bitmap) {
        int w = mBounds.width();
        int h = mBounds.height();
        bitmap.getPixels(mPixelsARGB, 0 /* the first index to write into the array */,
                w /* stride */,
                mBounds.left, mBounds.top,
                w, h);
        int sum = 0;
        for (int i = 0; i < w * h; i++) {
            if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) {
                    sum++;
            }
        }
        float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
        boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
        if (DEBUG) {
            Log.d(TAG, "Total # pixel that is different (id="+ mFileId + "):" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height());
        }
        return transparentImage;
    }
    }


    /**
    /**
@@ -79,7 +226,15 @@ public class IconNormalizer {
     *
     *
     * @param outBounds optional rect to receive the fraction distance from each edge.
     * @param outBounds optional rect to receive the fraction distance from each edge.
     */
     */
    public synchronized float getScale(Drawable d, RectF outBounds) {
    public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
            @Nullable Path path, @Nullable boolean[] outMaskShape) {
        if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
                mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
            if (outBounds != null) {
                outBounds.set(mBounds);
            }
            return mAdaptiveIconScale;
        }
        int width = d.getIntrinsicWidth();
        int width = d.getIntrinsicWidth();
        int height = d.getIntrinsicHeight();
        int height = d.getIntrinsicHeight();
        if (width <= 0 || height <= 0) {
        if (width <= 0 || height <= 0) {
@@ -171,18 +326,28 @@ public class IconNormalizer {
        } else {
        } else {
            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
        }
        }
        mBounds.left = leftX;
        mBounds.right = rightX;


        if (outBounds != null) {
        mBounds.top = topY;
            outBounds.left = ((float) leftX) / width;
        mBounds.bottom = bottomY;
            outBounds.right = 1 - ((float) rightX) / width;


            outBounds.top = ((float) topY) / height;
        if (outBounds != null) {
            outBounds.bottom = 1 - ((float) bottomY) / height;
            outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top),
                    1 - ((float) mBounds.right) / width,
                    1 - ((float) mBounds.bottom) / height);
        }
        }


        if (outMaskShape != null && outMaskShape.length > 0) {
            outMaskShape[0] = isShape(path);
        }
        float areaScale = area / (width * height);
        float areaScale = area / (width * height);
        // Use sqrt of the final ratio as the images is scaled across both width and height.
        // Use sqrt of the final ratio as the images is scaled across both width and height.
        float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
        float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
        if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable &&
                mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
            mAdaptiveIconScale = scale;
        }
        return scale;
        return scale;
    }
    }


Loading