mobile/android/base/favicons/decoders/FaviconDecoder.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.favicons.decoders;
     7 import android.graphics.Bitmap;
     8 import android.util.Base64;
     9 import android.util.Log;
    11 import org.mozilla.gecko.gfx.BitmapUtils;
    13 import java.util.Iterator;
    14 import java.util.NoSuchElementException;
    16 /**
    17  * Class providing static utility methods for decoding favicons.
    18  */
    19 public class FaviconDecoder {
    20     private static final String LOG_TAG = "GeckoFaviconDecoder";
    22     static enum ImageMagicNumbers {
    23         // It is irritating that Java bytes are signed...
    24         PNG(new byte[] {(byte) (0x89 & 0xFF), 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}),
    25         GIF(new byte[] {0x47, 0x49, 0x46, 0x38}),
    26         JPEG(new byte[] {-0x1, -0x28, -0x1, -0x20}),
    27         BMP(new byte[] {0x42, 0x4d}),
    28         WEB(new byte[] {0x57, 0x45, 0x42, 0x50, 0x0a});
    30         public byte[] value;
    32         private ImageMagicNumbers(byte[] value) {
    33             this.value = value;
    34         }
    35     }
    37     /**
    38      * Check for image format magic numbers of formats supported by Android.
    39      * @param buffer Byte buffer to check for magic numbers
    40      * @param offset Offset at which to look for magic numbers.
    41      * @return true if the buffer contains a bitmap decodable by Android (Or at least, a sequence
    42      *         starting with the magic numbers thereof). false otherwise.
    43      */
    44     private static boolean isDecodableByAndroid(byte[] buffer, int offset) {
    45         for (ImageMagicNumbers m : ImageMagicNumbers.values()) {
    46             if (bufferStartsWith(buffer, m.value, offset)) {
    47                 return true;
    48             }
    49         }
    51         return false;
    52     }
    54     /**
    55      * Utility function to check for the existence of a test byte sequence at a given offset in a
    56      * buffer.
    57      *
    58      * @param buffer Byte buffer to search.
    59      * @param test Byte sequence to search for.
    60      * @param bufferOffset Index in input buffer to expect test sequence.
    61      * @return true if buffer contains the byte sequence given in test at offset bufferOffset, false
    62      *         otherwise.
    63      */
    64     static boolean bufferStartsWith(byte[] buffer, byte[] test, int bufferOffset) {
    65         if (buffer.length < test.length) {
    66             return false;
    67         }
    69         for (int i = 0; i < test.length; ++i) {
    70             if (buffer[bufferOffset + i] != test[i]) {
    71                 return false;
    72             }
    73         }
    74         return true;
    75     }
    77     /**
    78      * Decode the favicon present in the region of the provided byte[] starting at offset and
    79      * proceeding for length bytes, if any. Returns either the resulting LoadFaviconResult or null if the
    80      * given range does not contain a bitmap we know how to decode.
    81      *
    82      * @param buffer Byte array containing the favicon to decode.
    83      * @param offset The index of the first byte in the array of the region of interest.
    84      * @param length The length of the region in the array to decode.
    85      * @return The decoded version of the bitmap in the described region, or null if none can be
    86      *         decoded.
    87      */
    88     public static LoadFaviconResult decodeFavicon(byte[] buffer, int offset, int length) {
    89         LoadFaviconResult result;
    90         if (isDecodableByAndroid(buffer, offset)) {
    91             result = new LoadFaviconResult();
    92             result.offset = offset;
    93             result.length = length;
    94             result.isICO = false;
    96             Bitmap decodedImage = BitmapUtils.decodeByteArray(buffer, offset, length);
    97             if (decodedImage == null) {
    98                 // What we got wasn't decodable after all. Probably corrupted image, or we got a muffled OOM.
    99                 return null;
   100             }
   102             // We assume here that decodeByteArray doesn't hold on to the entire supplied
   103             // buffer -- worst case, each of our buffers will be twice the necessary size.
   104             result.bitmapsDecoded = new SingleBitmapIterator(decodedImage);
   105             result.faviconBytes = buffer;
   107             return result;
   108         }
   110         // If it's not decodable by Android, it might be an ICO. Let's try.
   111         ICODecoder decoder = new ICODecoder(buffer, offset, length);
   113         result = decoder.decode();
   115         if (result == null) {
   116             return null;
   117         }
   119         return result;
   120     }
   122     public static LoadFaviconResult decodeDataURI(String uri) {
   123         if (uri == null) {
   124             Log.w(LOG_TAG, "Can't decode null data: URI.");
   125             return null;
   126         }
   128         if (!uri.startsWith("data:image/")) {
   129             Log.w(LOG_TAG, "Can't decode non-image data: URI.");
   130             return null;
   131         }
   133         // Otherwise, let's attack this blindly. Strictly we should be parsing.
   134         int offset = uri.indexOf(',') + 1;
   135         if (offset == 0) {
   136             Log.w(LOG_TAG, "No ',' in data: URI; malformed?");
   137             return null;
   138         }
   140         try {
   141             String base64 = uri.substring(offset);
   142             byte[] raw = Base64.decode(base64, Base64.DEFAULT);
   143             return decodeFavicon(raw);
   144         } catch (Exception e) {
   145             Log.w(LOG_TAG, "Couldn't decode data: URI.", e);
   146             return null;
   147         }
   148     }
   150     public static LoadFaviconResult decodeFavicon(byte[] buffer) {
   151         return decodeFavicon(buffer, 0, buffer.length);
   152     }
   154     /**
   155      * Returns the smallest bitmap in the icon represented by the provided
   156      * data: URI that's larger than the desired width, or the largest if
   157      * there is no larger icon.
   158      *
   159      * Returns null if no bitmap could be extracted.
   160      *
   161      * Bug 961600: we shouldn't be doing all of this work. The favicon cache
   162      * should be used, and will give us the right size icon.
   163      */
   164     public static Bitmap getMostSuitableBitmapFromDataURI(String iconURI, int desiredWidth) {
   165         LoadFaviconResult result = FaviconDecoder.decodeDataURI(iconURI);
   166         if (result == null) {
   167             // Nothing we can do.
   168             Log.w(LOG_TAG, "Unable to decode icon URI.");
   169             return null;
   170         }
   172         final Iterator<Bitmap> bitmaps = result.getBitmaps();
   173         if (!bitmaps.hasNext()) {
   174             Log.w(LOG_TAG, "No bitmaps in decoded icon.");
   175             return null;
   176         }
   178         Bitmap bitmap = bitmaps.next();
   179         if (!bitmaps.hasNext()) {
   180             // We're done! There was only one, so this is as big as it gets.
   181             return bitmap;
   182         }
   184         // Find a bitmap of the most suitable size.
   185         int currentWidth = bitmap.getWidth();
   186         while ((currentWidth < desiredWidth) &&
   187                bitmaps.hasNext()) {
   188             final Bitmap b = bitmaps.next();
   189             if (b.getWidth() > currentWidth) {
   190                 currentWidth = b.getWidth();
   191                 bitmap = b;
   192             }
   193         }
   195         return bitmap;
   196     }
   198     /**
   199      * Iterator to hold a single bitmap.
   200      */
   201     static class SingleBitmapIterator implements Iterator<Bitmap> {
   202         private Bitmap bitmap;
   204         public SingleBitmapIterator(Bitmap b) {
   205             bitmap = b;
   206         }
   208         /**
   209          * Slightly cheating here - this iterator supports peeking (Handy in a couple of obscure
   210          * places where the runtime type of the Iterator under consideration is known and
   211          * destruction of it is discouraged.
   212          *
   213          * @return The bitmap carried by this SingleBitmapIterator.
   214          */
   215         public Bitmap peek() {
   216             return bitmap;
   217         }
   219         @Override
   220         public boolean hasNext() {
   221             return bitmap != null;
   222         }
   224         @Override
   225         public Bitmap next() {
   226             if (bitmap == null) {
   227                 throw new NoSuchElementException("Element already returned from SingleBitmapIterator.");
   228             }
   230             Bitmap ret = bitmap;
   231             bitmap = null;
   232             return ret;
   233         }
   235         @Override
   236         public void remove() {
   237             throw new UnsupportedOperationException("remove() not supported on SingleBitmapIterator.");
   238         }
   239     }
   240 }

mercurial