Merge "Add shape detection logic only for circle icons" into ub-launcher3-qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ed45267bf9
@@ -46,13 +46,15 @@ public class BaseIconFactory implements AutoCloseable {
|
||||
|
||||
private IconNormalizer mNormalizer;
|
||||
private ShadowGenerator mShadowGenerator;
|
||||
private final boolean mShapeDetection;
|
||||
|
||||
private Drawable mWrapperIcon;
|
||||
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
||||
|
||||
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
|
||||
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
|
||||
boolean shapeDetection) {
|
||||
mContext = context.getApplicationContext();
|
||||
|
||||
mShapeDetection = shapeDetection;
|
||||
mFillResIconDpi = fillResIconDpi;
|
||||
mIconBitmapSize = iconBitmapSize;
|
||||
|
||||
@@ -64,6 +66,10 @@ public class BaseIconFactory implements AutoCloseable {
|
||||
clear();
|
||||
}
|
||||
|
||||
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
|
||||
this(context, fillResIconDpi, iconBitmapSize, false);
|
||||
}
|
||||
|
||||
protected void clear() {
|
||||
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
||||
mDisableColorExtractor = false;
|
||||
@@ -78,7 +84,7 @@ public class BaseIconFactory implements AutoCloseable {
|
||||
|
||||
public IconNormalizer getNormalizer() {
|
||||
if (mNormalizer == null) {
|
||||
mNormalizer = new IconNormalizer(mIconBitmapSize);
|
||||
mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
|
||||
}
|
||||
return mNormalizer;
|
||||
}
|
||||
@@ -212,18 +218,19 @@ public class BaseIconFactory implements AutoCloseable {
|
||||
}
|
||||
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
|
||||
dr.setBounds(0, 0, 1, 1);
|
||||
scale = getNormalizer().getScale(icon, outIconBounds);
|
||||
if (!(icon instanceof AdaptiveIconDrawable)) {
|
||||
boolean[] outShape = new boolean[1];
|
||||
scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
|
||||
if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
|
||||
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
|
||||
fsd.setDrawable(icon);
|
||||
fsd.setScale(scale);
|
||||
icon = dr;
|
||||
scale = getNormalizer().getScale(icon, outIconBounds);
|
||||
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
|
||||
|
||||
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
|
||||
}
|
||||
} else {
|
||||
scale = getNormalizer().getScale(icon, outIconBounds);
|
||||
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
|
||||
}
|
||||
|
||||
outScale[0] = scale;
|
||||
|
||||
@@ -18,16 +18,22 @@ package com.android.launcher3.icons;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
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.Region;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -51,6 +57,9 @@ public class IconNormalizer {
|
||||
|
||||
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;
|
||||
|
||||
// Ratio of the diameter of an normalized circular icon to the actual icon size.
|
||||
@@ -59,18 +68,24 @@ public class IconNormalizer {
|
||||
private final int mMaxSize;
|
||||
private final Bitmap mBitmap;
|
||||
private final Canvas mCanvas;
|
||||
private final Paint mPaintMaskShape;
|
||||
private final Paint mPaintMaskShapeOutline;
|
||||
private final byte[] mPixels;
|
||||
|
||||
private final RectF mAdaptiveIconBounds;
|
||||
private float mAdaptiveIconScale;
|
||||
|
||||
private boolean mEnableShapeDetection;
|
||||
|
||||
// for each y, stores the position of the leftmost x and the rightmost x
|
||||
private final float[] mLeftBorder;
|
||||
private final float[] mRightBorder;
|
||||
private final Rect mBounds;
|
||||
private final Path mShapePath;
|
||||
private final Matrix mMatrix;
|
||||
|
||||
/** package private **/
|
||||
IconNormalizer(int iconBitmapSize) {
|
||||
IconNormalizer(Context context, int iconBitmapSize, boolean shapeDetection) {
|
||||
// Use twice the icon size as maximum size to avoid scaling down twice.
|
||||
mMaxSize = iconBitmapSize * 2;
|
||||
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
|
||||
@@ -81,7 +96,22 @@ public class IconNormalizer {
|
||||
mBounds = new Rect();
|
||||
mAdaptiveIconBounds = new RectF();
|
||||
|
||||
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.CLEAR));
|
||||
|
||||
mShapePath = new Path();
|
||||
mMatrix = new Matrix();
|
||||
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
|
||||
mEnableShapeDetection = shapeDetection;
|
||||
}
|
||||
|
||||
private static float getScale(float hullArea, float boundingArea, float fullArea) {
|
||||
@@ -126,6 +156,72 @@ public class IconNormalizer {
|
||||
return getScale(hullArea, hullArea, size * size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
||||
// 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, mShapePath);
|
||||
|
||||
// XOR operation
|
||||
mCanvas.drawPath(mShapePath, mPaintMaskShape);
|
||||
|
||||
// DST_OUT operation around the mask path outline
|
||||
mCanvas.drawPath(mShapePath, mPaintMaskShapeOutline);
|
||||
|
||||
// Check if the result is almost transparent
|
||||
return isTransparentBitmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine if certain the bitmap is transparent.
|
||||
*/
|
||||
private boolean isTransparentBitmap() {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(mPixels);
|
||||
buffer.rewind();
|
||||
mBitmap.copyPixelsToBuffer(buffer);
|
||||
|
||||
int y = mBounds.top;
|
||||
// buffer position
|
||||
int index = y * mMaxSize;
|
||||
// buffer shift after every row, width of buffer = mMaxSize
|
||||
int rowSizeDiff = mMaxSize - mBounds.right;
|
||||
|
||||
int sum = 0;
|
||||
for (; y < mBounds.bottom; y++) {
|
||||
index += mBounds.left;
|
||||
for (int x = mBounds.left; x < mBounds.right; x++) {
|
||||
if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
|
||||
sum++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
index += rowSizeDiff;
|
||||
}
|
||||
|
||||
float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
|
||||
return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
|
||||
* matches the design guidelines for a launcher icon.
|
||||
@@ -140,7 +236,8 @@ public class IconNormalizer {
|
||||
*
|
||||
* @param outBounds optional rect to receive the fraction distance from each edge.
|
||||
*/
|
||||
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
|
||||
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds,
|
||||
@Nullable Path path, @Nullable boolean[] outMaskShape) {
|
||||
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
|
||||
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
|
||||
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
|
||||
@@ -242,7 +339,9 @@ public class IconNormalizer {
|
||||
1 - ((float) mBounds.right) / width,
|
||||
1 - ((float) mBounds.bottom) / height);
|
||||
}
|
||||
|
||||
if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
|
||||
outMaskShape[0] = isShape(path);
|
||||
}
|
||||
// Area of the rectangle required to fit the convex hull
|
||||
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
|
||||
return getScale(area, rectArea, width * height);
|
||||
|
||||
@@ -243,7 +243,7 @@ public class DragView extends View implements LauncherStateManager.StateListener
|
||||
nDr = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
|
||||
}
|
||||
Utilities.scaleRectAboutCenter(bounds,
|
||||
li.getNormalizer().getScale(nDr, null));
|
||||
li.getNormalizer().getScale(nDr, null, null, null));
|
||||
}
|
||||
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
|
||||
|
||||
|
||||
@@ -91,6 +91,10 @@ public abstract class IconShape {
|
||||
|
||||
private SparseArray<TypedValue> mAttrs;
|
||||
|
||||
public boolean enableShapeDetection(){
|
||||
return false;
|
||||
};
|
||||
|
||||
public abstract void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
|
||||
Paint paint);
|
||||
|
||||
@@ -194,6 +198,11 @@ public abstract class IconShape {
|
||||
protected float getStartRadius(Rect startRect) {
|
||||
return startRect.width() / 2f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableShapeDetection() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RoundedSquare extends SimpleRectShape {
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.ItemInfoWithIcon;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.graphics.IconShape;
|
||||
import com.android.launcher3.model.PackageItemInfo;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.util.Themes;
|
||||
@@ -50,11 +51,15 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
|
||||
private static LauncherIcons sPool;
|
||||
private static int sPoolId = 0;
|
||||
|
||||
public static LauncherIcons obtain(Context context) {
|
||||
return obtain(context, IconShape.getShape().enableShapeDetection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new Message instance from the global pool. Allows us to
|
||||
* avoid allocating new objects in many cases.
|
||||
*/
|
||||
public static LauncherIcons obtain(Context context) {
|
||||
public static LauncherIcons obtain(Context context, boolean shapeDetection) {
|
||||
int poolId;
|
||||
synchronized (sPoolSync) {
|
||||
if (sPool != null) {
|
||||
@@ -67,7 +72,8 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
|
||||
}
|
||||
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
|
||||
return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId);
|
||||
return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId,
|
||||
shapeDetection);
|
||||
}
|
||||
|
||||
public static void clearPool() {
|
||||
@@ -81,8 +87,9 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
|
||||
|
||||
private LauncherIcons next;
|
||||
|
||||
private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId) {
|
||||
super(context, fillResIconDpi, iconBitmapSize);
|
||||
private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
|
||||
boolean shapeDetection) {
|
||||
super(context, fillResIconDpi, iconBitmapSize, shapeDetection);
|
||||
mPoolId = poolId;
|
||||
}
|
||||
|
||||
|
||||
@@ -529,7 +529,8 @@ public class FloatingIconView extends View implements
|
||||
bounds.inset(mBlurSizeOutline / 2, mBlurSizeOutline / 2);
|
||||
|
||||
try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
|
||||
Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null));
|
||||
Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null,
|
||||
null, null));
|
||||
}
|
||||
|
||||
bounds.inset(
|
||||
|
||||
Reference in New Issue
Block a user