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