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