diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2017-03-28 17:01:05 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-03-28 17:01:05 +0000 |
commit | 9e7f23cf50f48f78e29f94a6018f328995ca9b70 (patch) | |
tree | 3bbe2d1069100ff63d1ea726d6e1ca81a1578f77 | |
parent | 9cbe86deeee33358e0c019f2e7dcd2c8c461075c (diff) | |
parent | f5a6ee0bfb26c3e001ff8cae314ef32a100913bc (diff) | |
download | android_packages_apps_Trebuchet-9e7f23cf50f48f78e29f94a6018f328995ca9b70.tar.gz android_packages_apps_Trebuchet-9e7f23cf50f48f78e29f94a6018f328995ca9b70.tar.bz2 android_packages_apps_Trebuchet-9e7f23cf50f48f78e29f94a6018f328995ca9b70.zip |
Merge "Legacy icon treatment" into ub-launcher3-master
4 files changed, 222 insertions, 20 deletions
diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/graphics/FixedScaleDrawable.java index 4be4bd552..7ee3d8002 100644 --- a/src/com/android/launcher3/graphics/FixedScaleDrawable.java +++ b/src/com/android/launcher3/graphics/FixedScaleDrawable.java @@ -19,15 +19,17 @@ public class FixedScaleDrawable extends DrawableWrapper { // TODO b/33553066 use the constant defined in MaskableIconDrawable private static final float LEGACY_ICON_SCALE = .7f * .6667f; + private float mScale; public FixedScaleDrawable() { super(new ColorDrawable()); + mScale = LEGACY_ICON_SCALE; } @Override public void draw(Canvas canvas) { int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.scale(LEGACY_ICON_SCALE, LEGACY_ICON_SCALE, + canvas.scale(mScale, mScale, getBounds().exactCenterX(), getBounds().exactCenterY()); super.draw(canvas); canvas.restoreToCount(saveCount); @@ -38,4 +40,8 @@ public class FixedScaleDrawable extends DrawableWrapper { @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { } + + public void setScale(float scale) { + mScale = scale * LEGACY_ICON_SCALE; + } } diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java index 70b3dd6c9..069895a38 100644 --- a/src/com/android/launcher3/graphics/IconNormalizer.java +++ b/src/com/android/launcher3/graphics/IconNormalizer.java @@ -20,15 +20,31 @@ import android.content.Context; 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.drawable.Drawable; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.Utilities; +import java.io.File; +import java.io.FileOutputStream; import java.nio.ByteBuffer; +import java.util.Random; 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 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 @@ -42,17 +58,35 @@ 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.1f; + private static final float SCALE_NOT_INITIALIZED = 0; + private static final Object LOCK = new Object(); private static IconNormalizer sIconNormalizer; private final int mMaxSize; private final Bitmap mBitmap; + private final Bitmap mBitmapARGB; private final Canvas mCanvas; + private final Paint mPaintMaskShape; 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 private final float[] mLeftBorder; private final float[] mRightBorder; + private final Rect mBounds; + private final RectF mShapeBounds; + private final Matrix mMatrix; + + private Paint mPaintIcon; + private Canvas mCanvasARGB; + + private File mDir; + private Random mRandom; private IconNormalizer(Context context) { // Use twice the icon size as maximum size to avoid scaling down twice. @@ -60,9 +94,112 @@ public class IconNormalizer { mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8); mCanvas = new Canvas(mBitmap); mPixels = new byte[mMaxSize * mMaxSize]; - + mPixelsARGB = new int[mMaxSize * mMaxSize]; mLeftBorder = 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); + mShapeBounds = new RectF(); + + mPaintIcon = new Paint(); + mPaintIcon.setColor(Color.WHITE); + mPaintMaskShape = new Paint(); + mPaintMaskShape.setColor(Color.RED); + mPaintMaskShape.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR)); + mMatrix = new Matrix(); + int[] mPixels = new int[mMaxSize * mMaxSize]; + mAdaptiveIconScale = SCALE_NOT_INITIALIZED; + + // Only needed when DEBUG == true + if (DEBUG) { + 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. + int id = mRandom.nextInt(); + mBitmapARGB.eraseColor(Color.TRANSPARENT); + mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon); + + if (DEBUG) { + final File beforeFile = new File(mDir, "isShape" + id + "_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.setScale(mBounds.width(), mBounds.height()); + mMatrix.postTranslate(mBounds.left, mBounds.top); + maskPath.transform(mMatrix); + maskPath.computeBounds(mShapeBounds, false); + + // XOR operation + mCanvasARGB.drawPath(maskPath, mPaintMaskShape); + + if (DEBUG) { + final File afterFile = new File(mDir, "isShape" + id + "_after.png"); + try { + mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100, + new FileOutputStream(afterFile)); + } catch (Exception e) {} + } + + // Check if the XOR operation result is almost transparent + if (!isTransparentBitmap(mBitmapARGB)) { + 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:" + percentageDiffPixels + "="+ sum + "/" + mBounds.width() * mBounds.height()); + } + return transparentImage; } /** @@ -79,7 +216,13 @@ public class IconNormalizer { * * @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) { + outBounds.set(mBounds); + return mAdaptiveIconScale; + } int width = d.getIntrinsicWidth(); int height = d.getIntrinsicHeight(); if (width <= 0 || height <= 0) { @@ -169,20 +312,30 @@ public class IconNormalizer { if (hullByRect < CIRCLE_AREA_BY_RECT) { scaleRequired = MAX_CIRCLE_AREA_FACTOR; } 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) { - outBounds.left = ((float) leftX) / width; - outBounds.right = 1 - ((float) rightX) / width; + mBounds.top = topY; + mBounds.bottom = bottomY; - outBounds.top = ((float) topY) / height; - outBounds.bottom = 1 - ((float) bottomY) / height; + if (outBounds != null) { + 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); // 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; + if (Utilities.isAtLeastO() && d instanceof AdaptiveIconDrawable && + mAdaptiveIconScale == SCALE_NOT_INITIALIZED) { + mAdaptiveIconScale = scale; + } return scale; } diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index ef54661d3..ab589f198 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -28,6 +28,7 @@ import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PaintDrawable; @@ -94,8 +95,29 @@ public class LauncherIcons { */ public static Bitmap createBadgedIconBitmap( Drawable icon, UserHandle user, Context context) { - float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? - 1 : IconNormalizer.getInstance(context).getScale(icon, null); + + IconNormalizer normalizer; + float scale = 1f; + if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) { + normalizer = IconNormalizer.getInstance(context); + if (Utilities.isAtLeastO()) { + boolean[] outShape = new boolean[1]; + AdaptiveIconDrawable dr = (AdaptiveIconDrawable) + context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); + dr.setBounds(0, 0, 1, 1); + scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape); + if (FeatureFlags.LEGACY_ICON_TREATMENT && + !outShape[0]){ + Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale); + if (wrappedIcon != icon) { + icon = wrappedIcon; + scale = normalizer.getScale(icon, null, null, null); + } + } + } else { + scale = normalizer.getScale(icon, null, null, null); + } + } Bitmap bitmap = createIconBitmap(icon, context, scale); return badgeIconForUser(bitmap, user, context); } @@ -124,8 +146,29 @@ public class LauncherIcons { */ public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context) { RectF iconBounds = new RectF(); - float scale = FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? - 1 : IconNormalizer.getInstance(context).getScale(icon, iconBounds); + IconNormalizer normalizer; + float scale = 1f; + if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) { + normalizer = IconNormalizer.getInstance(context); + if (Utilities.isAtLeastO()) { + boolean[] outShape = new boolean[1]; + AdaptiveIconDrawable dr = (AdaptiveIconDrawable) + context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); + dr.setBounds(0, 0, 1, 1); + scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape); + if (Utilities.isAtLeastO() && FeatureFlags.LEGACY_ICON_TREATMENT && + !outShape[0]) { + Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale); + if (wrappedIcon != icon) { + icon = wrappedIcon; + scale = normalizer.getScale(icon, iconBounds, null, null); + } + } + } else { + scale = normalizer.getScale(icon, iconBounds, null, null); + } + + } scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds)); return createIconBitmap(icon, context, scale); } @@ -165,10 +208,8 @@ public class LauncherIcons { * @param scale the scale to apply before drawing {@param icon} on the canvas */ public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) { - icon = wrapToAdaptiveIconDrawable(context, icon); synchronized (sCanvas) { final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize; - int width = iconBitmapSize; int height = iconBitmapSize; @@ -236,7 +277,7 @@ public class LauncherIcons { * shrink the legacy icon and set it as foreground. Use color drawable as background to * create AdaptiveIconDrawable. */ - static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable) { + static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) { if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) { return drawable; } @@ -246,8 +287,10 @@ public class LauncherIcons { if (!clazz.isAssignableFrom(drawable.getClass())) { Drawable iconWrapper = context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate(); - ((FixedScaleDrawable) clazz.getMethod("getForeground").invoke(iconWrapper)) - .setDrawable(drawable); + FixedScaleDrawable fsd = ((FixedScaleDrawable) clazz.getMethod("getForeground") + .invoke(iconWrapper)); + fsd.setDrawable(drawable); + fsd.setScale(scale); return iconWrapper; } diff --git a/src_flags/com/android/launcher3/config/FeatureFlags.java b/src_flags/com/android/launcher3/config/FeatureFlags.java index 4b3e2e40a..1c3e19f80 100644 --- a/src_flags/com/android/launcher3/config/FeatureFlags.java +++ b/src_flags/com/android/launcher3/config/FeatureFlags.java @@ -49,8 +49,8 @@ public final class FeatureFlags { public static final boolean LIGHT_STATUS_BAR = false; // When enabled icons are badged with the number of notifications associated with that app. public static final boolean BADGE_ICONS = true; - // When enabled, icons not supporting {@link MaskableIconDrawable} will be wrapped in this class. - public static final boolean LEGACY_ICON_TREATMENT = false; + // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}. + public static final boolean LEGACY_ICON_TREATMENT = true; // When enabled, adaptive icons would have shadows baked when being stored to icon cache. public static final boolean ADAPTIVE_ICON_SHADOW = true; // When enabled, app discovery will be enabled if service is implemented |