Bitmap_Delegate.java revision 9a4fe29c8d92014d2d9a848e9116b8cc9d0842f9
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        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeCopyPixelsToBuffer");
387    }
388
389    @LayoutlibDelegate
390    /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) {
391        // FIXME implement native delegate
392        throw new UnsupportedOperationException("Native delegate needed for Bitmap.nativeCopyPixelsFromBuffer");
393    }
394
395    @LayoutlibDelegate
396    /*package*/ static int nativeGenerationId(int nativeBitmap) {
397        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
398        if (delegate == null) {
399            return 0;
400        }
401
402        return delegate.mGenerationId;
403    }
404
405    @LayoutlibDelegate
406    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
407        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
408        // used during aidl call so really this should not be called.
409        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
410                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
411                null /*data*/);
412        return null;
413    }
414
415    @LayoutlibDelegate
416    /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
417            int density, Parcel p) {
418        // This is only called when sending a bitmap through aidl, so really this should not
419        // be called.
420        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
421                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
422                null /*data*/);
423        return false;
424    }
425
426    @LayoutlibDelegate
427    /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
428            int[] offsetXY) {
429        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
430        if (bitmap == null) {
431            return null;
432        }
433
434        // get the paint which can be null if nativePaint is 0.
435        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
436
437        if (paint != null && paint.getMaskFilter() != null) {
438            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
439                    "MaskFilter not supported in Bitmap.extractAlpha",
440                    null, null /*data*/);
441        }
442
443        int alpha = paint != null ? paint.getAlpha() : 0xFF;
444        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
445
446        // create the delegate. The actual Bitmap config is only an alpha channel
447        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
448
449        // the density doesn't matter, it's set by the Java method.
450        return createBitmap(delegate, false /*isMutable*/,
451                Density.DEFAULT_DENSITY /*density*/);
452    }
453
454    @LayoutlibDelegate
455    /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
456        // nothing to be done here.
457    }
458
459    @LayoutlibDelegate
460    /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
461        // get the delegate from the native int.
462        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
463        if (delegate == null) {
464            return;
465        }
466
467        delegate.mHasAlpha = hasAlpha;
468    }
469
470    @LayoutlibDelegate
471    /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
472        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
473        if (delegate1 == null) {
474            return false;
475        }
476
477        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
478        if (delegate2 == null) {
479            return false;
480        }
481
482        BufferedImage image1 = delegate1.getImage();
483        BufferedImage image2 = delegate2.getImage();
484        if (delegate1.mConfig != delegate2.mConfig ||
485                image1.getWidth() != image2.getWidth() ||
486                image1.getHeight() != image2.getHeight()) {
487            return false;
488        }
489
490        // get the internal data
491        int w = image1.getWidth();
492        int h = image2.getHeight();
493        int[] argb1 = new int[w*h];
494        int[] argb2 = new int[w*h];
495
496        image1.getRGB(0, 0, w, h, argb1, 0, w);
497        image2.getRGB(0, 0, w, h, argb2, 0, w);
498
499        // compares
500        if (delegate1.mConfig == Config.ALPHA_8) {
501            // in this case we have to manually compare the alpha channel as the rest is garbage.
502            final int length = w*h;
503            for (int i = 0 ; i < length ; i++) {
504                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
505                    return false;
506                }
507            }
508            return true;
509        }
510
511        return Arrays.equals(argb1, argb2);
512    }
513
514    // ---- Private delegate/helper methods ----
515
516    private Bitmap_Delegate(BufferedImage image, Config config) {
517        mImage = image;
518        mConfig = config;
519    }
520
521    private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
522        // get its native_int
523        int nativeInt = sManager.addDelegate(delegate);
524
525        // and create/return a new Bitmap with it
526        return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density);
527    }
528
529    /**
530     * Creates and returns a copy of a given BufferedImage.
531     * <p/>
532     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
533     *
534     * @param image the image to copy
535     * @param imageType the type of the new image
536     * @param alpha an optional alpha modifier
537     * @return a new BufferedImage
538     */
539    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
540        int w = image.getWidth();
541        int h = image.getHeight();
542
543        BufferedImage result = new BufferedImage(w, h, imageType);
544
545        int[] argb = new int[w * h];
546        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
547
548        if (alpha != 255) {
549            final int length = argb.length;
550            for (int i = 0 ; i < length; i++) {
551                int a = (argb[i] >>> 24 * alpha) / 255;
552                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
553            }
554        }
555
556        result.setRGB(0, 0, w, h, argb, 0, w);
557
558        return result;
559    }
560
561}
562