mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-20 03:08:19 +00:00
Merge "Normalizing app icons based on the standard icon guidelines" into ub-launcher3-burnaby-polish
This commit is contained in:
@@ -782,7 +782,7 @@ public class IconCache {
|
||||
}
|
||||
|
||||
private static final class IconDB extends SQLiteOpenHelper {
|
||||
private final static int DB_VERSION = 7;
|
||||
private final static int DB_VERSION = 8;
|
||||
|
||||
private final static String TABLE_NAME = "icons";
|
||||
private final static String COLUMN_ROWID = "rowid";
|
||||
|
||||
@@ -65,10 +65,10 @@ public class InvariantDeviceProfile {
|
||||
*/
|
||||
public int numFolderRows;
|
||||
public int numFolderColumns;
|
||||
float iconSize;
|
||||
int iconBitmapSize;
|
||||
int fillResIconDpi;
|
||||
float iconTextSize;
|
||||
public float iconSize;
|
||||
public int iconBitmapSize;
|
||||
public int fillResIconDpi;
|
||||
public float iconTextSize;
|
||||
|
||||
/**
|
||||
* Number of icons inside the hotseat area.
|
||||
|
||||
@@ -61,6 +61,7 @@ import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.util.IconNormalizer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -197,12 +198,15 @@ public final class Utilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}
|
||||
* Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
|
||||
* The bitmap is also visually normalized with other icons.
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public static Bitmap createBadgedIconBitmap(
|
||||
Drawable icon, UserHandleCompat user, Context context) {
|
||||
Bitmap bitmap = createIconBitmap(icon, context);
|
||||
float scale = LauncherAppState.isDogfoodBuild() ?
|
||||
IconNormalizer.getInstance().getScale(icon) : 1;
|
||||
Bitmap bitmap = createIconBitmap(icon, context, scale);
|
||||
if (Utilities.ATLEAST_LOLLIPOP && user != null
|
||||
&& !UserHandleCompat.myUserHandle().equals(user)) {
|
||||
BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
|
||||
@@ -222,6 +226,13 @@ public final class Utilities {
|
||||
* Returns a bitmap suitable for the all apps view.
|
||||
*/
|
||||
public static Bitmap createIconBitmap(Drawable icon, Context context) {
|
||||
return createIconBitmap(icon, context, 1.0f /* scale */);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scale the scale to apply before drawing {@param icon} on the canvas
|
||||
*/
|
||||
public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
|
||||
synchronized (sCanvas) {
|
||||
final int iconBitmapSize = getIconBitmapSize();
|
||||
|
||||
@@ -277,7 +288,10 @@ public final class Utilities {
|
||||
|
||||
sOldBounds.set(icon.getBounds());
|
||||
icon.setBounds(left, top, left+width, top+height);
|
||||
canvas.save(Canvas.MATRIX_SAVE_FLAG);
|
||||
canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
|
||||
icon.draw(canvas);
|
||||
canvas.restore();
|
||||
icon.setBounds(sOldBounds);
|
||||
canvas.setBitmap(null);
|
||||
|
||||
@@ -334,7 +348,7 @@ public final class Utilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}.
|
||||
* Inverse of {@link #getDescendantCoordRelativeToParent(View, View, int[], boolean)}.
|
||||
*/
|
||||
public static float mapCoordInSelfToDescendent(View descendant, View root,
|
||||
int[] coord) {
|
||||
|
||||
239
src/com/android/launcher3/util/IconNormalizer.java
Normal file
239
src/com/android/launcher3/util/IconNormalizer.java
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.util;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class IconNormalizer {
|
||||
|
||||
// Ratio of icon visible area to full icon size for a square shaped icon
|
||||
private static final float MAX_SQUARE_AREA_FACTOR = 359.0f / 576;
|
||||
// Ratio of icon visible area to full icon size for a circular shaped icon
|
||||
private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
|
||||
|
||||
private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
|
||||
|
||||
// Slope used to calculate icon visible area to full icon size for any generic shaped icon.
|
||||
private static final float LINEAR_SCALE_SLOPE =
|
||||
(MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
|
||||
|
||||
private static final int MIN_VISIBLE_ALPHA = 40;
|
||||
|
||||
private static final Object LOCK = new Object();
|
||||
private static IconNormalizer sIconNormalizer;
|
||||
|
||||
private final int mMaxSize;
|
||||
private final Bitmap mBitmap;
|
||||
private final Canvas mCanvas;
|
||||
private final byte[] mPixels;
|
||||
|
||||
// for each y, stores the position of the leftmost x and the rightmost x
|
||||
private final float[] mLeftBorder;
|
||||
private final float[] mRightBorder;
|
||||
|
||||
private IconNormalizer() {
|
||||
// Use twice the icon size as maximum size to avoid scaling down twice.
|
||||
mMaxSize = LauncherAppState.getInstance().getInvariantDeviceProfile().iconBitmapSize * 2;
|
||||
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
|
||||
mCanvas = new Canvas(mBitmap);
|
||||
mPixels = new byte[mMaxSize * mMaxSize];
|
||||
|
||||
mLeftBorder = new float[mMaxSize];
|
||||
mRightBorder = new float[mMaxSize];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* We first calculate the convex hull of the visible portion of the icon.
|
||||
* This hull then compared with the bounding rectangle of the hull to find how closely it
|
||||
* resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
|
||||
* ideal solution but it gives satisfactory result without affecting the performance.
|
||||
*
|
||||
* This closeness is used to determine the ratio of hull area to the full icon size.
|
||||
* Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
|
||||
*/
|
||||
public synchronized float getScale(Drawable d) {
|
||||
int width = d.getIntrinsicWidth();
|
||||
int height = d.getIntrinsicHeight();
|
||||
if (width <= 0 || height <= 0) {
|
||||
width = width <= 0 || width > mMaxSize ? mMaxSize : width;
|
||||
height = height <= 0 || height > mMaxSize ? mMaxSize : height;
|
||||
} else if (width > mMaxSize || height > mMaxSize) {
|
||||
int max = Math.max(width, height);
|
||||
width = mMaxSize * width / max;
|
||||
height = mMaxSize * height / max;
|
||||
}
|
||||
|
||||
mBitmap.eraseColor(Color.TRANSPARENT);
|
||||
d.setBounds(0, 0, width, height);
|
||||
d.draw(mCanvas);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(mPixels);
|
||||
buffer.rewind();
|
||||
mBitmap.copyPixelsToBuffer(buffer);
|
||||
|
||||
// Overall bounds of the visible icon.
|
||||
int topY = -1;
|
||||
int bottomY = -1;
|
||||
int leftX = mMaxSize + 1;
|
||||
int rightX = -1;
|
||||
|
||||
// Create border by going through all pixels one row at a time and for each row find
|
||||
// the first and the last non-transparent pixel. Set those values to mLeftBorder and
|
||||
// mRightBorder and use -1 if there are no visible pixel in the row.
|
||||
|
||||
// buffer position
|
||||
int index = 0;
|
||||
// buffer shift after every row, width of buffer = mMaxSize
|
||||
int rowSizeDiff = mMaxSize - width;
|
||||
// first and last position for any row.
|
||||
int firstX, lastX;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
firstX = lastX = -1;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
|
||||
if (firstX == -1) {
|
||||
firstX = x;
|
||||
}
|
||||
lastX = x;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
index += rowSizeDiff;
|
||||
|
||||
mLeftBorder[y] = firstX;
|
||||
mRightBorder[y] = lastX;
|
||||
|
||||
// If there is at least one visible pixel, update the overall bounds.
|
||||
if (firstX != -1) {
|
||||
bottomY = y;
|
||||
if (topY == -1) {
|
||||
topY = y;
|
||||
}
|
||||
|
||||
leftX = Math.min(leftX, firstX);
|
||||
rightX = Math.max(rightX, lastX);
|
||||
}
|
||||
}
|
||||
|
||||
if (topY == -1 || rightX == -1) {
|
||||
// No valid pixels found. Do not scale.
|
||||
return 1;
|
||||
}
|
||||
|
||||
convertToConvexArray(mLeftBorder, 1, topY, bottomY);
|
||||
convertToConvexArray(mRightBorder, -1, topY, bottomY);
|
||||
|
||||
// Area of the convex hull
|
||||
float area = 0;
|
||||
for (int y = 0; y < height; y++) {
|
||||
if (mLeftBorder[y] <= -1) {
|
||||
continue;
|
||||
}
|
||||
area += mRightBorder[y] - mLeftBorder[y] + 1;
|
||||
}
|
||||
|
||||
// Area of the rectangle required to fit the convex hull
|
||||
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
|
||||
float hullByRect = area / rectArea;
|
||||
|
||||
float scaleRequired;
|
||||
if (hullByRect < CIRCLE_AREA_BY_RECT) {
|
||||
scaleRequired = MAX_CIRCLE_AREA_FACTOR;
|
||||
} else {
|
||||
scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
|
||||
}
|
||||
|
||||
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;
|
||||
return scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies {@param xCordinates} to represent a convex border. Fills in all missing values
|
||||
* (except on either ends) with appropriate values.
|
||||
* @param xCordinates map of x coordinate per y.
|
||||
* @param direction 1 for left border and -1 for right border.
|
||||
* @param topY the first Y position (inclusive) with a valid value.
|
||||
* @param bottomY the last Y position (inclusive) with a valid value.
|
||||
*/
|
||||
private static void convertToConvexArray(
|
||||
float[] xCordinates, int direction, int topY, int bottomY) {
|
||||
int total = xCordinates.length;
|
||||
// The tangent at each pixel.
|
||||
float[] angles = new float[total - 1];
|
||||
|
||||
int first = topY; // First valid y coordinate
|
||||
int last = -1; // Last valid y coordinate which didn't have a missing value
|
||||
|
||||
float lastAngle = Float.MAX_VALUE;
|
||||
|
||||
for (int i = topY + 1; i <= bottomY; i++) {
|
||||
if (xCordinates[i] <= -1) {
|
||||
continue;
|
||||
}
|
||||
int start;
|
||||
|
||||
if (lastAngle == Float.MAX_VALUE) {
|
||||
start = first;
|
||||
} else {
|
||||
float currentAngle = (xCordinates[i] - xCordinates[last]) / (i - last);
|
||||
start = last;
|
||||
// If this position creates a concave angle, keep moving up until we find a
|
||||
// position which creates a convex angle.
|
||||
if ((currentAngle - lastAngle) * direction < 0) {
|
||||
while (start > first) {
|
||||
start --;
|
||||
currentAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
|
||||
if ((currentAngle - angles[start]) * direction >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset from last check
|
||||
lastAngle = (xCordinates[i] - xCordinates[start]) / (i - start);
|
||||
// Update all the points from start.
|
||||
for (int j = start; j < i; j++) {
|
||||
angles[j] = lastAngle;
|
||||
xCordinates[j] = xCordinates[start] + lastAngle * (j - start);
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
public static IconNormalizer getInstance() {
|
||||
synchronized (LOCK) {
|
||||
if (sIconNormalizer == null) {
|
||||
sIconNormalizer = new IconNormalizer();
|
||||
}
|
||||
}
|
||||
return sIconNormalizer;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user