Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.graphics;
     18 
     19 import com.android.ide.common.rendering.api.LayoutLog;
     20 import com.android.layoutlib.bridge.Bridge;
     21 import com.android.layoutlib.bridge.impl.DelegateManager;
     22 import com.android.resources.Density;
     23 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
     24 
     25 import android.graphics.Bitmap;
     26 import android.graphics.Bitmap.Config;
     27 import android.os.Parcel;
     28 
     29 import java.awt.Graphics2D;
     30 import java.awt.image.BufferedImage;
     31 import java.io.File;
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.io.OutputStream;
     35 import java.nio.Buffer;
     36 import java.util.Arrays;
     37 
     38 import javax.imageio.ImageIO;
     39 
     40 /**
     41  * Delegate implementing the native methods of android.graphics.Bitmap
     42  *
     43  * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
     44  * by calls to methods of the same name in this delegate class.
     45  *
     46  * This class behaves like the original native implementation, but in Java, keeping previously
     47  * native data into its own objects and mapping them to int that are sent back and forth between
     48  * it and the original Bitmap class.
     49  *
     50  * @see DelegateManager
     51  *
     52  */
     53 public final class Bitmap_Delegate {
     54 
     55     // ---- delegate manager ----
     56     private static final DelegateManager<Bitmap_Delegate> sManager =
     57             new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
     58 
     59     // ---- delegate helper data ----
     60 
     61     // ---- delegate data ----
     62     private final Config mConfig;
     63     private BufferedImage mImage;
     64     private boolean mHasAlpha = true;
     65 
     66     // ---- Public Helper methods ----
     67 
     68     /**
     69      * Returns the native delegate associated to a given {@link Bitmap_Delegate} object.
     70      */
     71     public static Bitmap_Delegate getDelegate(Bitmap bitmap) {
     72         return sManager.getDelegate(bitmap.mNativeBitmap);
     73     }
     74 
     75     /**
     76      * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
     77      */
     78     public static Bitmap_Delegate getDelegate(int native_bitmap) {
     79         return sManager.getDelegate(native_bitmap);
     80     }
     81 
     82     /**
     83      * Creates and returns a {@link Bitmap} initialized with the given file content.
     84      *
     85      * @param input the file from which to read the bitmap content
     86      * @param isMutable whether the bitmap is mutable
     87      * @param density the density associated with the bitmap
     88      *
     89      * @see Bitmap#isMutable()
     90      * @see Bitmap#getDensity()
     91      */
     92     public static Bitmap createBitmap(File input, boolean isMutable, Density density)
     93             throws IOException {
     94         // create a delegate with the content of the file.
     95         Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
     96 
     97         return createBitmap(delegate, isMutable, density.getDpiValue());
     98     }
     99 
    100     /**
    101      * Creates and returns a {@link Bitmap} initialized with the given stream content.
    102      *
    103      * @param input the stream from which to read the bitmap content
    104      * @param isMutable whether the bitmap is mutable
    105      * @param density the density associated with the bitmap
    106      *
    107      * @see Bitmap#isMutable()
    108      * @see Bitmap#getDensity()
    109      */
    110     public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
    111             throws IOException {
    112         // create a delegate with the content of the stream.
    113         Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
    114 
    115         return createBitmap(delegate, isMutable, density.getDpiValue());
    116     }
    117 
    118     /**
    119      * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
    120      *
    121      * @param image the bitmap content
    122      * @param isMutable whether the bitmap is mutable
    123      * @param density the density associated with the bitmap
    124      *
    125      * @see Bitmap#isMutable()
    126      * @see Bitmap#getDensity()
    127      */
    128     public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
    129             Density density) throws IOException {
    130         // create a delegate with the given image.
    131         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
    132 
    133         return createBitmap(delegate, isMutable, density.getDpiValue());
    134     }
    135 
    136     /**
    137      * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
    138      */
    139     public static BufferedImage getImage(Bitmap bitmap) {
    140         // get the delegate from the native int.
    141         Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap);
    142         if (delegate == null) {
    143             return null;
    144         }
    145 
    146         return delegate.mImage;
    147     }
    148 
    149     public static int getBufferedImageType(int nativeBitmapConfig) {
    150         switch (Config.sConfigs[nativeBitmapConfig]) {
    151             case ALPHA_8:
    152                 return BufferedImage.TYPE_INT_ARGB;
    153             case RGB_565:
    154                 return BufferedImage.TYPE_INT_ARGB;
    155             case ARGB_4444:
    156                 return BufferedImage.TYPE_INT_ARGB;
    157             case ARGB_8888:
    158                 return BufferedImage.TYPE_INT_ARGB;
    159         }
    160 
    161         return BufferedImage.TYPE_INT_ARGB;
    162     }
    163 
    164     /**
    165      * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
    166      */
    167     public BufferedImage getImage() {
    168         return mImage;
    169     }
    170 
    171     /**
    172      * Returns the Android bitmap config. Note that this not the config of the underlying
    173      * Java2D bitmap.
    174      */
    175     public Config getConfig() {
    176         return mConfig;
    177     }
    178 
    179     /**
    180      * Returns the hasAlpha rendering hint
    181      * @return true if the bitmap alpha should be used at render time
    182      */
    183     public boolean hasAlpha() {
    184         return mHasAlpha && mConfig != Config.RGB_565;
    185     }
    186 
    187     // ---- native methods ----
    188 
    189     @LayoutlibDelegate
    190     /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
    191             int height, int nativeConfig, boolean mutable) {
    192         int imageType = getBufferedImageType(nativeConfig);
    193 
    194         // create the image
    195         BufferedImage image = new BufferedImage(width, height, imageType);
    196 
    197         if (colors != null) {
    198             image.setRGB(0, 0, width, height, colors, offset, stride);
    199         }
    200 
    201         // create a delegate with the content of the stream.
    202         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
    203 
    204         return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
    205     }
    206 
    207     @LayoutlibDelegate
    208     /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
    209         Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
    210         if (srcBmpDelegate == null) {
    211             return null;
    212         }
    213 
    214         BufferedImage srcImage = srcBmpDelegate.getImage();
    215 
    216         int width = srcImage.getWidth();
    217         int height = srcImage.getHeight();
    218 
    219         int imageType = getBufferedImageType(nativeConfig);
    220 
    221         // create the image
    222         BufferedImage image = new BufferedImage(width, height, imageType);
    223 
    224         // copy the source image into the image.
    225         int[] argb = new int[width * height];
    226         srcImage.getRGB(0, 0, width, height, argb, 0, width);
    227         image.setRGB(0, 0, width, height, argb, 0, width);
    228 
    229         // create a delegate with the content of the stream.
    230         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
    231 
    232         return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity());
    233     }
    234 
    235     @LayoutlibDelegate
    236     /*package*/ static void nativeDestructor(int nativeBitmap) {
    237         sManager.removeJavaReferenceFor(nativeBitmap);
    238     }
    239 
    240     @LayoutlibDelegate
    241     /*package*/ static void nativeRecycle(int nativeBitmap) {
    242         sManager.removeJavaReferenceFor(nativeBitmap);
    243     }
    244 
    245     @LayoutlibDelegate
    246     /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
    247             OutputStream stream, byte[] tempStorage) {
    248         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
    249                 "Bitmap.compress() is not supported", null /*data*/);
    250         return true;
    251     }
    252 
    253     @LayoutlibDelegate
    254     /*package*/ static void nativeErase(int nativeBitmap, int color) {
    255         // get the delegate from the native int.
    256         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    257         if (delegate == null) {
    258             return;
    259         }
    260 
    261         BufferedImage image = delegate.mImage;
    262 
    263         Graphics2D g = image.createGraphics();
    264         try {
    265             g.setColor(new java.awt.Color(color, true));
    266 
    267             g.fillRect(0, 0, image.getWidth(), image.getHeight());
    268         } finally {
    269             g.dispose();
    270         }
    271     }
    272 
    273     @LayoutlibDelegate
    274     /*package*/ static int nativeWidth(int nativeBitmap) {
    275         // get the delegate from the native int.
    276         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    277         if (delegate == null) {
    278             return 0;
    279         }
    280 
    281         return delegate.mImage.getWidth();
    282     }
    283 
    284     @LayoutlibDelegate
    285     /*package*/ static int nativeHeight(int nativeBitmap) {
    286         // get the delegate from the native int.
    287         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    288         if (delegate == null) {
    289             return 0;
    290         }
    291 
    292         return delegate.mImage.getHeight();
    293     }
    294 
    295     @LayoutlibDelegate
    296     /*package*/ static int nativeRowBytes(int nativeBitmap) {
    297         // get the delegate from the native int.
    298         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    299         if (delegate == null) {
    300             return 0;
    301         }
    302 
    303         return delegate.mImage.getWidth();
    304     }
    305 
    306     @LayoutlibDelegate
    307     /*package*/ static int nativeConfig(int nativeBitmap) {
    308         // get the delegate from the native int.
    309         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    310         if (delegate == null) {
    311             return 0;
    312         }
    313 
    314         return delegate.mConfig.nativeInt;
    315     }
    316 
    317     @LayoutlibDelegate
    318     /*package*/ static boolean nativeHasAlpha(int nativeBitmap) {
    319         // get the delegate from the native int.
    320         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    321         if (delegate == null) {
    322             return true;
    323         }
    324 
    325         return delegate.mHasAlpha;
    326     }
    327 
    328     @LayoutlibDelegate
    329     /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
    330         // get the delegate from the native int.
    331         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    332         if (delegate == null) {
    333             return 0;
    334         }
    335 
    336         return delegate.mImage.getRGB(x, y);
    337     }
    338 
    339     @LayoutlibDelegate
    340     /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
    341             int stride, int x, int y, int width, int height) {
    342         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    343         if (delegate == null) {
    344             return;
    345         }
    346 
    347         delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
    348     }
    349 
    350 
    351     @LayoutlibDelegate
    352     /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
    353         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    354         if (delegate == null) {
    355             return;
    356         }
    357 
    358         delegate.getImage().setRGB(x, y, color);
    359     }
    360 
    361     @LayoutlibDelegate
    362     /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
    363             int stride, int x, int y, int width, int height) {
    364         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    365         if (delegate == null) {
    366             return;
    367         }
    368 
    369         delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
    370     }
    371 
    372     @LayoutlibDelegate
    373     /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) {
    374         // FIXME implement native delegate
    375         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
    376                 "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
    377     }
    378 
    379     @LayoutlibDelegate
    380     /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) {
    381         // FIXME implement native delegate
    382         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
    383                 "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
    384     }
    385 
    386     @LayoutlibDelegate
    387     /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
    388         // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
    389         // used during aidl call so really this should not be called.
    390         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
    391                 "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
    392                 null /*data*/);
    393         return null;
    394     }
    395 
    396     @LayoutlibDelegate
    397     /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
    398             int density, Parcel p) {
    399         // This is only called when sending a bitmap through aidl, so really this should not
    400         // be called.
    401         Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
    402                 "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
    403                 null /*data*/);
    404         return false;
    405     }
    406 
    407     @LayoutlibDelegate
    408     /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
    409             int[] offsetXY) {
    410         Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
    411         if (bitmap == null) {
    412             return null;
    413         }
    414 
    415         // get the paint which can be null if nativePaint is 0.
    416         Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
    417 
    418         if (paint != null && paint.getMaskFilter() != null) {
    419             Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
    420                     "MaskFilter not supported in Bitmap.extractAlpha",
    421                     null, null /*data*/);
    422         }
    423 
    424         int alpha = paint != null ? paint.getAlpha() : 0xFF;
    425         BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
    426 
    427         // create the delegate. The actual Bitmap config is only an alpha channel
    428         Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
    429 
    430         // the density doesn't matter, it's set by the Java method.
    431         return createBitmap(delegate, false /*isMutable*/,
    432                 Density.DEFAULT_DENSITY /*density*/);
    433     }
    434 
    435     @LayoutlibDelegate
    436     /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
    437         // nothing to be done here.
    438     }
    439 
    440     @LayoutlibDelegate
    441     /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
    442         // get the delegate from the native int.
    443         Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
    444         if (delegate == null) {
    445             return;
    446         }
    447 
    448         delegate.mHasAlpha = hasAlpha;
    449     }
    450 
    451     @LayoutlibDelegate
    452     /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
    453         Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
    454         if (delegate1 == null) {
    455             return false;
    456         }
    457 
    458         Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
    459         if (delegate2 == null) {
    460             return false;
    461         }
    462 
    463         BufferedImage image1 = delegate1.getImage();
    464         BufferedImage image2 = delegate2.getImage();
    465         if (delegate1.mConfig != delegate2.mConfig ||
    466                 image1.getWidth() != image2.getWidth() ||
    467                 image1.getHeight() != image2.getHeight()) {
    468             return false;
    469         }
    470 
    471         // get the internal data
    472         int w = image1.getWidth();
    473         int h = image2.getHeight();
    474         int[] argb1 = new int[w*h];
    475         int[] argb2 = new int[w*h];
    476 
    477         image1.getRGB(0, 0, w, h, argb1, 0, w);
    478         image2.getRGB(0, 0, w, h, argb2, 0, w);
    479 
    480         // compares
    481         if (delegate1.mConfig == Config.ALPHA_8) {
    482             // in this case we have to manually compare the alpha channel as the rest is garbage.
    483             final int length = w*h;
    484             for (int i = 0 ; i < length ; i++) {
    485                 if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
    486                     return false;
    487                 }
    488             }
    489             return true;
    490         }
    491 
    492         return Arrays.equals(argb1, argb2);
    493     }
    494 
    495     // ---- Private delegate/helper methods ----
    496 
    497     private Bitmap_Delegate(BufferedImage image, Config config) {
    498         mImage = image;
    499         mConfig = config;
    500     }
    501 
    502     private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
    503         // get its native_int
    504         int nativeInt = sManager.addNewDelegate(delegate);
    505 
    506         // and create/return a new Bitmap with it
    507         return new Bitmap(nativeInt, isMutable, null /*ninePatchChunk*/, density);
    508     }
    509 
    510     /**
    511      * Creates and returns a copy of a given BufferedImage.
    512      * <p/>
    513      * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
    514      *
    515      * @param image the image to copy
    516      * @param imageType the type of the new image
    517      * @param alpha an optional alpha modifier
    518      * @return a new BufferedImage
    519      */
    520     /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
    521         int w = image.getWidth();
    522         int h = image.getHeight();
    523 
    524         BufferedImage result = new BufferedImage(w, h, imageType);
    525 
    526         int[] argb = new int[w * h];
    527         image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
    528 
    529         if (alpha != 255) {
    530             final int length = argb.length;
    531             for (int i = 0 ; i < length; i++) {
    532                 int a = (argb[i] >>> 24 * alpha) / 255;
    533                 argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
    534             }
    535         }
    536 
    537         result.setRGB(0, 0, w, h, argb, 0, w);
    538 
    539         return result;
    540     }
    541 
    542 }
    543