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