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