Bitmap_Delegate.java revision 42b832b6c5d5bc57886626949f3ed9a219a5f800
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.RenderResources;
21import com.android.ide.common.rendering.api.ResourceValue;
22import com.android.layoutlib.bridge.Bridge;
23import com.android.layoutlib.bridge.android.BridgeContext;
24import com.android.layoutlib.bridge.impl.DelegateManager;
25import com.android.layoutlib.bridge.impl.RenderAction;
26import com.android.resources.Density;
27import com.android.resources.ResourceType;
28import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
29
30import android.annotation.Nullable;
31import android.graphics.Bitmap.Config;
32import android.os.Parcel;
33
34import java.awt.Graphics2D;
35import java.awt.image.BufferedImage;
36import java.io.File;
37import java.io.IOException;
38import java.io.InputStream;
39import java.io.OutputStream;
40import java.nio.Buffer;
41import java.util.Arrays;
42import java.util.EnumSet;
43import java.util.Set;
44
45import javax.imageio.ImageIO;
46import libcore.util.NativeAllocationRegistry_Delegate;
47
48/**
49 * Delegate implementing the native methods of android.graphics.Bitmap
50 *
51 * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced
52 * by calls to methods of the same name in this delegate class.
53 *
54 * This class behaves like the original native implementation, but in Java, keeping previously
55 * native data into its own objects and mapping them to int that are sent back and forth between
56 * it and the original Bitmap class.
57 *
58 * @see DelegateManager
59 *
60 */
61public final class Bitmap_Delegate {
62
63
64    public enum BitmapCreateFlags {
65        PREMULTIPLIED, MUTABLE
66    }
67
68    // ---- delegate manager ----
69    private static final DelegateManager<Bitmap_Delegate> sManager =
70            new DelegateManager<>(Bitmap_Delegate.class);
71    private static long sFinalizer = -1;
72
73    // ---- delegate helper data ----
74
75    // ---- delegate data ----
76    private final Config mConfig;
77    private final BufferedImage mImage;
78    private boolean mHasAlpha = true;
79    private boolean mHasMipMap = false;      // TODO: check the default.
80    private boolean mIsPremultiplied = true;
81    private int mGenerationId = 0;
82
83
84    // ---- Public Helper methods ----
85
86    /**
87     * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object.
88     */
89    public static Bitmap_Delegate getDelegate(long native_bitmap) {
90        return sManager.getDelegate(native_bitmap);
91    }
92
93    @Nullable
94    public static Bitmap_Delegate getDelegate(@Nullable Bitmap bitmap) {
95        // refSkPixelRef is a hack to get the native pointer: see #nativeRefPixelRef()
96        return bitmap == null ? null : getDelegate(bitmap.refSkPixelRef());
97    }
98
99    /**
100     * Creates and returns a {@link Bitmap} initialized with the given file content.
101     *
102     * @param input the file from which to read the bitmap content
103     * @param isMutable whether the bitmap is mutable
104     * @param density the density associated with the bitmap
105     *
106     * @see Bitmap#isMutable()
107     * @see Bitmap#getDensity()
108     */
109    public static Bitmap createBitmap(File input, boolean isMutable, Density density)
110            throws IOException {
111        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
112    }
113
114    /**
115     * Creates and returns a {@link Bitmap} initialized with the given file content.
116     *
117     * @param input the file from which to read the bitmap content
118     * @param density the density associated with the bitmap
119     *
120     * @see Bitmap#isPremultiplied()
121     * @see Bitmap#isMutable()
122     * @see Bitmap#getDensity()
123     */
124    private static Bitmap createBitmap(File input, Set<BitmapCreateFlags> createFlags,
125            Density density) throws IOException {
126        // create a delegate with the content of the file.
127        BufferedImage image = ImageIO.read(input);
128        if (image == null && input.exists()) {
129            // There was a problem decoding the image, or the decoder isn't registered. Webp maybe.
130            // Replace with a broken image icon.
131            BridgeContext currentContext = RenderAction.getCurrentContext();
132            if (currentContext != null) {
133                RenderResources resources = currentContext.getRenderResources();
134                ResourceValue broken = resources.getFrameworkResource(ResourceType.DRAWABLE,
135                        "ic_menu_report_image");
136                File brokenFile = new File(broken.getValue());
137                if (brokenFile.exists()) {
138                    image = ImageIO.read(brokenFile);
139                }
140            }
141        }
142        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
143
144        return createBitmap(delegate, createFlags, density.getDpiValue());
145    }
146
147    /**
148     * Creates and returns a {@link Bitmap} initialized with the given stream content.
149     *
150     * @param input the stream from which to read the bitmap content
151     * @param isMutable whether the bitmap is mutable
152     * @param density the density associated with the bitmap
153     *
154     * @see Bitmap#isMutable()
155     * @see Bitmap#getDensity()
156     */
157    public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
158            throws IOException {
159        return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density);
160    }
161
162    /**
163     * Creates and returns a {@link Bitmap} initialized with the given stream content.
164     *
165     * @param input the stream from which to read the bitmap content
166     * @param density the density associated with the bitmap
167     *
168     * @see Bitmap#isPremultiplied()
169     * @see Bitmap#isMutable()
170     * @see Bitmap#getDensity()
171     */
172    public static Bitmap createBitmap(InputStream input, Set<BitmapCreateFlags> createFlags,
173            Density density) throws IOException {
174        // create a delegate with the content of the stream.
175        Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input), Config.ARGB_8888);
176
177        return createBitmap(delegate, createFlags, density.getDpiValue());
178    }
179
180    /**
181     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
182     *
183     * @param image the bitmap content
184     * @param isMutable whether the bitmap is mutable
185     * @param density the density associated with the bitmap
186     *
187     * @see Bitmap#isMutable()
188     * @see Bitmap#getDensity()
189     */
190    public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) {
191        return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density);
192    }
193
194    /**
195     * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
196     *
197     * @param image the bitmap content
198     * @param density the density associated with the bitmap
199     *
200     * @see Bitmap#isPremultiplied()
201     * @see Bitmap#isMutable()
202     * @see Bitmap#getDensity()
203     */
204    public static Bitmap createBitmap(BufferedImage image, Set<BitmapCreateFlags> createFlags,
205            Density density) {
206        // create a delegate with the given image.
207        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888);
208
209        return createBitmap(delegate, createFlags, density.getDpiValue());
210    }
211
212    private static int getBufferedImageType() {
213        return BufferedImage.TYPE_INT_ARGB;
214    }
215
216    /**
217     * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}.
218     */
219    public BufferedImage getImage() {
220        return mImage;
221    }
222
223    /**
224     * Returns the Android bitmap config. Note that this not the config of the underlying
225     * Java2D bitmap.
226     */
227    public Config getConfig() {
228        return mConfig;
229    }
230
231    /**
232     * Returns the hasAlpha rendering hint
233     * @return true if the bitmap alpha should be used at render time
234     */
235    public boolean hasAlpha() {
236        return mHasAlpha && mConfig != Config.RGB_565;
237    }
238
239    /**
240     * Update the generationId.
241     *
242     * @see Bitmap#getGenerationId()
243     */
244    public void change() {
245        mGenerationId++;
246    }
247
248    // ---- native methods ----
249
250    @LayoutlibDelegate
251    /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width,
252            int height, int nativeConfig, boolean isMutable) {
253        int imageType = getBufferedImageType();
254
255        // create the image
256        BufferedImage image = new BufferedImage(width, height, imageType);
257
258        if (colors != null) {
259            image.setRGB(0, 0, width, height, colors, offset, stride);
260        }
261
262        // create a delegate with the content of the stream.
263        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
264
265        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
266                            Bitmap.getDefaultDensity());
267    }
268
269    @LayoutlibDelegate
270    /*package*/ static Bitmap nativeCopy(long srcBitmap, int nativeConfig, boolean isMutable) {
271        Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap);
272        if (srcBmpDelegate == null) {
273            return null;
274        }
275
276        BufferedImage srcImage = srcBmpDelegate.getImage();
277
278        int width = srcImage.getWidth();
279        int height = srcImage.getHeight();
280
281        int imageType = getBufferedImageType();
282
283        // create the image
284        BufferedImage image = new BufferedImage(width, height, imageType);
285
286        // copy the source image into the image.
287        int[] argb = new int[width * height];
288        srcImage.getRGB(0, 0, width, height, argb, 0, width);
289        image.setRGB(0, 0, width, height, argb, 0, width);
290
291        // create a delegate with the content of the stream.
292        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig));
293
294        return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable),
295                Bitmap.getDefaultDensity());
296    }
297
298    @LayoutlibDelegate
299    /*package*/ static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
300        // Unused method; no implementation provided.
301        assert false;
302        return null;
303    }
304
305    @LayoutlibDelegate
306    /*package*/ static long nativeGetNativeFinalizer() {
307        synchronized (Bitmap_Delegate.class) {
308            if (sFinalizer == -1) {
309                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
310            }
311            return sFinalizer;
312        }
313    }
314
315    @LayoutlibDelegate
316    /*package*/ static boolean nativeRecycle(long nativeBitmap) {
317        // In our case reycle() is a no-op. We will let the finalizer to dispose the bitmap.
318        return true;
319    }
320
321    @LayoutlibDelegate
322    /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height,
323            int config, int allocSize, boolean isPremultiplied) {
324        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
325                "Bitmap.reconfigure() is not supported", null /*data*/);
326    }
327
328    @LayoutlibDelegate
329    /*package*/ static boolean nativeCompress(long nativeBitmap, int format, int quality,
330            OutputStream stream, byte[] tempStorage) {
331        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
332                "Bitmap.compress() is not supported", null /*data*/);
333        return true;
334    }
335
336    @LayoutlibDelegate
337    /*package*/ static void nativeErase(long nativeBitmap, int color) {
338        // get the delegate from the native int.
339        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
340        if (delegate == null) {
341            return;
342        }
343
344        BufferedImage image = delegate.mImage;
345
346        Graphics2D g = image.createGraphics();
347        try {
348            g.setColor(new java.awt.Color(color, true));
349
350            g.fillRect(0, 0, image.getWidth(), image.getHeight());
351        } finally {
352            g.dispose();
353        }
354    }
355
356    @LayoutlibDelegate
357    /*package*/ static int nativeRowBytes(long nativeBitmap) {
358        // get the delegate from the native int.
359        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
360        if (delegate == null) {
361            return 0;
362        }
363
364        return delegate.mImage.getWidth();
365    }
366
367    @LayoutlibDelegate
368    /*package*/ static int nativeConfig(long nativeBitmap) {
369        // get the delegate from the native int.
370        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
371        if (delegate == null) {
372            return 0;
373        }
374
375        return delegate.mConfig.nativeInt;
376    }
377
378    @LayoutlibDelegate
379    /*package*/ static boolean nativeHasAlpha(long nativeBitmap) {
380        // get the delegate from the native int.
381        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
382        return delegate == null || delegate.mHasAlpha;
383
384    }
385
386    @LayoutlibDelegate
387    /*package*/ static boolean nativeHasMipMap(long nativeBitmap) {
388        // get the delegate from the native int.
389        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
390        return delegate == null || delegate.mHasMipMap;
391
392    }
393
394    @LayoutlibDelegate
395    /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) {
396        // get the delegate from the native int.
397        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
398        if (delegate == null) {
399            return 0;
400        }
401
402        return delegate.mImage.getRGB(x, y);
403    }
404
405    @LayoutlibDelegate
406    /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
407            int stride, int x, int y, int width, int height) {
408        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
409        if (delegate == null) {
410            return;
411        }
412
413        delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride);
414    }
415
416
417    @LayoutlibDelegate
418    /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
419        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
420        if (delegate == null) {
421            return;
422        }
423
424        delegate.getImage().setRGB(x, y, color);
425    }
426
427    @LayoutlibDelegate
428    /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset,
429            int stride, int x, int y, int width, int height) {
430        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
431        if (delegate == null) {
432            return;
433        }
434
435        delegate.getImage().setRGB(x, y, width, height, colors, offset, stride);
436    }
437
438    @LayoutlibDelegate
439    /*package*/ static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) {
440        // FIXME implement native delegate
441        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
442                "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/);
443    }
444
445    @LayoutlibDelegate
446    /*package*/ static void nativeCopyPixelsFromBuffer(long nb, Buffer src) {
447        // FIXME implement native delegate
448        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
449                "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/);
450    }
451
452    @LayoutlibDelegate
453    /*package*/ static int nativeGenerationId(long nativeBitmap) {
454        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
455        if (delegate == null) {
456            return 0;
457        }
458
459        return delegate.mGenerationId;
460    }
461
462    @LayoutlibDelegate
463    /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) {
464        // This is only called by Bitmap.CREATOR (Parcelable.Creator<Bitmap>), which is only
465        // used during aidl call so really this should not be called.
466        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
467                "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.",
468                null /*data*/);
469        return null;
470    }
471
472    @LayoutlibDelegate
473    /*package*/ static boolean nativeWriteToParcel(long nativeBitmap, boolean isMutable,
474            int density, Parcel p) {
475        // This is only called when sending a bitmap through aidl, so really this should not
476        // be called.
477        Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED,
478                "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.",
479                null /*data*/);
480        return false;
481    }
482
483    @LayoutlibDelegate
484    /*package*/ static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint,
485            int[] offsetXY) {
486        Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap);
487        if (bitmap == null) {
488            return null;
489        }
490
491        // get the paint which can be null if nativePaint is 0.
492        Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint);
493
494        if (paint != null && paint.getMaskFilter() != null) {
495            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
496                    "MaskFilter not supported in Bitmap.extractAlpha",
497                    null, null /*data*/);
498        }
499
500        int alpha = paint != null ? paint.getAlpha() : 0xFF;
501        BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha);
502
503        // create the delegate. The actual Bitmap config is only an alpha channel
504        Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8);
505
506        // the density doesn't matter, it's set by the Java method.
507        return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE),
508                Density.DEFAULT_DENSITY /*density*/);
509    }
510
511    @LayoutlibDelegate
512    /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
513        // get the delegate from the native int.
514        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
515        return delegate != null && delegate.mIsPremultiplied;
516
517    }
518
519    @LayoutlibDelegate
520    /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
521        // get the delegate from the native int.
522        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
523        if (delegate == null) {
524            return;
525        }
526
527        delegate.mIsPremultiplied = isPremul;
528    }
529
530    @LayoutlibDelegate
531    /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha,
532            boolean isPremul) {
533        // get the delegate from the native int.
534        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
535        if (delegate == null) {
536            return;
537        }
538
539        delegate.mHasAlpha = hasAlpha;
540    }
541
542    @LayoutlibDelegate
543    /*package*/ static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) {
544        // get the delegate from the native int.
545        Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
546        if (delegate == null) {
547            return;
548        }
549
550        delegate.mHasMipMap = hasMipMap;
551    }
552
553    @LayoutlibDelegate
554    /*package*/ static boolean nativeSameAs(long nb0, long nb1) {
555        Bitmap_Delegate delegate1 = sManager.getDelegate(nb0);
556        if (delegate1 == null) {
557            return false;
558        }
559
560        Bitmap_Delegate delegate2 = sManager.getDelegate(nb1);
561        if (delegate2 == null) {
562            return false;
563        }
564
565        BufferedImage image1 = delegate1.getImage();
566        BufferedImage image2 = delegate2.getImage();
567        if (delegate1.mConfig != delegate2.mConfig ||
568                image1.getWidth() != image2.getWidth() ||
569                image1.getHeight() != image2.getHeight()) {
570            return false;
571        }
572
573        // get the internal data
574        int w = image1.getWidth();
575        int h = image2.getHeight();
576        int[] argb1 = new int[w*h];
577        int[] argb2 = new int[w*h];
578
579        image1.getRGB(0, 0, w, h, argb1, 0, w);
580        image2.getRGB(0, 0, w, h, argb2, 0, w);
581
582        // compares
583        if (delegate1.mConfig == Config.ALPHA_8) {
584            // in this case we have to manually compare the alpha channel as the rest is garbage.
585            final int length = w*h;
586            for (int i = 0 ; i < length ; i++) {
587                if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) {
588                    return false;
589                }
590            }
591            return true;
592        }
593
594        return Arrays.equals(argb1, argb2);
595    }
596
597    // Only used by AssetAtlasService, which we don't care about.
598    @LayoutlibDelegate
599    /*package*/ static long nativeRefPixelRef(long nativeBitmap) {
600        // Hack: This is called by Bitmap.refSkPixelRef() and LayoutLib uses that method to get
601        // the native pointer from a Bitmap. So, we return nativeBitmap here.
602        return nativeBitmap;
603    }
604
605    // ---- Private delegate/helper methods ----
606
607    private Bitmap_Delegate(BufferedImage image, Config config) {
608        mImage = image;
609        mConfig = config;
610    }
611
612    private static Bitmap createBitmap(Bitmap_Delegate delegate,
613            Set<BitmapCreateFlags> createFlags, int density) {
614        // get its native_int
615        long nativeInt = sManager.addNewDelegate(delegate);
616
617        int width = delegate.mImage.getWidth();
618        int height = delegate.mImage.getHeight();
619        boolean isMutable = createFlags.contains(BitmapCreateFlags.MUTABLE);
620        boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED);
621
622        // and create/return a new Bitmap with it
623        return new Bitmap(nativeInt, null /* buffer */, width, height, density, isMutable,
624                          isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */);
625    }
626
627    private static Set<BitmapCreateFlags> getPremultipliedBitmapCreateFlags(boolean isMutable) {
628        Set<BitmapCreateFlags> createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED);
629        if (isMutable) {
630            createFlags.add(BitmapCreateFlags.MUTABLE);
631        }
632        return createFlags;
633    }
634
635    /**
636     * Creates and returns a copy of a given BufferedImage.
637     * <p/>
638     * if alpha is different than 255, then it is applied to the alpha channel of each pixel.
639     *
640     * @param image the image to copy
641     * @param imageType the type of the new image
642     * @param alpha an optional alpha modifier
643     * @return a new BufferedImage
644     */
645    /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) {
646        int w = image.getWidth();
647        int h = image.getHeight();
648
649        BufferedImage result = new BufferedImage(w, h, imageType);
650
651        int[] argb = new int[w * h];
652        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
653
654        if (alpha != 255) {
655            final int length = argb.length;
656            for (int i = 0 ; i < length; i++) {
657                int a = (argb[i] >>> 24 * alpha) / 255;
658                argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF);
659            }
660        }
661
662        result.setRGB(0, 0, w, h, argb, 0, w);
663
664        return result;
665    }
666
667}
668