Bitmap_Delegate.java revision 8a80a8555238cc564f445f902aff5231993a8f96
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;
26import android.graphics.Bitmap.Config;
27import android.os.Parcel;
28
29import java.awt.Graphics2D;
30import java.awt.image.BufferedImage;
31import java.io.File;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.nio.Buffer;
36import java.util.Arrays;
37
38import javax.imageio.ImageIO;
39
40/**
41 * Delegate implementing the native methods of android.graphics.Bitmap
42 *
43 * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
44 * by calls to methods of the same name in this delegate class.
45 *
46 * This class behaves like the original native implementation, but in Java, keeping previously
47 * native data into its own objects and mapping them to int that are sent back and forth between
48 * it and the original Bitmap class.
49 *
50 * @see DelegateManager
51 *
52 */
53public final class Bitmap_Delegate {
54
55    // ---- delegate manager ----
56    private static final DelegateManager<Bitmap_Delegate> sManager =
57            new DelegateManager<Bitmap_Delegate>();
58
59    // ---- delegate helper data ----
60
61    // ---- delegate data ----
62    private final Config mConfig;
63    private BufferedImage mImage;
64    private boolean mHasAlpha = true;
65    private int mGenerationId = 0;
66
67
68    // ---- Public Helper methods ----
69
70    /**
71     * Returns the native delegate associated to a given {@link Bitmap_Delegate} object.
72     */
73    public static Bitmap_Delegate getDelegate(Bitmap bitmap) {
74        return sManager.getDelegate(bitmap.mNativeBitmap);
75    }
76
77    /**
78     * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
79     */
80    public static Bitmap_Delegate getDelegate(int native_bitmap) {
81        return sManager.getDelegate(native_bitmap);
82    }
83
84    /**
85     * Creates and returns a {@link Bitmap} initialized with the given file content.
86     *
87     * @param input the file from which to read the bitmap content
88     * @param isMutable whether the bitmap is mutable
89     * @param density the density associated with the bitmap
90     *
91     * @see Bitmap#isMutable()
92     * @see Bitmap#getDensity()
93     */
94    public static Bitmap createBitmap(File input, boolean isMutable, Density density)
95            throws IOException {
96        // create a delegate with the content of the file.
97        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
98
99        return createBitmap(delegate, isMutable, density.getDpiValue());
100    }
101
102    /**
103     * Creates and returns a {@link Bitmap} initialized with the given stream content.
104     *
105     * @param input the stream from which to read the bitmap content
106     * @param isMutable whether the bitmap is mutable
107     * @param density the density associated with the bitmap
108     *
109     * @see Bitmap#isMutable()
110     * @see Bitmap#getDensity()
111     */
112    public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
113            throws IOException {
114        // create a delegate with the content of the stream.
115        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
116
117        return createBitmap(delegate, isMutable, density.getDpiValue());
118    }
119
120    /**
121     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
122     *
123     * @param image the bitmap content
124     * @param isMutable whether the bitmap is mutable
125     * @param density the density associated with the bitmap
126     *
127     * @see Bitmap#isMutable()
128     * @see Bitmap#getDensity()
129     */
130    public static Bitmap createBitmap(BufferedImage image, boolean isMutable,
131            Density density) throws IOException {
132        // create a delegate with the given image.
133        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
134
135        return createBitmap(delegate, isMutable, density.getDpiValue());
136    }
137
138    /**
139     * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
140     */
141    public static BufferedImage getImage(Bitmap bitmap) {
142        // get the delegate from the native int.
143        Bitmap_Delegate delegate = sManager.getDelegate(bitmap.mNativeBitmap);
144        if (delegate == null) {
145            return null;
146        }
147
148        return delegate.mImage;
149    }
150
151    public static int getBufferedImageType(int nativeBitmapConfig) {
152        switch (Config.sConfigs[nativeBitmapConfig]) {
153            case ALPHA_8:
154                return BufferedImage.TYPE_INT_ARGB;
155            case RGB_565:
156                return BufferedImage.TYPE_INT_ARGB;
157            case ARGB_4444:
158                return BufferedImage.TYPE_INT_ARGB;
159            case ARGB_8888:
160                return BufferedImage.TYPE_INT_ARGB;
161        }
162
163        return BufferedImage.TYPE_INT_ARGB;
164    }
165
166    /**
167     * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
168     */
169    public BufferedImage getImage() {
170        return mImage;
171    }
172
173    /**
174     * Returns the Android bitmap config. Note that this not the config of the underlying
175     * Java2D bitmap.
176     */
177    public Config getConfig() {
178        return mConfig;
179    }
180
181    /**
182     * Returns the hasAlpha rendering hint
183     * @return true if the bitmap alpha should be used at render time
184     */
185    public boolean hasAlpha() {
186        return mHasAlpha && mConfig != Config.RGB_565;
187    }
188
189    /**
190     * Update the generationId.
191     *
192     * @see Bitmap#getGenerationId()
193     */
194    public void change() {
195        mGenerationId++;
196    }
197
198    // ---- native methods ----
199
200    @LayoutlibDelegate
201    /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
202            int height, int nativeConfig, boolean mutable) {
203        int imageType = getBufferedImageType(nativeConfig);
204
205        // create the image
206        BufferedImage image = new BufferedImage(width, height, imageType);
207
208        if (colors != null) {
209            image.setRGB(0, 0, width, height, colors, offset, stride);
210        }
211
212        // create a delegate with the content of the stream.
213        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
214
215        return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
216    }
217
218    @LayoutlibDelegate
219    /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
220        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
221        if (srcBmpDelegate == null) {
222            return null;
223        }
224
225        BufferedImage srcImage = srcBmpDelegate.getImage();
226
227        int width = srcImage.getWidth();
228        int height = srcImage.getHeight();
229
230        int imageType = getBufferedImageType(nativeConfig);
231
232        // create the image
233        BufferedImage image = new BufferedImage(width, height, imageType);
234
235        // copy the source image into the image.
236        int[] argb = new int[width * height];
237        srcImage.getRGB(0, 0, width, height, argb, 0, width);
238        image.setRGB(0, 0, width, height, argb, 0, width);
239
240        // create a delegate with the content of the stream.
241        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.sConfigs[nativeConfig]);
242
243        return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity());
244    }
245
246    @LayoutlibDelegate
247    /*package*/ static void nativeDestructor(int nativeBitmap) {
248        sManager.removeDelegate(nativeBitmap);
249    }
250
251    @LayoutlibDelegate
252    /*package*/ static void nativeRecycle(int nativeBitmap) {
253        sManager.removeDelegate(nativeBitmap);
254    }
255
256    @LayoutlibDelegate
257    /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
258            OutputStream stream, byte[] tempStorage) {
259        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
260                "Bitmap.compress() is not supported", null /*data*/);
261        return true;
262    }
263
264    @LayoutlibDelegate
265    /*package*/ static void nativeErase(int nativeBitmap, int color) {
266        // get the delegate from the native int.
267        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
268        if (delegate == null) {
269            return;
270        }
271
272        BufferedImage image = delegate.mImage;
273
274        Graphics2D g = image.createGraphics();
275        try {
276            g.setColor(new java.awt.Color(color, true));
277
278            g.fillRect(0, 0, image.getWidth(), image.getHeight());
279        } finally {
280            g.dispose();
281        }
282    }
283
284    @LayoutlibDelegate
285    /*package*/ static int nativeWidth(int nativeBitmap) {
286        // get the delegate from the native int.
287        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
288        if (delegate == null) {
289            return 0;
290        }
291
292        return delegate.mImage.getWidth();
293    }
294
295    @LayoutlibDelegate
296    /*package*/ static int nativeHeight(int nativeBitmap) {
297        // get the delegate from the native int.
298        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
299        if (delegate == null) {
300            return 0;
301        }
302
303        return delegate.mImage.getHeight();
304    }
305
306    @LayoutlibDelegate
307    /*package*/ static int nativeRowBytes(int nativeBitmap) {
308        // get the delegate from the native int.
309        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
310        if (delegate == null) {
311            return 0;
312        }
313
314        return delegate.mImage.getWidth();
315    }
316
317    @LayoutlibDelegate
318    /*package*/ static int nativeConfig(int nativeBitmap) {
319        // get the delegate from the native int.
320        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
321        if (delegate == null) {
322            return 0;
323        }
324
325        return delegate.mConfig.nativeInt;
326    }
327
328    @LayoutlibDelegate
329    /*package*/ static boolean nativeHasAlpha(int nativeBitmap) {
330        // get the delegate from the native int.
331        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
332        if (delegate == null) {
333            return true;
334        }
335
336        return delegate.mHasAlpha;
337    }
338
339    @LayoutlibDelegate
340    /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
341        // get the delegate from the native int.
342        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
343        if (delegate == null) {
344            return 0;
345        }
346
347        return delegate.mImage.getRGB(x, y);
348    }
349
350    @LayoutlibDelegate
351    /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
352            int stride, int x, int y, int width, int height) {
353        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
354        if (delegate == null) {
355            return;
356        }
357
358        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
359    }
360
361
362    @LayoutlibDelegate
363    /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
364        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
365        if (delegate == null) {
366            return;
367        }
368
369        delegate.getImage().setRGB(x, y, color);
370    }
371
372    @LayoutlibDelegate
373    /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
374            int stride, int x, int y, int width, int height) {
375        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
376        if (delegate == null) {
377            return;
378        }
379
380        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
381    }
382
383    @LayoutlibDelegate
384    /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) {
385        // FIXME implement native delegate
386        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
387                "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
388    }
389
390    @LayoutlibDelegate
391    /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) {
392        // FIXME implement native delegate
393        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
394                "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
395    }
396
397    @LayoutlibDelegate
398    /*package*/ static int nativeGenerationId(int nativeBitmap) {
399        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
400        if (delegate == null) {
401            return 0;
402        }
403
404        return delegate.mGenerationId;
405    }
406
407    @LayoutlibDelegate
408    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
409        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
410        // used during aidl call so really this should not be called.
411        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
412                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
413                null /*data*/);
414        return null;
415    }
416
417    @LayoutlibDelegate
418    /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
419            int density, Parcel p) {
420        // This is only called when sending a bitmap through aidl, so really this should not
421        // be called.
422        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
423                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
424                null /*data*/);
425        return false;
426    }
427
428    @LayoutlibDelegate
429    /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
430            int[] offsetXY) {
431        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
432        if (bitmap == null) {
433            return null;
434        }
435
436        // get the paint which can be null if nativePaint is 0.
437        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
438
439        if (paint != null && paint.getMaskFilter() != null) {
440            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
441                    "MaskFilter not supported in Bitmap.extractAlpha",
442                    null, null /*data*/);
443        }
444
445        int alpha = paint != null ? paint.getAlpha() : 0xFF;
446        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
447
448        // create the delegate. The actual Bitmap config is only an alpha channel
449        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
450
451        // the density doesn't matter, it's set by the Java method.
452        return createBitmap(delegate, false /*isMutable*/,
453                Density.DEFAULT_DENSITY /*density*/);
454    }
455
456    @LayoutlibDelegate
457    /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
458        // nothing to be done here.
459    }
460
461    @LayoutlibDelegate
462    /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
463        // get the delegate from the native int.
464        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
465        if (delegate == null) {
466            return;
467        }
468
469        delegate.mHasAlpha = hasAlpha;
470    }
471
472    @LayoutlibDelegate
473    /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
474        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
475        if (delegate1 == null) {
476            return false;
477        }
478
479        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
480        if (delegate2 == null) {
481            return false;
482        }
483
484        BufferedImage image1 = delegate1.getImage();
485        BufferedImage image2 = delegate2.getImage();
486        if (delegate1.mConfig != delegate2.mConfig ||
487                image1.getWidth() != image2.getWidth() ||
488                image1.getHeight() != image2.getHeight()) {
489            return false;
490        }
491
492        // get the internal data
493        int w = image1.getWidth();
494        int h = image2.getHeight();
495        int[] argb1 = new int[w*h];
496        int[] argb2 = new int[w*h];
497
498        image1.getRGB(0, 0, w, h, argb1, 0, w);
499        image2.getRGB(0, 0, w, h, argb2, 0, w);
500
501        // compares
502        if (delegate1.mConfig == Config.ALPHA_8) {
503            // in this case we have to manually compare the alpha channel as the rest is garbage.
504            final int length = w*h;
505            for (int i = 0 ; i < length ; i++) {
506                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
507                    return false;
508                }
509            }
510            return true;
511        }
512
513        return Arrays.equals(argb1, argb2);
514    }
515
516    // ---- Private delegate/helper methods ----
517
518    private Bitmap_Delegate(BufferedImage image, Config config) {
519        mImage = image;
520        mConfig = config;
521    }
522
523    private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
524        // get its native_int
525        int nativeInt = sManager.addDelegate(delegate);
526
527        // and create/return a new Bitmap with it
528        return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density);
529    }
530
531    /**
532     * Creates and returns a copy of a given BufferedImage.
533     * <p/>
534     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
535     *
536     * @param image the image to copy
537     * @param imageType the type of the new image
538     * @param alpha an optional alpha modifier
539     * @return a new BufferedImage
540     */
541    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
542        int w = image.getWidth();
543        int h = image.getHeight();
544
545        BufferedImage result = new BufferedImage(w, h, imageType);
546
547        int[] argb = new int[w * h];
548        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
549
550        if (alpha != 255) {
551            final int length = argb.length;
552            for (int i = 0 ; i < length; i++) {
553                int a = (argb[i] >>> 24 * alpha) / 255;
554                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
555            }
556        }
557
558        result.setRGB(0, 0, w, h, argb, 0, w);
559
560        return result;
561    }
562
563}
564