use of com.android.launcher3.AdaptiveIconCompat in project Neo-Launcher by NeoApplications.
the class BaseIconFactory method normalizeAndWrapToAdaptiveIcon.
private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon, boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
float scale;
if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
if (mWrapperIcon == null || !((AdaptiveIconCompat) mWrapperIcon).isMaskValid()) {
mWrapperIcon = AdaptiveIconCompat.wrap(mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate());
}
AdaptiveIconCompat dr = (AdaptiveIconCompat) mWrapperIcon;
dr.setBounds(0, 0, 1, 1);
boolean[] outShape = new boolean[1];
scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
if (!outShape[0] && (icon instanceof NonAdaptiveIconDrawable)) {
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
fsd.setDrawable(icon);
fsd.setScale(scale);
icon = dr;
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
}
} else {
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
}
outScale[0] = scale;
return icon;
}
use of com.android.launcher3.AdaptiveIconCompat in project Neo-Launcher by NeoApplications.
the class BaseIconFactory method createBadgedIconBitmap.
/**
* Creates bitmap using the source drawable and various parameters.
* The bitmap is visually normalized with other icons and has enough spacing to add shadow.
*
* @param icon source of the icon
* @param user info can be used for a badge
* @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated
* @param isInstantApp info can be used for a badge
* @param scale returns the scale result from normalization
* @return a bitmap suitable for disaplaying as an icon at various system UIs.
*/
public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user, boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
if (scale == null) {
scale = new float[1];
}
icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
Bitmap bitmap = createIconBitmap(icon, scale[0]);
if (ATLEAST_OREO && icon instanceof AdaptiveIconCompat) {
mCanvas.setBitmap(bitmap);
getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
mCanvas.setBitmap(null);
}
if (isInstantApp) {
badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
}
if (user != null) {
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
Drawable badged = mPm.getUserBadgedIcon(drawable, user);
if (badged instanceof BitmapDrawable) {
bitmap = ((BitmapDrawable) badged).getBitmap();
} else {
bitmap = createIconBitmap(badged, 1f);
}
}
return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
}
use of com.android.launcher3.AdaptiveIconCompat in project Neo-Launcher by NeoApplications.
the class AdaptiveIconGenerator method loop.
private void loop() {
if (Utilities.ATLEAST_OREO && shouldWrap) {
if (roundIcon != null && roundIcon instanceof AdaptiveIconCompat) {
icon = roundIcon;
}
Drawable extractee = icon;
if (icon instanceof AdaptiveIconCompat) {
if (!treatWhite) {
onExitLoop();
return;
}
AdaptiveIconCompat aid = (AdaptiveIconCompat) extractee;
// we still check this seperately as this is the only information we need from the background
if (!isSingleColor(aid.getBackground(), Color.WHITE)) {
onExitLoop();
return;
}
isBackgroundWhite = true;
extractee = aid.getForeground();
}
if (extractee == null) {
Log.e("AdaptiveIconGenerator", "extractee is null, skipping.");
onExitLoop();
return;
}
LauncherIcons li = LauncherIcons.obtain(context);
IconNormalizer normalizer = li.getNormalizer();
li.recycle();
boolean[] outShape = new boolean[1];
RectF bounds = new RectF();
initTmpIfNeeded();
// scale = normalizer.getScale(extractee, bounds, tmp.getIconMask(), outShape);
scale = normalizer.getScale(extractee, bounds, tmp.getIconMask(), outShape, MIN_VISIBLE_ALPHA);
matchesMaskShape = outShape[0];
if (extractee instanceof ColorDrawable) {
isFullBleed = true;
fullBleedChecked = true;
}
width = extractee.getIntrinsicWidth();
height = extractee.getIntrinsicHeight();
aWidth = width * (1 - (bounds.left + bounds.right));
aHeight = height * (1 - (bounds.top + bounds.bottom));
// Check if the icon is squareish
final float ratio = aHeight / aWidth;
boolean isSquareish = 0.999 < ratio && ratio < 1.0001;
boolean almostSquarish = isSquareish || (0.97 < ratio && ratio < 1.005);
if (!isSquareish) {
isFullBleed = false;
fullBleedChecked = true;
}
final Bitmap bitmap = Utilities.drawableToBitmap(extractee);
if (bitmap == null) {
onExitLoop();
return;
}
if (!bitmap.hasAlpha()) {
isFullBleed = true;
fullBleedChecked = true;
}
final int size = height * width;
SparseIntArray rgbScoreHistogram = new SparseIntArray(NUMBER_OF_COLORS_GUESSTIMATION);
final int[] pixels = new int[size];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
/*
* Calculate the number of padding pixels around the actual icon (i)
* +----------------+
* | top |
* +---+--------+---+
* | | | |
* | l | i | r |
* | | | |
* +---+--------+---+
* | bottom |
* +----------------+
*/
float adjHeight = height - bounds.top - bounds.bottom;
float l = bounds.left * width * adjHeight;
float top = bounds.top * height * width;
float r = bounds.right * width * adjHeight;
float bottom = bounds.bottom * height * width;
int addPixels = Math.round(l + top + r + bottom);
// Any icon with less than 10% transparent pixels (padding excluded) is considered "full-bleed-ish"
final int maxTransparent = (int) (round(size * .10) + addPixels);
// Any icon with less than 27% transparent pixels (padding excluded) doesn't need a color mix-in
final int noMixinScore = (int) (round(size * .27) + addPixels);
int highScore = 0;
int bestRGB = 0;
int transparentScore = 0;
for (int pixel : pixels) {
int alpha = 0xFF & (pixel >> 24);
if (alpha < MIN_VISIBLE_ALPHA) {
// Drop mostly-transparent pixels.
transparentScore++;
if (transparentScore > maxTransparent) {
isFullBleed = false;
fullBleedChecked = true;
if (!extractColor && transparentScore > noMixinScore) {
break;
}
}
continue;
}
// Reduce color complexity.
int rgb = ColorExtractor.posterize(pixel);
if (rgb < 0) {
// Defensively avoid array bounds violations.
continue;
}
int currentScore = rgbScoreHistogram.get(rgb) + 1;
rgbScoreHistogram.append(rgb, currentScore);
if (currentScore > highScore) {
highScore = currentScore;
bestRGB = rgb;
}
}
// add back the alpha channel
bestRGB |= 0xff << 24;
// not yet checked = not set to false = has to be full bleed, isBackgroundWhite = true = is adaptive
isFullBleed |= !fullBleedChecked && !isBackgroundWhite;
// return early if a mix-in isnt needed
noMixinNeeded = !isFullBleed && !isBackgroundWhite && almostSquarish && transparentScore <= noMixinScore;
if (isFullBleed || noMixinNeeded) {
backgroundColor = bestRGB;
onExitLoop();
return;
}
if (!extractColor) {
backgroundColor = Color.WHITE;
onExitLoop();
return;
}
// "single color"
final int numColors = rgbScoreHistogram.size();
boolean singleColor = numColors <= SINGLE_COLOR_LIMIT;
// Convert to HSL to get the lightness and adjust the color
final float[] hsl = new float[3];
ColorUtils.colorToHSL(bestRGB, hsl);
float lightness = hsl[2];
boolean light = lightness > .5;
// Apply dark background to mostly white icons
boolean veryLight = lightness > .75 && singleColor;
// Apply light background to mostly dark icons
boolean veryDark = lightness < .35 && singleColor;
// Adjust color to reach suitable contrast depending on the relationship between the colors
final int opaqueSize = size - transparentScore;
final float pxPerColor = opaqueSize / (float) numColors;
float mixRatio = min(max(pxPerColor / highScore, .15f), .7f);
// Vary color mix-in based on lightness and amount of colors
int fill = (light && !veryLight) || veryDark ? 0xFFFFFFFF : 0xFF333333;
backgroundColor = ColorUtils.blendARGB(bestRGB, fill, mixRatio);
// backgroundColor = Utilities.findDominantColorByHue(bitmap, 20);
}
onExitLoop();
}
use of com.android.launcher3.AdaptiveIconCompat in project Neo-Launcher by NeoApplications.
the class IconNormalizer method getScale.
/**
* 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.
* <p>
* 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.
* <p>
* 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}
*
* @param outBounds optional rect to receive the fraction distance from each edge.
*/
public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds, @Nullable Path path, @Nullable boolean[] outMaskShape, int minVisibleAlpha) {
if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconCompat) {
if (mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
mAdaptiveIconScale = normalizeAdaptiveIcon(d, mMaxSize, mAdaptiveIconBounds);
}
if (outBounds != null) {
outBounds.set(mAdaptiveIconBounds);
}
return mAdaptiveIconScale;
}
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) > minVisibleAlpha) {
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;
}
mBounds.left = leftX;
mBounds.right = rightX;
mBounds.top = topY;
mBounds.bottom = bottomY;
if (outBounds != null) {
outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height, 1 - ((float) mBounds.right) / width, 1 - ((float) mBounds.bottom) / height);
}
if (outMaskShape != null && mEnableShapeDetection && outMaskShape.length > 0) {
outMaskShape[0] = isShape(path, minVisibleAlpha);
}
// Area of the rectangle required to fit the convex hull
float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
return getScale(area, rectArea, width * height);
}
use of com.android.launcher3.AdaptiveIconCompat in project Neo-Launcher by NeoApplications.
the class AdaptiveIconGenerator method genResult.
private Drawable genResult() {
if (!Utilities.ATLEAST_OREO || !shouldWrap) {
if (roundIcon != null) {
if (icon instanceof AdaptiveIconCompat && !(roundIcon instanceof AdaptiveIconCompat)) {
return icon;
}
return roundIcon;
}
return icon;
}
if (icon instanceof AdaptiveIconCompat) {
if (!treatWhite || !isBackgroundWhite) {
return icon;
}
if (((AdaptiveIconCompat) icon).getBackground() instanceof ColorDrawable) {
AdaptiveIconCompat mutIcon = (AdaptiveIconCompat) icon.mutate();
((ColorDrawable) mutIcon.getBackground()).setColor(backgroundColor);
return mutIcon;
}
return new AdaptiveIconCompat(new ColorDrawable(backgroundColor), ((AdaptiveIconCompat) icon).getForeground());
}
initTmpIfNeeded();
((FixedScaleDrawable) tmp.getForeground()).setDrawable(icon);
if (matchesMaskShape || isFullBleed || noMixinNeeded) {
float scale;
if (noMixinNeeded) {
float upScale = min(width / aWidth, height / aHeight);
scale = NO_MIXIN_ICON_SCALE * upScale;
} else {
float upScale = max(width / aWidth, height / aHeight);
scale = FULL_BLEED_ICON_SCALE * upScale;
}
((FixedScaleDrawable) tmp.getForeground()).setScale(scale);
} else {
((FixedScaleDrawable) tmp.getForeground()).setScale(scale);
}
((ColorDrawable) tmp.getBackground()).setColor(backgroundColor);
return tmp;
}
Aggregations