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