blob: 8d69dbafe3ff8c826aa21285a681b043643442a2 [file] [log] [blame]
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.browser;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.View;
import java.util.HashMap;
import java.util.Map;
/**
* This represents a WebSite Tile that is created from a Drawable and will scale across any
* area this is externally layouted to. There are 3 possible looks:
* - just the favicon (TYPE_SMALL)
* - drop-shadow plus a thin overlay border (1dp) (TYPE_MEDIUM)
* - centered favicon, extended color, rounded base (TYPE_LARGE)
*
* By centralizing everything in this class we make customization of looks much easier.
*
* NOTES:
* - do not set a background from the outside; this overrides it automatically
*/
public class SiteTileView extends View {
// public trust level constants
public static final int TRUST_UNKNOWN = 0; // default
public static final int TRUST_AVOID = 0x01;
public static final int TRUST_UNTRUSTED = 0x02;
public static final int TRUST_TRUSTED = 0x04;
private static final int TRUST_MASK = 0x07;
// static configuration
private static final int THRESHOLD_MEDIUM_DP = 32;
private static final int THRESHOLD_LARGE_DP = 64;
private static final int LARGE_FAVICON_SIZE_DP = 48;
private static final int BACKGROUND_DRAWABLE_RES = R.drawable.img_tile_background;
private static final int DEFAULT_SITE_FAVICON = 0;
private static final float FILLER_RADIUS_DP = 2f; // sync with the bg image radius
private static final int FILLER_FALLBACK_COLOR = Color.WHITE; // in case there is no favicon
private static final boolean BADGE_SHOW_BLOCKED_COUNT = false;
// internal enums
private static final int TYPE_SMALL = 1;
private static final int TYPE_MEDIUM = 2;
private static final int TYPE_LARGE = 3;
private static final int TYPE_AUTO = 0;
private static final int COLOR_AUTO = 0;
// configuration
private Bitmap mFaviconBitmap = null;
private Paint mFundamentalPaint = null;
private int mFaviconWidth = 0;
private int mFaviconHeight = 0;
private int mForcedFundamentalColor = COLOR_AUTO;
private boolean mBackgroundDisabled = false;
private int mTrustLevel = TRUST_UNKNOWN;
private int mBadgeBlockedObjectsCount = 0;
private boolean mBadgeHasCertIssues = false;
// runtime params set on Layout
private int mCurrentWidth = 0;
private int mCurrentHeight = 0;
private int mCurrentType = TYPE_MEDIUM;
private int mPaddingLeft = 0;
private int mPaddingTop = 0;
private int mPaddingRight = 0;
private int mPaddingBottom = 0;
private boolean mCurrentShadowDrawn = false;
// static objects, to be recycled amongst instances (this is an optimization)
// NOTE: package-visible statics are for optimized usage inside FolderTileView as well
private static int sMediumPxThreshold = -1;
private static int sLargePxThreshold = -1;
private static int sLargeFaviconPx = -1;
/* package */ static float sRoundedRadius = -1;
private static Paint sBitmapPaint = null;
private static Paint sBadgeTextPaint = null;
private static Rect sSrcRect = new Rect();
private static Rect sDstRect = new Rect();
/* package */ static RectF sRectF = new RectF();
private static Drawable sBackgroundDrawable = null;
private static class BadgeAssets {
Drawable back;
Drawable accent;
int textColor;
}
private static Map<Integer, BadgeAssets> sBadges;
private static Bitmap sDefaultSiteBitmap = null;
/* package */ static Rect sBackgroundDrawablePadding = new Rect();
/* XML constructors */
public SiteTileView(Context context) {
super(context);
xmlInit(null, 0);
}
public SiteTileView(Context context, AttributeSet attrs) {
super(context, attrs);
xmlInit(attrs, 0);
}
public SiteTileView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
xmlInit(attrs, defStyle);
}
/* Programmatic Constructors */
public SiteTileView(Context context, Bitmap favicon) {
super(context);
init(favicon, COLOR_AUTO);
}
public SiteTileView(Context context, Bitmap favicon, int fundamentalColor) {
super(context);
init(favicon, fundamentalColor);
}
/**
* Changes the current favicon (and associated fundamental color) on the fly
*/
public void replaceFavicon(Bitmap favicon) {
replaceFavicon(favicon, COLOR_AUTO);
}
/**
* Changes the current favicon (and associated fundamental color) on the fly
* @param favicon the new favicon
* @param fundamentalColor the new fudamental color, or COLOR_AUTO
*/
public void replaceFavicon(Bitmap favicon, int fundamentalColor) {
init(favicon, fundamentalColor);
requestLayout();
}
/**
* Disables the automatic background and filling. Useful for things that are not really
* "Website Tiles", like folders.
* @param disabled true to disable the background (defaults to false)
*/
public void setBackgroundDisabled(boolean disabled) {
if (mBackgroundDisabled != disabled) {
mBackgroundDisabled = disabled;
invalidate();
}
}
/**
* This results in the Badge being updated
* @param trustLevel one of the TRUST_ constants
*/
public void setTrustLevel(int trustLevel) {
if (mTrustLevel != trustLevel) {
mTrustLevel = trustLevel;
invalidate();
}
}
/**
* Tells that there will be some message about issues inside
* @param certIssues true if there are issues.
*/
public void setBadgeHasCertIssues(boolean certIssues) {
if (certIssues != mBadgeHasCertIssues) {
mBadgeHasCertIssues = certIssues;
invalidate();
}
}
/**
* Sets the number of objects blocked (a positive contribution to the page). Presentation
* may or may not have the number indication.
* @param sessionCounter Counter of blocked objects. Use 0 to not display anything.
*/
public void setBadgeBlockedObjectsCount(int sessionCounter) {
if (sessionCounter != mBadgeBlockedObjectsCount) {
// repaint if going from or to 0, or if showing the ads count
//noinspection PointlessBooleanExpression,ConstantConditions
if (mBadgeBlockedObjectsCount == 0 || sessionCounter == 0 || BADGE_SHOW_BLOCKED_COUNT)
invalidate();
mBadgeBlockedObjectsCount = sessionCounter;
}
}
/**
* @return The fundamental color representing the site.
*/
public int getFundamentalColor() {
if (mForcedFundamentalColor != COLOR_AUTO)
return mForcedFundamentalColor;
if (mFundamentalPaint == null)
mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, COLOR_AUTO);
return mFundamentalPaint.getColor();
}
/*** private stuff ahead ***/
private boolean requiresBadge() {
return !mBackgroundDisabled && (mTrustLevel != TRUST_UNKNOWN || mBadgeHasCertIssues
|| mBadgeBlockedObjectsCount > 0);
}
private int computeBadgeMessages() {
// special case, for TRUST_AVOID, always show the common accent
if (mTrustLevel == TRUST_AVOID)
return 0;
// recompute number of 'messages' inside the badge
int count = 0;
if (mBadgeHasCertIssues)
count++;
if (mBadgeBlockedObjectsCount > 0)
count++;
// add the number of blocked objects (-1, for having already counted the message) if needed
if (BADGE_SHOW_BLOCKED_COUNT)
count += mBadgeBlockedObjectsCount - 1;
return count;
}
private void xmlInit(AttributeSet attrs, int defStyle) {
// load attributes
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.SiteTileView, defStyle, 0);
// fetch the drawable, if defined - then just extract and use the bitmap
final Drawable drawable = a.getDrawable(R.styleable.SiteTileView_android_src);
final Bitmap favicon = drawable instanceof BitmapDrawable ?
((BitmapDrawable) drawable).getBitmap() : null;
// check if we want it background-less (disable shadow and filler)
setBackgroundDisabled(a.getBoolean(R.styleable.SiteTileView_disableBackground, false));
// read the trust level (unknown, aka 'default', if not present)
setTrustLevel(a.getInteger(R.styleable.SiteTileView_trustLevel, TRUST_UNKNOWN)
& TRUST_MASK);
// read the amount of blocked objects (or 0 if not present)
setBadgeBlockedObjectsCount(a.getInteger(R.styleable.SiteTileView_blockedObjects, 0));
// delete attribute resolution
a.recycle();
// proceed with real initialization
init(favicon, COLOR_AUTO);
}
private void init(Bitmap favicon, int fundamentalColor) {
mFaviconBitmap = favicon;
// show a default favicon if nothing is set (consider removing this, it's ugly)
if (mFaviconBitmap == null && DEFAULT_SITE_FAVICON != 0) {
if (sDefaultSiteBitmap == null)
sDefaultSiteBitmap = BitmapFactory.decodeResource(getResources(),
DEFAULT_SITE_FAVICON);
mFaviconBitmap = sDefaultSiteBitmap;
fundamentalColor = 0xFF262626;
}
if (mFaviconBitmap != null) {
mFaviconWidth = mFaviconBitmap.getWidth();
mFaviconHeight = mFaviconBitmap.getHeight();
}
// don't compute the paint right now, just save any hint for later
mFundamentalPaint = null;
mForcedFundamentalColor = fundamentalColor;
// shared (static) resources initialization; except for background, inited on-demand
ensureCommonLoaded(getResources());
// change when clicked
setClickable(true);
}
static void ensureCommonLoaded(Resources r) {
// check if already initialized
if (sMediumPxThreshold != -1)
return;
// heuristics thresholds
final DisplayMetrics displayMetrics = r.getDisplayMetrics();
sMediumPxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
THRESHOLD_MEDIUM_DP, displayMetrics);
sLargePxThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
THRESHOLD_LARGE_DP, displayMetrics);
sLargeFaviconPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
LARGE_FAVICON_SIZE_DP, displayMetrics);
// rounded radius
sRoundedRadius = FILLER_RADIUS_DP > 0 ? TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, FILLER_RADIUS_DP, displayMetrics) : 0;
// bitmap paint (copy, smooth scale)
sBitmapPaint = new Paint();
sBitmapPaint.setColor(Color.BLACK);
sBitmapPaint.setFilterBitmap(true);
// badge text paint (anti-aliased)
sBadgeTextPaint = new Paint();
sBadgeTextPaint.setAntiAlias(true);
Typeface badgeTypeface = Typeface.create("sans-serif-medium", Typeface.NORMAL);
if (badgeTypeface != null)
sBadgeTextPaint.setTypeface(badgeTypeface);
// load the background (could be loaded on demand, but in the end it's always needed)
sBackgroundDrawable = r.getDrawable(BACKGROUND_DRAWABLE_RES);
if (sBackgroundDrawable != null)
sBackgroundDrawable.getPadding(sBackgroundDrawablePadding);
// load all the badge drawables
sBadges = new HashMap<>();
loadBadgeResources(r, TRUST_AVOID, R.drawable.img_deco_tile_avoid,
R.drawable.img_deco_tile_avoid_accent, R.color.TileBadgeTextAvoid);
loadBadgeResources(r, TRUST_UNTRUSTED, R.drawable.img_deco_tile_untrusted,
R.drawable.img_deco_tile_untrusted_accent, R.color.TileBadgeTextUntrusted);
loadBadgeResources(r, TRUST_UNKNOWN, R.drawable.img_deco_tile_unknown,
R.drawable.img_deco_tile_unknown_accent, R.color.TileBadgeTextUnknown);
loadBadgeResources(r, TRUST_TRUSTED, R.drawable.img_deco_tile_verified,
R.drawable.img_deco_tile_verified_accent, R.color.TileBadgeTextVerified);
}
private static void loadBadgeResources(Resources r, int t, int back, int accent, int color) {
BadgeAssets ba = new BadgeAssets();
ba.back = back == 0 ? null : r.getDrawable(back);
ba.accent = accent == 0 ? null : r.getDrawable(accent);
ba.textColor = color == 0 ? Color.TRANSPARENT : r.getColor(color);
sBadges.put(t, ba);
}
static Rect getBackgroundDrawablePadding() {
return sBackgroundDrawablePadding != null ? sBackgroundDrawablePadding : new Rect();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mCurrentWidth = right - left;
mCurrentHeight = bottom - top;
// auto-determine the "TYPE_" from the physical size of the layout
if (mCurrentWidth < sMediumPxThreshold && mCurrentHeight < sMediumPxThreshold)
mCurrentType = TYPE_SMALL;
else if (mCurrentWidth < sLargePxThreshold && mCurrentHeight < sLargePxThreshold)
mCurrentType = TYPE_MEDIUM;
else
mCurrentType = TYPE_LARGE;
// set or remove the background (if the need changed!)
boolean requiresBackgroundDrawable = mCurrentType >= TYPE_MEDIUM;
if (requiresBackgroundDrawable && !mCurrentShadowDrawn) {
// draw the background
mCurrentShadowDrawn = mCurrentType >= TYPE_LARGE;
// background -> padding
mPaddingLeft = sBackgroundDrawablePadding.left;
mPaddingTop = sBackgroundDrawablePadding.top;
mPaddingRight = sBackgroundDrawablePadding.right;
mPaddingBottom = sBackgroundDrawablePadding.bottom;
} else if (!requiresBackgroundDrawable && mCurrentShadowDrawn) {
// turn off background drawing
mCurrentShadowDrawn = false;
// no background -> no padding
mPaddingLeft = 0;
mPaddingTop = 0;
mPaddingRight = 0;
mPaddingBottom = 0;
}
// just proceed, do nothing here
super.onLayout(changed, left, top, right, bottom);
}
@Override
public void setPressed(boolean pressed) {
super.setPressed(pressed);
// schedule a repaint to show pressed/released
invalidate();
}
@Override
public void setSelected(boolean selected) {
super.setSelected(selected);
// schedule a repaint to show selected
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Selection State: make everything smaller
if (isSelected()) {
float scale = 0.8f;
canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
canvas.scale(scale, scale);
}
// Pressed state: make the button reach the finger
if (isPressed()) {
float scale = 1.1f;
canvas.translate(mCurrentWidth * (1 - scale) / 2, mCurrentHeight * (1 - scale) / 2);
canvas.scale(scale, scale);
}
final int left = mPaddingLeft;
final int top = mPaddingTop;
final int right = mCurrentWidth - mPaddingRight;
final int bottom = mCurrentHeight - mPaddingBottom;
final int contentWidth = right - left;
final int contentHeight = bottom - top;
// A. the background drawable (if set)
boolean requiresBackground = mCurrentShadowDrawn && sBackgroundDrawable != null
&& !isPressed() && !mBackgroundDisabled;
if (requiresBackground) {
sBackgroundDrawable.setBounds(0, 0, mCurrentWidth, mCurrentHeight);
sBackgroundDrawable.draw(canvas);
}
// B. (when needed) draw the background rectangle; sharp our rounded
boolean requiresFundamentalFiller = mCurrentType >= TYPE_LARGE && !mBackgroundDisabled;
if (requiresFundamentalFiller) {
// create the filler paint on demand (not all icons need it)
if (mFundamentalPaint == null)
mFundamentalPaint = createFundamentalPaint(mFaviconBitmap, mForcedFundamentalColor);
// paint if not white, since requiresBackground already painted it white
int fundamentalColor = mFundamentalPaint.getColor();
if (fundamentalColor != COLOR_AUTO &&
(fundamentalColor != Color.WHITE || !requiresBackground)) {
if (sRoundedRadius >= 1.) {
sRectF.set(left, top, right, bottom);
canvas.drawRoundRect(sRectF, sRoundedRadius, sRoundedRadius, mFundamentalPaint);
} else
canvas.drawRect(left, top, right, bottom, mFundamentalPaint);
}
}
// C. (if present) draw the favicon
boolean requiresFavicon = mFaviconBitmap != null
&& mFaviconWidth > 1 && mFaviconHeight > 1;
if (requiresFavicon) {
// destination can either fill, or auto-center
boolean fillSpace = mCurrentType <= TYPE_MEDIUM;
if (fillSpace || contentWidth < sLargeFaviconPx || contentHeight < sLargeFaviconPx) {
sDstRect.set(left, top, right, bottom);
} else {
int dstLeft = left + (contentWidth - sLargeFaviconPx) / 2;
int dstTop = top + (contentHeight - sLargeFaviconPx) / 2;
sDstRect.set(dstLeft, dstTop, dstLeft + sLargeFaviconPx, dstTop + sLargeFaviconPx);
}
// source has to 'crop proportionally' to keep the dest aspect ratio
sSrcRect.set(0, 0, mFaviconWidth, mFaviconHeight);
int sW = sSrcRect.width();
int sH = sSrcRect.height();
int dW = sDstRect.width();
int dH = sDstRect.height();
if (sW > 4 && sH > 4 && dW > 4 && dH > 4) {
float hScale = (float) dW / (float) sW;
float vScale = (float) dH / (float) sH;
if (hScale == vScale) {
// no transformation needed, just zoom
} else if (hScale < vScale) {
// horizontal crop
float hCrop = 1 - hScale / vScale;
int hCropPx = (int) (sW * hCrop / 2 + 0.5);
sSrcRect.left += hCropPx;
sSrcRect.right -= hCropPx;
canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
} else {
// vertical crop
float vCrop = 1 - vScale / hScale;
int vCropPx = (int) (sH * vCrop / 2 + 0.5);
sSrcRect.top += vCropPx;
sSrcRect.bottom -= vCropPx;
}
}
// blit favicon, croppped, scaled
canvas.drawBitmap(mFaviconBitmap, sSrcRect, sDstRect, sBitmapPaint);
}
// D. show badge, if requested
if (requiresBadge()) {
// retrieve the badge resources
final BadgeAssets ba = sBadges.get(mTrustLevel);
if (ba != null) {
// paint back
final Drawable back = ba.back;
int badgeL = 0, badgeT = 0, badgeW = 0, badgeH = 0;
if (back != null) {
badgeW = back.getIntrinsicWidth();
badgeH = back.getIntrinsicHeight();
badgeL = mCurrentWidth - mPaddingRight / 3 - badgeW;
badgeT = mCurrentHeight - mPaddingBottom / 3 - badgeH;
back.setBounds(badgeL, badgeT, badgeL + badgeW, badgeT + badgeH);
back.draw(canvas);
}
int messagesCount = computeBadgeMessages();
// paint accent, if 0 messages
if (messagesCount < 1) {
final Drawable accent = ba.accent;
if (accent != null && badgeW > 0 && badgeH > 0) {
int accentW = accent.getIntrinsicWidth();
int accentH = accent.getIntrinsicHeight();
int accentL = badgeL + (badgeW - accentW) / 2;
int accentT = badgeT + (badgeH - accentH) / 2;
accent.setBounds(accentL, accentT, accentL + accentW, accentT + accentH);
accent.draw(canvas);
}
}
// at least 1 message, draw text
else if (Color.alpha(ba.textColor) > 0) {
float textSize = Math.min(2 * contentWidth / 5, sMediumPxThreshold / 4) * 1.1f;
sBadgeTextPaint.setTextSize(textSize);
sBadgeTextPaint.setColor(ba.textColor);
final String text = String.valueOf(messagesCount);
int textWidth = Math.round(sBadgeTextPaint.measureText(text) / 2);
int textCx = badgeL + badgeW / 2;
int textCy = badgeT + badgeH / 2;
canvas.drawText(text, textCx - textWidth, textCy + textSize / 3 + 1,
sBadgeTextPaint);
}
}
}
/*if (true) { // DEBUG TYPE
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setTextSize(20);
canvas.drawText(String.valueOf(mCurrentType), 30, 30, paint);
}*/
}
/**
* Creates a fill Paint from the favicon, or using the forced color (if not COLOR_AUTO)
*/
private static Paint createFundamentalPaint(Bitmap favicon, int forceFillColor) {
final Paint fillPaint = new Paint();
if (forceFillColor != COLOR_AUTO)
fillPaint.setColor(forceFillColor);
else
fillPaint.setColor(guessFundamentalColor(favicon));
return fillPaint;
}
/**
* This uses very stupid mechanism - a 9x9 grid sample on the borders and center - and selects
* the color with the most frequency, or the center.
*
* @param bitmap the bitmap to guesss the color about
* @return a Color
*/
private static int guessFundamentalColor(Bitmap bitmap) {
if (bitmap == null)
return FILLER_FALLBACK_COLOR;
int height = bitmap.getHeight();
int width = bitmap.getWidth();
if (height < 2 || width < 2)
return FILLER_FALLBACK_COLOR;
// pick up to 9 colors
// NOTE: the order of sampling sets the precendece, in case of ties
int[] pxColors = new int[9];
int idx = 0;
if ((pxColors[idx] = sampleColor(bitmap, width / 2, height / 2)) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, width / 2, height - 1)) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, width - 1, height - 1)) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, width - 1, height / 2)) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, 0, 0 )) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, width / 2, 0 )) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, width - 1, 0 )) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, 0 , height / 2)) != 0) idx++;
if ((pxColors[idx] = sampleColor(bitmap, 0 , height - 1)) != 0) idx++;
// find the most popular
int popColor = -1;
int popCount = -1;
for (int i = 0; i < idx; i++) {
int thisColor = pxColors[i];
int thisCount = 0;
for (int j = 0; j < idx; j++) {
if (pxColors[j] == thisColor)
thisCount++;
}
if (thisCount > popCount) {
popColor = thisColor;
popCount = thisCount;
}
}
return popCount > -1 ? popColor : FILLER_FALLBACK_COLOR;
}
/**
* @return Color, but if it's 0, you should discard it (not representative)
*/
private static int sampleColor(Bitmap bitmap, int x, int y) {
final int color = bitmap.getPixel(x, y);
// discard semi-transparent pixels, because they're probably from a spurious border
//if ((color >>> 24) <= 128)
// return 0;
// compose transparent pixels with white, since the BG will be white anyway
final int alpha = Color.alpha(color);
if (alpha == 0)
return Color.WHITE;
if (alpha < 255) {
// perform simplified Porter-Duff source-over
int dstContribution = 255 - alpha;
return Color.argb(255,
((alpha * Color.red(color)) >> 8) + dstContribution,
((alpha * Color.green(color)) >> 8) + dstContribution,
((alpha * Color.blue(color)) >> 8) + dstContribution
);
}
// discard black pixels, because black is not a color (well, not a good looking one)
if ((color & 0xFFFFFF) == 0)
return 0;
return color;
}
}