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