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