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(int 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(int 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(int nativeBitmap) {
307        sManager.removeJavaReferenceFor(nativeBitmap);
308    }
309
310    @LayoutlibDelegate
311    /*package*/ static boolean nativeRecycle(int nativeBitmap) {
312        sManager.removeJavaReferenceFor(nativeBitmap);
313        return true;
314    }
315
316    @LayoutlibDelegate
317    /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
318            OutputStream stream, byte[] tempStorage) {
319        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
320                "Bitmap.compress() is not supported", null /*data*/);
321        return true;
322    }
323
324    @LayoutlibDelegate
325    /*package*/ static void nativeErase(int nativeBitmap, int color) {
326        // get the delegate from the native int.
327        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
328        if (delegate == null) {
329            return;
330        }
331
332        BufferedImage image = delegate.mImage;
333
334        Graphics2D g = image.createGraphics();
335        try {
336            g.setColor(new java.awt.Color(color, true));
337
338            g.fillRect(0, 0, image.getWidth(), image.getHeight());
339        } finally {
340            g.dispose();
341        }
342    }
343
344    @LayoutlibDelegate
345    /*package*/ static int nativeWidth(int nativeBitmap) {
346        // get the delegate from the native int.
347        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
348        if (delegate == null) {
349            return 0;
350        }
351
352        return delegate.mImage.getWidth();
353    }
354
355    @LayoutlibDelegate
356    /*package*/ static int nativeHeight(int nativeBitmap) {
357        // get the delegate from the native int.
358        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
359        if (delegate == null) {
360            return 0;
361        }
362
363        return delegate.mImage.getHeight();
364    }
365
366    @LayoutlibDelegate
367    /*package*/ static int nativeRowBytes(int nativeBitmap) {
368        // get the delegate from the native int.
369        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
370        if (delegate == null) {
371            return 0;
372        }
373
374        return delegate.mImage.getWidth();
375    }
376
377    @LayoutlibDelegate
378    /*package*/ static int nativeConfig(int nativeBitmap) {
379        // get the delegate from the native int.
380        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
381        if (delegate == null) {
382            return 0;
383        }
384
385        return delegate.mConfig.nativeInt;
386    }
387
388    @LayoutlibDelegate
389    /*package*/ static boolean nativeHasAlpha(int nativeBitmap) {
390        // get the delegate from the native int.
391        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
392        if (delegate == null) {
393            return true;
394        }
395
396        return delegate.mHasAlpha;
397    }
398
399    @LayoutlibDelegate
400    /*package*/ static boolean nativeHasMipMap(int nativeBitmap) {
401        // get the delegate from the native int.
402        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
403        if (delegate == null) {
404            return true;
405        }
406
407        return delegate.mHasMipMap;
408    }
409
410    @LayoutlibDelegate
411    /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
412        // get the delegate from the native int.
413        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
414        if (delegate == null) {
415            return 0;
416        }
417
418        return delegate.mImage.getRGB(x, y);
419    }
420
421    @LayoutlibDelegate
422    /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
423            int stride, int x, int y, int width, int height) {
424        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
425        if (delegate == null) {
426            return;
427        }
428
429        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
430    }
431
432
433    @LayoutlibDelegate
434    /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
435        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
436        if (delegate == null) {
437            return;
438        }
439
440        delegate.getImage().setRGB(x, y, color);
441    }
442
443    @LayoutlibDelegate
444    /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
445            int stride, int x, int y, int width, int height) {
446        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
447        if (delegate == null) {
448            return;
449        }
450
451        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
452    }
453
454    @LayoutlibDelegate
455    /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) {
456        // FIXME implement native delegate
457        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
458                "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
459    }
460
461    @LayoutlibDelegate
462    /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) {
463        // FIXME implement native delegate
464        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
465                "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
466    }
467
468    @LayoutlibDelegate
469    /*package*/ static int nativeGenerationId(int nativeBitmap) {
470        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
471        if (delegate == null) {
472            return 0;
473        }
474
475        return delegate.mGenerationId;
476    }
477
478    @LayoutlibDelegate
479    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
480        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
481        // used during aidl call so really this should not be called.
482        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
483                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
484                null /*data*/);
485        return null;
486    }
487
488    @LayoutlibDelegate
489    /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
490            int density, Parcel p) {
491        // This is only called when sending a bitmap through aidl, so really this should not
492        // be called.
493        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
494                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
495                null /*data*/);
496        return false;
497    }
498
499    @LayoutlibDelegate
500    /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
501            int[] offsetXY) {
502        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
503        if (bitmap == null) {
504            return null;
505        }
506
507        // get the paint which can be null if nativePaint is 0.
508        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
509
510        if (paint != null && paint.getMaskFilter() != null) {
511            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
512                    "MaskFilter not supported in Bitmap.extractAlpha",
513                    null, null /*data*/);
514        }
515
516        int alpha = paint != null ? paint.getAlpha() : 0xFF;
517        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
518
519        // create the delegate. The actual Bitmap config is only an alpha channel
520        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
521
522        // the density doesn't matter, it's set by the Java method.
523        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
524                Density.DEFAULT_DENSITY /*density*/);
525    }
526
527    @LayoutlibDelegate
528    /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
529        // nothing to be done here.
530    }
531
532    @LayoutlibDelegate
533    /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
534        // get the delegate from the native int.
535        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
536        if (delegate == null) {
537            return;
538        }
539
540        delegate.mHasAlpha = hasAlpha;
541    }
542
543    @LayoutlibDelegate
544    /*package*/ static void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) {
545        // get the delegate from the native int.
546        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
547        if (delegate == null) {
548            return;
549        }
550
551        delegate.mHasMipMap = hasMipMap;
552    }
553
554    @LayoutlibDelegate
555    /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
556        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
557        if (delegate1 == null) {
558            return false;
559        }
560
561        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
562        if (delegate2 == null) {
563            return false;
564        }
565
566        BufferedImage image1 = delegate1.getImage();
567        BufferedImage image2 = delegate2.getImage();
568        if (delegate1.mConfig != delegate2.mConfig ||
569                image1.getWidth() != image2.getWidth() ||
570                image1.getHeight() != image2.getHeight()) {
571            return false;
572        }
573
574        // get the internal data
575        int w = image1.getWidth();
576        int h = image2.getHeight();
577        int[] argb1 = new int[w*h];
578        int[] argb2 = new int[w*h];
579
580        image1.getRGB(0, 0, w, h, argb1, 0, w);
581        image2.getRGB(0, 0, w, h, argb2, 0, w);
582
583        // compares
584        if (delegate1.mConfig == Config.ALPHA_8) {
585            // in this case we have to manually compare the alpha channel as the rest is garbage.
586            final int length = w*h;
587            for (int i = 0 ; i < length ; i++) {
588                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
589                    return false;
590                }
591            }
592            return true;
593        }
594
595        return Arrays.equals(argb1, argb2);
596    }
597
598    // ---- Private delegate/helper methods ----
599
600    private Bitmap_Delegate(BufferedImage image, Config config) {
601        mImage = image;
602        mConfig = config;
603    }
604
605    private static Bitmap createBitmap(Bitmap_Delegate delegate,
606            Set<BitmapCreateFlags> createFlags, int density) {
607        // get its native_int
608        int nativeInt = sManager.addNewDelegate(delegate);
609
610        int width = delegate.mImage.getWidth();
611        int height = delegate.mImage.getHeight();
612        boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
613        boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
614
615        // and create/return a new Bitmap with it
616        return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable,
617                          isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
618    }
619
620    private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
621        Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
622        if (isMutable) {
623            createFlags.add(BitmapCreateFlags.MUTABLE);
624        }
625        return createFlags;
626    }
627
628    /**
629     * Creates and returns a copy of a given BufferedImage.
630     * <p/>
631     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
632     *
633     * @param image the image to copy
634     * @param imageType the type of the new image
635     * @param alpha an optional alpha modifier
636     * @return a new BufferedImage
637     */
638    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
639        int w = image.getWidth();
640        int h = image.getHeight();
641
642        BufferedImage result = new BufferedImage(w, h, imageType);
643
644        int[] argb = new int[w * h];
645        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
646
647        if (alpha != 255) {
648            final int length = argb.length;
649            for (int i = 0 ; i < length; i++) {
650                int a = (argb[i] >>> 24 * alpha) / 255;
651                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
652            }
653        }
654
655        result.setRGB(0, 0, w, h, argb, 0, w);
656
657        return result;
658    }
659
660}
661