michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.widget; michael@0: michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.favicons.Favicons; michael@0: michael@0: import android.content.Context; michael@0: import android.graphics.Bitmap; michael@0: import android.graphics.Canvas; michael@0: import android.graphics.Paint; michael@0: import android.graphics.RectF; michael@0: import android.util.AttributeSet; michael@0: import android.widget.ImageView; michael@0: /** michael@0: * Special version of ImageView for favicons. michael@0: * Displays solid colour background around Favicon to fill space not occupied by the icon. Colour michael@0: * selected is the dominant colour of the provided Favicon. michael@0: */ michael@0: public class FaviconView extends ImageView { michael@0: private Bitmap mIconBitmap; michael@0: michael@0: // Reference to the unscaled bitmap, if any, to prevent repeated assignments of the same bitmap michael@0: // to the view from causing repeated rescalings (Some of the callers do this) michael@0: private Bitmap mUnscaledBitmap; michael@0: michael@0: // Key into the Favicon dominant colour cache. Should be the Favicon URL if the image displayed michael@0: // here is a Favicon managed by the caching system. If not, any appropriately unique-to-this-image michael@0: // string is acceptable. michael@0: private String mIconKey; michael@0: michael@0: private int mActualWidth; michael@0: private int mActualHeight; michael@0: michael@0: // Flag indicating if the most recently assigned image is considered likely to need scaling. michael@0: private boolean mScalingExpected; michael@0: michael@0: // Dominant color of the favicon. michael@0: private int mDominantColor; michael@0: michael@0: // Stroke width for the border. michael@0: private static float sStrokeWidth; michael@0: michael@0: // Paint for drawing the stroke. michael@0: private static Paint sStrokePaint; michael@0: michael@0: // Paint for drawing the background. michael@0: private static Paint sBackgroundPaint; michael@0: michael@0: // Size of the stroke rectangle. michael@0: private final RectF mStrokeRect; michael@0: michael@0: // Size of the background rectangle. michael@0: private final RectF mBackgroundRect; michael@0: michael@0: // Initializing the static paints. michael@0: static { michael@0: sStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); michael@0: sStrokePaint.setStyle(Paint.Style.STROKE); michael@0: michael@0: sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); michael@0: sBackgroundPaint.setStyle(Paint.Style.FILL); michael@0: } michael@0: michael@0: public FaviconView(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: setScaleType(ImageView.ScaleType.CENTER); michael@0: michael@0: mStrokeRect = new RectF(); michael@0: mBackgroundRect = new RectF(); michael@0: michael@0: if (sStrokeWidth == 0) { michael@0: sStrokeWidth = getResources().getDisplayMetrics().density; michael@0: sStrokePaint.setStrokeWidth(sStrokeWidth); michael@0: } michael@0: michael@0: mStrokeRect.left = mStrokeRect.top = sStrokeWidth; michael@0: mBackgroundRect.left = mBackgroundRect.top = sStrokeWidth * 2.0f; michael@0: } michael@0: michael@0: @Override michael@0: protected void onSizeChanged(int w, int h, int oldw, int oldh){ michael@0: super.onSizeChanged(w, h, oldw, oldh); michael@0: michael@0: // No point rechecking the image if there hasn't really been any change. michael@0: if (w == mActualWidth && h == mActualHeight) { michael@0: return; michael@0: } michael@0: michael@0: mActualWidth = w; michael@0: mActualHeight = h; michael@0: michael@0: mStrokeRect.right = w - sStrokeWidth; michael@0: mStrokeRect.bottom = h - sStrokeWidth; michael@0: mBackgroundRect.right = mStrokeRect.right - sStrokeWidth; michael@0: mBackgroundRect.bottom = mStrokeRect.bottom - sStrokeWidth; michael@0: michael@0: formatImage(); michael@0: } michael@0: michael@0: @Override michael@0: public void onDraw(Canvas canvas) { michael@0: super.onDraw(canvas); michael@0: michael@0: // 27.5% transparent dominant color. michael@0: sBackgroundPaint.setColor(mDominantColor & 0x46FFFFFF); michael@0: canvas.drawRect(mStrokeRect, sBackgroundPaint); michael@0: michael@0: sStrokePaint.setColor(mDominantColor); michael@0: canvas.drawRoundRect(mStrokeRect, sStrokeWidth, sStrokeWidth, sStrokePaint); michael@0: } michael@0: michael@0: /** michael@0: * Formats the image for display, if the prerequisite data are available. Upscales tiny Favicons to michael@0: * normal sized ones, replaces null bitmaps with the default Favicon, and fills all remaining space michael@0: * in this view with the coloured background. michael@0: */ michael@0: private void formatImage() { michael@0: // If we're called before bitmap is set, or before size is set, show blank. michael@0: if (mIconBitmap == null || mActualWidth == 0 || mActualHeight == 0) { michael@0: showNoImage(); michael@0: return; michael@0: } michael@0: michael@0: if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) { michael@0: scaleBitmap(); michael@0: // Don't scale the image every time something changes. michael@0: mScalingExpected = false; michael@0: } michael@0: michael@0: setImageBitmap(mIconBitmap); michael@0: michael@0: // After scaling, determine if we have empty space around the scaled image which we need to michael@0: // fill with the coloured background. If applicable, show it. michael@0: // We assume Favicons are still squares and only bother with the background if more than 3px michael@0: // of it would be displayed. michael@0: if (Math.abs(mIconBitmap.getWidth() - mActualWidth) > 3) { michael@0: mDominantColor = Favicons.getFaviconColor(mIconKey); michael@0: if (mDominantColor == -1) { michael@0: mDominantColor = 0; michael@0: } michael@0: } else { michael@0: mDominantColor = 0; michael@0: } michael@0: } michael@0: michael@0: private void scaleBitmap() { michael@0: // If the Favicon can be resized to fill the view exactly without an enlargment of more than michael@0: // a factor of two, do so. michael@0: int doubledSize = mIconBitmap.getWidth()*2; michael@0: if (mActualWidth > doubledSize) { michael@0: // If the view is more than twice the size of the image, just double the image size michael@0: // and do the rest with padding. michael@0: mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, doubledSize, doubledSize, true); michael@0: } else { michael@0: // Otherwise, scale the image to fill the view. michael@0: mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, mActualWidth, mActualWidth, true); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Sets the icon displayed in this Favicon view to the bitmap provided. If the size of the view michael@0: * has been set, the display will be updated right away, otherwise the update will be deferred michael@0: * until then. The key provided is used to cache the result of the calculation of the dominant michael@0: * colour of the provided image - this value is used to draw the coloured background in this view michael@0: * if the icon is not large enough to fill it. michael@0: * michael@0: * @param bitmap favicon image michael@0: * @param key string used as a key to cache the dominant color of this image michael@0: * @param allowScaling If true, allows the provided bitmap to be scaled by this FaviconView. michael@0: * Typically, you should prefer using Favicons obtained via the caching system michael@0: * (Favicons class), so as to exploit caching. michael@0: */ michael@0: private void updateImageInternal(Bitmap bitmap, String key, boolean allowScaling) { michael@0: if (bitmap == null) { michael@0: showDefaultFavicon(); michael@0: return; michael@0: } michael@0: michael@0: // Reassigning the same bitmap? Don't bother. michael@0: if (mUnscaledBitmap == bitmap) { michael@0: return; michael@0: } michael@0: mUnscaledBitmap = bitmap; michael@0: mIconBitmap = bitmap; michael@0: mIconKey = key; michael@0: mScalingExpected = allowScaling; michael@0: michael@0: // Possibly update the display. michael@0: formatImage(); michael@0: } michael@0: michael@0: public void showDefaultFavicon() { michael@0: setImageResource(R.drawable.favicon); michael@0: mDominantColor = 0; michael@0: } michael@0: michael@0: private void showNoImage() { michael@0: setImageDrawable(null); michael@0: mDominantColor = 0; michael@0: } michael@0: michael@0: /** michael@0: * Clear image and background shown by this view. michael@0: */ michael@0: public void clearImage() { michael@0: showNoImage(); michael@0: mUnscaledBitmap = null; michael@0: mIconBitmap = null; michael@0: mIconKey = null; michael@0: mScalingExpected = false; michael@0: } michael@0: michael@0: /** michael@0: * Update the displayed image and apply the scaling logic. michael@0: * The scaling logic will attempt to resize the image to fit correctly inside the view in a way michael@0: * that avoids unreasonable levels of loss of quality. michael@0: * Scaling is necessary only when the icon being provided is not drawn from the Favicon cache michael@0: * introduced in Bug 914296. michael@0: * michael@0: * Due to Bug 913746, icons bundled for search engines are not available to the cache, so must michael@0: * always have the scaling logic applied here. At the time of writing, this is the only case in michael@0: * which the scaling logic here is applied. michael@0: * michael@0: * @param bitmap The bitmap to display in this favicon view. michael@0: * @param key The key to use into the dominant colours cache when selecting a background colour. michael@0: */ michael@0: public void updateAndScaleImage(Bitmap bitmap, String key) { michael@0: updateImageInternal(bitmap, key, true); michael@0: } michael@0: michael@0: /** michael@0: * Update the image displayed in the Favicon view without scaling. Images larger than the view michael@0: * will be centrally cropped. Images smaller than the view will be placed centrally and the michael@0: * extra space filled with the dominant colour of the provided image. michael@0: * michael@0: * @param bitmap The bitmap to display in this favicon view. michael@0: * @param key The key to use into the dominant colours cache when selecting a background colour. michael@0: */ michael@0: public void updateImage(Bitmap bitmap, String key) { michael@0: updateImageInternal(bitmap, key, false); michael@0: } michael@0: michael@0: public Bitmap getBitmap() { michael@0: return mIconBitmap; michael@0: } michael@0: }