mobile/android/base/gfx/BitmapUtils.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko.gfx;
michael@0 7
michael@0 8 import java.io.IOException;
michael@0 9 import java.io.InputStream;
michael@0 10 import java.lang.reflect.Field;
michael@0 11 import java.net.MalformedURLException;
michael@0 12 import java.net.URL;
michael@0 13
michael@0 14 import org.mozilla.gecko.R;
michael@0 15 import org.mozilla.gecko.util.GeckoJarReader;
michael@0 16 import org.mozilla.gecko.util.ThreadUtils;
michael@0 17 import org.mozilla.gecko.util.UiAsyncTask;
michael@0 18 import org.mozilla.gecko.Tab;
michael@0 19 import org.mozilla.gecko.Tabs;
michael@0 20 import org.mozilla.gecko.ThumbnailHelper;
michael@0 21
michael@0 22 import android.content.Context;
michael@0 23 import android.content.res.Resources;
michael@0 24 import android.graphics.Bitmap;
michael@0 25 import android.graphics.BitmapFactory;
michael@0 26 import android.graphics.Canvas;
michael@0 27 import android.graphics.Color;
michael@0 28 import android.graphics.drawable.BitmapDrawable;
michael@0 29 import android.graphics.drawable.Drawable;
michael@0 30 import android.net.Uri;
michael@0 31 import android.text.TextUtils;
michael@0 32 import android.util.Base64;
michael@0 33 import android.util.Log;
michael@0 34
michael@0 35 public final class BitmapUtils {
michael@0 36 private static final String LOGTAG = "GeckoBitmapUtils";
michael@0 37
michael@0 38 private BitmapUtils() {}
michael@0 39
michael@0 40 public interface BitmapLoader {
michael@0 41 public void onBitmapFound(Drawable d);
michael@0 42 }
michael@0 43
michael@0 44 private static void runOnBitmapFoundOnUiThread(final BitmapLoader loader, final Drawable d) {
michael@0 45 if (ThreadUtils.isOnUiThread()) {
michael@0 46 loader.onBitmapFound(d);
michael@0 47 return;
michael@0 48 }
michael@0 49
michael@0 50 ThreadUtils.postToUiThread(new Runnable() {
michael@0 51 @Override
michael@0 52 public void run() {
michael@0 53 loader.onBitmapFound(d);
michael@0 54 }
michael@0 55 });
michael@0 56 }
michael@0 57
michael@0 58 /**
michael@0 59 * Attempts to find a drawable associated with a given string, using its URI scheme to determine
michael@0 60 * how to load the drawable. The BitmapLoader's `onBitmapFound` method is always called, and
michael@0 61 * will be called with `null` if no drawable is found.
michael@0 62 *
michael@0 63 * The BitmapLoader `onBitmapFound` method always runs on the UI thread.
michael@0 64 */
michael@0 65 public static void getDrawable(final Context context, final String data, final BitmapLoader loader) {
michael@0 66 if (TextUtils.isEmpty(data)) {
michael@0 67 runOnBitmapFoundOnUiThread(loader, null);
michael@0 68 return;
michael@0 69 }
michael@0 70
michael@0 71 if (data.startsWith("data")) {
michael@0 72 final BitmapDrawable d = new BitmapDrawable(context.getResources(), getBitmapFromDataURI(data));
michael@0 73 runOnBitmapFoundOnUiThread(loader, d);
michael@0 74 return;
michael@0 75 }
michael@0 76
michael@0 77 if (data.startsWith("thumbnail:")) {
michael@0 78 getThumbnailDrawable(context, data, loader);
michael@0 79 return;
michael@0 80 }
michael@0 81
michael@0 82 if (data.startsWith("jar:") || data.startsWith("file://")) {
michael@0 83 (new UiAsyncTask<Void, Void, Drawable>(ThreadUtils.getBackgroundHandler()) {
michael@0 84 @Override
michael@0 85 public Drawable doInBackground(Void... params) {
michael@0 86 try {
michael@0 87 if (data.startsWith("jar:jar")) {
michael@0 88 return GeckoJarReader.getBitmapDrawable(context.getResources(), data);
michael@0 89 }
michael@0 90
michael@0 91 // Don't attempt to validate the JAR signature when loading an add-on icon
michael@0 92 if (data.startsWith("jar:file")) {
michael@0 93 return GeckoJarReader.getBitmapDrawable(context.getResources(), Uri.decode(data));
michael@0 94 }
michael@0 95
michael@0 96 final URL url = new URL(data);
michael@0 97 final InputStream is = (InputStream) url.getContent();
michael@0 98 try {
michael@0 99 return Drawable.createFromStream(is, "src");
michael@0 100 } finally {
michael@0 101 is.close();
michael@0 102 }
michael@0 103 } catch (Exception e) {
michael@0 104 Log.w(LOGTAG, "Unable to set icon", e);
michael@0 105 }
michael@0 106 return null;
michael@0 107 }
michael@0 108
michael@0 109 @Override
michael@0 110 public void onPostExecute(Drawable drawable) {
michael@0 111 loader.onBitmapFound(drawable);
michael@0 112 }
michael@0 113 }).execute();
michael@0 114 return;
michael@0 115 }
michael@0 116
michael@0 117 if (data.startsWith("-moz-icon://")) {
michael@0 118 final Uri imageUri = Uri.parse(data);
michael@0 119 final String ssp = imageUri.getSchemeSpecificPart();
michael@0 120 final String resource = ssp.substring(ssp.lastIndexOf('/') + 1);
michael@0 121
michael@0 122 try {
michael@0 123 final Drawable d = context.getPackageManager().getApplicationIcon(resource);
michael@0 124 runOnBitmapFoundOnUiThread(loader, d);
michael@0 125 } catch(Exception ex) { }
michael@0 126
michael@0 127 return;
michael@0 128 }
michael@0 129
michael@0 130 if (data.startsWith("drawable://")) {
michael@0 131 final Uri imageUri = Uri.parse(data);
michael@0 132 final int id = getResource(imageUri, R.drawable.ic_status_logo);
michael@0 133 final Drawable d = context.getResources().getDrawable(id);
michael@0 134
michael@0 135 runOnBitmapFoundOnUiThread(loader, d);
michael@0 136 return;
michael@0 137 }
michael@0 138
michael@0 139 runOnBitmapFoundOnUiThread(loader, null);
michael@0 140 }
michael@0 141
michael@0 142 public static void getThumbnailDrawable(final Context context, final String data, final BitmapLoader loader) {
michael@0 143 int id = Integer.parseInt(data.substring(10), 10);
michael@0 144 final Tab tab = Tabs.getInstance().getTab(id);
michael@0 145 runOnBitmapFoundOnUiThread(loader, tab.getThumbnail());
michael@0 146 Tabs.registerOnTabsChangedListener(new Tabs.OnTabsChangedListener() {
michael@0 147 public void onTabChanged(Tab t, Tabs.TabEvents msg, Object data) {
michael@0 148 if (tab == t && msg == Tabs.TabEvents.THUMBNAIL) {
michael@0 149 Tabs.unregisterOnTabsChangedListener(this);
michael@0 150 runOnBitmapFoundOnUiThread(loader, t.getThumbnail());
michael@0 151 }
michael@0 152 }
michael@0 153 });
michael@0 154 ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
michael@0 155 }
michael@0 156
michael@0 157 public static Bitmap decodeByteArray(byte[] bytes) {
michael@0 158 return decodeByteArray(bytes, null);
michael@0 159 }
michael@0 160
michael@0 161 public static Bitmap decodeByteArray(byte[] bytes, BitmapFactory.Options options) {
michael@0 162 return decodeByteArray(bytes, 0, bytes.length, options);
michael@0 163 }
michael@0 164
michael@0 165 public static Bitmap decodeByteArray(byte[] bytes, int offset, int length) {
michael@0 166 return decodeByteArray(bytes, offset, length, null);
michael@0 167 }
michael@0 168
michael@0 169 public static Bitmap decodeByteArray(byte[] bytes, int offset, int length, BitmapFactory.Options options) {
michael@0 170 if (bytes.length <= 0) {
michael@0 171 throw new IllegalArgumentException("bytes.length " + bytes.length
michael@0 172 + " must be a positive number");
michael@0 173 }
michael@0 174
michael@0 175 Bitmap bitmap = null;
michael@0 176 try {
michael@0 177 bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options);
michael@0 178 } catch (OutOfMemoryError e) {
michael@0 179 Log.e(LOGTAG, "decodeByteArray(bytes.length=" + bytes.length
michael@0 180 + ", options= " + options + ") OOM!", e);
michael@0 181 return null;
michael@0 182 }
michael@0 183
michael@0 184 if (bitmap == null) {
michael@0 185 Log.w(LOGTAG, "decodeByteArray() returning null because BitmapFactory returned null");
michael@0 186 return null;
michael@0 187 }
michael@0 188
michael@0 189 if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
michael@0 190 Log.w(LOGTAG, "decodeByteArray() returning null because BitmapFactory returned "
michael@0 191 + "a bitmap with dimensions " + bitmap.getWidth()
michael@0 192 + "x" + bitmap.getHeight());
michael@0 193 return null;
michael@0 194 }
michael@0 195
michael@0 196 return bitmap;
michael@0 197 }
michael@0 198
michael@0 199 public static Bitmap decodeStream(InputStream inputStream) {
michael@0 200 try {
michael@0 201 return BitmapFactory.decodeStream(inputStream);
michael@0 202 } catch (OutOfMemoryError e) {
michael@0 203 Log.e(LOGTAG, "decodeStream() OOM!", e);
michael@0 204 return null;
michael@0 205 }
michael@0 206 }
michael@0 207
michael@0 208 public static Bitmap decodeUrl(Uri uri) {
michael@0 209 return decodeUrl(uri.toString());
michael@0 210 }
michael@0 211
michael@0 212 public static Bitmap decodeUrl(String urlString) {
michael@0 213 URL url;
michael@0 214
michael@0 215 try {
michael@0 216 url = new URL(urlString);
michael@0 217 } catch(MalformedURLException e) {
michael@0 218 Log.w(LOGTAG, "decodeUrl: malformed URL " + urlString);
michael@0 219 return null;
michael@0 220 }
michael@0 221
michael@0 222 return decodeUrl(url);
michael@0 223 }
michael@0 224
michael@0 225 public static Bitmap decodeUrl(URL url) {
michael@0 226 InputStream stream = null;
michael@0 227
michael@0 228 try {
michael@0 229 stream = url.openStream();
michael@0 230 } catch(IOException e) {
michael@0 231 Log.w(LOGTAG, "decodeUrl: IOException downloading " + url);
michael@0 232 return null;
michael@0 233 }
michael@0 234
michael@0 235 if (stream == null) {
michael@0 236 Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url);
michael@0 237 return null;
michael@0 238 }
michael@0 239
michael@0 240 Bitmap bitmap = decodeStream(stream);
michael@0 241
michael@0 242 try {
michael@0 243 stream.close();
michael@0 244 } catch(IOException e) {
michael@0 245 Log.w(LOGTAG, "decodeUrl: IOException closing stream " + url, e);
michael@0 246 }
michael@0 247
michael@0 248 return bitmap;
michael@0 249 }
michael@0 250
michael@0 251 public static Bitmap decodeResource(Context context, int id) {
michael@0 252 return decodeResource(context, id, null);
michael@0 253 }
michael@0 254
michael@0 255 public static Bitmap decodeResource(Context context, int id, BitmapFactory.Options options) {
michael@0 256 Resources resources = context.getResources();
michael@0 257 try {
michael@0 258 return BitmapFactory.decodeResource(resources, id, options);
michael@0 259 } catch (OutOfMemoryError e) {
michael@0 260 Log.e(LOGTAG, "decodeResource() OOM! Resource id=" + id, e);
michael@0 261 return null;
michael@0 262 }
michael@0 263 }
michael@0 264
michael@0 265 public static int getDominantColor(Bitmap source) {
michael@0 266 return getDominantColor(source, true);
michael@0 267 }
michael@0 268
michael@0 269 public static int getDominantColor(Bitmap source, boolean applyThreshold) {
michael@0 270 if (source == null)
michael@0 271 return Color.argb(255,255,255,255);
michael@0 272
michael@0 273 // Keep track of how many times a hue in a given bin appears in the image.
michael@0 274 // Hue values range [0 .. 360), so dividing by 10, we get 36 bins.
michael@0 275 int[] colorBins = new int[36];
michael@0 276
michael@0 277 // The bin with the most colors. Initialize to -1 to prevent accidentally
michael@0 278 // thinking the first bin holds the dominant color.
michael@0 279 int maxBin = -1;
michael@0 280
michael@0 281 // Keep track of sum hue/saturation/value per hue bin, which we'll use to
michael@0 282 // compute an average to for the dominant color.
michael@0 283 float[] sumHue = new float[36];
michael@0 284 float[] sumSat = new float[36];
michael@0 285 float[] sumVal = new float[36];
michael@0 286 float[] hsv = new float[3];
michael@0 287
michael@0 288 int height = source.getHeight();
michael@0 289 int width = source.getWidth();
michael@0 290 int[] pixels = new int[width * height];
michael@0 291 source.getPixels(pixels, 0, width, 0, 0, width, height);
michael@0 292 for (int row = 0; row < height; row++) {
michael@0 293 for (int col = 0; col < width; col++) {
michael@0 294 int c = pixels[col + row * width];
michael@0 295 // Ignore pixels with a certain transparency.
michael@0 296 if (Color.alpha(c) < 128)
michael@0 297 continue;
michael@0 298
michael@0 299 Color.colorToHSV(c, hsv);
michael@0 300
michael@0 301 // If a threshold is applied, ignore arbitrarily chosen values for "white" and "black".
michael@0 302 if (applyThreshold && (hsv[1] <= 0.35f || hsv[2] <= 0.35f))
michael@0 303 continue;
michael@0 304
michael@0 305 // We compute the dominant color by putting colors in bins based on their hue.
michael@0 306 int bin = (int) Math.floor(hsv[0] / 10.0f);
michael@0 307
michael@0 308 // Update the sum hue/saturation/value for this bin.
michael@0 309 sumHue[bin] = sumHue[bin] + hsv[0];
michael@0 310 sumSat[bin] = sumSat[bin] + hsv[1];
michael@0 311 sumVal[bin] = sumVal[bin] + hsv[2];
michael@0 312
michael@0 313 // Increment the number of colors in this bin.
michael@0 314 colorBins[bin]++;
michael@0 315
michael@0 316 // Keep track of the bin that holds the most colors.
michael@0 317 if (maxBin < 0 || colorBins[bin] > colorBins[maxBin])
michael@0 318 maxBin = bin;
michael@0 319 }
michael@0 320 }
michael@0 321
michael@0 322 // maxBin may never get updated if the image holds only transparent and/or black/white pixels.
michael@0 323 if (maxBin < 0)
michael@0 324 return Color.argb(255,255,255,255);
michael@0 325
michael@0 326 // Return a color with the average hue/saturation/value of the bin with the most colors.
michael@0 327 hsv[0] = sumHue[maxBin]/colorBins[maxBin];
michael@0 328 hsv[1] = sumSat[maxBin]/colorBins[maxBin];
michael@0 329 hsv[2] = sumVal[maxBin]/colorBins[maxBin];
michael@0 330 return Color.HSVToColor(hsv);
michael@0 331 }
michael@0 332
michael@0 333 /**
michael@0 334 * Decodes a bitmap from a Base64 data URI.
michael@0 335 *
michael@0 336 * @param dataURI a Base64-encoded data URI string
michael@0 337 * @return the decoded bitmap, or null if the data URI is invalid
michael@0 338 */
michael@0 339 public static Bitmap getBitmapFromDataURI(String dataURI) {
michael@0 340 String base64 = dataURI.substring(dataURI.indexOf(',') + 1);
michael@0 341 try {
michael@0 342 byte[] raw = Base64.decode(base64, Base64.DEFAULT);
michael@0 343 return BitmapUtils.decodeByteArray(raw);
michael@0 344 } catch (Exception e) {
michael@0 345 Log.e(LOGTAG, "exception decoding bitmap from data URI: " + dataURI, e);
michael@0 346 }
michael@0 347 return null;
michael@0 348 }
michael@0 349
michael@0 350 public static Bitmap getBitmapFromDrawable(Drawable drawable) {
michael@0 351 if (drawable instanceof BitmapDrawable) {
michael@0 352 return ((BitmapDrawable) drawable).getBitmap();
michael@0 353 }
michael@0 354
michael@0 355 int width = drawable.getIntrinsicWidth();
michael@0 356 width = width > 0 ? width : 1;
michael@0 357 int height = drawable.getIntrinsicHeight();
michael@0 358 height = height > 0 ? height : 1;
michael@0 359
michael@0 360 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
michael@0 361 Canvas canvas = new Canvas(bitmap);
michael@0 362 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
michael@0 363 drawable.draw(canvas);
michael@0 364
michael@0 365 return bitmap;
michael@0 366 }
michael@0 367
michael@0 368 public static int getResource(Uri resourceUrl, int defaultIcon) {
michael@0 369 int icon = defaultIcon;
michael@0 370
michael@0 371 final String scheme = resourceUrl.getScheme();
michael@0 372 if ("drawable".equals(scheme)) {
michael@0 373 String resource = resourceUrl.getSchemeSpecificPart();
michael@0 374 resource = resource.substring(resource.lastIndexOf('/') + 1);
michael@0 375
michael@0 376 try {
michael@0 377 return Integer.parseInt(resource);
michael@0 378 } catch(NumberFormatException ex) {
michael@0 379 // This isn't a resource id, try looking for a string
michael@0 380 }
michael@0 381
michael@0 382 try {
michael@0 383 final Class<R.drawable> drawableClass = R.drawable.class;
michael@0 384 final Field f = drawableClass.getField(resource);
michael@0 385 icon = f.getInt(null);
michael@0 386 } catch (final NoSuchFieldException e1) {
michael@0 387
michael@0 388 // just means the resource doesn't exist for fennec. Check in Android resources
michael@0 389 try {
michael@0 390 final Class<android.R.drawable> drawableClass = android.R.drawable.class;
michael@0 391 final Field f = drawableClass.getField(resource);
michael@0 392 icon = f.getInt(null);
michael@0 393 } catch (final NoSuchFieldException e2) {
michael@0 394 // This drawable doesn't seem to exist...
michael@0 395 } catch(Exception e3) {
michael@0 396 Log.i(LOGTAG, "Exception getting drawable", e3);
michael@0 397 }
michael@0 398
michael@0 399 } catch (Exception e4) {
michael@0 400 Log.i(LOGTAG, "Exception getting drawable", e4);
michael@0 401 }
michael@0 402
michael@0 403 resourceUrl = null;
michael@0 404 }
michael@0 405 return icon;
michael@0 406 }
michael@0 407 }
michael@0 408

mercurial