/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics; import com.android.ide.common.rendering.api.AssetRepository; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.impl.RenderAction; import com.android.resources.Density; import com.android.resources.ResourceType; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.annotation.Nullable; import android.graphics.Bitmap.Config; import android.hardware.HardwareBuffer; import android.os.Parcel; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.Buffer; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import javax.imageio.ImageIO; import libcore.util.NativeAllocationRegistry_Delegate; import static android.content.res.AssetManager.ACCESS_STREAMING; /** * Delegate implementing the native methods of android.graphics.Bitmap * * Through the layoutlib_create tool, the original native methods of Bitmap have been replaced * by calls to methods of the same name in this delegate class. * * This class behaves like the original native implementation, but in Java, keeping previously * native data into its own objects and mapping them to int that are sent back and forth between * it and the original Bitmap class. * * @see DelegateManager * */ public final class Bitmap_Delegate { public enum BitmapCreateFlags { NONE, PREMULTIPLIED, MUTABLE } // ---- delegate manager ---- private static final DelegateManager sManager = new DelegateManager<>(Bitmap_Delegate.class); private static long sFinalizer = -1; // ---- delegate helper data ---- // ---- delegate data ---- private final Config mConfig; private final BufferedImage mImage; private boolean mHasAlpha = true; private boolean mHasMipMap = false; // TODO: check the default. private boolean mIsPremultiplied = true; private int mGenerationId = 0; private boolean mIsMutable; // ---- Public Helper methods ---- /** * Returns the native delegate associated to a given an int referencing a {@link Bitmap} object. */ public static Bitmap_Delegate getDelegate(long native_bitmap) { return sManager.getDelegate(native_bitmap); } /** * Creates and returns a {@link Bitmap} initialized with the given stream content. * * @param input the stream from which to read the bitmap content * @param isMutable whether the bitmap is mutable * @param density the density associated with the bitmap * * @see Bitmap#isMutable() * @see Bitmap#getDensity() */ public static Bitmap createBitmap(@Nullable InputStream input, boolean isMutable, Density density) throws IOException { return createBitmap(input, getPremultipliedBitmapCreateFlags(isMutable), density); } /** * Creates and returns a {@link Bitmap} initialized with the given file content. * * @param input the file from which to read the bitmap content * @param density the density associated with the bitmap * * @see Bitmap#isPremultiplied() * @see Bitmap#isMutable() * @see Bitmap#getDensity() */ static Bitmap createBitmap(@Nullable InputStream input, Set createFlags, Density density) throws IOException { // create a delegate with the content of the file. BufferedImage image = input == null ? null : ImageIO.read(input); if (image == null) { // There was a problem decoding the image, or the decoder isn't registered. Webp maybe. // Replace with a broken image icon. BridgeContext currentContext = RenderAction.getCurrentContext(); if (currentContext != null) { RenderResources resources = currentContext.getRenderResources(); ResourceValue broken = resources.getResolvedResource( BridgeContext.createFrameworkResourceReference( ResourceType.DRAWABLE, "ic_menu_report_image")); AssetRepository assetRepository = currentContext.getAssets().getAssetRepository(); try (InputStream stream = assetRepository.openNonAsset(0, broken.getValue(), ACCESS_STREAMING)) { if (stream != null) { image = ImageIO.read(stream); } } } } Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); delegate.mIsMutable = createFlags.contains(BitmapCreateFlags.MUTABLE); return createBitmap(delegate, createFlags, density.getDpiValue()); } /** * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} * * @param image the bitmap content * @param isMutable whether the bitmap is mutable * @param density the density associated with the bitmap * * @see Bitmap#isMutable() * @see Bitmap#getDensity() */ public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density) { return createBitmap(image, getPremultipliedBitmapCreateFlags(isMutable), density); } /** * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage} * * @param image the bitmap content * @param density the density associated with the bitmap * * @see Bitmap#isPremultiplied() * @see Bitmap#isMutable() * @see Bitmap#getDensity() */ public static Bitmap createBitmap(BufferedImage image, Set createFlags, Density density) { // create a delegate with the given image. Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ARGB_8888); delegate.mIsMutable = createFlags.contains(BitmapCreateFlags.MUTABLE); return createBitmap(delegate, createFlags, density.getDpiValue()); } private static int getBufferedImageType() { return BufferedImage.TYPE_INT_ARGB; } /** * Returns the {@link BufferedImage} used by the delegate of the given {@link Bitmap}. */ public BufferedImage getImage() { return mImage; } /** * Returns the Android bitmap config. Note that this not the config of the underlying * Java2D bitmap. */ public Config getConfig() { return mConfig; } /** * Returns the hasAlpha rendering hint * @return true if the bitmap alpha should be used at render time */ public boolean hasAlpha() { return mHasAlpha && mConfig != Config.RGB_565; } /** * Update the generationId. * * @see Bitmap#getGenerationId() */ public void change() { mGenerationId++; } // ---- native methods ---- @LayoutlibDelegate /*package*/ static Bitmap nativeCreate(int[] colors, int offset, int stride, int width, int height, int nativeConfig, boolean isMutable, long nativeColorSpace) { int imageType = getBufferedImageType(); // create the image BufferedImage image = new BufferedImage(width, height, imageType); if (colors != null) { image.setRGB(0, 0, width, height, colors, offset, stride); } // create a delegate with the content of the stream. Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig)); delegate.mIsMutable = isMutable; return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable), Bitmap.getDefaultDensity()); } @LayoutlibDelegate /*package*/ static Bitmap nativeCopy(long srcBitmap, int nativeConfig, boolean isMutable) { Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(srcBitmap); if (srcBmpDelegate == null) { return null; } BufferedImage srcImage = srcBmpDelegate.getImage(); int width = srcImage.getWidth(); int height = srcImage.getHeight(); int imageType = getBufferedImageType(); // create the image BufferedImage image = new BufferedImage(width, height, imageType); // copy the source image into the image. int[] argb = new int[width * height]; srcImage.getRGB(0, 0, width, height, argb, 0, width); image.setRGB(0, 0, width, height, argb, 0, width); // create a delegate with the content of the stream. Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.nativeToConfig(nativeConfig)); delegate.mIsMutable = isMutable; return createBitmap(delegate, getPremultipliedBitmapCreateFlags(isMutable), Bitmap.getDefaultDensity()); } @LayoutlibDelegate /*package*/ static Bitmap nativeCopyAshmem(long nativeSrcBitmap) { // Unused method; no implementation provided. assert false; return null; } @LayoutlibDelegate /*package*/ static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) { // Unused method; no implementation provided. assert false; return null; } @LayoutlibDelegate /*package*/ static long nativeGetNativeFinalizer() { synchronized (Bitmap_Delegate.class) { if (sFinalizer == -1) { sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor); } return sFinalizer; } } @LayoutlibDelegate /*package*/ static void nativeRecycle(long nativeBitmap) { // In our case recycle() is a no-op. We will let the finalizer to dispose the bitmap. } @LayoutlibDelegate /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height, int config, boolean isPremultiplied) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.reconfigure() is not supported", null /*data*/); } @LayoutlibDelegate /*package*/ static boolean nativeCompress(long nativeBitmap, int format, int quality, OutputStream stream, byte[] tempStorage) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.compress() is not supported", null /*data*/); return true; } @LayoutlibDelegate /*package*/ static void nativeErase(long nativeBitmap, int color) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } BufferedImage image = delegate.mImage; Graphics2D g = image.createGraphics(); try { g.setColor(new java.awt.Color(color, true)); g.fillRect(0, 0, image.getWidth(), image.getHeight()); } finally { g.dispose(); } } @LayoutlibDelegate /*package*/ static void nativeErase(long nativeBitmap, long colorSpacePtr, long color) { nativeErase(nativeBitmap, Color.toArgb(color)); } @LayoutlibDelegate /*package*/ static int nativeRowBytes(long nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } return delegate.mImage.getWidth(); } @LayoutlibDelegate /*package*/ static int nativeConfig(long nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } return delegate.mConfig.nativeInt; } @LayoutlibDelegate /*package*/ static boolean nativeHasAlpha(long nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); return delegate == null || delegate.mHasAlpha; } @LayoutlibDelegate /*package*/ static boolean nativeHasMipMap(long nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); return delegate == null || delegate.mHasMipMap; } @LayoutlibDelegate /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } return delegate.mImage.getRGB(x, y); } @LayoutlibDelegate /*package*/ static long nativeGetColor(long nativeBitmap, int x, int y) { return nativeGetPixel(nativeBitmap, x, y); } @LayoutlibDelegate /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset, int stride, int x, int y, int width, int height) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } delegate.getImage().getRGB(x, y, width, height, pixels, offset, stride); } @LayoutlibDelegate /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } delegate.getImage().setRGB(x, y, color); } @LayoutlibDelegate /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset, int stride, int x, int y, int width, int height) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } delegate.getImage().setRGB(x, y, width, height, colors, offset, stride); } @LayoutlibDelegate /*package*/ static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) { // FIXME implement native delegate Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Bitmap.copyPixelsToBuffer is not supported.", null, null /*data*/); } @LayoutlibDelegate /*package*/ static void nativeCopyPixelsFromBuffer(long nb, Buffer src) { // FIXME implement native delegate Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Bitmap.copyPixelsFromBuffer is not supported.", null, null /*data*/); } @LayoutlibDelegate /*package*/ static int nativeGenerationId(long nativeBitmap) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } return delegate.mGenerationId; } @LayoutlibDelegate /*package*/ static Bitmap nativeCreateFromParcel(Parcel p) { // This is only called by Bitmap.CREATOR (Parcelable.Creator), which is only // used during aidl call so really this should not be called. Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "AIDL is not suppored, and therefore Bitmaps cannot be created from parcels.", null /*data*/); return null; } @LayoutlibDelegate /*package*/ static boolean nativeWriteToParcel(long nativeBitmap, boolean isMutable, int density, Parcel p) { // This is only called when sending a bitmap through aidl, so really this should not // be called. Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "AIDL is not suppored, and therefore Bitmaps cannot be written to parcels.", null /*data*/); return false; } @LayoutlibDelegate /*package*/ static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint, int[] offsetXY) { Bitmap_Delegate bitmap = sManager.getDelegate(nativeBitmap); if (bitmap == null) { return null; } // get the paint which can be null if nativePaint is 0. Paint_Delegate paint = Paint_Delegate.getDelegate(nativePaint); if (paint != null && paint.getMaskFilter() != null) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, "MaskFilter not supported in Bitmap.extractAlpha", null, null /*data*/); } int alpha = paint != null ? paint.getAlpha() : 0xFF; BufferedImage image = createCopy(bitmap.getImage(), BufferedImage.TYPE_INT_ARGB, alpha); // create the delegate. The actual Bitmap config is only an alpha channel Bitmap_Delegate delegate = new Bitmap_Delegate(image, Config.ALPHA_8); delegate.mIsMutable = true; // the density doesn't matter, it's set by the Java method. return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.MUTABLE), Density.DEFAULT_DENSITY /*density*/); } @LayoutlibDelegate /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); return delegate != null && delegate.mIsPremultiplied; } @LayoutlibDelegate /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } delegate.mIsPremultiplied = isPremul; } @LayoutlibDelegate /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha, boolean isPremul) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } delegate.mHasAlpha = hasAlpha; } @LayoutlibDelegate /*package*/ static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; } delegate.mHasMipMap = hasMipMap; } @LayoutlibDelegate /*package*/ static boolean nativeSameAs(long nb0, long nb1) { Bitmap_Delegate delegate1 = sManager.getDelegate(nb0); if (delegate1 == null) { return false; } Bitmap_Delegate delegate2 = sManager.getDelegate(nb1); if (delegate2 == null) { return false; } BufferedImage image1 = delegate1.getImage(); BufferedImage image2 = delegate2.getImage(); if (delegate1.mConfig != delegate2.mConfig || image1.getWidth() != image2.getWidth() || image1.getHeight() != image2.getHeight()) { return false; } // get the internal data int w = image1.getWidth(); int h = image2.getHeight(); int[] argb1 = new int[w*h]; int[] argb2 = new int[w*h]; image1.getRGB(0, 0, w, h, argb1, 0, w); image2.getRGB(0, 0, w, h, argb2, 0, w); // compares if (delegate1.mConfig == Config.ALPHA_8) { // in this case we have to manually compare the alpha channel as the rest is garbage. final int length = w*h; for (int i = 0 ; i < length ; i++) { if ((argb1[i] & 0xFF000000) != (argb2[i] & 0xFF000000)) { return false; } } return true; } return Arrays.equals(argb1, argb2); } @LayoutlibDelegate /*package*/ static int nativeGetAllocationByteCount(long nativeBitmap) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } int size = nativeRowBytes(nativeBitmap) * delegate.mImage.getHeight(); return size < 0 ? Integer.MAX_VALUE : size; } @LayoutlibDelegate /*package*/ static void nativePrepareToDraw(long nativeBitmap) { // do nothing as Bitmap_Delegate does not have caches } @LayoutlibDelegate /*package*/ static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) { Bitmap_Delegate srcBmpDelegate = sManager.getDelegate(nativeBitmap); if (srcBmpDelegate == null) { return null; } BufferedImage srcImage = srcBmpDelegate.getImage(); // create the image BufferedImage image = new BufferedImage(srcImage.getColorModel(), srcImage.copyData(null), srcImage.isAlphaPremultiplied(), null); // create a delegate with the content of the stream. Bitmap_Delegate delegate = new Bitmap_Delegate(image, srcBmpDelegate.getConfig()); delegate.mIsMutable = srcBmpDelegate.mIsMutable; return createBitmap(delegate, EnumSet.of(BitmapCreateFlags.NONE), Bitmap.getDefaultDensity()); } @LayoutlibDelegate /*package*/ static Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer, long nativeColorSpace) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.nativeWrapHardwareBufferBitmap() is not supported", null, null, null); return null; } @LayoutlibDelegate /*package*/ static GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.nativeCreateGraphicBufferHandle() is not supported", null /*data*/); return null; } @LayoutlibDelegate /*package*/ static boolean nativeIsSRGB(long nativeBitmap) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Color spaces are not supported", null /*data*/); return false; } @LayoutlibDelegate /*package*/ static ColorSpace nativeComputeColorSpace(long nativePtr) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Color spaces are not supported", null /*data*/); return null; } @LayoutlibDelegate /*package*/ static void nativeSetColorSpace(long nativePtr, long nativeColorSpace) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Color spaces are not supported", null /*data*/); } @LayoutlibDelegate /*package*/ static boolean nativeIsSRGBLinear(long nativePtr) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Color spaces are not supported", null /*data*/); return false; } @LayoutlibDelegate /*package*/ static void nativeSetImmutable(long nativePtr) { Bitmap_Delegate bmpDelegate = sManager.getDelegate(nativePtr); if (bmpDelegate == null) { return; } bmpDelegate.mIsMutable = false; } @LayoutlibDelegate /*package*/ static boolean nativeIsImmutable(long nativePtr) { Bitmap_Delegate bmpDelegate = sManager.getDelegate(nativePtr); if (bmpDelegate == null) { return false; } return !bmpDelegate.mIsMutable; } // ---- Private delegate/helper methods ---- private Bitmap_Delegate(BufferedImage image, Config config) { mImage = image; mConfig = config; } private static Bitmap createBitmap(Bitmap_Delegate delegate, Set createFlags, int density) { // get its native_int long nativeInt = sManager.addNewDelegate(delegate); int width = delegate.mImage.getWidth(); int height = delegate.mImage.getHeight(); boolean isPremultiplied = createFlags.contains(BitmapCreateFlags.PREMULTIPLIED); // and create/return a new Bitmap with it return new Bitmap(nativeInt, width, height, density, isPremultiplied, null /*ninePatchChunk*/, null /* layoutBounds */, true /* fromMalloc */); } private static Set getPremultipliedBitmapCreateFlags(boolean isMutable) { Set createFlags = EnumSet.of(BitmapCreateFlags.PREMULTIPLIED); if (isMutable) { createFlags.add(BitmapCreateFlags.MUTABLE); } return createFlags; } /** * Creates and returns a copy of a given BufferedImage. *

* if alpha is different than 255, then it is applied to the alpha channel of each pixel. * * @param image the image to copy * @param imageType the type of the new image * @param alpha an optional alpha modifier * @return a new BufferedImage */ /*package*/ static BufferedImage createCopy(BufferedImage image, int imageType, int alpha) { int w = image.getWidth(); int h = image.getHeight(); BufferedImage result = new BufferedImage(w, h, imageType); int[] argb = new int[w * h]; image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth()); if (alpha != 255) { final int length = argb.length; for (int i = 0 ; i < length; i++) { int a = (argb[i] >>> 24 * alpha) / 255; argb[i] = (a << 24) | (argb[i] & 0x00FFFFFF); } } result.setRGB(0, 0, w, h, argb, 0, w); return result; } }