|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 package org.mozilla.gecko.widget; |
|
7 |
|
8 import org.mozilla.gecko.R; |
|
9 import org.mozilla.gecko.favicons.Favicons; |
|
10 |
|
11 import android.content.Context; |
|
12 import android.graphics.Bitmap; |
|
13 import android.graphics.Canvas; |
|
14 import android.graphics.Paint; |
|
15 import android.graphics.RectF; |
|
16 import android.util.AttributeSet; |
|
17 import android.widget.ImageView; |
|
18 /** |
|
19 * Special version of ImageView for favicons. |
|
20 * Displays solid colour background around Favicon to fill space not occupied by the icon. Colour |
|
21 * selected is the dominant colour of the provided Favicon. |
|
22 */ |
|
23 public class FaviconView extends ImageView { |
|
24 private Bitmap mIconBitmap; |
|
25 |
|
26 // Reference to the unscaled bitmap, if any, to prevent repeated assignments of the same bitmap |
|
27 // to the view from causing repeated rescalings (Some of the callers do this) |
|
28 private Bitmap mUnscaledBitmap; |
|
29 |
|
30 // Key into the Favicon dominant colour cache. Should be the Favicon URL if the image displayed |
|
31 // here is a Favicon managed by the caching system. If not, any appropriately unique-to-this-image |
|
32 // string is acceptable. |
|
33 private String mIconKey; |
|
34 |
|
35 private int mActualWidth; |
|
36 private int mActualHeight; |
|
37 |
|
38 // Flag indicating if the most recently assigned image is considered likely to need scaling. |
|
39 private boolean mScalingExpected; |
|
40 |
|
41 // Dominant color of the favicon. |
|
42 private int mDominantColor; |
|
43 |
|
44 // Stroke width for the border. |
|
45 private static float sStrokeWidth; |
|
46 |
|
47 // Paint for drawing the stroke. |
|
48 private static Paint sStrokePaint; |
|
49 |
|
50 // Paint for drawing the background. |
|
51 private static Paint sBackgroundPaint; |
|
52 |
|
53 // Size of the stroke rectangle. |
|
54 private final RectF mStrokeRect; |
|
55 |
|
56 // Size of the background rectangle. |
|
57 private final RectF mBackgroundRect; |
|
58 |
|
59 // Initializing the static paints. |
|
60 static { |
|
61 sStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
|
62 sStrokePaint.setStyle(Paint.Style.STROKE); |
|
63 |
|
64 sBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
|
65 sBackgroundPaint.setStyle(Paint.Style.FILL); |
|
66 } |
|
67 |
|
68 public FaviconView(Context context, AttributeSet attrs) { |
|
69 super(context, attrs); |
|
70 setScaleType(ImageView.ScaleType.CENTER); |
|
71 |
|
72 mStrokeRect = new RectF(); |
|
73 mBackgroundRect = new RectF(); |
|
74 |
|
75 if (sStrokeWidth == 0) { |
|
76 sStrokeWidth = getResources().getDisplayMetrics().density; |
|
77 sStrokePaint.setStrokeWidth(sStrokeWidth); |
|
78 } |
|
79 |
|
80 mStrokeRect.left = mStrokeRect.top = sStrokeWidth; |
|
81 mBackgroundRect.left = mBackgroundRect.top = sStrokeWidth * 2.0f; |
|
82 } |
|
83 |
|
84 @Override |
|
85 protected void onSizeChanged(int w, int h, int oldw, int oldh){ |
|
86 super.onSizeChanged(w, h, oldw, oldh); |
|
87 |
|
88 // No point rechecking the image if there hasn't really been any change. |
|
89 if (w == mActualWidth && h == mActualHeight) { |
|
90 return; |
|
91 } |
|
92 |
|
93 mActualWidth = w; |
|
94 mActualHeight = h; |
|
95 |
|
96 mStrokeRect.right = w - sStrokeWidth; |
|
97 mStrokeRect.bottom = h - sStrokeWidth; |
|
98 mBackgroundRect.right = mStrokeRect.right - sStrokeWidth; |
|
99 mBackgroundRect.bottom = mStrokeRect.bottom - sStrokeWidth; |
|
100 |
|
101 formatImage(); |
|
102 } |
|
103 |
|
104 @Override |
|
105 public void onDraw(Canvas canvas) { |
|
106 super.onDraw(canvas); |
|
107 |
|
108 // 27.5% transparent dominant color. |
|
109 sBackgroundPaint.setColor(mDominantColor & 0x46FFFFFF); |
|
110 canvas.drawRect(mStrokeRect, sBackgroundPaint); |
|
111 |
|
112 sStrokePaint.setColor(mDominantColor); |
|
113 canvas.drawRoundRect(mStrokeRect, sStrokeWidth, sStrokeWidth, sStrokePaint); |
|
114 } |
|
115 |
|
116 /** |
|
117 * Formats the image for display, if the prerequisite data are available. Upscales tiny Favicons to |
|
118 * normal sized ones, replaces null bitmaps with the default Favicon, and fills all remaining space |
|
119 * in this view with the coloured background. |
|
120 */ |
|
121 private void formatImage() { |
|
122 // If we're called before bitmap is set, or before size is set, show blank. |
|
123 if (mIconBitmap == null || mActualWidth == 0 || mActualHeight == 0) { |
|
124 showNoImage(); |
|
125 return; |
|
126 } |
|
127 |
|
128 if (mScalingExpected && mActualWidth != mIconBitmap.getWidth()) { |
|
129 scaleBitmap(); |
|
130 // Don't scale the image every time something changes. |
|
131 mScalingExpected = false; |
|
132 } |
|
133 |
|
134 setImageBitmap(mIconBitmap); |
|
135 |
|
136 // After scaling, determine if we have empty space around the scaled image which we need to |
|
137 // fill with the coloured background. If applicable, show it. |
|
138 // We assume Favicons are still squares and only bother with the background if more than 3px |
|
139 // of it would be displayed. |
|
140 if (Math.abs(mIconBitmap.getWidth() - mActualWidth) > 3) { |
|
141 mDominantColor = Favicons.getFaviconColor(mIconKey); |
|
142 if (mDominantColor == -1) { |
|
143 mDominantColor = 0; |
|
144 } |
|
145 } else { |
|
146 mDominantColor = 0; |
|
147 } |
|
148 } |
|
149 |
|
150 private void scaleBitmap() { |
|
151 // If the Favicon can be resized to fill the view exactly without an enlargment of more than |
|
152 // a factor of two, do so. |
|
153 int doubledSize = mIconBitmap.getWidth()*2; |
|
154 if (mActualWidth > doubledSize) { |
|
155 // If the view is more than twice the size of the image, just double the image size |
|
156 // and do the rest with padding. |
|
157 mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, doubledSize, doubledSize, true); |
|
158 } else { |
|
159 // Otherwise, scale the image to fill the view. |
|
160 mIconBitmap = Bitmap.createScaledBitmap(mIconBitmap, mActualWidth, mActualWidth, true); |
|
161 } |
|
162 } |
|
163 |
|
164 /** |
|
165 * Sets the icon displayed in this Favicon view to the bitmap provided. If the size of the view |
|
166 * has been set, the display will be updated right away, otherwise the update will be deferred |
|
167 * until then. The key provided is used to cache the result of the calculation of the dominant |
|
168 * colour of the provided image - this value is used to draw the coloured background in this view |
|
169 * if the icon is not large enough to fill it. |
|
170 * |
|
171 * @param bitmap favicon image |
|
172 * @param key string used as a key to cache the dominant color of this image |
|
173 * @param allowScaling If true, allows the provided bitmap to be scaled by this FaviconView. |
|
174 * Typically, you should prefer using Favicons obtained via the caching system |
|
175 * (Favicons class), so as to exploit caching. |
|
176 */ |
|
177 private void updateImageInternal(Bitmap bitmap, String key, boolean allowScaling) { |
|
178 if (bitmap == null) { |
|
179 showDefaultFavicon(); |
|
180 return; |
|
181 } |
|
182 |
|
183 // Reassigning the same bitmap? Don't bother. |
|
184 if (mUnscaledBitmap == bitmap) { |
|
185 return; |
|
186 } |
|
187 mUnscaledBitmap = bitmap; |
|
188 mIconBitmap = bitmap; |
|
189 mIconKey = key; |
|
190 mScalingExpected = allowScaling; |
|
191 |
|
192 // Possibly update the display. |
|
193 formatImage(); |
|
194 } |
|
195 |
|
196 public void showDefaultFavicon() { |
|
197 setImageResource(R.drawable.favicon); |
|
198 mDominantColor = 0; |
|
199 } |
|
200 |
|
201 private void showNoImage() { |
|
202 setImageDrawable(null); |
|
203 mDominantColor = 0; |
|
204 } |
|
205 |
|
206 /** |
|
207 * Clear image and background shown by this view. |
|
208 */ |
|
209 public void clearImage() { |
|
210 showNoImage(); |
|
211 mUnscaledBitmap = null; |
|
212 mIconBitmap = null; |
|
213 mIconKey = null; |
|
214 mScalingExpected = false; |
|
215 } |
|
216 |
|
217 /** |
|
218 * Update the displayed image and apply the scaling logic. |
|
219 * The scaling logic will attempt to resize the image to fit correctly inside the view in a way |
|
220 * that avoids unreasonable levels of loss of quality. |
|
221 * Scaling is necessary only when the icon being provided is not drawn from the Favicon cache |
|
222 * introduced in Bug 914296. |
|
223 * |
|
224 * Due to Bug 913746, icons bundled for search engines are not available to the cache, so must |
|
225 * always have the scaling logic applied here. At the time of writing, this is the only case in |
|
226 * which the scaling logic here is applied. |
|
227 * |
|
228 * @param bitmap The bitmap to display in this favicon view. |
|
229 * @param key The key to use into the dominant colours cache when selecting a background colour. |
|
230 */ |
|
231 public void updateAndScaleImage(Bitmap bitmap, String key) { |
|
232 updateImageInternal(bitmap, key, true); |
|
233 } |
|
234 |
|
235 /** |
|
236 * Update the image displayed in the Favicon view without scaling. Images larger than the view |
|
237 * will be centrally cropped. Images smaller than the view will be placed centrally and the |
|
238 * extra space filled with the dominant colour of the provided image. |
|
239 * |
|
240 * @param bitmap The bitmap to display in this favicon view. |
|
241 * @param key The key to use into the dominant colours cache when selecting a background colour. |
|
242 */ |
|
243 public void updateImage(Bitmap bitmap, String key) { |
|
244 updateImageInternal(bitmap, key, false); |
|
245 } |
|
246 |
|
247 public Bitmap getBitmap() { |
|
248 return mIconBitmap; |
|
249 } |
|
250 } |