mobile/android/base/widget/FaviconView.java

changeset 0
6474c204b198
     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 +}

mercurial