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