• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
getDelegate(Bitmap bitmap)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      */
getDelegate(int native_bitmap)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      */
createBitmap(File input, boolean isMutable, Density density)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      */
createBitmap(InputStream input, boolean isMutable, Density density)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      */
createBitmap(BufferedImage image, boolean isMutable, Density density)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      */
getImage(Bitmap bitmap)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 
getBufferedImageType(int nativeBitmapConfig)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      */
getImage()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      */
getConfig()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      */
hasAlpha()184     public boolean hasAlpha() {
185         return mHasAlpha && mConfig != Config.RGB_565;
186     }
187 
188     /**
189      * Update the generationId.
190      *
191      * @see Bitmap#getGenerationId()
192      */
change()193     public void change() {
194         mGenerationId++;
195     }
196 
197     // ---- native methods ----
198 
199     @LayoutlibDelegate
nativeCreate(int[] colors, int offset, int stride, int width, int height, int nativeConfig, boolean mutable)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
nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable)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
nativeDestructor(int nativeBitmap)246     /*package*/ static void nativeDestructor(int nativeBitmap) {
247         sManager.removeJavaReferenceFor(nativeBitmap);
248     }
249 
250     @LayoutlibDelegate
nativeRecycle(int nativeBitmap)251     /*package*/ static void nativeRecycle(int nativeBitmap) {
252         sManager.removeJavaReferenceFor(nativeBitmap);
253     }
254 
255     @LayoutlibDelegate
nativeCompress(int nativeBitmap, int format, int quality, OutputStream stream, byte[] tempStorage)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
nativeErase(int nativeBitmap, int color)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
nativeWidth(int nativeBitmap)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
nativeHeight(int nativeBitmap)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
nativeRowBytes(int nativeBitmap)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
nativeConfig(int nativeBitmap)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
nativeHasAlpha(int nativeBitmap)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
nativeGetPixel(int nativeBitmap, int x, int y)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
nativeGetPixels(int nativeBitmap, int[] pixels, int offset, int stride, int x, int y, int width, int height)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
nativeSetPixel(int nativeBitmap, int x, int y, int color)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
nativeSetPixels(int nativeBitmap, int[] colors, int offset, int stride, int x, int y, int width, int height)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
nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst)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
nativeCopyPixelsFromBuffer(int nb, Buffer src)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
nativeGenerationId(int nativeBitmap)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
nativeCreateFromParcel(Parcel p)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
nativeWriteToParcel(int nativeBitmap, boolean isMutable, int density, Parcel p)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
nativeExtractAlpha(int nativeBitmap, int nativePaint, int[] offsetXY)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
nativePrepareToDraw(int nativeBitmap)456     /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
457         // nothing to be done here.
458     }
459 
460     @LayoutlibDelegate
nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha)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
nativeSameAs(int nb0, int nb1)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 
Bitmap_Delegate(BufferedImage image, Config config)517     private Bitmap_Delegate(BufferedImage image, Config config) {
518         mImage = image;
519         mConfig = config;
520     }
521 
createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density)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*/, density);
528     }
529 
530     /**
531      * Creates and returns a copy of a given BufferedImage.
532      * <p/>
533      * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
534      *
535      * @param image the image to copy
536      * @param imageType the type of the new image
537      * @param alpha an optional alpha modifier
538      * @return a new BufferedImage
539      */
createCopy(BufferedImage image, int imageType, int alpha)540     /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
541         int w = image.getWidth();
542         int h = image.getHeight();
543 
544         BufferedImage result = new BufferedImage(w, h, imageType);
545 
546         int[] argb = new int[w * h];
547         image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
548 
549         if (alpha != 255) {
550             final int length = argb.length;
551             for (int i = 0 ; i < length; i++) {
552                 int a = (argb[i] >>> 24 * alpha) / 255;
553                 argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
554             }
555         }
556 
557         result.setRGB(0, 0, w, h, argb, 0, w);
558 
559         return result;
560     }
561 
562 }
563