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