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