Bitmap_Delegate.java revision ab775ecdd189b32e35b0d3f4a821502f88b03a4b
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;
36
37import javax.imageio.ImageIO;
38
39/**
40 * Delegate implementing the native methods of android.graphics.Bitmap
41 *
42 * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
43 * by calls to methods of the same name in this delegate class.
44 *
45 * This class behaves like the original native implementation, but in Java, keeping previously
46 * native data into its own objects and mapping them to int that are sent back and forth between
47 * it and the original Bitmap class.
48 *
49 * @see DelegateManager
50 *
51 */
52public final class Bitmap_Delegate {
53
54    // ---- delegate manager ----
55    private static final DelegateManager<Bitmap_Delegate> sManager =
56            new DelegateManager<Bitmap_Delegate>(Bitmap_Delegate.class);
57
58    // ---- delegate helper data ----
59
60    // ---- delegate data ----
61    private final Config mConfig;
62    private BufferedImage mImage;
63    private boolean mHasAlpha = true;
64    private boolean mHasMipMap = false;      // TODO: check the default.
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.nativeToConfig(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    public boolean hasMipMap() {
190        // TODO: check if more checks are required as in hasAlpha.
191        return mHasMipMap;
192    }
193    /**
194     * Update the generationId.
195     *
196     * @see Bitmap#getGenerationId()
197     */
198    public void change() {
199        mGenerationId++;
200    }
201
202    // ---- native methods ----
203
204    @LayoutlibDelegate
205    /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
206            int height, int nativeConfig, boolean mutable) {
207        int imageType = getBufferedImageType(nativeConfig);
208
209        // create the image
210        BufferedImage image = new BufferedImage(width, height, imageType);
211
212        if (colors != null) {
213            image.setRGB(0, 0, width, height, colors, offset, stride);
214        }
215
216        // create a delegate with the content of the stream.
217        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
218
219        return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
220    }
221
222    @LayoutlibDelegate
223    /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
224        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
225        if (srcBmpDelegate == null) {
226            return null;
227        }
228
229        BufferedImage srcImage = srcBmpDelegate.getImage();
230
231        int width = srcImage.getWidth();
232        int height = srcImage.getHeight();
233
234        int imageType = getBufferedImageType(nativeConfig);
235
236        // create the image
237        BufferedImage image = new BufferedImage(width, height, imageType);
238
239        // copy the source image into the image.
240        int[] argb = new int[width * height];
241        srcImage.getRGB(0, 0, width, height, argb, 0, width);
242        image.setRGB(0, 0, width, height, argb, 0, width);
243
244        // create a delegate with the content of the stream.
245        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
246
247        return createBitmap(delegate, isMutable, Bitmap.getDefaultDensity());
248    }
249
250    @LayoutlibDelegate
251    /*package*/ static void nativeDestructor(int nativeBitmap) {
252        sManager.removeJavaReferenceFor(nativeBitmap);
253    }
254
255    @LayoutlibDelegate
256    /*package*/ static void nativeRecycle(int nativeBitmap) {
257        sManager.removeJavaReferenceFor(nativeBitmap);
258    }
259
260    @LayoutlibDelegate
261    /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
262            OutputStream stream, byte[] tempStorage) {
263        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
264                "Bitmap.compress() is not supported", null /*data*/);
265        return true;
266    }
267
268    @LayoutlibDelegate
269    /*package*/ static void nativeErase(int nativeBitmap, int color) {
270        // get the delegate from the native int.
271        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
272        if (delegate == null) {
273            return;
274        }
275
276        BufferedImage image = delegate.mImage;
277
278        Graphics2D g = image.createGraphics();
279        try {
280            g.setColor(new java.awt.Color(color, true));
281
282            g.fillRect(0, 0, image.getWidth(), image.getHeight());
283        } finally {
284            g.dispose();
285        }
286    }
287
288    @LayoutlibDelegate
289    /*package*/ static int nativeWidth(int nativeBitmap) {
290        // get the delegate from the native int.
291        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
292        if (delegate == null) {
293            return 0;
294        }
295
296        return delegate.mImage.getWidth();
297    }
298
299    @LayoutlibDelegate
300    /*package*/ static int nativeHeight(int nativeBitmap) {
301        // get the delegate from the native int.
302        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
303        if (delegate == null) {
304            return 0;
305        }
306
307        return delegate.mImage.getHeight();
308    }
309
310    @LayoutlibDelegate
311    /*package*/ static int nativeRowBytes(int nativeBitmap) {
312        // get the delegate from the native int.
313        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
314        if (delegate == null) {
315            return 0;
316        }
317
318        return delegate.mImage.getWidth();
319    }
320
321    @LayoutlibDelegate
322    /*package*/ static int nativeConfig(int nativeBitmap) {
323        // get the delegate from the native int.
324        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
325        if (delegate == null) {
326            return 0;
327        }
328
329        return delegate.mConfig.nativeInt;
330    }
331
332    @LayoutlibDelegate
333    /*package*/ static boolean nativeHasAlpha(int nativeBitmap) {
334        // get the delegate from the native int.
335        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
336        if (delegate == null) {
337            return true;
338        }
339
340        return delegate.mHasAlpha;
341    }
342
343    @LayoutlibDelegate
344    /*package*/ static boolean nativeHasMipMap(int nativeBitmap) {
345        // get the delegate from the native int.
346        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
347        if (delegate == null) {
348            return true;
349        }
350
351        return delegate.mHasMipMap;
352    }
353
354    @LayoutlibDelegate
355    /*package*/ static int nativeGetPixel(int nativeBitmap, int x, int y) {
356        // get the delegate from the native int.
357        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
358        if (delegate == null) {
359            return 0;
360        }
361
362        return delegate.mImage.getRGB(x, y);
363    }
364
365    @LayoutlibDelegate
366    /*package*/ static void nativeGetPixels(int nativeBitmap, int[] pixels, int offset,
367            int stride, int x, int y, int width, int height) {
368        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
369        if (delegate == null) {
370            return;
371        }
372
373        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
374    }
375
376
377    @LayoutlibDelegate
378    /*package*/ static void nativeSetPixel(int nativeBitmap, int x, int y, int color) {
379        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
380        if (delegate == null) {
381            return;
382        }
383
384        delegate.getImage().setRGB(x, y, color);
385    }
386
387    @LayoutlibDelegate
388    /*package*/ static void nativeSetPixels(int nativeBitmap, int[] colors, int offset,
389            int stride, int x, int y, int width, int height) {
390        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
391        if (delegate == null) {
392            return;
393        }
394
395        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
396    }
397
398    @LayoutlibDelegate
399    /*package*/ static void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst) {
400        // FIXME implement native delegate
401        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
402                "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
403    }
404
405    @LayoutlibDelegate
406    /*package*/ static void nativeCopyPixelsFromBuffer(int nb, Buffer src) {
407        // FIXME implement native delegate
408        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
409                "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
410    }
411
412    @LayoutlibDelegate
413    /*package*/ static int nativeGenerationId(int nativeBitmap) {
414        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
415        if (delegate == null) {
416            return 0;
417        }
418
419        return delegate.mGenerationId;
420    }
421
422    @LayoutlibDelegate
423    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
424        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
425        // used during aidl call so really this should not be called.
426        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
427                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
428                null /*data*/);
429        return null;
430    }
431
432    @LayoutlibDelegate
433    /*package*/ static boolean nativeWriteToParcel(int nativeBitmap, boolean isMutable,
434            int density, Parcel p) {
435        // This is only called when sending a bitmap through aidl, so really this should not
436        // be called.
437        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
438                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
439                null /*data*/);
440        return false;
441    }
442
443    @LayoutlibDelegate
444    /*package*/ static Bitmap nativeExtractAlpha(int nativeBitmap, int nativePaint,
445            int[] offsetXY) {
446        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
447        if (bitmap == null) {
448            return null;
449        }
450
451        // get the paint which can be null if nativePaint is 0.
452        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
453
454        if (paint != null && paint.getMaskFilter() != null) {
455            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
456                    "MaskFilter not supported in Bitmap.extractAlpha",
457                    null, null /*data*/);
458        }
459
460        int alpha = paint != null ? paint.getAlpha() : 0xFF;
461        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
462
463        // create the delegate. The actual Bitmap config is only an alpha channel
464        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
465
466        // the density doesn't matter, it's set by the Java method.
467        return createBitmap(delegate, false /*isMutable*/,
468                Density.DEFAULT_DENSITY /*density*/);
469    }
470
471    @LayoutlibDelegate
472    /*package*/ static void nativePrepareToDraw(int nativeBitmap) {
473        // nothing to be done here.
474    }
475
476    @LayoutlibDelegate
477    /*package*/ static void nativeSetHasAlpha(int nativeBitmap, boolean hasAlpha) {
478        // get the delegate from the native int.
479        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
480        if (delegate == null) {
481            return;
482        }
483
484        delegate.mHasAlpha = hasAlpha;
485    }
486
487    @LayoutlibDelegate
488    /*package*/ static void nativeSetHasMipMap(int nativeBitmap, boolean hasMipMap) {
489        // get the delegate from the native int.
490        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
491        if (delegate == null) {
492            return;
493        }
494
495        delegate.mHasMipMap = hasMipMap;
496    }
497
498    @LayoutlibDelegate
499    /*package*/ static boolean nativeSameAs(int nb0, int nb1) {
500        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
501        if (delegate1 == null) {
502            return false;
503        }
504
505        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
506        if (delegate2 == null) {
507            return false;
508        }
509
510        BufferedImage image1 = delegate1.getImage();
511        BufferedImage image2 = delegate2.getImage();
512        if (delegate1.mConfig != delegate2.mConfig ||
513                image1.getWidth() != image2.getWidth() ||
514                image1.getHeight() != image2.getHeight()) {
515            return false;
516        }
517
518        // get the internal data
519        int w = image1.getWidth();
520        int h = image2.getHeight();
521        int[] argb1 = new int[w*h];
522        int[] argb2 = new int[w*h];
523
524        image1.getRGB(0, 0, w, h, argb1, 0, w);
525        image2.getRGB(0, 0, w, h, argb2, 0, w);
526
527        // compares
528        if (delegate1.mConfig == Config.ALPHA_8) {
529            // in this case we have to manually compare the alpha channel as the rest is garbage.
530            final int length = w*h;
531            for (int i = 0 ; i < length ; i++) {
532                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
533                    return false;
534                }
535            }
536            return true;
537        }
538
539        return Arrays.equals(argb1, argb2);
540    }
541
542    // ---- Private delegate/helper methods ----
543
544    private Bitmap_Delegate(BufferedImage image, Config config) {
545        mImage = image;
546        mConfig = config;
547    }
548
549    private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
550        // get its native_int
551        int nativeInt = sManager.addNewDelegate(delegate);
552
553        // and create/return a new Bitmap with it
554        // TODO: pass correct width, height, isPremultiplied
555        return new Bitmap(nativeInt, null /* buffer */, -1 /* width */, -1 /* height */, density,
556                          isMutable, true /* isPremultiplied */,
557                          null /*ninePatchChunk*/, null /* layoutBounds */);
558    }
559
560    /**
561     * Creates and returns a copy of a given BufferedImage.
562     * <p/>
563     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
564     *
565     * @param image the image to copy
566     * @param imageType the type of the new image
567     * @param alpha an optional alpha modifier
568     * @return a new BufferedImage
569     */
570    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
571        int w = image.getWidth();
572        int h = image.getHeight();
573
574        BufferedImage result = new BufferedImage(w, h, imageType);
575
576        int[] argb = new int[w * h];
577        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
578
579        if (alpha != 255) {
580            final int length = argb.length;
581            for (int i = 0 ; i < length; i++) {
582                int a = (argb[i] >>> 24 * alpha) / 255;
583                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
584            }
585        }
586
587        result.setRGB(0, 0, w, h, argb, 0, w);
588
589        return result;
590    }
591
592}
593