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