From 1abf5d62429e5a9329520b2f7c2b5a5e7a8e72ec Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Fri, 16 Aug 2013 12:47:03 -0700 Subject: [PATCH] Add inPremutiplied option to BitmapFactory.Options, functionality in Bitmap bug:2248948 Change-Id: I8fdd649332667598504a1076d5a447572bd53086 --- api/current.txt | 2 + core/java/android/view/GLES20Canvas.java | 14 +- core/jni/android/graphics/Bitmap.cpp | 161 +++++++++++++++------ core/jni/android/graphics/BitmapFactory.cpp | 16 +- core/jni/android/graphics/BitmapFactory.h | 1 + core/jni/android/graphics/BitmapRegionDecoder.cpp | 10 +- core/jni/android/graphics/Canvas.cpp | 7 +- core/jni/android/graphics/Graphics.cpp | 28 ++-- core/jni/android/graphics/GraphicsJNI.h | 22 ++- core/jni/android_emoji_EmojiFactory.cpp | 16 +- core/jni/android_view_GLES20Canvas.cpp | 2 +- core/jni/android_view_SurfaceControl.cpp | 3 +- graphics/java/android/graphics/Bitmap.java | 104 ++++++++----- graphics/java/android/graphics/BitmapFactory.java | 21 +++ graphics/java/android/graphics/Canvas.java | 22 ++- .../src/android/graphics/Bitmap_Delegate.java | 6 +- 16 files changed, 296 insertions(+), 139 deletions(-) diff --git a/api/current.txt b/api/current.txt index 0d4dccf50146..eaf28f3b8d8c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8831,6 +8831,7 @@ package android.graphics { method public void setHeight(int); method public void setPixel(int, int, int); method public void setPixels(int[], int, int, int, int, int, int); + method public final void setPremultiplied(boolean); method public void setWidth(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -8880,6 +8881,7 @@ package android.graphics { field public boolean inMutable; field public boolean inPreferQualityOverSpeed; field public android.graphics.Bitmap.Config inPreferredConfig; + field public boolean inPremultiplied; field public boolean inPurgeable; field public int inSampleSize; field public boolean inScaled; diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index d2d1f1b396dc..beefc21faf84 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -776,7 +776,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPatch(NinePatch patch, Rect dst, Paint paint) { Bitmap bitmap = patch.getBitmap(); - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing patches int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; try { @@ -791,7 +791,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawPatch(NinePatch patch, RectF dst, Paint paint) { Bitmap bitmap = patch.getBitmap(); - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing patches int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; try { @@ -808,7 +808,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -824,7 +824,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -841,7 +841,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -868,7 +868,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); // Shaders are ignored when drawing bitmaps int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { @@ -944,7 +944,7 @@ class GLES20Canvas extends HardwareCanvas { @Override public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint) { - if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); + throwIfCannotDraw(bitmap); if (meshWidth < 0 || meshHeight < 0 || vertOffset < 0 || colorOffset < 0) { throw new ArrayIndexOutOfBoundsException(); } diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 0ea3bf733c90..fd9fbae209e1 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -38,6 +38,23 @@ static void FromColor_D32(void* dst, const SkColor src[], int width, } } +static void FromColor_D32_Raw(void* dst, const SkColor src[], int width, + int, int) { + // SkColor's ordering may be different from SkPMColor + if (SK_COLOR_MATCHES_PMCOLOR_BYTE_ORDER) { + memcpy(dst, src, width * sizeof(SkColor)); + return; + } + + // order isn't same, repack each pixel manually + SkPMColor* d = (SkPMColor*)dst; + for (int i = 0; i < width; i++) { + SkColor c = *src++; + *d++ = SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), + SkColorGetG(c), SkColorGetB(c)); + } +} + static void FromColor_D565(void* dst, const SkColor src[], int width, int x, int y) { uint16_t* d = (uint16_t*)dst; @@ -56,19 +73,35 @@ static void FromColor_D4444(void* dst, const SkColor src[], int width, DITHER_4444_SCAN(y); for (int stop = x + width; x < stop; x++) { - SkPMColor c = SkPreMultiplyColor(*src++); - *d++ = SkDitherARGB32To4444(c, DITHER_VALUE(x)); -// *d++ = SkPixel32ToPixel4444(c); + SkPMColor pmc = SkPreMultiplyColor(*src++); + *d++ = SkDitherARGB32To4444(pmc, DITHER_VALUE(x)); +// *d++ = SkPixel32ToPixel4444(pmc); + } +} + +static void FromColor_D4444_Raw(void* dst, const SkColor src[], int width, + int x, int y) { + SkPMColor16* d = (SkPMColor16*)dst; + + DITHER_4444_SCAN(y); + for (int stop = x + width; x < stop; x++) { + SkColor c = *src++; + + // SkPMColor is used because the ordering is ARGB32, even though the target actually premultiplied + SkPMColor pmc = SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c), + SkColorGetG(c), SkColorGetB(c)); + *d++ = SkDitherARGB32To4444(pmc, DITHER_VALUE(x)); +// *d++ = SkPixel32ToPixel4444(pmc); } } // can return NULL -static FromColorProc ChooseFromColorProc(SkBitmap::Config config) { +static FromColorProc ChooseFromColorProc(SkBitmap::Config config, bool isPremultiplied) { switch (config) { case SkBitmap::kARGB_8888_Config: - return FromColor_D32; + return isPremultiplied ? FromColor_D32 : FromColor_D32_Raw; case SkBitmap::kARGB_4444_Config: - return FromColor_D4444; + return isPremultiplied ? FromColor_D4444 : FromColor_D4444_Raw; case SkBitmap::kRGB_565_Config: return FromColor_D565; default: @@ -77,13 +110,12 @@ static FromColorProc ChooseFromColorProc(SkBitmap::Config config) { return NULL; } -bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, - int srcOffset, int srcStride, - int x, int y, int width, int height, - const SkBitmap& dstBitmap) { +bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int srcStride, + int x, int y, int width, int height, + const SkBitmap& dstBitmap, bool isPremultiplied) { SkAutoLockPixels alp(dstBitmap); void* dst = dstBitmap.getPixels(); - FromColorProc proc = ChooseFromColorProc(dstBitmap.config()); + FromColorProc proc = ChooseFromColorProc(dstBitmap.config(), isPremultiplied); if (NULL == dst || NULL == proc) { return false; @@ -122,6 +154,17 @@ static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width, } while (--width != 0); } +static void ToColor_S32_Raw(SkColor dst[], const void* src, int width, + SkColorTable*) { + SkASSERT(width > 0); + const SkPMColor* s = (const SkPMColor*)src; + do { + SkPMColor c = *s++; + *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c), + SkGetPackedG32(c), SkGetPackedB32(c)); + } while (--width != 0); +} + static void ToColor_S32_Opaque(SkColor dst[], const void* src, int width, SkColorTable*) { SkASSERT(width > 0); @@ -142,6 +185,17 @@ static void ToColor_S4444_Alpha(SkColor dst[], const void* src, int width, } while (--width != 0); } +static void ToColor_S4444_Raw(SkColor dst[], const void* src, int width, + SkColorTable*) { + SkASSERT(width > 0); + const SkPMColor16* s = (const SkPMColor16*)src; + do { + SkPMColor c = SkPixel4444ToPixel32(*s++); + *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c), + SkGetPackedG32(c), SkGetPackedB32(c)); + } while (--width != 0); +} + static void ToColor_S4444_Opaque(SkColor dst[], const void* src, int width, SkColorTable*) { SkASSERT(width > 0); @@ -175,6 +229,19 @@ static void ToColor_SI8_Alpha(SkColor dst[], const void* src, int width, ctable->unlockColors(false); } +static void ToColor_SI8_Raw(SkColor dst[], const void* src, int width, + SkColorTable* ctable) { + SkASSERT(width > 0); + const uint8_t* s = (const uint8_t*)src; + const SkPMColor* colors = ctable->lockColors(); + do { + SkPMColor c = colors[*s++]; + *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c), + SkGetPackedG32(c), SkGetPackedB32(c)); + } while (--width != 0); + ctable->unlockColors(false); +} + static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width, SkColorTable* ctable) { SkASSERT(width > 0); @@ -189,19 +256,22 @@ static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width, } // can return NULL -static ToColorProc ChooseToColorProc(const SkBitmap& src) { +static ToColorProc ChooseToColorProc(const SkBitmap& src, bool isPremultiplied) { switch (src.config()) { case SkBitmap::kARGB_8888_Config: - return src.isOpaque() ? ToColor_S32_Opaque : ToColor_S32_Alpha; + if (src.isOpaque()) return ToColor_S32_Opaque; + return isPremultiplied ? ToColor_S32_Alpha : ToColor_S32_Raw; case SkBitmap::kARGB_4444_Config: - return src.isOpaque() ? ToColor_S4444_Opaque : ToColor_S4444_Alpha; + if (src.isOpaque()) return ToColor_S4444_Opaque; + return isPremultiplied ? ToColor_S4444_Alpha : ToColor_S4444_Raw; case SkBitmap::kRGB_565_Config: return ToColor_S565; case SkBitmap::kIndex8_Config: if (src.getColorTable() == NULL) { return NULL; } - return src.isOpaque() ? ToColor_SI8_Opaque : ToColor_SI8_Alpha; + if (src.isOpaque()) return ToColor_SI8_Opaque; + return isPremultiplied ? ToColor_SI8_Raw : ToColor_SI8_Alpha; default: break; } @@ -211,6 +281,12 @@ static ToColorProc ChooseToColorProc(const SkBitmap& src) { /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// +static int getPremulBitmapCreateFlags(bool isMutable) { + int flags = GraphicsJNI::kBitmapCreateFlag_Premultiplied; + if (isMutable) flags |= GraphicsJNI::kBitmapCreateFlag_Mutable; + return flags; +} + static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, int offset, int stride, int width, int height, SkBitmap::Config config, jboolean isMutable) { @@ -236,10 +312,12 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, } if (jColors != NULL) { - GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap); + GraphicsJNI::SetPixels(env, jColors, offset, stride, + 0, 0, width, height, bitmap, true); } - return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL, NULL); + return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, + getPremulBitmapCreateFlags(isMutable), NULL, NULL); } static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src, @@ -250,8 +328,8 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src, if (!src->copyTo(&result, dstConfig, &allocator)) { return NULL; } - - return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL, NULL); + return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), + getPremulBitmapCreateFlags(isMutable), NULL, NULL); } static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) { @@ -347,14 +425,6 @@ static void Bitmap_erase(JNIEnv* env, jobject, SkBitmap* bitmap, jint color) { bitmap->eraseColor(color); } -static int Bitmap_width(JNIEnv* env, jobject, SkBitmap* bitmap) { - return bitmap->width(); -} - -static int Bitmap_height(JNIEnv* env, jobject, SkBitmap* bitmap) { - return bitmap->height(); -} - static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) { return bitmap->rowBytes(); } @@ -449,7 +519,9 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { bitmap->unlockPixels(); blob.release(); - return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, NULL, density); + + return GraphicsJNI::createBitmap(env, bitmap, buffer, getPremulBitmapCreateFlags(isMutable), + NULL, NULL, density); } static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, @@ -527,16 +599,17 @@ static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, env->ReleaseIntArrayElements(offsetXY, array, 0); } - return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL, NULL); + return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), + GraphicsJNI::kBitmapCreateFlag_Mutable, NULL, NULL); } /////////////////////////////////////////////////////////////////////////////// static int Bitmap_getPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, - int x, int y) { + int x, int y, bool isPremultiplied) { SkAutoLockPixels alp(*bitmap); - ToColorProc proc = ChooseToColorProc(*bitmap); + ToColorProc proc = ChooseToColorProc(*bitmap, isPremultiplied); if (NULL == proc) { return 0; } @@ -551,11 +624,11 @@ static int Bitmap_getPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, } static void Bitmap_getPixels(JNIEnv* env, jobject, const SkBitmap* bitmap, - jintArray pixelArray, int offset, int stride, - int x, int y, int width, int height) { + jintArray pixelArray, int offset, int stride, + int x, int y, int width, int height, bool isPremultiplied) { SkAutoLockPixels alp(*bitmap); - ToColorProc proc = ChooseToColorProc(*bitmap); + ToColorProc proc = ChooseToColorProc(*bitmap, isPremultiplied); if (NULL == proc) { return; } @@ -578,13 +651,13 @@ static void Bitmap_getPixels(JNIEnv* env, jobject, const SkBitmap* bitmap, /////////////////////////////////////////////////////////////////////////////// static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, - int x, int y, SkColor color) { + int x, int y, SkColor color, bool isPremultiplied) { SkAutoLockPixels alp(*bitmap); if (NULL == bitmap->getPixels()) { return; } - FromColorProc proc = ChooseFromColorProc(bitmap->config()); + FromColorProc proc = ChooseFromColorProc(bitmap->config(), isPremultiplied); if (NULL == proc) { return; } @@ -594,10 +667,10 @@ static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap, } static void Bitmap_setPixels(JNIEnv* env, jobject, const SkBitmap* bitmap, - jintArray pixelArray, int offset, int stride, - int x, int y, int width, int height) { + jintArray pixelArray, int offset, int stride, + int x, int y, int width, int height, bool isPremultiplied) { GraphicsJNI::SetPixels(env, pixelArray, offset, stride, - x, y, width, height, *bitmap); + x, y, width, height, *bitmap, isPremultiplied); } static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject, @@ -693,8 +766,6 @@ static JNINativeMethod gBitmapMethods[] = { { "nativeCompress", "(IIILjava/io/OutputStream;[B)Z", (void*)Bitmap_compress }, { "nativeErase", "(II)V", (void*)Bitmap_erase }, - { "nativeWidth", "(I)I", (void*)Bitmap_width }, - { "nativeHeight", "(I)I", (void*)Bitmap_height }, { "nativeRowBytes", "(I)I", (void*)Bitmap_rowBytes }, { "nativeConfig", "(I)I", (void*)Bitmap_config }, { "nativeHasAlpha", "(I)Z", (void*)Bitmap_hasAlpha }, @@ -709,10 +780,10 @@ static JNINativeMethod gBitmapMethods[] = { { "nativeExtractAlpha", "(II[I)Landroid/graphics/Bitmap;", (void*)Bitmap_extractAlpha }, { "nativeGenerationId", "(I)I", (void*)Bitmap_getGenerationId }, - { "nativeGetPixel", "(III)I", (void*)Bitmap_getPixel }, - { "nativeGetPixels", "(I[IIIIIII)V", (void*)Bitmap_getPixels }, - { "nativeSetPixel", "(IIII)V", (void*)Bitmap_setPixel }, - { "nativeSetPixels", "(I[IIIIIII)V", (void*)Bitmap_setPixels }, + { "nativeGetPixel", "(IIIZ)I", (void*)Bitmap_getPixel }, + { "nativeGetPixels", "(I[IIIIIIIZ)V", (void*)Bitmap_getPixels }, + { "nativeSetPixel", "(IIIIZ)V", (void*)Bitmap_setPixel }, + { "nativeSetPixels", "(I[IIIIIIIZ)V", (void*)Bitmap_setPixels }, { "nativeCopyPixelsToBuffer", "(ILjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsToBuffer }, { "nativeCopyPixelsFromBuffer", "(ILjava/nio/Buffer;)V", diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 49e3aa4fa039..c433874dc00d 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -14,6 +14,7 @@ #include "AutoDecodeCancel.h" #include "Utils.h" #include "JNIHelp.h" +#include "GraphicsJNI.h" #include #include @@ -25,6 +26,7 @@ jfieldID gOptions_justBoundsFieldID; jfieldID gOptions_sampleSizeFieldID; jfieldID gOptions_configFieldID; +jfieldID gOptions_premultipliedFieldID; jfieldID gOptions_mutableFieldID; jfieldID gOptions_ditherFieldID; jfieldID gOptions_purgeableFieldID; @@ -213,6 +215,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, float scale = 1.0f; bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)); bool preferQualityOverSpeed = false; + bool requireUnpremultiplied = false; jobject javaBitmap = NULL; @@ -233,6 +236,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); + requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); if (env->GetBooleanField(options, gOptions_scaledFieldID)) { @@ -256,6 +260,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, decoder->setSampleSize(sampleSize); decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); + decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); SkBitmap* outputBitmap = NULL; unsigned int existingBufferSize = 0; @@ -434,14 +439,20 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, adb.detach(); if (javaBitmap != NULL) { - GraphicsJNI::reinitBitmap(env, javaBitmap); + bool isPremultiplied = !requireUnpremultiplied; + GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap, isPremultiplied); outputBitmap->notifyPixelsChanged(); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } + + int bitmapCreateFlags = 0x0; + if (isMutable) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Mutable; + if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; + // now create the java bitmap return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(), - isMutable, ninePatchChunk, layoutBounds, -1); + bitmapCreateFlags, ninePatchChunk, layoutBounds, -1); } static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, @@ -624,6 +635,7 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I"); gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig", "Landroid/graphics/Bitmap$Config;"); + gOptions_premultipliedFieldID = getFieldIDCheck(env, options_class, "inPremultiplied", "Z"); gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z"); gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z"); gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z"); diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h index f2aaab71c93e..97dcc6d36a60 100644 --- a/core/jni/android/graphics/BitmapFactory.h +++ b/core/jni/android/graphics/BitmapFactory.h @@ -7,6 +7,7 @@ extern jclass gOptions_class; extern jfieldID gOptions_justBoundsFieldID; extern jfieldID gOptions_sampleSizeFieldID; extern jfieldID gOptions_configFieldID; +extern jfieldID gOptions_premultipliedFieldID; extern jfieldID gOptions_ditherFieldID; extern jfieldID gOptions_purgeableFieldID; extern jfieldID gOptions_shareableFieldID; diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 10ef60a4c408..0c0ebbb00a59 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -201,6 +201,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b SkBitmap::Config prefConfig = SkBitmap::kNo_Config; bool doDither = true; bool preferQualityOverSpeed = false; + bool requireUnpremultiplied = false; if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); @@ -216,11 +217,13 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b gOptions_preferQualityOverSpeedFieldID); // Get the bitmap for re-use if it exists. tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); + requireUnpremultiplied = env->GetBooleanField(options, gOptions_premultipliedFieldID); } decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); - AutoDecoderCancel adc(options, decoder); + decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); + AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added @@ -270,7 +273,10 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); jbyteArray buff = allocator->getStorageObjAndReset(); - return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, NULL, -1); + + int bitmapCreateFlags = 0; + if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; + return GraphicsJNI::createBitmap(env, bitmap, buff, bitmapCreateFlags, NULL, NULL, -1); } static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) { diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 6c4526eccfdf..813dd5af1e28 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -547,18 +547,17 @@ public: jboolean hasAlpha, SkPaint* paint) { SkBitmap bitmap; - bitmap.setConfig(hasAlpha ? SkBitmap::kARGB_8888_Config : SkBitmap::kRGB_565_Config, width, height); if (!bitmap.allocPixels()) { return; } - + if (!GraphicsJNI::SetPixels(env, jcolors, offset, stride, - 0, 0, width, height, bitmap)) { + 0, 0, width, height, bitmap, true)) { return; } - + canvas->drawBitmap(bitmap, SkFloatToScalar(x), SkFloatToScalar(y), paint); } diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 7c420ada2845..ef5b7c91e67a 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -347,27 +347,32 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) /////////////////////////////////////////////////////////////////////////////////////////// jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - bool isMutable, jbyteArray ninepatch, jintArray layoutbounds, - int density) + int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density) { SkASSERT(bitmap); SkASSERT(bitmap->pixelRef()); + bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable; + bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied; + jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, - static_cast(reinterpret_cast(bitmap)), - buffer, isMutable, ninepatch, layoutbounds, density); + static_cast(reinterpret_cast(bitmap)), buffer, + bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied, + ninepatch, layoutbounds); hasException(env); // For the side effect of logging. return obj; } -jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, - jbyteArray ninepatch, int density) +jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags, + jbyteArray ninepatch, int density) { - return createBitmap(env, bitmap, NULL, isMutable, ninepatch, NULL, density); + return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninepatch, NULL, density); } -void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap) +void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap, + bool isPremultiplied) { - env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID); + env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID, + bitmap->width(), bitmap->height(), isPremultiplied); } int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap) @@ -593,9 +598,8 @@ int register_android_graphics_Graphics(JNIEnv* env) gBitmap_class = make_globalref(env, "android/graphics/Bitmap"); gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I"); - gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "", - "(I[BZ[B[II)V"); - gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "()V"); + gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "", "(I[BIIIZZ[B[I)V"); + gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V"); gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I"); gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder"); gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "", "(I)V"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index b67652719526..5a2a6f826fbc 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -17,6 +17,12 @@ class SkPicture; class GraphicsJNI { public: + enum BitmapCreateFlags { + kBitmapCreateFlag_None = 0x0, + kBitmapCreateFlag_Mutable = 0x1, + kBitmapCreateFlag_Premultiplied = 0x2, + }; + // returns true if an exception is set (and dumps it out to the Log) static bool hasException(JNIEnv*); @@ -53,13 +59,13 @@ public: storage array (may be null). */ static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - bool isMutable, jbyteArray ninepatch, jintArray layoutbounds, - int density = -1); + int bitmapCreateFlags, jbyteArray ninepatch, jintArray layoutbounds, int density = -1); - static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, - jbyteArray ninepatch, int density = -1); + static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags, + jbyteArray ninepatch, int density = -1); - static void reinitBitmap(JNIEnv* env, jobject javaBitmap); + static void reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap, + bool isPremultiplied); static int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap); @@ -68,14 +74,14 @@ public: static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); static jbyteArray allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, - SkColorTable* ctable); + SkColorTable* ctable); /** Copy the colors in colors[] to the bitmap, convert to the correct format along the way. */ static bool SetPixels(JNIEnv* env, jintArray colors, int srcOffset, - int srcStride, int x, int y, int width, int height, - const SkBitmap& dstBitmap); + int srcStride, int x, int y, int width, int height, + const SkBitmap& dstBitmap, bool isPremultiplied); static jbyteArray getBitmapStorageObj(SkPixelRef *pixref); }; diff --git a/core/jni/android_emoji_EmojiFactory.cpp b/core/jni/android_emoji_EmojiFactory.cpp index 43839972227d..52769340b6bc 100644 --- a/core/jni/android_emoji_EmojiFactory.cpp +++ b/core/jni/android_emoji_EmojiFactory.cpp @@ -6,6 +6,7 @@ #include #include "EmojiFactory.h" +#include "GraphicsJNI.h" #include #include @@ -92,9 +93,6 @@ static EmojiFactoryCaller* gCaller; static pthread_once_t g_once = PTHREAD_ONCE_INIT; static bool lib_emoji_factory_is_ready; -static jclass gBitmap_class; -static jmethodID gBitmap_constructorMethodID; - static jclass gEmojiFactory_class; static jmethodID gEmojiFactory_constructorMethodID; @@ -172,13 +170,8 @@ static jobject android_emoji_EmojiFactory_getBitmapFromAndroidPua( return NULL; } - jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, - static_cast(reinterpret_cast(bitmap)), NULL, false, NULL, -1); - if (env->ExceptionCheck() != 0) { - ALOGE("*** Uncaught exception returned from Java call!\n"); - env->ExceptionDescribe(); - } - return obj; + return GraphicsJNI::createBitmap(env, bitmap, + GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); } static void android_emoji_EmojiFactory_destructor( @@ -281,9 +274,6 @@ static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz, } int register_android_emoji_EmojiFactory(JNIEnv* env) { - gBitmap_class = make_globalref(env, "android/graphics/Bitmap"); - gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "", - "(I[BZ[BI)V"); gEmojiFactory_class = make_globalref(env, "android/emoji/EmojiFactory"); gEmojiFactory_constructorMethodID = env->GetMethodID( gEmojiFactory_class, "", "(ILjava/lang/String;)V"); diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 6d46cf9941b1..dc90da734de7 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -417,7 +417,7 @@ static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz, return; } - if (!GraphicsJNI::SetPixels(env, colors, offset, stride, 0, 0, width, height, *bitmap)) { + if (!GraphicsJNI::SetPixels(env, colors, offset, stride, 0, 0, width, height, *bitmap, true)) { delete bitmap; return; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 19f56cd569dc..67eade83cdc4 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -195,7 +195,8 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, jobject display bitmap->setPixels(NULL); } - return GraphicsJNI::createBitmap(env, bitmap, false, NULL); + return GraphicsJNI::createBitmap(env, bitmap, + GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); } static void nativeScreenshot(JNIEnv* env, jclass clazz, diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 47cf87501f07..c971da5cc607 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -57,10 +57,20 @@ public final class Bitmap implements Parcelable { private final BitmapFinalizer mFinalizer; private final boolean mIsMutable; + + /** + * Represents whether the Bitmap's content is expected to be pre-multiplied. + * Note that isPremultiplied() does not directly return this value, because + * isPremultiplied() may never return true for a 565 Bitmap. + * + * setPremultiplied() does directly set the value so that setConfig() and + * setPremultiplied() aren't order dependent, despite being setters. + */ + private boolean mIsPremultiplied; private byte[] mNinePatchChunk; // may be null private int[] mLayoutBounds; // may be null - private int mWidth = -1; - private int mHeight = -1; + private int mWidth; + private int mHeight; private boolean mRecycled; // Package-scoped for fast access. @@ -89,32 +99,26 @@ public final class Bitmap implements Parcelable { } /** - * Private constructor that must received an already allocated native - * bitmap int (pointer). - */ - @SuppressWarnings({"UnusedDeclaration"}) // called from JNI - Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, - int density) { - this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density); - } - - /** * Private constructor that must received an already allocated native bitmap * int (pointer). */ @SuppressWarnings({"UnusedDeclaration"}) // called from JNI - Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, - int[] layoutBounds, int density) { + Bitmap(int nativeBitmap, byte[] buffer, int width, int height, int density, + boolean isMutable, boolean isPremultiplied, + byte[] ninePatchChunk, int[] layoutBounds) { if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } + mWidth = width; + mHeight = height; + mIsMutable = isMutable; + mIsPremultiplied = isPremultiplied; mBuffer = buffer; // we delete this in our finalizer mNativeBitmap = nativeBitmap; mFinalizer = new BitmapFinalizer(nativeBitmap); - mIsMutable = isMutable; mNinePatchChunk = ninePatchChunk; mLayoutBounds = layoutBounds; if (density >= 0) { @@ -123,11 +127,14 @@ public final class Bitmap implements Parcelable { } /** - * Native bitmap has been reconfigured, so discard cached width/height + * Native bitmap has been reconfigured, so set premult and cached + * width/height values */ @SuppressWarnings({"UnusedDeclaration"}) // called from JNI - void reinit() { - mWidth = mHeight = -1; + void reinit(int width, int height, boolean isPremultiplied) { + mWidth = width; + mHeight = height; + mIsPremultiplied = isPremultiplied; } /** @@ -206,7 +213,7 @@ public final class Bitmap implements Parcelable { throw new IllegalStateException("only mutable bitmaps may be reconfigured"); } if (mBuffer == null) { - throw new IllegalStateException("only non-purgeable bitmaps may be reconfigured"); + throw new IllegalStateException("native-backed bitmaps may not be reconfigured"); } nativeReconfigure(mNativeBitmap, width, height, config.nativeInt, mBuffer.length); @@ -543,6 +550,7 @@ public final class Bitmap implements Parcelable { checkRecycled("Can't copy a recycled bitmap"); Bitmap b = nativeCopy(mNativeBitmap, config.nativeInt, isMutable); if (b != null) { + b.mIsPremultiplied = mIsPremultiplied; b.mDensity = mDensity; } return b; @@ -719,6 +727,7 @@ public final class Bitmap implements Parcelable { // The new bitmap was created from a known bitmap source so assume that // they use the same density bitmap.mDensity = source.mDensity; + bitmap.mIsPremultiplied = source.mIsPremultiplied; canvas.setBitmap(bitmap); canvas.drawBitmap(source, srcR, dstR, paint); @@ -1001,22 +1010,48 @@ public final class Bitmap implements Parcelable { *

This method only returns true if {@link #hasAlpha()} returns true. * A bitmap with no alpha channel can be used both as a pre-multiplied and * as a non pre-multiplied bitmap.

- * + * + *

Only pre-multiplied bitmaps may be drawn by the view system or + * {@link Canvas}. If a non-pre-multiplied bitmap with an alpha channel is + * drawn to a Canvas, a RuntimeException will be thrown.

+ * * @return true if the underlying pixels have been pre-multiplied, false * otherwise + * + * @see Bitmap#setPremultiplied(boolean) + * @see BitmapFactory.Options#inPremultiplied */ public final boolean isPremultiplied() { - return getConfig() != Config.RGB_565 && hasAlpha(); + return mIsPremultiplied && getConfig() != Config.RGB_565 && hasAlpha(); + } + + /** + * Sets whether the bitmap should treat its data as pre-multiplied. + * + *

Bitmaps are always treated as pre-multiplied by the view system and + * {@link Canvas} for performance reasons. Storing un-pre-multiplied data in + * a Bitmap (through {@link #setPixel}, {@link #setPixels}, or {@link + * BitmapFactory.Options#inPremultiplied BitmapFactory.Options.inPremultiplied}) + * can lead to incorrect blending if drawn by the framework.

+ * + *

This method will not affect the behavior of a bitmap without an alpha + * channel, or if {@link #hasAlpha()} returns false.

+ * + * @see Bitmap#isPremultiplied() + * @see BitmapFactory.Options#inPremultiplied + */ + public final void setPremultiplied(boolean premultiplied) { + mIsPremultiplied = premultiplied; } /** Returns the bitmap's width */ public final int getWidth() { - return mWidth == -1 ? mWidth = nativeWidth(mNativeBitmap) : mWidth; + return mWidth; } /** Returns the bitmap's height */ public final int getHeight() { - return mHeight == -1 ? mHeight = nativeHeight(mNativeBitmap) : mHeight; + return mHeight; } /** @@ -1236,7 +1271,7 @@ public final class Bitmap implements Parcelable { public int getPixel(int x, int y) { checkRecycled("Can't call getPixel() on a recycled bitmap"); checkPixelAccess(x, y); - return nativeGetPixel(mNativeBitmap, x, y); + return nativeGetPixel(mNativeBitmap, x, y, mIsPremultiplied); } /** @@ -1270,7 +1305,7 @@ public final class Bitmap implements Parcelable { } checkPixelsAccess(x, y, width, height, offset, stride, pixels); nativeGetPixels(mNativeBitmap, pixels, offset, stride, - x, y, width, height); + x, y, width, height, mIsPremultiplied); } /** @@ -1350,7 +1385,7 @@ public final class Bitmap implements Parcelable { throw new IllegalStateException(); } checkPixelAccess(x, y); - nativeSetPixel(mNativeBitmap, x, y, color); + nativeSetPixel(mNativeBitmap, x, y, color, mIsPremultiplied); } /** @@ -1387,7 +1422,7 @@ public final class Bitmap implements Parcelable { } checkPixelsAccess(x, y, width, height, offset, stride, pixels); nativeSetPixels(mNativeBitmap, pixels, offset, stride, - x, y, width, height); + x, y, width, height, mIsPremultiplied); } public static final Parcelable.Creator CREATOR @@ -1535,21 +1570,20 @@ public final class Bitmap implements Parcelable { int quality, OutputStream stream, byte[] tempStorage); private static native void nativeErase(int nativeBitmap, int color); - private static native int nativeWidth(int nativeBitmap); - private static native int nativeHeight(int nativeBitmap); private static native int nativeRowBytes(int nativeBitmap); private static native int nativeConfig(int nativeBitmap); - private static native int nativeGetPixel(int nativeBitmap, int x, int y); + private static native int nativeGetPixel(int nativeBitmap, int x, int y, + boolean isPremultiplied); private static native void nativeGetPixels(int nativeBitmap, int[] pixels, - int offset, int stride, int x, - int y, int width, int height); + int offset, int stride, int x, int y, + int width, int height, boolean isPremultiplied); private static native void nativeSetPixel(int nativeBitmap, int x, int y, - int color); + int color, boolean isPremultiplied); private static native void nativeSetPixels(int nativeBitmap, int[] colors, - int offset, int stride, int x, - int y, int width, int height); + int offset, int stride, int x, int y, + int width, int height, boolean isPremultiplied); private static native void nativeCopyPixelsToBuffer(int nativeBitmap, Buffer dst); private static native void nativeCopyPixelsFromBuffer(int nb, Buffer src); diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index a4124bfc8481..1c426fdb228e 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -44,6 +44,7 @@ public class BitmapFactory { public Options() { inDither = false; inScaled = true; + inPremultiplied = true; } /** @@ -125,6 +126,26 @@ public class BitmapFactory { public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888; /** + * If true (which is the default), the resulting bitmap will have its + * color channels pre-multipled by the alpha channel. + * + *

This should NOT be set to false for images to be directly drawn by + * the view system or through a {@link Canvas}. The view system and + * {@link Canvas} assume all drawn images are pre-multiplied to simplify + * draw-time blending, and will throw a RuntimeException when + * un-premultiplied are drawn.

+ * + *

This is likely only useful if you want to manipulate raw encoded + * image data, e.g. with RenderScript or custom OpenGL.

+ * + *

This does not affect bitmaps without an alpha channel.

+ * + * @see Bitmap#hasAlpha() + * @see Bitmap#isPremultiplied() + */ + public boolean inPremultiplied; + + /** * If dither is true, the decoder will attempt to dither the decoded * image. */ diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 0ea4074c7eb0..d46238fc42a2 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -136,7 +136,7 @@ public class Canvas { if (!bitmap.isMutable()) { throw new IllegalStateException("Immutable bitmap passed to Canvas constructor"); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); mNativeCanvas = initRaster(bitmap.ni()); mFinalizer = new CanvasFinalizer(mNativeCanvas); mBitmap = bitmap; @@ -225,7 +225,7 @@ public class Canvas { if (!bitmap.isMutable()) { throw new IllegalStateException(); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); safeCanvasSwap(initRaster(bitmap.ni()), true); mDensity = bitmap.mDensity; @@ -1075,11 +1075,19 @@ public class Canvas { public void drawPath(Path path, Paint paint) { native_drawPath(mNativeCanvas, path.ni(), paint.mNativePaint); } - - private static void throwIfRecycled(Bitmap bitmap) { + + /** + * @hide + */ + protected static void throwIfCannotDraw(Bitmap bitmap) { if (bitmap.isRecycled()) { throw new RuntimeException("Canvas: trying to use a recycled bitmap " + bitmap); } + if (!bitmap.isPremultiplied() && bitmap.getConfig() == Bitmap.Config.ARGB_8888 && + bitmap.hasAlpha()) { + throw new RuntimeException("Canvas: trying to use a non-premultiplied bitmap " + + bitmap); + } } /** @@ -1128,7 +1136,7 @@ public class Canvas { * @param paint The paint used to draw the bitmap (may be null) */ public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) { - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top, paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity, bitmap.mDensity); } @@ -1159,7 +1167,7 @@ public class Canvas { if (dst == null) { throw new NullPointerException(); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } @@ -1190,7 +1198,7 @@ public class Canvas { if (dst == null) { throw new NullPointerException(); } - throwIfRecycled(bitmap); + throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); } diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index 96616aaeca4f..4121f798ba9a 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -551,8 +551,10 @@ public final class Bitmap_Delegate { int nativeInt = sManager.addNewDelegate(delegate); // and create/return a new Bitmap with it - return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, - density); + // TODO: pass correct width, height, isPremultiplied + return new Bitmap(nativeInt, null /* buffer */, -1 /* width */, -1 /* height */, density, + isMutable, true /* isPremultiplied */, + null /*ninePatchChunk*/, null /* layoutBounds */); } /** -- 2.11.0