1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/widget/FaviconView.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,250 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.widget; 1.10 + 1.11 +import org.mozilla.gecko.R; 1.12 +import org.mozilla.gecko.favicons.Favicons; 1.13 + 1.14 +import android.content.Context; 1.15 +import android.graphics.Bitmap; 1.16 +import android.graphics.Canvas; 1.17 +import android.graphics.Paint; 1.18 +import android.graphics.RectF; 1.19 +import android.util.AttributeSet; 1.20 +import android.widget.ImageView; 1.21 +/** 1.22 + * Special version of ImageView for favicons. 1.23 + * Displays solid colour background around Favicon to fill space not occupied by the icon. Colour 1.24 + * selected is the dominant colour of the provided Favicon. 1.25 + */ 1.26 +public class FaviconView extends ImageView { 1.27 + private Bitmap mIconBitmap; 1.28 + 1.29 + // Reference to the unscaled bitmap, if any, to prevent repeated assignments of the same bitmap 1.30 + // to the view from causing repeated rescalings (Some of the callers do this) 1.31 + private Bitmap mUnscaledBitmap; 1.32 + 1.33 + // Key into the Favicon dominant colour cache. Should be the Favicon URL if the image displayed 1.34 + // here is a Favicon managed by the caching system. If not, any appropriately unique-to-this-image 1.35 + // string is acceptable. 1.36 + private String mIconKey; 1.37 + 1.38 + private int mActualWidth; 1.39 + private int mActualHeight; 1.40 + 1.41 + // Flag indicating if the most recently assigned image is considered likely to need scaling. 1.42 + private boolean mScalingExpected; 1.43 + 1.44 + // Dominant color of the favicon. 1.45 + private int mDominantColor; 1.46 + 1.47 + // Stroke width for the border. 1.48 + private static float sStrokeWidth; 1.49 + 1.50 + // Paint for drawing the stroke. 1.51 + private static Paint sStrokePaint; 1.52 + 1.53 + // Paint for drawing the background. 1.54 + private static Paint sBackgroundPaint; 1.55 + 1.56 + // Size of the stroke rectangle. 1.57 + private final RectF mStrokeRect; 1.58 + 1.59 + // Size of the background rectangle. 1.60 + private final RectF mBackgroundRect; 1.61 + 1.62 + // Initializing the static paints. 1.63 + static { 1.64 + sStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1.65 + sStrokePaint.setStyle(Paint.Style.STROKE); 1.66 + 1.67 + sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1.68 + sBackgroundPaint.setStyle(Paint.Style.FILL); 1.69 + } 1.70 + 1.71 + public FaviconView(Context context, AttributeSet attrs) { 1.72 + super(context, attrs); 1.73 + setScaleType(ImageView.ScaleType.CENTER); 1.74 + 1.75 + mStrokeRect = new RectF(); 1.76 + mBackgroundRect = new RectF(); 1.77 + 1.78 + if (sStrokeWidth == 0) { 1.79 + sStrokeWidth = getResources().getDisplayMetrics().density; 1.80 + sStrokePaint.setStrokeWidth(sStrokeWidth); 1.81 + } 1.82 + 1.83 + mStrokeRect.left = mStrokeRect.top = sStrokeWidth; 1.84 + mBackgroundRect.left = mBackgroundRect.top = sStrokeWidth * 2.0f; 1.85 + } 1.86 + 1.87 + @Override 1.88 + protected void onSizeChanged(int w, int h, int oldw, int oldh){ 1.89 + super.onSizeChanged(w, h, oldw, oldh); 1.90 + 1.91 + // No point rechecking the image if there hasn't really been any change. 1.92 + if (w == mActualWidth && h == mActualHeight) { 1.93 + return; 1.94 + } 1.95 + 1.96 + mActualWidth = w; 1.97 + mActualHeight = h; 1.98 + 1.99 + mStrokeRect.right = w - sStrokeWidth; 1.100 + mStrokeRect.bottom = h - sStrokeWidth; 1.101 + mBackgroundRect.right = mStrokeRect.right - sStrokeWidth; 1.102 + mBackgroundRect.bottom = mStrokeRect.bottom - sStrokeWidth; 1.103 + 1.104 + formatImage(); 1.105 + } 1.106 + 1.107 + @Override 1.108 + public void onDraw(Canvas canvas) { 1.109 + super.onDraw(canvas); 1.110 + 1.111 + // 27.5% transparent dominant color. 1.112 + sBackgroundPaint.setColor(mDominantColor & 0x46FFFFFF); 1.113 + canvas.drawRect(mStrokeRect, sBackgroundPaint); 1.114 + 1.115 + sStrokePaint.setColor(mDominantColor); 1.116 + canvas.drawRoundRect(mStrokeRect, sStrokeWidth, sStrokeWidth, sStrokePaint); 1.117 + } 1.118 + 1.119 + /** 1.120 + * Formats the image for display, if the prerequisite data are available. Upscales tiny Favicons to 1.121 + * normal sized ones, replaces null bitmaps with the default Favicon, and fills all remaining space 1.122 + * in this view with the coloured background. 1.123 + */ 1.124 + private void formatImage() { 1.125 + // If we're called before bitmap is set, or before size is set, show blank. 1.126 + if (mIconBitmap == null || mActualWidth == 0 || mActualHeight == 0) { 1.127 + showNoImage(); 1.128 + return; 1.129 + } 1.130 + 1.131 + if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) { 1.132 + scaleBitmap(); 1.133 + // Don't scale the image every time something changes. 1.134 + mScalingExpected = false; 1.135 + } 1.136 + 1.137 + setImageBitmap(mIconBitmap); 1.138 + 1.139 + // After scaling, determine if we have empty space around the scaled image which we need to 1.140 + // fill with the coloured background. If applicable, show it. 1.141 + // We assume Favicons are still squares and only bother with the background if more than 3px 1.142 + // of it would be displayed. 1.143 + if (Math.abs(mIconBitmap.getWidth() - mActualWidth) > 3) { 1.144 + mDominantColor = Favicons.getFaviconColor(mIconKey); 1.145 + if (mDominantColor == -1) { 1.146 + mDominantColor = 0; 1.147 + } 1.148 + } else { 1.149 + mDominantColor = 0; 1.150 + } 1.151 + } 1.152 + 1.153 + private void scaleBitmap() { 1.154 + // If the Favicon can be resized to fill the view exactly without an enlargment of more than 1.155 + // a factor of two, do so. 1.156 + int doubledSize = mIconBitmap.getWidth()*2; 1.157 + if (mActualWidth > doubledSize) { 1.158 + // If the view is more than twice the size of the image, just double the image size 1.159 + // and do the rest with padding. 1.160 + mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, doubledSize, doubledSize, true); 1.161 + } else { 1.162 + // Otherwise, scale the image to fill the view. 1.163 + mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, mActualWidth, mActualWidth, true); 1.164 + } 1.165 + } 1.166 + 1.167 + /** 1.168 + * Sets the icon displayed in this Favicon view to the bitmap provided. If the size of the view 1.169 + * has been set, the display will be updated right away, otherwise the update will be deferred 1.170 + * until then. The key provided is used to cache the result of the calculation of the dominant 1.171 + * colour of the provided image - this value is used to draw the coloured background in this view 1.172 + * if the icon is not large enough to fill it. 1.173 + * 1.174 + * @param bitmap favicon image 1.175 + * @param key string used as a key to cache the dominant color of this image 1.176 + * @param allowScaling If true, allows the provided bitmap to be scaled by this FaviconView. 1.177 + * Typically, you should prefer using Favicons obtained via the caching system 1.178 + * (Favicons class), so as to exploit caching. 1.179 + */ 1.180 + private void updateImageInternal(Bitmap bitmap, String key, boolean allowScaling) { 1.181 + if (bitmap == null) { 1.182 + showDefaultFavicon(); 1.183 + return; 1.184 + } 1.185 + 1.186 + // Reassigning the same bitmap? Don't bother. 1.187 + if (mUnscaledBitmap == bitmap) { 1.188 + return; 1.189 + } 1.190 + mUnscaledBitmap = bitmap; 1.191 + mIconBitmap = bitmap; 1.192 + mIconKey = key; 1.193 + mScalingExpected = allowScaling; 1.194 + 1.195 + // Possibly update the display. 1.196 + formatImage(); 1.197 + } 1.198 + 1.199 + public void showDefaultFavicon() { 1.200 + setImageResource(R.drawable.favicon); 1.201 + mDominantColor = 0; 1.202 + } 1.203 + 1.204 + private void showNoImage() { 1.205 + setImageDrawable(null); 1.206 + mDominantColor = 0; 1.207 + } 1.208 + 1.209 + /** 1.210 + * Clear image and background shown by this view. 1.211 + */ 1.212 + public void clearImage() { 1.213 + showNoImage(); 1.214 + mUnscaledBitmap = null; 1.215 + mIconBitmap = null; 1.216 + mIconKey = null; 1.217 + mScalingExpected = false; 1.218 + } 1.219 + 1.220 + /** 1.221 + * Update the displayed image and apply the scaling logic. 1.222 + * The scaling logic will attempt to resize the image to fit correctly inside the view in a way 1.223 + * that avoids unreasonable levels of loss of quality. 1.224 + * Scaling is necessary only when the icon being provided is not drawn from the Favicon cache 1.225 + * introduced in Bug 914296. 1.226 + * 1.227 + * Due to Bug 913746, icons bundled for search engines are not available to the cache, so must 1.228 + * always have the scaling logic applied here. At the time of writing, this is the only case in 1.229 + * which the scaling logic here is applied. 1.230 + * 1.231 + * @param bitmap The bitmap to display in this favicon view. 1.232 + * @param key The key to use into the dominant colours cache when selecting a background colour. 1.233 + */ 1.234 + public void updateAndScaleImage(Bitmap bitmap, String key) { 1.235 + updateImageInternal(bitmap, key, true); 1.236 + } 1.237 + 1.238 + /** 1.239 + * Update the image displayed in the Favicon view without scaling. Images larger than the view 1.240 + * will be centrally cropped. Images smaller than the view will be placed centrally and the 1.241 + * extra space filled with the dominant colour of the provided image. 1.242 + * 1.243 + * @param bitmap The bitmap to display in this favicon view. 1.244 + * @param key The key to use into the dominant colours cache when selecting a background colour. 1.245 + */ 1.246 + public void updateImage(Bitmap bitmap, String key) { 1.247 + updateImageInternal(bitmap, key, false); 1.248 + } 1.249 + 1.250 + public Bitmap getBitmap() { 1.251 + return mIconBitmap; 1.252 + } 1.253 +}