mobile/android/base/gfx/BitmapUtils.java

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/gfx/BitmapUtils.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,408 @@
     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
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +package org.mozilla.gecko.gfx;
    1.10 +
    1.11 +import java.io.IOException;
    1.12 +import java.io.InputStream;
    1.13 +import java.lang.reflect.Field;
    1.14 +import java.net.MalformedURLException;
    1.15 +import java.net.URL;
    1.16 +
    1.17 +import org.mozilla.gecko.R;
    1.18 +import org.mozilla.gecko.util.GeckoJarReader;
    1.19 +import org.mozilla.gecko.util.ThreadUtils;
    1.20 +import org.mozilla.gecko.util.UiAsyncTask;
    1.21 +import org.mozilla.gecko.Tab;
    1.22 +import org.mozilla.gecko.Tabs;
    1.23 +import org.mozilla.gecko.ThumbnailHelper;
    1.24 +
    1.25 +import android.content.Context;
    1.26 +import android.content.res.Resources;
    1.27 +import android.graphics.Bitmap;
    1.28 +import android.graphics.BitmapFactory;
    1.29 +import android.graphics.Canvas;
    1.30 +import android.graphics.Color;
    1.31 +import android.graphics.drawable.BitmapDrawable;
    1.32 +import android.graphics.drawable.Drawable;
    1.33 +import android.net.Uri;
    1.34 +import android.text.TextUtils;
    1.35 +import android.util.Base64;
    1.36 +import android.util.Log;
    1.37 +
    1.38 +public final class BitmapUtils {
    1.39 +    private static final String LOGTAG = "GeckoBitmapUtils";
    1.40 +
    1.41 +    private BitmapUtils() {}
    1.42 +
    1.43 +    public interface BitmapLoader {
    1.44 +        public void onBitmapFound(Drawable d);
    1.45 +    }
    1.46 +
    1.47 +    private static void runOnBitmapFoundOnUiThread(final BitmapLoader loader, final Drawable d) {
    1.48 +        if (ThreadUtils.isOnUiThread()) {
    1.49 +            loader.onBitmapFound(d);
    1.50 +            return;
    1.51 +        }
    1.52 +
    1.53 +        ThreadUtils.postToUiThread(new Runnable() {
    1.54 +            @Override
    1.55 +            public void run() {
    1.56 +                loader.onBitmapFound(d);
    1.57 +            }
    1.58 +        });
    1.59 +    }
    1.60 +
    1.61 +    /**
    1.62 +     * Attempts to find a drawable associated with a given string, using its URI scheme to determine
    1.63 +     * how to load the drawable. The BitmapLoader's `onBitmapFound` method is always called, and
    1.64 +     * will be called with `null` if no drawable is found.
    1.65 +     *
    1.66 +     * The BitmapLoader `onBitmapFound` method always runs on the UI thread.
    1.67 +     */
    1.68 +    public static void getDrawable(final Context context, final String data, final BitmapLoader loader) {
    1.69 +        if (TextUtils.isEmpty(data)) {
    1.70 +            runOnBitmapFoundOnUiThread(loader, null);
    1.71 +            return;
    1.72 +        }
    1.73 +
    1.74 +        if (data.startsWith("data")) {
    1.75 +            final BitmapDrawable d = new BitmapDrawable(context.getResources(), getBitmapFromDataURI(data));
    1.76 +            runOnBitmapFoundOnUiThread(loader, d);
    1.77 +            return;
    1.78 +        }
    1.79 +
    1.80 +        if (data.startsWith("thumbnail:")) {
    1.81 +            getThumbnailDrawable(context, data, loader);
    1.82 +            return;
    1.83 +        }
    1.84 +
    1.85 +        if (data.startsWith("jar:") || data.startsWith("file://")) {
    1.86 +            (new UiAsyncTask<Void, Void, Drawable>(ThreadUtils.getBackgroundHandler()) {
    1.87 +                @Override
    1.88 +                public Drawable doInBackground(Void... params) {
    1.89 +                    try {
    1.90 +                        if (data.startsWith("jar:jar")) {
    1.91 +                            return GeckoJarReader.getBitmapDrawable(context.getResources(), data);
    1.92 +                        }
    1.93 +
    1.94 +                        // Don't attempt to validate the JAR signature when loading an add-on icon
    1.95 +                        if (data.startsWith("jar:file")) {
    1.96 +                            return GeckoJarReader.getBitmapDrawable(context.getResources(), Uri.decode(data));
    1.97 +                        }
    1.98 +
    1.99 +                        final URL url = new URL(data);
   1.100 +                        final InputStream is = (InputStream) url.getContent();
   1.101 +                        try {
   1.102 +                            return Drawable.createFromStream(is, "src");
   1.103 +                        } finally {
   1.104 +                            is.close();
   1.105 +                        }
   1.106 +                    } catch (Exception e) {
   1.107 +                        Log.w(LOGTAG, "Unable to set icon", e);
   1.108 +                    }
   1.109 +                    return null;
   1.110 +                }
   1.111 +
   1.112 +                @Override
   1.113 +                public void onPostExecute(Drawable drawable) {
   1.114 +                    loader.onBitmapFound(drawable);
   1.115 +                }
   1.116 +            }).execute();
   1.117 +            return;
   1.118 +        }
   1.119 +
   1.120 +        if (data.startsWith("-moz-icon://")) {
   1.121 +            final Uri imageUri = Uri.parse(data);
   1.122 +            final String ssp = imageUri.getSchemeSpecificPart();
   1.123 +            final String resource = ssp.substring(ssp.lastIndexOf('/') + 1);
   1.124 +
   1.125 +            try {
   1.126 +                final Drawable d = context.getPackageManager().getApplicationIcon(resource);
   1.127 +                runOnBitmapFoundOnUiThread(loader, d);
   1.128 +            } catch(Exception ex) { }
   1.129 +
   1.130 +            return;
   1.131 +        }
   1.132 +
   1.133 +        if (data.startsWith("drawable://")) {
   1.134 +            final Uri imageUri = Uri.parse(data);
   1.135 +            final int id = getResource(imageUri, R.drawable.ic_status_logo);
   1.136 +            final Drawable d = context.getResources().getDrawable(id);
   1.137 +
   1.138 +            runOnBitmapFoundOnUiThread(loader, d);
   1.139 +            return;
   1.140 +        }
   1.141 +
   1.142 +        runOnBitmapFoundOnUiThread(loader, null);
   1.143 +    }
   1.144 +
   1.145 +    public static void getThumbnailDrawable(final Context context, final String data, final BitmapLoader loader) {
   1.146 +         int id = Integer.parseInt(data.substring(10), 10);
   1.147 +         final Tab tab = Tabs.getInstance().getTab(id);
   1.148 +         runOnBitmapFoundOnUiThread(loader, tab.getThumbnail());
   1.149 +         Tabs.registerOnTabsChangedListener(new Tabs.OnTabsChangedListener() {
   1.150 +                 public void onTabChanged(Tab t, Tabs.TabEvents msg, Object data) {
   1.151 +                     if (tab == t && msg == Tabs.TabEvents.THUMBNAIL) {
   1.152 +                         Tabs.unregisterOnTabsChangedListener(this);
   1.153 +                         runOnBitmapFoundOnUiThread(loader, t.getThumbnail());
   1.154 +                     }
   1.155 +                 }
   1.156 +             });
   1.157 +         ThumbnailHelper.getInstance().getAndProcessThumbnailFor(tab);
   1.158 +    }
   1.159 +
   1.160 +    public static Bitmap decodeByteArray(byte[] bytes) {
   1.161 +        return decodeByteArray(bytes, null);
   1.162 +    }
   1.163 +
   1.164 +    public static Bitmap decodeByteArray(byte[] bytes, BitmapFactory.Options options) {
   1.165 +        return decodeByteArray(bytes, 0, bytes.length, options);
   1.166 +    }
   1.167 +
   1.168 +    public static Bitmap decodeByteArray(byte[] bytes, int offset, int length) {
   1.169 +        return decodeByteArray(bytes, offset, length, null);
   1.170 +    }
   1.171 +
   1.172 +    public static Bitmap decodeByteArray(byte[] bytes, int offset, int length, BitmapFactory.Options options) {
   1.173 +        if (bytes.length <= 0) {
   1.174 +            throw new IllegalArgumentException("bytes.length " + bytes.length
   1.175 +                                               + " must be a positive number");
   1.176 +        }
   1.177 +
   1.178 +        Bitmap bitmap = null;
   1.179 +        try {
   1.180 +            bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, options);
   1.181 +        } catch (OutOfMemoryError e) {
   1.182 +            Log.e(LOGTAG, "decodeByteArray(bytes.length=" + bytes.length
   1.183 +                          + ", options= " + options + ") OOM!", e);
   1.184 +            return null;
   1.185 +        }
   1.186 +
   1.187 +        if (bitmap == null) {
   1.188 +            Log.w(LOGTAG, "decodeByteArray() returning null because BitmapFactory returned null");
   1.189 +            return null;
   1.190 +        }
   1.191 +
   1.192 +        if (bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
   1.193 +            Log.w(LOGTAG, "decodeByteArray() returning null because BitmapFactory returned "
   1.194 +                          + "a bitmap with dimensions " + bitmap.getWidth()
   1.195 +                          + "x" + bitmap.getHeight());
   1.196 +            return null;
   1.197 +        }
   1.198 +
   1.199 +        return bitmap;
   1.200 +    }
   1.201 +
   1.202 +    public static Bitmap decodeStream(InputStream inputStream) {
   1.203 +        try {
   1.204 +            return BitmapFactory.decodeStream(inputStream);
   1.205 +        } catch (OutOfMemoryError e) {
   1.206 +            Log.e(LOGTAG, "decodeStream() OOM!", e);
   1.207 +            return null;
   1.208 +        }
   1.209 +    }
   1.210 +
   1.211 +    public static Bitmap decodeUrl(Uri uri) {
   1.212 +        return decodeUrl(uri.toString());
   1.213 +    }
   1.214 +
   1.215 +    public static Bitmap decodeUrl(String urlString) {
   1.216 +        URL url;
   1.217 +
   1.218 +        try {
   1.219 +            url = new URL(urlString);
   1.220 +        } catch(MalformedURLException e) {
   1.221 +            Log.w(LOGTAG, "decodeUrl: malformed URL " + urlString);
   1.222 +            return null;
   1.223 +        }
   1.224 +
   1.225 +        return decodeUrl(url);
   1.226 +    }
   1.227 +
   1.228 +    public static Bitmap decodeUrl(URL url) {
   1.229 +        InputStream stream = null;
   1.230 +
   1.231 +        try {
   1.232 +            stream = url.openStream();
   1.233 +        } catch(IOException e) {
   1.234 +            Log.w(LOGTAG, "decodeUrl: IOException downloading " + url);
   1.235 +            return null;
   1.236 +        }
   1.237 +
   1.238 +        if (stream == null) {
   1.239 +            Log.w(LOGTAG, "decodeUrl: stream not found downloading " + url);
   1.240 +            return null;
   1.241 +        }
   1.242 +
   1.243 +        Bitmap bitmap = decodeStream(stream);
   1.244 +
   1.245 +        try {
   1.246 +            stream.close();
   1.247 +        } catch(IOException e) {
   1.248 +            Log.w(LOGTAG, "decodeUrl: IOException closing stream " + url, e);
   1.249 +        }
   1.250 +
   1.251 +        return bitmap;
   1.252 +    }
   1.253 +
   1.254 +    public static Bitmap decodeResource(Context context, int id) {
   1.255 +        return decodeResource(context, id, null);
   1.256 +    }
   1.257 +
   1.258 +    public static Bitmap decodeResource(Context context, int id, BitmapFactory.Options options) {
   1.259 +        Resources resources = context.getResources();
   1.260 +        try {
   1.261 +            return BitmapFactory.decodeResource(resources, id, options);
   1.262 +        } catch (OutOfMemoryError e) {
   1.263 +            Log.e(LOGTAG, "decodeResource() OOM! Resource id=" + id, e);
   1.264 +            return null;
   1.265 +        }
   1.266 +    }
   1.267 +
   1.268 +    public static int getDominantColor(Bitmap source) {
   1.269 +        return getDominantColor(source, true);
   1.270 +    }
   1.271 +
   1.272 +    public static int getDominantColor(Bitmap source, boolean applyThreshold) {
   1.273 +      if (source == null)
   1.274 +        return Color.argb(255,255,255,255);
   1.275 +
   1.276 +      // Keep track of how many times a hue in a given bin appears in the image.
   1.277 +      // Hue values range [0 .. 360), so dividing by 10, we get 36 bins.
   1.278 +      int[] colorBins = new int[36];
   1.279 +
   1.280 +      // The bin with the most colors. Initialize to -1 to prevent accidentally
   1.281 +      // thinking the first bin holds the dominant color.
   1.282 +      int maxBin = -1;
   1.283 +
   1.284 +      // Keep track of sum hue/saturation/value per hue bin, which we'll use to
   1.285 +      // compute an average to for the dominant color.
   1.286 +      float[] sumHue = new float[36];
   1.287 +      float[] sumSat = new float[36];
   1.288 +      float[] sumVal = new float[36];
   1.289 +      float[] hsv = new float[3];
   1.290 +
   1.291 +      int height = source.getHeight();
   1.292 +      int width = source.getWidth();
   1.293 +      int[] pixels = new int[width * height];
   1.294 +      source.getPixels(pixels, 0, width, 0, 0, width, height);
   1.295 +      for (int row = 0; row < height; row++) {
   1.296 +        for (int col = 0; col < width; col++) {
   1.297 +          int c = pixels[col + row * width];
   1.298 +          // Ignore pixels with a certain transparency.
   1.299 +          if (Color.alpha(c) < 128)
   1.300 +            continue;
   1.301 +
   1.302 +          Color.colorToHSV(c, hsv);
   1.303 +
   1.304 +          // If a threshold is applied, ignore arbitrarily chosen values for "white" and "black".
   1.305 +          if (applyThreshold && (hsv[1] <= 0.35f || hsv[2] <= 0.35f))
   1.306 +            continue;
   1.307 +
   1.308 +          // We compute the dominant color by putting colors in bins based on their hue.
   1.309 +          int bin = (int) Math.floor(hsv[0] / 10.0f);
   1.310 +
   1.311 +          // Update the sum hue/saturation/value for this bin.
   1.312 +          sumHue[bin] = sumHue[bin] + hsv[0];
   1.313 +          sumSat[bin] = sumSat[bin] + hsv[1];
   1.314 +          sumVal[bin] = sumVal[bin] + hsv[2];
   1.315 +
   1.316 +          // Increment the number of colors in this bin.
   1.317 +          colorBins[bin]++;
   1.318 +
   1.319 +          // Keep track of the bin that holds the most colors.
   1.320 +          if (maxBin < 0 || colorBins[bin] > colorBins[maxBin])
   1.321 +            maxBin = bin;
   1.322 +        }
   1.323 +      }
   1.324 +
   1.325 +      // maxBin may never get updated if the image holds only transparent and/or black/white pixels.
   1.326 +      if (maxBin < 0)
   1.327 +        return Color.argb(255,255,255,255);
   1.328 +
   1.329 +      // Return a color with the average hue/saturation/value of the bin with the most colors.
   1.330 +      hsv[0] = sumHue[maxBin]/colorBins[maxBin];
   1.331 +      hsv[1] = sumSat[maxBin]/colorBins[maxBin];
   1.332 +      hsv[2] = sumVal[maxBin]/colorBins[maxBin];
   1.333 +      return Color.HSVToColor(hsv);
   1.334 +    }
   1.335 +
   1.336 +    /**
   1.337 +     * Decodes a bitmap from a Base64 data URI.
   1.338 +     *
   1.339 +     * @param dataURI a Base64-encoded data URI string
   1.340 +     * @return        the decoded bitmap, or null if the data URI is invalid
   1.341 +     */
   1.342 +    public static Bitmap getBitmapFromDataURI(String dataURI) {
   1.343 +        String base64 = dataURI.substring(dataURI.indexOf(',') + 1);
   1.344 +        try {
   1.345 +            byte[] raw = Base64.decode(base64, Base64.DEFAULT);
   1.346 +            return BitmapUtils.decodeByteArray(raw);
   1.347 +        } catch (Exception e) {
   1.348 +            Log.e(LOGTAG, "exception decoding bitmap from data URI: " + dataURI, e);
   1.349 +        }
   1.350 +        return null;
   1.351 +    }
   1.352 +
   1.353 +    public static Bitmap getBitmapFromDrawable(Drawable drawable) {
   1.354 +        if (drawable instanceof BitmapDrawable) {
   1.355 +            return ((BitmapDrawable) drawable).getBitmap();
   1.356 +        }
   1.357 +
   1.358 +        int width = drawable.getIntrinsicWidth();
   1.359 +        width = width > 0 ? width : 1;
   1.360 +        int height = drawable.getIntrinsicHeight();
   1.361 +        height = height > 0 ? height : 1;
   1.362 +
   1.363 +        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
   1.364 +        Canvas canvas = new Canvas(bitmap);
   1.365 +        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
   1.366 +        drawable.draw(canvas);
   1.367 +
   1.368 +        return bitmap;
   1.369 +    }
   1.370 +
   1.371 +    public static int getResource(Uri resourceUrl, int defaultIcon) {
   1.372 +        int icon = defaultIcon;
   1.373 +
   1.374 +        final String scheme = resourceUrl.getScheme();
   1.375 +        if ("drawable".equals(scheme)) {
   1.376 +            String resource = resourceUrl.getSchemeSpecificPart();
   1.377 +            resource = resource.substring(resource.lastIndexOf('/') + 1);
   1.378 +
   1.379 +            try {
   1.380 +                return Integer.parseInt(resource);
   1.381 +            } catch(NumberFormatException ex) {
   1.382 +                // This isn't a resource id, try looking for a string
   1.383 +            }
   1.384 +
   1.385 +            try {
   1.386 +                final Class<R.drawable> drawableClass = R.drawable.class;
   1.387 +                final Field f = drawableClass.getField(resource);
   1.388 +                icon = f.getInt(null);
   1.389 +            } catch (final NoSuchFieldException e1) {
   1.390 +
   1.391 +                // just means the resource doesn't exist for fennec. Check in Android resources
   1.392 +                try {
   1.393 +                    final Class<android.R.drawable> drawableClass = android.R.drawable.class;
   1.394 +                    final Field f = drawableClass.getField(resource);
   1.395 +                    icon = f.getInt(null);
   1.396 +                } catch (final NoSuchFieldException e2) {
   1.397 +                    // This drawable doesn't seem to exist...
   1.398 +                } catch(Exception e3) {
   1.399 +                    Log.i(LOGTAG, "Exception getting drawable", e3);
   1.400 +                }
   1.401 +
   1.402 +            } catch (Exception e4) {
   1.403 +              Log.i(LOGTAG, "Exception getting drawable", e4);
   1.404 +            }
   1.405 +
   1.406 +            resourceUrl = null;
   1.407 +        }
   1.408 +        return icon;
   1.409 +    }
   1.410 +}
   1.411 +

mercurial