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;
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;
@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 {
@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 {
@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 {
@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 {
@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 {
@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 {
@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();
}
}\r
}\r
\r
+static void FromColor_D32_Raw(void* dst, const SkColor src[], int width,\r
+ int, int) {\r
+ // SkColor's ordering may be different from SkPMColor\r
+ if (SK_COLOR_MATCHES_PMCOLOR_BYTE_ORDER) {\r
+ memcpy(dst, src, width * sizeof(SkColor));\r
+ return;\r
+ }\r
+\r
+ // order isn't same, repack each pixel manually\r
+ SkPMColor* d = (SkPMColor*)dst;\r
+ for (int i = 0; i < width; i++) {\r
+ SkColor c = *src++;\r
+ *d++ = SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c),\r
+ SkColorGetG(c), SkColorGetB(c));\r
+ }\r
+}\r
+\r
static void FromColor_D565(void* dst, const SkColor src[], int width,\r
int x, int y) {\r
uint16_t* d = (uint16_t*)dst;\r
\r
DITHER_4444_SCAN(y);\r
for (int stop = x + width; x < stop; x++) {\r
- SkPMColor c = SkPreMultiplyColor(*src++);\r
- *d++ = SkDitherARGB32To4444(c, DITHER_VALUE(x));\r
-// *d++ = SkPixel32ToPixel4444(c);\r
+ SkPMColor pmc = SkPreMultiplyColor(*src++);\r
+ *d++ = SkDitherARGB32To4444(pmc, DITHER_VALUE(x));\r
+// *d++ = SkPixel32ToPixel4444(pmc);\r
+ }\r
+}\r
+\r
+static void FromColor_D4444_Raw(void* dst, const SkColor src[], int width,\r
+ int x, int y) {\r
+ SkPMColor16* d = (SkPMColor16*)dst;\r
+\r
+ DITHER_4444_SCAN(y);\r
+ for (int stop = x + width; x < stop; x++) {\r
+ SkColor c = *src++;\r
+\r
+ // SkPMColor is used because the ordering is ARGB32, even though the target actually premultiplied\r
+ SkPMColor pmc = SkPackARGB32NoCheck(SkColorGetA(c), SkColorGetR(c),\r
+ SkColorGetG(c), SkColorGetB(c));\r
+ *d++ = SkDitherARGB32To4444(pmc, DITHER_VALUE(x));\r
+// *d++ = SkPixel32ToPixel4444(pmc);\r
}\r
}\r
\r
// can return NULL\r
-static FromColorProc ChooseFromColorProc(SkBitmap::Config config) {\r
+static FromColorProc ChooseFromColorProc(SkBitmap::Config config, bool isPremultiplied) {\r
switch (config) {\r
case SkBitmap::kARGB_8888_Config:\r
- return FromColor_D32;\r
+ return isPremultiplied ? FromColor_D32 : FromColor_D32_Raw;\r
case SkBitmap::kARGB_4444_Config:\r
- return FromColor_D4444;\r
+ return isPremultiplied ? FromColor_D4444 : FromColor_D4444_Raw;\r
case SkBitmap::kRGB_565_Config:\r
return FromColor_D565;\r
default:\r
return NULL;\r
}\r
\r
-bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors,\r
- int srcOffset, int srcStride,\r
- int x, int y, int width, int height,\r
- const SkBitmap& dstBitmap) {\r
+bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int srcStride,\r
+ int x, int y, int width, int height,\r
+ const SkBitmap& dstBitmap, bool isPremultiplied) {\r
SkAutoLockPixels alp(dstBitmap);\r
void* dst = dstBitmap.getPixels();\r
- FromColorProc proc = ChooseFromColorProc(dstBitmap.config());\r
+ FromColorProc proc = ChooseFromColorProc(dstBitmap.config(), isPremultiplied);\r
\r
if (NULL == dst || NULL == proc) {\r
return false;\r
} while (--width != 0);\r
}\r
\r
+static void ToColor_S32_Raw(SkColor dst[], const void* src, int width,\r
+ SkColorTable*) {\r
+ SkASSERT(width > 0);\r
+ const SkPMColor* s = (const SkPMColor*)src;\r
+ do {\r
+ SkPMColor c = *s++;\r
+ *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c),\r
+ SkGetPackedG32(c), SkGetPackedB32(c));\r
+ } while (--width != 0);\r
+}\r
+\r
static void ToColor_S32_Opaque(SkColor dst[], const void* src, int width,\r
SkColorTable*) {\r
SkASSERT(width > 0);\r
} while (--width != 0);\r
}\r
\r
+static void ToColor_S4444_Raw(SkColor dst[], const void* src, int width,\r
+ SkColorTable*) {\r
+ SkASSERT(width > 0);\r
+ const SkPMColor16* s = (const SkPMColor16*)src;\r
+ do {\r
+ SkPMColor c = SkPixel4444ToPixel32(*s++);\r
+ *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c),\r
+ SkGetPackedG32(c), SkGetPackedB32(c));\r
+ } while (--width != 0);\r
+}\r
+\r
static void ToColor_S4444_Opaque(SkColor dst[], const void* src, int width,\r
SkColorTable*) {\r
SkASSERT(width > 0);\r
ctable->unlockColors(false);\r
}\r
\r
+static void ToColor_SI8_Raw(SkColor dst[], const void* src, int width,\r
+ SkColorTable* ctable) {\r
+ SkASSERT(width > 0);\r
+ const uint8_t* s = (const uint8_t*)src;\r
+ const SkPMColor* colors = ctable->lockColors();\r
+ do {\r
+ SkPMColor c = colors[*s++];\r
+ *dst++ = SkColorSetARGB(SkGetPackedA32(c), SkGetPackedR32(c),\r
+ SkGetPackedG32(c), SkGetPackedB32(c));\r
+ } while (--width != 0);\r
+ ctable->unlockColors(false);\r
+}\r
+\r
static void ToColor_SI8_Opaque(SkColor dst[], const void* src, int width,\r
SkColorTable* ctable) {\r
SkASSERT(width > 0);\r
}\r
\r
// can return NULL\r
-static ToColorProc ChooseToColorProc(const SkBitmap& src) {\r
+static ToColorProc ChooseToColorProc(const SkBitmap& src, bool isPremultiplied) {\r
switch (src.config()) {\r
case SkBitmap::kARGB_8888_Config:\r
- return src.isOpaque() ? ToColor_S32_Opaque : ToColor_S32_Alpha;\r
+ if (src.isOpaque()) return ToColor_S32_Opaque;\r
+ return isPremultiplied ? ToColor_S32_Alpha : ToColor_S32_Raw;\r
case SkBitmap::kARGB_4444_Config:\r
- return src.isOpaque() ? ToColor_S4444_Opaque : ToColor_S4444_Alpha;\r
+ if (src.isOpaque()) return ToColor_S4444_Opaque;\r
+ return isPremultiplied ? ToColor_S4444_Alpha : ToColor_S4444_Raw;\r
case SkBitmap::kRGB_565_Config:\r
return ToColor_S565;\r
case SkBitmap::kIndex8_Config:\r
if (src.getColorTable() == NULL) {\r
return NULL;\r
}\r
- return src.isOpaque() ? ToColor_SI8_Opaque : ToColor_SI8_Alpha;\r
+ if (src.isOpaque()) return ToColor_SI8_Opaque;\r
+ return isPremultiplied ? ToColor_SI8_Raw : ToColor_SI8_Alpha;\r
default:\r
break;\r
}\r
///////////////////////////////////////////////////////////////////////////////\r
///////////////////////////////////////////////////////////////////////////////\r
\r
+static int getPremulBitmapCreateFlags(bool isMutable) {\r
+ int flags = GraphicsJNI::kBitmapCreateFlag_Premultiplied;\r
+ if (isMutable) flags |= GraphicsJNI::kBitmapCreateFlag_Mutable;\r
+ return flags;\r
+}\r
+\r
static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors,\r
int offset, int stride, int width, int height,\r
SkBitmap::Config config, jboolean isMutable) {\r
}\r
\r
if (jColors != NULL) {\r
- GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap);\r
+ GraphicsJNI::SetPixels(env, jColors, offset, stride,\r
+ 0, 0, width, height, bitmap, true);\r
}\r
\r
- return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL, NULL);\r
+ return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff,\r
+ getPremulBitmapCreateFlags(isMutable), NULL, NULL);\r
}\r
\r
static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,\r
if (!src->copyTo(&result, dstConfig, &allocator)) {\r
return NULL;\r
}\r
-\r
- return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL, NULL);\r
+ return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(),\r
+ getPremulBitmapCreateFlags(isMutable), NULL, NULL);\r
}\r
\r
static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {\r
bitmap->eraseColor(color);\r
}\r
\r
-static int Bitmap_width(JNIEnv* env, jobject, SkBitmap* bitmap) {\r
- return bitmap->width();\r
-}\r
-\r
-static int Bitmap_height(JNIEnv* env, jobject, SkBitmap* bitmap) {\r
- return bitmap->height();\r
-}\r
-\r
static int Bitmap_rowBytes(JNIEnv* env, jobject, SkBitmap* bitmap) {\r
return bitmap->rowBytes();\r
}\r
bitmap->unlockPixels();\r
\r
blob.release();\r
- return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, NULL, density);\r
+\r
+ return GraphicsJNI::createBitmap(env, bitmap, buffer, getPremulBitmapCreateFlags(isMutable),\r
+ NULL, NULL, density);\r
}\r
\r
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,\r
env->ReleaseIntArrayElements(offsetXY, array, 0);\r
}\r
\r
- return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL, NULL);\r
+ return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(),\r
+ GraphicsJNI::kBitmapCreateFlag_Mutable, NULL, NULL);\r
}\r
\r
///////////////////////////////////////////////////////////////////////////////\r
\r
static int Bitmap_getPixel(JNIEnv* env, jobject, const SkBitmap* bitmap,\r
- int x, int y) {\r
+ int x, int y, bool isPremultiplied) {\r
SkAutoLockPixels alp(*bitmap);\r
\r
- ToColorProc proc = ChooseToColorProc(*bitmap);\r
+ ToColorProc proc = ChooseToColorProc(*bitmap, isPremultiplied);\r
if (NULL == proc) {\r
return 0;\r
}\r
}\r
\r
static void Bitmap_getPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,\r
- jintArray pixelArray, int offset, int stride,\r
- int x, int y, int width, int height) {\r
+ jintArray pixelArray, int offset, int stride,\r
+ int x, int y, int width, int height, bool isPremultiplied) {\r
SkAutoLockPixels alp(*bitmap);\r
\r
- ToColorProc proc = ChooseToColorProc(*bitmap);\r
+ ToColorProc proc = ChooseToColorProc(*bitmap, isPremultiplied);\r
if (NULL == proc) {\r
return;\r
}\r
///////////////////////////////////////////////////////////////////////////////\r
\r
static void Bitmap_setPixel(JNIEnv* env, jobject, const SkBitmap* bitmap,\r
- int x, int y, SkColor color) {\r
+ int x, int y, SkColor color, bool isPremultiplied) {\r
SkAutoLockPixels alp(*bitmap);\r
if (NULL == bitmap->getPixels()) {\r
return;\r
}\r
\r
- FromColorProc proc = ChooseFromColorProc(bitmap->config());\r
+ FromColorProc proc = ChooseFromColorProc(bitmap->config(), isPremultiplied);\r
if (NULL == proc) {\r
return;\r
}\r
}\r
\r
static void Bitmap_setPixels(JNIEnv* env, jobject, const SkBitmap* bitmap,\r
- jintArray pixelArray, int offset, int stride,\r
- int x, int y, int width, int height) {\r
+ jintArray pixelArray, int offset, int stride,\r
+ int x, int y, int width, int height, bool isPremultiplied) {\r
GraphicsJNI::SetPixels(env, pixelArray, offset, stride,\r
- x, y, width, height, *bitmap);\r
+ x, y, width, height, *bitmap, isPremultiplied);\r
}\r
\r
static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject,\r
{ "nativeCompress", "(IIILjava/io/OutputStream;[B)Z",\r
(void*)Bitmap_compress },\r
{ "nativeErase", "(II)V", (void*)Bitmap_erase },\r
- { "nativeWidth", "(I)I", (void*)Bitmap_width },\r
- { "nativeHeight", "(I)I", (void*)Bitmap_height },\r
{ "nativeRowBytes", "(I)I", (void*)Bitmap_rowBytes },\r
{ "nativeConfig", "(I)I", (void*)Bitmap_config },\r
{ "nativeHasAlpha", "(I)Z", (void*)Bitmap_hasAlpha },\r
{ "nativeExtractAlpha", "(II[I)Landroid/graphics/Bitmap;",\r
(void*)Bitmap_extractAlpha },\r
{ "nativeGenerationId", "(I)I", (void*)Bitmap_getGenerationId },\r
- { "nativeGetPixel", "(III)I", (void*)Bitmap_getPixel },\r
- { "nativeGetPixels", "(I[IIIIIII)V", (void*)Bitmap_getPixels },\r
- { "nativeSetPixel", "(IIII)V", (void*)Bitmap_setPixel },\r
- { "nativeSetPixels", "(I[IIIIIII)V", (void*)Bitmap_setPixels },\r
+ { "nativeGetPixel", "(IIIZ)I", (void*)Bitmap_getPixel },\r
+ { "nativeGetPixels", "(I[IIIIIIIZ)V", (void*)Bitmap_getPixels },\r
+ { "nativeSetPixel", "(IIIIZ)V", (void*)Bitmap_setPixel },\r
+ { "nativeSetPixels", "(I[IIIIIIIZ)V", (void*)Bitmap_setPixels },\r
{ "nativeCopyPixelsToBuffer", "(ILjava/nio/Buffer;)V",\r
(void*)Bitmap_copyPixelsToBuffer },\r
{ "nativeCopyPixelsFromBuffer", "(ILjava/nio/Buffer;)V",\r
#include "AutoDecodeCancel.h"
#include "Utils.h"
#include "JNIHelp.h"
+#include "GraphicsJNI.h"
#include <android_runtime/AndroidRuntime.h>
#include <androidfw/Asset.h>
jfieldID gOptions_justBoundsFieldID;
jfieldID gOptions_sampleSizeFieldID;
jfieldID gOptions_configFieldID;
+jfieldID gOptions_premultipliedFieldID;
jfieldID gOptions_mutableFieldID;
jfieldID gOptions_ditherFieldID;
jfieldID gOptions_purgeableFieldID;
float scale = 1.0f;
bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options));
bool preferQualityOverSpeed = false;
+ bool requireUnpremultiplied = false;
jobject javaBitmap = NULL;
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)) {
decoder->setSampleSize(sampleSize);
decoder->setDitherImage(doDither);
decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
+ decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
SkBitmap* outputBitmap = NULL;
unsigned int existingBufferSize = 0;
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,
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");
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;
SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
bool doDither = true;
bool preferQualityOverSpeed = false;
+ bool requireUnpremultiplied = false;
if (NULL != options) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
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
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) {
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);
}
///////////////////////////////////////////////////////////////////////////////////////////
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<jint>(reinterpret_cast<uintptr_t>(bitmap)),
- buffer, isMutable, ninepatch, layoutbounds, density);
+ static_cast<jint>(reinterpret_cast<uintptr_t>(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)
gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I");
- gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
- "(I[BZ[B[II)V");
- gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "()V");
+ gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(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, "<init>", "(I)V");
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*);
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);
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);
};
#include <ScopedUtfChars.h>
#include "EmojiFactory.h"
+#include "GraphicsJNI.h"
#include <nativehelper/JNIHelp.h>
#include <dlfcn.h>
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;
return NULL;
}
- jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
- static_cast<jint>(reinterpret_cast<uintptr_t>(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(
}
int register_android_emoji_EmojiFactory(JNIEnv* env) {
- gBitmap_class = make_globalref(env, "android/graphics/Bitmap");
- gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>",
- "(I[BZ[BI)V");
gEmojiFactory_class = make_globalref(env, "android/emoji/EmojiFactory");
gEmojiFactory_constructorMethodID = env->GetMethodID(
gEmojiFactory_class, "<init>", "(ILjava/lang/String;)V");
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;
}
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,
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.
}
/**
- * 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) {
}
/**
- * 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;
}
/**
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);
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;
// 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);
* <p>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.</p>
- *
+ *
+ * <p>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.</p>
+ *
* @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.
+ *
+ * <p>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.</p>
+ *
+ * <p>This method will not affect the behavior of a bitmap without an alpha
+ * channel, or if {@link #hasAlpha()} returns false.</p>
+ *
+ * @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;
}
/**
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);
}
/**
}
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
nativeGetPixels(mNativeBitmap, pixels, offset, stride,
- x, y, width, height);
+ x, y, width, height, mIsPremultiplied);
}
/**
throw new IllegalStateException();
}
checkPixelAccess(x, y);
- nativeSetPixel(mNativeBitmap, x, y, color);
+ nativeSetPixel(mNativeBitmap, x, y, color, mIsPremultiplied);
}
/**
}
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<Bitmap> CREATOR
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);
public Options() {
inDither = false;
inScaled = true;
+ inPremultiplied = true;
}
/**
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.
+ *
+ * <p>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.</p>
+ *
+ * <p>This is likely only useful if you want to manipulate raw encoded
+ * image data, e.g. with RenderScript or custom OpenGL.</p>
+ *
+ * <p>This does not affect bitmaps without an alpha channel.</p>
+ *
+ * @see Bitmap#hasAlpha()
+ * @see Bitmap#isPremultiplied()
+ */
+ public boolean inPremultiplied;
+
+ /**
* If dither is true, the decoder will attempt to dither the decoded
* image.
*/
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;
if (!bitmap.isMutable()) {
throw new IllegalStateException();
}
- throwIfRecycled(bitmap);
+ throwIfCannotDraw(bitmap);
safeCanvasSwap(initRaster(bitmap.ni()), true);
mDensity = bitmap.mDensity;
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);
+ }
}
/**
* @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);
}
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);
}
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);
}
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 */);
}
/**