/** * Given either a SkStream or a SkData, try to decode the encoded * image using the specified options and report errors. */ static void test_options(skiatest::Reporter* reporter, const SkDecodingImageGenerator::Options& opts, SkStreamRewindable* encodedStream, SkData* encodedData, bool useData, const SkString& path) { SkBitmap bm; bool success = false; if (useData) { if (NULL == encodedData) { return; } success = SkInstallDiscardablePixelRef( SkDecodingImageGenerator::Create(encodedData, opts), &bm); } else { if (NULL == encodedStream) { return; } success = SkInstallDiscardablePixelRef( SkDecodingImageGenerator::Create(encodedStream->duplicate(), opts), &bm); } if (!success) { if (opts.fUseRequestedColorType && (kARGB_4444_SkColorType == opts.fRequestedColorType)) { return; // Ignore known conversion inabilities. } // If we get here, it's a failure and we will need more // information about why it failed. ERRORF(reporter, "Bounds decode failed [sampleSize=%d dither=%s " "colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage), options_colorType(opts), path.c_str()); return; } #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) // Android is the only system that use Skia's image decoders in // production. For now, we'll only verify that samplesize works // on systems where it already is known to work. REPORTER_ASSERT(reporter, check_rounding(bm.height(), kExpectedHeight, opts.fSampleSize)); REPORTER_ASSERT(reporter, check_rounding(bm.width(), kExpectedWidth, opts.fSampleSize)); // The ImageDecoder API doesn't guarantee that SampleSize does // anything at all, but the decoders that this test excercises all // produce an output size in the following range: // (((sample_size * out_size) > (in_size - sample_size)) // && out_size <= SkNextPow2(((in_size - 1) / sample_size) + 1)); #endif // SK_BUILD_FOR_ANDROID || SK_BUILD_FOR_UNIX SkAutoLockPixels alp(bm); if (bm.getPixels() == NULL) { ERRORF(reporter, "Pixel decode failed [sampleSize=%d dither=%s " "colorType=%s %s]", opts.fSampleSize, yn(opts.fDitherImage), options_colorType(opts), path.c_str()); return; } SkColorType requestedColorType = opts.fRequestedColorType; REPORTER_ASSERT(reporter, (!opts.fUseRequestedColorType) || (bm.colorType() == requestedColorType)); // Condition under which we should check the decoding results: if ((kN32_SkColorType == bm.colorType()) && (!path.endsWith(".jpg")) // lossy && (opts.fSampleSize == 1)) { // scaled const SkColor* correctPixels = kExpectedPixels; SkASSERT(bm.height() == kExpectedHeight); SkASSERT(bm.width() == kExpectedWidth); int pixelErrors = 0; for (int y = 0; y < bm.height(); ++y) { for (int x = 0; x < bm.width(); ++x) { if (*correctPixels != bm.getColor(x, y)) { ++pixelErrors; } ++correctPixels; } } if (pixelErrors != 0) { ERRORF(reporter, "Pixel-level mismatch (%d of %d) " "[sampleSize=%d dither=%s colorType=%s %s]", pixelErrors, kExpectedHeight * kExpectedWidth, opts.fSampleSize, yn(opts.fDitherImage), options_colorType(opts), path.c_str()); } } }
bool SkMagnifierImageFilter::onFilterImage(Proxy*, const SkBitmap& src, const Context&, SkBitmap* dst, SkIPoint* offset) const { if ((src.colorType() != kN32_SkColorType) || (fSrcRect.width() >= src.width()) || (fSrcRect.height() >= src.height())) { return false; } SkAutoLockPixels alp(src); SkASSERT(src.getPixels()); if (!src.getPixels() || src.width() <= 0 || src.height() <= 0) { return false; } if (!dst->tryAllocPixels(src.info())) { return false; } SkScalar inv_inset = fInset > 0 ? SkScalarInvert(fInset) : SK_Scalar1; SkScalar inv_x_zoom = fSrcRect.width() / src.width(); SkScalar inv_y_zoom = fSrcRect.height() / src.height(); SkColor* sptr = src.getAddr32(0, 0); SkColor* dptr = dst->getAddr32(0, 0); int width = src.width(), height = src.height(); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { SkScalar x_dist = SkMin32(x, width - x - 1) * inv_inset; SkScalar y_dist = SkMin32(y, height - y - 1) * inv_inset; SkScalar weight = 0; static const SkScalar kScalar2 = SkScalar(2); // To create a smooth curve at the corners, we need to work on // a square twice the size of the inset. if (x_dist < kScalar2 && y_dist < kScalar2) { x_dist = kScalar2 - x_dist; y_dist = kScalar2 - y_dist; SkScalar dist = SkScalarSqrt(SkScalarSquare(x_dist) + SkScalarSquare(y_dist)); dist = SkMaxScalar(kScalar2 - dist, 0); weight = SkMinScalar(SkScalarSquare(dist), SK_Scalar1); } else { SkScalar sqDist = SkMinScalar(SkScalarSquare(x_dist), SkScalarSquare(y_dist)); weight = SkMinScalar(sqDist, SK_Scalar1); } SkScalar x_interp = SkScalarMul(weight, (fSrcRect.x() + x * inv_x_zoom)) + (SK_Scalar1 - weight) * x; SkScalar y_interp = SkScalarMul(weight, (fSrcRect.y() + y * inv_y_zoom)) + (SK_Scalar1 - weight) * y; int x_val = SkPin32(SkScalarFloorToInt(x_interp), 0, width - 1); int y_val = SkPin32(SkScalarFloorToInt(y_interp), 0, height - 1); *dptr = sptr[y_val * width + x_val]; dptr++; } } return true; }
SkBitmap* MpoDecoder::decodeBuffer(JNIEnv* env, jobject options, SkStream* stream) { int sampleSize = 1; int preferSize = 0; int postproc = 0; int postprocflag = 0; SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; SkBitmap::Config prefConfig = SkBitmap::kNo_Config; bool doDither = true; bool isPurgeable = options != NULL && env->GetBooleanField(options, options_purgeableFieldID); if (NULL != options) { sampleSize = env->GetIntField(options, options_sampleSizeFieldID); //preferSize = env->GetIntField(options, options_preferSizeFieldID); //postproc = env->GetBooleanField(options, options_postprocFieldID); //postprocflag = env->GetIntField(options, options_postprocflagFieldID); if (env->GetBooleanField(options, options_justBoundsFieldID)) { mode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on env->SetIntField(options, options_widthFieldID, -1); env->SetIntField(options, options_heightFieldID, -1); env->SetObjectField(options, options_mimeFieldID, 0); jobject jconfig = env->GetObjectField(options, options_configFieldID); prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); doDither = env->GetBooleanField(options, options_ditherFieldID); } SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (NULL == decoder) { XLOGE("SkImageDecoder-Factory() returned false"); return NULL; } decoder->setSampleSize(sampleSize); decoder->setDitherImage(doDither); //decoder->setPreferSize(preferSize); //decoder->setPostProcFlag((postproc | (postprocflag << 4))); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, options_mCancelID)) { XLOGE("Decoding is cancelled by requestCancelDecode"); return NULL; } SkImageDecoder::Mode decodeMode = mode; if (isPurgeable) { decodeMode = SkImageDecoder::kDecodeBounds_Mode; } SkBitmap* bitmap = new SkBitmap; if (!decoder->decode(stream, bitmap, prefConfig, decodeMode)) { XLOGE("SkImageDecoder-decode() returned false"); return NULL; } // update options (if any) if (NULL != options) { env->SetIntField(options, options_widthFieldID, bitmap->width()); env->SetIntField(options, options_heightFieldID, bitmap->height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? env->SetObjectField(options, options_mimeFieldID,env->NewStringUTF("image/mpo")); } // if we're in justBounds mode, return now (skip the java bitmap) if (SkImageDecoder::kDecodeBounds_Mode == mode) { delete bitmap; return NULL; } else { return bitmap; } }
static void assert_bounds_equal(skiatest::Reporter* reporter, const SkBitmap& bm1, const SkBitmap& bm2) { REPORTER_ASSERT(reporter, bm1.width() == bm2.width()); REPORTER_ASSERT(reporter, bm1.height() == bm2.height()); }
void SkBitmapDevice::writePixels(const SkBitmap& bitmap, int x, int y, SkCanvas::Config8888 config8888) { if (bitmap.isNull() || bitmap.getTexture()) { return; } const SkBitmap* sprite = &bitmap; // check whether we have to handle a config8888 that doesn't match SkPMColor if (SkBitmap::kARGB_8888_Config == bitmap.config() && SkCanvas::kNative_Premul_Config8888 != config8888 && kPMColorAlias != config8888) { // We're going to have to convert from a config8888 to the native config // First we clip to the device bounds. SkBitmap dstBmp = this->accessBitmap(true); SkIRect spriteRect = SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height()); SkIRect devRect = SkIRect::MakeWH(dstBmp.width(), dstBmp.height()); if (!spriteRect.intersect(devRect)) { return; } // write directly to the device if it has pixels and is SkPMColor bool drawSprite; if (SkBitmap::kARGB_8888_Config == dstBmp.config() && !dstBmp.isNull()) { // we can write directly to the dst when doing the conversion dstBmp.extractSubset(&dstBmp, spriteRect); drawSprite = false; } else { // we convert to a temporary bitmap and draw that as a sprite dstBmp.setConfig(SkBitmap::kARGB_8888_Config, spriteRect.width(), spriteRect.height()); if (!dstBmp.allocPixels()) { return; } drawSprite = true; } // copy pixels to dstBmp and convert from config8888 to native config. SkAutoLockPixels alp(bitmap); uint32_t* srcPixels = bitmap.getAddr32(spriteRect.fLeft - x, spriteRect.fTop - y); SkCopyConfig8888ToBitmap(dstBmp, srcPixels, bitmap.rowBytes(), config8888); if (drawSprite) { // we've clipped the sprite when we made a copy x = spriteRect.fLeft; y = spriteRect.fTop; sprite = &dstBmp; } else { return; } } SkPaint paint; paint.setXfermodeMode(SkXfermode::kSrc_Mode); SkRasterClip clip(SkIRect::MakeWH(fBitmap.width(), fBitmap.height())); SkDraw draw; draw.fRC = &clip; draw.fClip = &clip.bwRgn(); draw.fBitmap = &fBitmap; // canvas should have already called accessBitmap draw.fMatrix = &SkMatrix::I(); this->drawSprite(draw, *sprite, x, y, paint); }
static SkBitmap createBitmapWithSpace(const SkBitmap& bitmap, int spaceWidth, int spaceHeight) { SkImageInfo info = bitmap.info(); info = SkImageInfo::Make(info.width() + spaceWidth, info.height() + spaceHeight, info.colorType(), kPremul_SkAlphaType); SkBitmap result; result.allocPixels(info); result.eraseColor(SK_ColorTRANSPARENT); bitmap.copyPixelsTo(reinterpret_cast<uint8_t*>(result.getPixels()), result.rowBytes() * result.height(), result.rowBytes()); return result; }
void SkScalerContext_FreeType_Base::generateGlyphImage( FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform) { const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag); const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); switch ( face->glyph->format ) { case FT_GLYPH_FORMAT_OUTLINE: { FT_Outline* outline = &face->glyph->outline; int dx = 0, dy = 0; if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) { dx = SkFixedToFDot6(glyph.getSubXFixed()); dy = SkFixedToFDot6(glyph.getSubYFixed()); // negate dy since freetype-y-goes-up and skia-y-goes-down dy = -dy; } memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Outline_Translate(outline, dx, dy); FT_Error err = FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); if (err) { SK_TRACEFTR(err, "Could not render glyph."); return; } SkMask mask; glyph.toMask(&mask); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE memset(mask.fImage, 0x80, mask.fBounds.height() * mask.fRowBytes); #endif FT_GlyphSlotRec& ftGlyph = *face->glyph; if (!SkIRect::Intersects(mask.fBounds, SkIRect::MakeXYWH( ftGlyph.bitmap_left, -ftGlyph.bitmap_top, ftGlyph.bitmap.width, ftGlyph.bitmap.rows))) { return; } // If the FT_Bitmap extent is larger, discard bits of the bitmap outside the mask. // If the SkMask extent is larger, shrink mask to fit bitmap (clearing discarded). unsigned char* origBuffer = ftGlyph.bitmap.buffer; // First align the top left (origin). if (-ftGlyph.bitmap_top < mask.fBounds.fTop) { int32_t topDiff = mask.fBounds.fTop - (-ftGlyph.bitmap_top); ftGlyph.bitmap.buffer += ftGlyph.bitmap.pitch * topDiff; ftGlyph.bitmap.rows -= topDiff; ftGlyph.bitmap_top = -mask.fBounds.fTop; } if (ftGlyph.bitmap_left < mask.fBounds.fLeft) { int32_t leftDiff = mask.fBounds.fLeft - ftGlyph.bitmap_left; ftGlyph.bitmap.buffer += leftDiff; ftGlyph.bitmap.width -= leftDiff; ftGlyph.bitmap_left = mask.fBounds.fLeft; } if (mask.fBounds.fTop < -ftGlyph.bitmap_top) { mask.fImage += mask.fRowBytes * (-ftGlyph.bitmap_top - mask.fBounds.fTop); mask.fBounds.fTop = -ftGlyph.bitmap_top; } if (mask.fBounds.fLeft < ftGlyph.bitmap_left) { mask.fImage += sizeof(uint16_t) * (ftGlyph.bitmap_left - mask.fBounds.fLeft); mask.fBounds.fLeft = ftGlyph.bitmap_left; } // Origins aligned, clean up the width and height. int ftVertScale = (doVert ? 3 : 1); int ftHoriScale = (doVert ? 1 : 3); if (mask.fBounds.height() * ftVertScale < SkToInt(ftGlyph.bitmap.rows)) { ftGlyph.bitmap.rows = mask.fBounds.height() * ftVertScale; } if (mask.fBounds.width() * ftHoriScale < SkToInt(ftGlyph.bitmap.width)) { ftGlyph.bitmap.width = mask.fBounds.width() * ftHoriScale; } if (SkToInt(ftGlyph.bitmap.rows) < mask.fBounds.height() * ftVertScale) { mask.fBounds.fBottom = mask.fBounds.fTop + ftGlyph.bitmap.rows / ftVertScale; } if (SkToInt(ftGlyph.bitmap.width) < mask.fBounds.width() * ftHoriScale) { mask.fBounds.fRight = mask.fBounds.fLeft + ftGlyph.bitmap.width / ftHoriScale; } if (fPreBlend.isApplicable()) { copyFT2LCD16<true>(ftGlyph.bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16<false>(ftGlyph.bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } // Restore the buffer pointer so FreeType can properly free it. ftGlyph.bitmap.buffer = origBuffer; } else { FT_BBox bbox; FT_Bitmap target; FT_Outline_Get_CBox(outline, &bbox); /* what we really want to do for subpixel is offset(dx, dy) compute_bounds offset(bbox & !63) but that is two calls to offset, so we do the following, which achieves the same thing with only one offset call. */ FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63), dy - ((bbox.yMin + dy) & ~63)); target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE for (int y = 0; y < glyph.fHeight; ++y) { for (int x = 0; x < glyph.fWidth; ++x) { uint8_t& a = ((uint8_t*)glyph.fImage)[(glyph.rowBytes() * y) + x]; a = SkTMax<uint8_t>(a, 0x20); } } #endif } } break; case FT_GLYPH_FORMAT_BITMAP: { FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode); SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat); // Assume that the other formats do not exist. SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode || FT_PIXEL_MODE_GRAY == pixel_mode || FT_PIXEL_MODE_BGRA == pixel_mode); // These are the only formats this ScalerContext should request. SkASSERT(SkMask::kBW_Format == maskFormat || SkMask::kA8_Format == maskFormat || SkMask::kARGB32_Format == maskFormat || SkMask::kLCD16_Format == maskFormat); // If no scaling needed, directly copy glyph bitmap. if (bitmapTransform.isIdentity()) { SkMask dstMask; glyph.toMask(&dstMask); copyFTBitmap(face->glyph->bitmap, dstMask); break; } // Otherwise, scale the bitmap. // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB) SkBitmap unscaledBitmap; // TODO: mark this as sRGB when the blits will be sRGB. unscaledBitmap.allocPixels(SkImageInfo::Make(face->glyph->bitmap.width, face->glyph->bitmap.rows, SkColorType_for_FTPixelMode(pixel_mode), kPremul_SkAlphaType)); SkMask unscaledBitmapAlias; unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels()); unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height()); unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes(); unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType()); copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias); // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD. // BW requires an A8 target for resizing, which can then be down sampled. // LCD should use a 4x A8 target, which will then be down sampled. // For simplicity, LCD uses A8 and is replicated. int bitmapRowBytes = 0; if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) { bitmapRowBytes = glyph.rowBytes(); } SkBitmap dstBitmap; // TODO: mark this as sRGB when the blits will be sRGB. dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight, SkColorType_for_SkMaskFormat(maskFormat), kPremul_SkAlphaType), bitmapRowBytes); if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { dstBitmap.allocPixels(); } else { dstBitmap.setPixels(glyph.fImage); } // Scale unscaledBitmap into dstBitmap. SkCanvas canvas(dstBitmap); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE canvas.clear(0x33FF0000); #else canvas.clear(SK_ColorTRANSPARENT); #endif canvas.translate(-glyph.fLeft, -glyph.fTop); canvas.concat(bitmapTransform); canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top); SkPaint paint; paint.setFilterQuality(kMedium_SkFilterQuality); canvas.drawBitmap(unscaledBitmap, 0, 0, &paint); // If the destination is BW or LCD, convert from A8. if (SkMask::kBW_Format == maskFormat) { // Copy the A8 dstBitmap into the A1 glyph.fImage. SkMask dstMask; glyph.toMask(&dstMask); packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes()); } else if (SkMask::kLCD16_Format == maskFormat) { // Copy the A8 dstBitmap into the LCD16 glyph.fImage. uint8_t* src = dstBitmap.getAddr8(0, 0); uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage); for (int y = dstBitmap.height(); y --> 0;) { for (int x = 0; x < dstBitmap.width(); ++x) { dst[x] = grayToRGB16(src[x]); } dst = (uint16_t*)((char*)dst + glyph.rowBytes()); src += dstBitmap.rowBytes(); } } } break; default: SkDEBUGFAIL("unknown glyph format"); memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); return; } // We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum, // it is optional #if defined(SK_GAMMA_APPLY_TO_A8) if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) { uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage; unsigned rowBytes = glyph.rowBytes(); for (int y = glyph.fHeight - 1; y >= 0; --y) { for (int x = glyph.fWidth - 1; x >= 0; --x) { dst[x] = fPreBlend.fG[dst[x]]; } dst += rowBytes; } } #endif }
// since we "may" create a purgeable imageref, we require the stream be ref'able // i.e. dynamically allocated, since its lifetime may exceed the current stack // frame. static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, jobject options, bool allowPurgeable, bool forcePurgeable = false, bool applyScale = false, float scale = 1.0f) { int sampleSize = 1; SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config; bool doDither = true; bool isMutable = false; bool willScale = applyScale && scale != 1.0f; bool isPurgeable = !willScale && (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options))); bool preferQualityOverSpeed = false; jobject javaBitmap = NULL; if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); if (optionsJustBounds(env, options)) { mode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); env->SetObjectField(options, gOptions_mimeFieldID, 0); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, gOptions_preferQualityOverSpeedFieldID); javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); } if (willScale && javaBitmap != NULL) { return nullObjectReturn("Cannot pre-scale a reused bitmap"); } SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (decoder == NULL) { return nullObjectReturn("SkImageDecoder::Factory returned null"); } decoder->setSampleSize(sampleSize); decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); NinePatchPeeker peeker(decoder); JavaPixelAllocator javaAllocator(env); SkBitmap* bitmap; if (javaBitmap == NULL) { bitmap = new SkBitmap; } else { if (sampleSize != 1) { return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1"); } bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID); // config of supplied bitmap overrules config set in options prefConfig = bitmap->getConfig(); } SkAutoTDelete<SkImageDecoder> add(decoder); SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL); decoder->setPeeker(&peeker); if (!isPurgeable) { decoder->setAllocator(&javaAllocator); } AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID"); } SkImageDecoder::Mode decodeMode = mode; if (isPurgeable) { decodeMode = SkImageDecoder::kDecodeBounds_Mode; } SkBitmap* decoded; if (willScale) { decoded = new SkBitmap; } else { decoded = bitmap; } SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL); if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) { return nullObjectReturn("decoder->decode returned false"); } int scaledWidth = decoded->width(); int scaledHeight = decoded->height(); if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); } // update options (if any) if (options != NULL) { env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); } // if we're in justBounds mode, return now (skip the java bitmap) if (mode == SkImageDecoder::kDecodeBounds_Mode) { return NULL; } jbyteArray ninePatchChunk = NULL; if (peeker.fPatch != NULL) { if (willScale) { scaleNinePatchChunk(peeker.fPatch, scale); } size_t ninePatchArraySize = peeker.fPatch->serializedSize(); ninePatchChunk = env->NewByteArray(ninePatchArraySize); if (ninePatchChunk == NULL) { return nullObjectReturn("ninePatchChunk == null"); } jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); if (array == NULL) { return nullObjectReturn("primitive array == null"); } peeker.fPatch->serialize(array); env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); } jintArray layoutBounds = NULL; if (peeker.fLayoutBounds != NULL) { layoutBounds = env->NewIntArray(4); if (layoutBounds == NULL) { return nullObjectReturn("layoutBounds == null"); } jint scaledBounds[4]; if (willScale) { for (int i=0; i<4; i++) { scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f); } } else { memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds)); } env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds); if (javaBitmap != NULL) { env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds); } } if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size const float sx = scaledWidth / float(decoded->width()); const float sy = scaledHeight / float(decoded->height()); SkBitmap::Config config = decoded->config(); switch (config) { case SkBitmap::kNo_Config: case SkBitmap::kIndex8_Config: case SkBitmap::kRLE_Index8_Config: config = SkBitmap::kARGB_8888_Config; break; default: break; } bitmap->setConfig(config, scaledWidth, scaledHeight); bitmap->setIsOpaque(decoded->isOpaque()); if (!bitmap->allocPixels(&javaAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } bitmap->eraseColor(0); SkPaint paint; paint.setFilterBitmap(true); SkCanvas canvas(*bitmap); canvas.scale(sx, sy); canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint); // Save off the unscaled version of bitmap to be used in later // transformations if it would reduce memory pressure. Only do // so if it is being upscaled more than 50%, is bigger than // 256x256, and not too big to be keeping a copy of (<1MB). const int numUnscaledPixels = decoded->width() * decoded->height(); if (sx > 1.5 && numUnscaledPixels > 65536 && numUnscaledPixels < 262144) { bitmap->setUnscaledBitmap(decoded); adb2.detach(); //responsibility for freeing decoded's memory is //transferred to bitmap's destructor } } if (padding) { if (peeker.fPatch != NULL) { GraphicsJNI::set_jrect(env, padding, peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); } else { GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1); } } SkPixelRef* pr; if (isPurgeable) { pr = installPixelRef(bitmap, stream, sampleSize, doDither); } else { // if we get here, we're in kDecodePixels_Mode and will therefore // already have a pixelref installed. pr = bitmap->pixelRef(); } if (pr == NULL) { return nullObjectReturn("Got null SkPixelRef"); } if (!isMutable) { // promise we will never change our pixels (great for sharing and pictures) pr->setImmutable(); } // detach bitmap from its autodeleter, since we want to own it now adb.detach(); if (javaBitmap != NULL) { // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } // now create the java bitmap return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(), isMutable, ninePatchChunk, layoutBounds, -1); }
static inline SkPMColor fetch(const SkBitmap& src, int x, int y) { x = SkClampMax(x, src.width() - 1); y = SkClampMax(y, src.height() - 1); return *src.getAddr32(x, y); }
void ASurface_scaleToFullScreen_skia(ASurface* aSurface, AndroidSurfaceInfo* src, AndroidSurfaceInfo* dst, size_t size) { SkBitmap dstBitmap; SkMatrix matrix; void *pixel = NULL; int swidth = 0; int sheight = 0; int i, width, height, buf_size; width = 0; height = 0; struct jpeg_ext *ext = (struct jpeg_ext *)src->bits; char *real_jpeg = (char *)src->bits + sizeof(struct jpeg_ext); LOGD("```````````````````````````````````"); LOGD("%s: %d\n", __func__, __LINE__); if(ext->fragment_num == 1) { if (ASurface_decode(&srcBitmap, real_jpeg, size, &swidth, &sheight)) { LOGE("decode error\n"); return; } LOGD("swidth = %d, sheight = %d\n", swidth, sheight); } else { SkBitmap sktemp[6]; width = 0; height = 0; for(i = 0; i < ext->fragment_num; i++) { if (ASurface_decode(&sktemp[i], real_jpeg + ext->fragment[i].offset, ext->fragment[i].size, &swidth, &sheight)) { LOGE("decode error\n"); return; } width = sktemp[i].width(); height += sktemp[i].height(); } srcBitmap.setConfig(SkBitmap::kRGB_565_Config, width, height); LOGD("%s: real height = %d width = %d\n", __func__, height, width); buf_size = width * height * 2; pixel = malloc(buf_size); if(!pixel) return; width = height = 0; for(i = 0; i < ext->fragment_num; i++) { void *frag_buf; width = sktemp[i].width(); frag_buf = sktemp[i].getPixels(); memcpy((char *)pixel + (height * width * 2), frag_buf, sktemp[i].getSize()); height += sktemp[i].height(); } srcBitmap.setPixels(pixel); } if(dst->w != dst->s) { void *buf_temp; int i, pixel_size; SkBitmap temp; if(dst->format == ANDROID_PIXEL_FORMAT_RGB_565) pixel_size = 2; else pixel_size = 4; buf_temp = malloc(dst->w * dst->h * pixel_size); if(!buf_temp) return; temp.setConfig(convertPixelFormat(dst->format), dst->w, dst->h); temp.setPixels(buf_temp); matrix.setRectToRect(SkRect::MakeWH(srcBitmap.width(), srcBitmap.height()), SkRect::MakeWH(temp.width(), temp.height()), SkMatrix::kFill_ScaleToFit); for(i = 0; i < (int)dst->h; i++) memcpy((char *)dst->bits + i * dst->s * pixel_size, (char *)buf_temp + i * dst->w * pixel_size, dst->w * pixel_size); dstBitmap.setConfig(convertPixelFormat(dst->format), dst->s, dst->h); dstBitmap.setPixels(dst->bits); aSurface->canvas->setBitmapDevice(dstBitmap); aSurface->canvas->drawBitmapMatrix(srcBitmap, matrix); free(buf_temp); } else { #ifndef SKIA_DECODE srcBitmap.setConfig(convertPixelFormat(ANDROID_PIXEL_FORMAT_RGBA_8888), swidth, sheight); srcBitmap.setPixels(outBuf2); #else #endif initBitmap(dstBitmap, dst); #if 0 /*original*/ LOGD("Jerry catch Tom: SKIA : g_clear_screen_client = %d\n", g_clear_screen_client); matrix.setRectToRect(SkRect::MakeWH(srcBitmap.width(), srcBitmap.height()), SkRect::MakeWH(dstBitmap.width(), dstBitmap.height()), SkMatrix::kFill_ScaleToFit); #else /* clear screen */ //memset(srcBitmap.getPixels(), 0, width * height * 2); LOGD("Hi, Tom, I'm Jerry!\n"); LOGD("dst->w = %d, dst->h = %d", dst->w, dst->h); memset(dst->bits, 0, dst->w * dst->h * 2); if (g_clear_screen_client) { matrix.setRectToRect(SkRect::MakeWH(srcBitmap.width(), srcBitmap.height()), SkRect::MakeWH(dstBitmap.width(), dstBitmap.height()), SkMatrix::kFill_ScaleToFit); aSurface->canvas->setBitmapDevice(dstBitmap); aSurface->canvas->drawBitmapMatrix(srcBitmap, matrix); } else { matrix.setRectToRect(SkRect::MakeWH(srcBitmap.width(), srcBitmap.height()), SkRect::MakeWH(dstBitmap.width(), dstBitmap.height()), SkMatrix::kCenter_ScaleToFit); aSurface->canvas->setBitmapDevice(dstBitmap); aSurface->canvas->drawBitmapMatrix(srcBitmap, matrix); } #endif } if(ext->fragment_num > 1) { free(pixel); srcBitmap.setPixels(NULL); } return; }
// Basic test of the SkSpecialImage public API (e.g., peekTexture, peekPixels & draw) static void test_image(const sk_sp<SkSpecialImage>& img, skiatest::Reporter* reporter, GrContext* context, bool peekTextureSucceeds, int offset, int size) { const SkIRect subset = img->subset(); REPORTER_ASSERT(reporter, offset == subset.left()); REPORTER_ASSERT(reporter, offset == subset.top()); REPORTER_ASSERT(reporter, kSmallerSize == subset.width()); REPORTER_ASSERT(reporter, kSmallerSize == subset.height()); //-------------- // Test that peekTexture reports the correct backing type REPORTER_ASSERT(reporter, peekTextureSucceeds == img->isTextureBacked()); #if SK_SUPPORT_GPU //-------------- // Test getTextureAsRef - as long as there is a context this should succeed if (context) { sk_sp<GrTexture> texture(img->asTextureRef(context)); REPORTER_ASSERT(reporter, texture); } #endif //-------------- // Test getROPixels - this should always succeed regardless of backing store SkBitmap bitmap; REPORTER_ASSERT(reporter, img->getROPixels(&bitmap)); if (context) { REPORTER_ASSERT(reporter, kSmallerSize == bitmap.width()); REPORTER_ASSERT(reporter, kSmallerSize == bitmap.height()); } else { REPORTER_ASSERT(reporter, size == bitmap.width()); REPORTER_ASSERT(reporter, size == bitmap.height()); } //-------------- // Test that draw restricts itself to the subset SkImageInfo info = SkImageInfo::MakeN32(kFullSize, kFullSize, kOpaque_SkAlphaType); sk_sp<SkSpecialSurface> surf(img->makeSurface(info)); SkCanvas* canvas = surf->getCanvas(); canvas->clear(SK_ColorBLUE); img->draw(canvas, SkIntToScalar(kPad), SkIntToScalar(kPad), nullptr); SkBitmap bm; bm.allocN32Pixels(kFullSize, kFullSize, true); bool result = canvas->readPixels(bm.info(), bm.getPixels(), bm.rowBytes(), 0, 0); SkASSERT_RELEASE(result); // Only the center (red) portion should've been drawn into the canvas REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kPad-1, kPad-1)); REPORTER_ASSERT(reporter, SK_ColorRED == bm.getColor(kPad, kPad)); REPORTER_ASSERT(reporter, SK_ColorRED == bm.getColor(kSmallerSize+kPad-1, kSmallerSize+kPad-1)); REPORTER_ASSERT(reporter, SK_ColorBLUE == bm.getColor(kSmallerSize+kPad, kSmallerSize+kPad)); //-------------- // Test that makeTightSubset & makeTightSurface return appropriately sized objects // of the correct backing type SkIRect newSubset = SkIRect::MakeWH(subset.width(), subset.height()); { sk_sp<SkImage> tightImg(img->makeTightSubset(newSubset)); REPORTER_ASSERT(reporter, tightImg->width() == subset.width()); REPORTER_ASSERT(reporter, tightImg->height() == subset.height()); REPORTER_ASSERT(reporter, peekTextureSucceeds == !!tightImg->getTexture()); SkPixmap tmpPixmap; REPORTER_ASSERT(reporter, peekTextureSucceeds != !!tightImg->peekPixels(&tmpPixmap)); } { SkImageInfo info = SkImageInfo::MakeN32(subset.width(), subset.height(), kPremul_SkAlphaType); sk_sp<SkSurface> tightSurf(img->makeTightSurface(info)); REPORTER_ASSERT(reporter, tightSurf->width() == subset.width()); REPORTER_ASSERT(reporter, tightSurf->height() == subset.height()); REPORTER_ASSERT(reporter, peekTextureSucceeds == !!tightSurf->getTextureHandle(SkSurface::kDiscardWrite_BackendHandleAccess)); SkPixmap tmpPixmap; REPORTER_ASSERT(reporter, peekTextureSucceeds != !!tightSurf->peekPixels(&tmpPixmap)); } }
static bool render_picture(const SkString& inputPath, const SkString* outputDir, sk_tools::PictureRenderer& renderer, bool validate, bool writeWholeImage, int clones) { SkBitmap* bitmap = NULL; bool success = render_picture(inputPath, writeWholeImage ? NULL : outputDir, renderer, validate || writeWholeImage ? &bitmap : NULL, clones); if (!success || ((validate || writeWholeImage) && bitmap == NULL)) { SkDebugf("Failed to draw the picture.\n"); SkDELETE(bitmap); return false; } if (validate) { SkBitmap* referenceBitmap = NULL; sk_tools::SimplePictureRenderer referenceRenderer; success = render_picture(inputPath, NULL, referenceRenderer, &referenceBitmap, 0); if (!success || !referenceBitmap) { SkDebugf("Failed to draw the reference picture.\n"); SkDELETE(bitmap); SkDELETE(referenceBitmap); return false; } if (success && (bitmap->width() != referenceBitmap->width())) { SkDebugf("Expected image width: %i, actual image width %i.\n", referenceBitmap->width(), bitmap->width()); SkDELETE(bitmap); SkDELETE(referenceBitmap); return false; } if (success && (bitmap->height() != referenceBitmap->height())) { SkDebugf("Expected image height: %i, actual image height %i", referenceBitmap->height(), bitmap->height()); SkDELETE(bitmap); SkDELETE(referenceBitmap); return false; } for (int y = 0; success && y < bitmap->height(); y++) { for (int x = 0; success && x < bitmap->width(); x++) { if (*referenceBitmap->getAddr32(x, y) != *bitmap->getAddr32(x, y)) { SkDebugf("Expected pixel at (%i %i): 0x%x, actual 0x%x\n", x, y, *referenceBitmap->getAddr32(x, y), *bitmap->getAddr32(x, y)); #ifdef VALIDATE_FAILURE_IS_A_TOOL_FAILURE SkDELETE(bitmap); SkDELETE(referenceBitmap); return false; #else goto DONE; #endif } } } DONE: SkDELETE(referenceBitmap); } if (writeWholeImage) { sk_tools::force_all_opaque(*bitmap); if (NULL != outputDir && writeWholeImage) { SkString inputFilename; sk_tools::get_basename(&inputFilename, inputPath); SkString outputPath; make_output_filepath(&outputPath, *outputDir, inputFilename); outputPath.append(".png"); if (!SkImageEncoder::EncodeFile(outputPath.c_str(), *bitmap, SkImageEncoder::kPNG_Type, 100)) { SkDebugf("Failed to draw the picture.\n"); success = false; } } } SkDELETE(bitmap); return success; }
CanvasLayer::CanvasLayer(const CanvasLayer& layer) : LayerAndroid(layer) , m_canvas(0) , m_bitmap(0) , m_gpuCanvas(0) { init(); if (!layer.m_canvas) { // The canvas has already been destroyed - this shouldn't happen ALOGW("Creating a CanvasLayer for a destroyed canvas!"); m_visibleContentRect = IntRect(); m_offsetFromRenderer = IntSize(); m_texture->setHwAccelerated(false); return; } // We are making a copy for the UI, sync the interesting bits m_visibleContentRect = layer.visibleContentRect(); m_offsetFromRenderer = layer.offsetFromRenderer(); bool previousState = m_texture->hasValidTexture(); if(layer.m_canvas->isUsingGpuRendering()) return; ImageBuffer* imageBuffer = layer.m_canvas->buffer(); if (!previousState && layer.m_dirtyCanvas.isEmpty() && imageBuffer && !(imageBuffer->drawsUsingRecording())) { // We were previously in software and don't have anything new to draw, // so stay in software m_bitmap = layer.bitmap(); SkSafeRef(m_bitmap); } else { if(imageBuffer && imageBuffer->drawsUsingRecording() && !layer.m_canvas->isUsingGpuRendering()) { bool canUseGpuRendering = imageBuffer->canUseGpuRendering(); if(canUseGpuRendering && layer.m_canvas->canUseGpuRendering()) { layer.m_canvas->enableGpuRendering(); CanvasLayer::setGpuCanvasStatus(layer.uniqueId(), true); } } // If recording is being used if(imageBuffer && imageBuffer->drawsUsingRecording()) { GraphicsContext* gc = imageBuffer->context(); //SkPicture* canvasRecording = gc->platformContext()->getRecordingPicture(); SkPicture* canvasRecording = CanvasLayer::getRecordingPicture(this); SkBitmap* bitmap = CanvasLayer::getRecordingBitmap(this); SkCanvas* canvas = CanvasLayer::getRecordingCanvas(this); if(canvasRecording == NULL) return; if(bitmap == NULL || bitmap->width() != canvasRecording->width() || bitmap->height() != canvasRecording->height()) { SkBitmap* newBitmap = new SkBitmap(); newBitmap->setConfig(SkBitmap::kARGB_8888_Config, canvasRecording->width(), canvasRecording->height()); newBitmap->allocPixels(); newBitmap->eraseColor(0); CanvasLayer::setRecordingBitmap(newBitmap, this); bitmap = newBitmap; if(canvas != NULL) canvas->setBitmapDevice(*bitmap); } if(canvas == NULL) { canvas = new SkCanvas(); canvas->setBitmapDevice(*bitmap); CanvasLayer::setRecordingCanvas(canvas, this); } canvas->drawARGB(0, 0, 0, 0, SkXfermode::kClear_Mode); canvasRecording->draw(canvas); if (!m_texture->uploadImageBitmap(bitmap)) { //SLOGD("+++++++++++++++++++++ Didn't upload bitmap .. fall back to software"); // TODO:: Fix this } } else { if (!m_texture->uploadImageBuffer(layer.m_canvas->buffer())) { // Blargh, no surface texture or ImageBuffer - fall back to software m_bitmap = layer.bitmap(); SkSafeRef(m_bitmap); // Merge the canvas invals with the layer's invals to repaint the needed // tiles. SkRegion::Iterator iter(layer.m_dirtyCanvas); const IntPoint& offset = m_visibleContentRect.location(); for (; !iter.done(); iter.next()) { SkIRect diff = iter.rect(); diff.fLeft += offset.x(); diff.fRight += offset.x(); diff.fTop += offset.y(); diff.fBottom += offset.y(); m_dirtyRegion.op(diff, SkRegion::kUnion_Op); } }else{ ImageBuffer* imageBuffer = layer.m_canvas->buffer(); bool recordingCanvasEnabled = layer.m_canvas->isRecordingCanvasEnabled(); if(recordingCanvasEnabled && imageBuffer && imageBuffer->isAnimating()){ SLOGD("[%s] Animation detected. Converting the HTML5 canvas buffer to a SkPicture.", __FUNCTION__); imageBuffer->convertToRecording(); } }//End of non-recording } if (previousState != m_texture->hasValidTexture()) { // Need to do a full inval of the canvas content as we are mode switching m_dirtyRegion.op(m_visibleContentRect.x(), m_visibleContentRect.y(), m_visibleContentRect.maxX(), m_visibleContentRect.maxY(), SkRegion::kUnion_Op); } } }
static bool dump_png(SkBitmap bitmap, const char* path, const char* md5) { const int w = bitmap.width(), h = bitmap.height(); sk_sp<SkData> encodedBitmap = sk_tools::encode_bitmap_for_png(bitmap); if (encodedBitmap.get() == nullptr) { return false; } uint32_t* rgba = static_cast<uint32_t*>(encodedBitmap.get()->writable_data()); // We don't need bitmap anymore. Might as well drop our ref. bitmap.reset(); FILE* f = fopen(path, "wb"); if (!f) { return false; } png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png) { fclose(f); return false; } png_infop info = png_create_info_struct(png); if (!info) { png_destroy_write_struct(&png, &info); fclose(f); return false; } SkString description; description.append("Key: "); for (int i = 0; i < FLAGS_key.count(); i++) { description.appendf("%s ", FLAGS_key[i]); } description.append("Properties: "); for (int i = 0; i < FLAGS_properties.count(); i++) { description.appendf("%s ", FLAGS_properties[i]); } description.appendf("MD5: %s", md5); png_text text[2]; text[0].key = (png_charp)"Author"; text[0].text = (png_charp)"DM dump_png()"; text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[1].key = (png_charp)"Description"; text[1].text = (png_charp)description.c_str(); text[1].compression = PNG_TEXT_COMPRESSION_NONE; png_set_text(png, info, text, 2); png_init_io(png, f); png_set_IHDR(png, info, (png_uint_32)w, (png_uint_32)h, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_write_info(png, info); for (int j = 0; j < h; j++) { png_bytep row = (png_bytep)(rgba + w*j); png_write_rows(png, &row, 1); } png_write_end(png, info); png_destroy_write_struct(&png, &info); fclose(f); return true; }
static void erodeY(const SkBitmap& src, SkBitmap* dst, int radiusY) { erode(src.getAddr32(0, 0), dst->getAddr32(0, 0), radiusY, src.height(), src.width(), src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels(), 1); }
/* * nine patch not supported * * purgeable not supported * reportSizeToVM not supported */ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint start_x, jint start_y, jint width, jint height, jobject options) { SkBitmapRegionDecoder *brd = reinterpret_cast<SkBitmapRegionDecoder*>(brdHandle); jobject tileBitmap = NULL; SkImageDecoder *decoder = brd->getDecoder(); int sampleSize = 1; SkColorType prefColorType = kUnknown_SkColorType; bool doDither = true; bool preferQualityOverSpeed = false; bool requireUnpremultiplied = false; if (NULL != options) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); env->SetObjectField(options, gOptions_mimeFieldID, 0); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); doDither = env->GetBooleanField(options, gOptions_ditherFieldID); preferQualityOverSpeed = env->GetBooleanField(options, 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); decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID");; } SkIRect region; region.fLeft = start_x; region.fTop = start_y; region.fRight = start_x + width; region.fBottom = start_y + height; SkBitmap* bitmap = NULL; SkAutoTDelete<SkBitmap> adb; if (tileBitmap != NULL) { // Re-use bitmap. bitmap = GraphicsJNI::getNativeBitmap(env, tileBitmap); } if (bitmap == NULL) { bitmap = new SkBitmap; adb.reset(bitmap); } if (!brd->decodeRegion(bitmap, region, prefColorType, sampleSize)) { return nullObjectReturn("decoder->decodeRegion returned false"); } // update options (if any) if (NULL != options) { env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); } if (tileBitmap != NULL) { return tileBitmap; } // detach bitmap from its autodeleter, since we want to own it now adb.detach(); JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); jbyteArray buff = allocator->getStorageObjAndReset(); int bitmapCreateFlags = 0; if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; return GraphicsJNI::createBitmap(env, bitmap, buff, bitmapCreateFlags, NULL, NULL, -1); }
bool encodeSkBitmapToPNG(const SkBitmap& image, Vector<unsigned char>* output) { if (image.config() != SkBitmap::kARGB_8888_Config) return false; // Only support ARGB at 8 bpp now. image.lockPixels(); bool result = encodeImpl(static_cast<unsigned char*>(image.getPixels()), image.width(), image.height(), image.rowBytes(), output, preMultipliedBGRAtoRGBA); image.unlockPixels(); return result; }
// since we "may" create a purgeable imageref, we require the stream be ref'able // i.e. dynamically allocated, since its lifetime may exceed the current stack // frame. static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options, bool allowPurgeable, bool forcePurgeable = false) { int sampleSize = 1; SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config; bool doDither = true; bool isMutable = false; float scale = 1.0f; bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)); bool preferQualityOverSpeed = false; bool requireUnpremultiplied = false; jobject javaBitmap = NULL; if (options != NULL) { sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); if (optionsJustBounds(env, options)) { mode = SkImageDecoder::kDecodeBounds_Mode; } // initialize these, in case we fail later on env->SetIntField(options, gOptions_widthFieldID, -1); env->SetIntField(options, gOptions_heightFieldID, -1); env->SetObjectField(options, gOptions_mimeFieldID, 0); jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); 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)) { const int density = env->GetIntField(options, gOptions_densityFieldID); const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { scale = (float) targetDensity / density; } } } const bool willScale = scale != 1.0f; isPurgeable &= !willScale; SkImageDecoder* decoder = SkImageDecoder::Factory(stream); if (decoder == NULL) { return nullObjectReturn("SkImageDecoder::Factory returned null"); } decoder->setSampleSize(sampleSize); decoder->setDitherImage(doDither); decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); SkBitmap* outputBitmap = NULL; unsigned int existingBufferSize = 0; if (javaBitmap != NULL) { outputBitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID); if (outputBitmap->isImmutable()) { ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; outputBitmap = NULL; } else { existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); } } SkAutoTDelete<SkBitmap> adb(outputBitmap == NULL ? new SkBitmap : NULL); if (outputBitmap == NULL) outputBitmap = adb.get(); NinePatchPeeker peeker(decoder); decoder->setPeeker(&peeker); SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode; JavaPixelAllocator javaAllocator(env); RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize); ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ? (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator; if (decodeMode != SkImageDecoder::kDecodeBounds_Mode) { if (!willScale) { // If the java allocator is being used to allocate the pixel memory, the decoder // need not write zeroes, since the memory is initialized to 0. decoder->setSkipWritingZeroes(outputAllocator == &javaAllocator); decoder->setAllocator(outputAllocator); } else if (javaBitmap != NULL) { // check for eventual scaled bounds at allocation time, so we don't decode the bitmap // only to find the scaled result too large to fit in the allocation decoder->setAllocator(&scaleCheckingAllocator); } } // Only setup the decoder to be deleted after its stack-based, refcounted // components (allocators, peekers, etc) are declared. This prevents RefCnt // asserts from firing due to the order objects are deleted from the stack. SkAutoTDelete<SkImageDecoder> add(decoder); AutoDecoderCancel adc(options, decoder); // To fix the race condition in case "requestCancelDecode" // happens earlier than AutoDecoderCancel object is added // to the gAutoDecoderCancelMutex linked list. if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) { return nullObjectReturn("gOptions_mCancelID"); } SkBitmap decodingBitmap; if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) { return nullObjectReturn("decoder->decode returned false"); } int scaledWidth = decodingBitmap.width(); int scaledHeight = decodingBitmap.height(); if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); } // update options (if any) if (options != NULL) { env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); env->SetObjectField(options, gOptions_mimeFieldID, getMimeTypeString(env, decoder->getFormat())); } // if we're in justBounds mode, return now (skip the java bitmap) if (mode == SkImageDecoder::kDecodeBounds_Mode) { return NULL; } jbyteArray ninePatchChunk = NULL; if (peeker.fPatch != NULL) { if (willScale) { scaleNinePatchChunk(peeker.fPatch, scale); } size_t ninePatchArraySize = peeker.fPatch->serializedSize(); ninePatchChunk = env->NewByteArray(ninePatchArraySize); if (ninePatchChunk == NULL) { return nullObjectReturn("ninePatchChunk == null"); } jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); if (array == NULL) { return nullObjectReturn("primitive array == null"); } peeker.fPatch->serialize(array); env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); } jintArray layoutBounds = NULL; if (peeker.fLayoutBounds != NULL) { layoutBounds = env->NewIntArray(4); if (layoutBounds == NULL) { return nullObjectReturn("layoutBounds == null"); } jint scaledBounds[4]; if (willScale) { for (int i=0; i<4; i++) { scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f); } } else { memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds)); } env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds); if (javaBitmap != NULL) { env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds); } } if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size const float sx = scaledWidth / float(decodingBitmap.width()); const float sy = scaledHeight / float(decodingBitmap.height()); // TODO: avoid copying when scaled size equals decodingBitmap size SkBitmap::Config config = configForScaledOutput(decodingBitmap.config()); outputBitmap->setConfig(config, scaledWidth, scaledHeight); outputBitmap->setIsOpaque(decodingBitmap.isOpaque()); if (!outputBitmap->allocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } // If outputBitmap's pixels are newly allocated by Java, there is no need // to erase to 0, since the pixels were initialized to 0. if (outputAllocator != &javaAllocator) { outputBitmap->eraseColor(0); } SkPaint paint; paint.setFilterBitmap(true); SkCanvas canvas(*outputBitmap); canvas.scale(sx, sy); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); } else { outputBitmap->swap(decodingBitmap); } if (padding) { if (peeker.fPatch != NULL) { GraphicsJNI::set_jrect(env, padding, peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); } else { GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1); } } SkPixelRef* pr; if (isPurgeable) { pr = installPixelRef(outputBitmap, stream, sampleSize, doDither); } else { // if we get here, we're in kDecodePixels_Mode and will therefore // already have a pixelref installed. pr = outputBitmap->pixelRef(); } if (pr == NULL) { return nullObjectReturn("Got null SkPixelRef"); } if (!isMutable && javaBitmap == NULL) { // promise we will never change our pixels (great for sharing and pictures) pr->setImmutable(); } // detach bitmap from its autodeleter, since we want to own it now adb.detach(); if (javaBitmap != NULL) { 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(), bitmapCreateFlags, ninePatchChunk, layoutBounds, -1); }
static SkSize computeSize(const SkBitmap& bm, const SkMatrix& mat) { SkRect bounds = SkRect::MakeWH(SkIntToScalar(bm.width()), SkIntToScalar(bm.height())); mat.mapRect(&bounds); return SkSize::Make(bounds.width(), bounds.height()); }
void onDraw(SkCanvas* canvas) override { static const char kText[] = "SKIA"; static const int kTextLen = SK_ARRAY_COUNT(kText) - 1; static const int kPointSize = 55; SkTDArray<LabeledMatrix> matrices; matrices.append()->fMatrix.reset(); matrices.top().fLabel = "Identity"; matrices.append()->fMatrix.setScale(1.2f, 0.8f); matrices.top().fLabel = "Scale"; matrices.append()->fMatrix.setRotate(10.f); matrices.top().fLabel = "Rotate"; matrices.append()->fMatrix.reset(); matrices.top().fMatrix.setPerspX(-0.0015f); matrices.top().fMatrix.setPerspY(+0.0015f); matrices.top().fLabel = "Persp"; SkTDArray<LabeledMatrix> localMatrices; localMatrices.append()->fMatrix.reset(); localMatrices.top().fLabel = "Identity"; localMatrices.append()->fMatrix.setScale(2.5f, 0.2f); localMatrices.top().fLabel = "Scale"; localMatrices.append()->fMatrix.setRotate(45.f); localMatrices.top().fLabel = "Rotate"; localMatrices.append()->fMatrix.reset(); localMatrices.top().fMatrix.setPerspX(-0.007f); localMatrices.top().fMatrix.setPerspY(+0.008f); localMatrices.top().fLabel = "Persp"; static SkBitmap bmp; if (bmp.isNull()) { makebm(&bmp, kPointSize / 2, kPointSize / 2); } SkPaint fillPaint; fillPaint.setAntiAlias(true); sk_tool_utils::set_portable_typeface_always(&fillPaint); fillPaint.setTextSize(SkIntToScalar(kPointSize)); fillPaint.setFilterQuality(kLow_SkFilterQuality); SkPaint outlinePaint; outlinePaint.setAntiAlias(true); sk_tool_utils::set_portable_typeface_always(&outlinePaint); outlinePaint.setTextSize(SkIntToScalar(kPointSize)); outlinePaint.setStyle(SkPaint::kStroke_Style); outlinePaint.setStrokeWidth(0.f); SkScalar w = fillPaint.measureText(kText, kTextLen); static SkScalar kPadY = 0.5f * kPointSize; static SkScalar kPadX = 1.5f * kPointSize; SkPaint strokePaint(fillPaint); strokePaint.setStyle(SkPaint::kStroke_Style); strokePaint.setStrokeWidth(kPointSize * 0.1f); SkPaint labelPaint; labelPaint.setColor(0xff000000); labelPaint.setAntiAlias(true); sk_tool_utils::set_portable_typeface_always(&labelPaint); labelPaint.setTextSize(12.f); canvas->translate(15.f, 15.f); canvas->drawBitmap(bmp, 0, 0); canvas->translate(0, bmp.height() + labelPaint.getTextSize() + 15.f); static const char kLabelLabel[] = "localM / canvasM"; canvas->drawText(kLabelLabel, strlen(kLabelLabel), 0, 0, labelPaint); canvas->translate(0, 15.f); canvas->save(); SkScalar maxLabelW = 0; canvas->translate(0, kPadY / 2 + kPointSize); for (int lm = 0; lm < localMatrices.count(); ++lm) { canvas->drawText(matrices[lm].fLabel, strlen(matrices[lm].fLabel), 0, labelPaint.getTextSize() - 1, labelPaint); SkScalar labelW = labelPaint.measureText(matrices[lm].fLabel, strlen(matrices[lm].fLabel)); maxLabelW = SkMaxScalar(maxLabelW, labelW); canvas->translate(0.f, 2 * kPointSize + 2.5f * kPadY); } canvas->restore(); canvas->translate(maxLabelW + kPadX / 2.f, 0.f); for (int s = 0; s < 2; ++s) { SkPaint& paint = s ? strokePaint : fillPaint; SkScalar columnH = 0; for (int m = 0; m < matrices.count(); ++m) { columnH = 0; canvas->save(); canvas->drawText(matrices[m].fLabel, strlen(matrices[m].fLabel), 0, labelPaint.getTextSize() - 1, labelPaint); canvas->translate(0, kPadY / 2 + kPointSize); columnH += kPadY / 2 + kPointSize; for (int lm = 0; lm < localMatrices.count(); ++lm) { paint.setShader( SkShader::CreateBitmapShader(bmp, SkShader::kMirror_TileMode, SkShader::kRepeat_TileMode, &localMatrices[lm].fMatrix))->unref(); canvas->save(); canvas->concat(matrices[m].fMatrix); canvas->drawText(kText, kTextLen, 0, 0, paint); canvas->drawText(kText, kTextLen, 0, 0, outlinePaint); canvas->restore(); SkPath path; path.arcTo(SkRect::MakeXYWH(-0.1f * w, 0.f, 1.2f * w, 2.f * kPointSize), 225.f, 359.f, false); path.close(); canvas->translate(0.f, kPointSize + kPadY); columnH += kPointSize + kPadY; canvas->save(); canvas->concat(matrices[m].fMatrix); canvas->drawTextOnPath(kText, kTextLen, path, NULL, paint); canvas->drawTextOnPath(kText, kTextLen, path, NULL, outlinePaint); canvas->restore(); SkPaint stroke; stroke.setStyle(SkPaint::kStroke_Style); canvas->translate(0.f, kPointSize + kPadY); columnH += kPointSize + kPadY; } canvas->restore(); canvas->translate(w + kPadX, 0.f); } if (0 == s) { canvas->drawLine(0.f, -kPadY, 0.f, columnH + kPadY, outlinePaint); canvas->translate(kPadX / 2, 0.f); static const char kFillLabel[] = "Filled"; static const char kStrokeLabel[] = "Stroked"; SkScalar y = columnH + kPadY / 2; SkScalar fillX = -outlinePaint.measureText(kFillLabel, strlen(kFillLabel)) - kPadX; SkScalar strokeX = kPadX; canvas->drawText(kFillLabel, strlen(kFillLabel), fillX, y, labelPaint); canvas->drawText(kStrokeLabel, strlen(kStrokeLabel), strokeX, y, labelPaint); } } }
SkImage_Raster(const SkBitmap& bm, const SkSurfaceProps* props) : INHERITED(bm.width(), bm.height(), props) , fBitmap(bm) {}
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets, const char* name) { Asset* asset = assets.open(name, Asset::ACCESS_BUFFER); if (!asset) return NO_INIT; SkBitmap bitmap; #ifdef SLIDE_HORIZONTAL SkBitmap origbitmap; SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(), &origbitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodePixels_Mode); #else SkImageDecoder::DecodeMemory(asset->getBuffer(false), asset->getLength(), &bitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodePixels_Mode); #endif asset->close(); delete asset; #ifdef SLIDE_HORIZONTAL // create a bitmap with rotated dimensions and draw the rotated original bitmap bitmap.setConfig(origbitmap.config(), origbitmap.height(), origbitmap.width()); bitmap.allocPixels(); SkCanvas canvas(bitmap); canvas.translate(SkIntToScalar(bitmap.width()), 0); canvas.rotate(SkIntToScalar(90)); canvas.drawBitmap(origbitmap, 0, 0, NULL); #endif // ensure we can call getPixels(). No need to call unlock, since the // bitmap will go out of scope when we return from this method. bitmap.lockPixels(); const int w = bitmap.width(); const int h = bitmap.height(); const void* p = bitmap.getPixels(); GLint crop[4] = { 0, h, w, -h }; texture->w = w; texture->h = h; glGenTextures(1, &texture->name); glBindTexture(GL_TEXTURE_2D, texture->name); switch (bitmap.getConfig()) { case SkBitmap::kA8_Config: glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, p); break; case SkBitmap::kARGB_4444_Config: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, p); break; case SkBitmap::kARGB_8888_Config: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, p); break; case SkBitmap::kRGB_565_Config: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); break; default: break; } glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); return NO_ERROR; }
void AllocatePixels(const SkBitmap& reference, SkBitmap* bitmap) { AllocatePixels(reference.colorType(), reference.width(), reference.height(), bitmap); }
status_t BootAnimation::initTexture(void* buffer, size_t len) { //StopWatch watch("blah"); SkBitmap bitmap; SkMemoryStream stream(buffer, len); SkImageDecoder* codec = SkImageDecoder::Factory(&stream); codec->setDitherImage(false); if (codec) { codec->decode(&stream, &bitmap, SkBitmap::kRGB_565_Config, SkImageDecoder::kDecodePixels_Mode); delete codec; } // ensure we can call getPixels(). No need to call unlock, since the // bitmap will go out of scope when we return from this method. bitmap.lockPixels(); const int w = bitmap.width(); const int h = bitmap.height(); const void* p = bitmap.getPixels(); GLint crop[4] = { 0, h, w, -h }; int tw = 1 << (31 - __builtin_clz(w)); int th = 1 << (31 - __builtin_clz(h)); if (tw < w) tw <<= 1; if (th < h) th <<= 1; switch (bitmap.getConfig()) { case SkBitmap::kARGB_8888_Config: if (tw != w || th != h) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, p); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tw, th, 0, GL_RGBA, GL_UNSIGNED_BYTE, p); } break; case SkBitmap::kRGB_565_Config: if (tw != w || th != h) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tw, th, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, p); } break; default: break; } glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); return NO_ERROR; }
void SkBitmapDevice::drawBitmapRect(const SkDraw& draw, const SkBitmap& bitmap, const SkRect* src, const SkRect& dst, const SkPaint& paint, SkCanvas::DrawBitmapRectFlags flags) { SkMatrix matrix; SkRect bitmapBounds, tmpSrc, tmpDst; SkBitmap tmpBitmap; bitmapBounds.isetWH(bitmap.width(), bitmap.height()); // Compute matrix from the two rectangles if (src) { tmpSrc = *src; } else { tmpSrc = bitmapBounds; } matrix.setRectToRect(tmpSrc, dst, SkMatrix::kFill_ScaleToFit); const SkRect* dstPtr = &dst; const SkBitmap* bitmapPtr = &bitmap; // clip the tmpSrc to the bounds of the bitmap, and recompute dstRect if // needed (if the src was clipped). No check needed if src==null. if (src) { if (!bitmapBounds.contains(*src)) { if (!tmpSrc.intersect(bitmapBounds)) { return; // nothing to draw } // recompute dst, based on the smaller tmpSrc matrix.mapRect(&tmpDst, tmpSrc); dstPtr = &tmpDst; } // since we may need to clamp to the borders of the src rect within // the bitmap, we extract a subset. SkIRect srcIR; tmpSrc.roundOut(&srcIR); if (!bitmap.extractSubset(&tmpBitmap, srcIR)) { return; } bitmapPtr = &tmpBitmap; // Since we did an extract, we need to adjust the matrix accordingly SkScalar dx = 0, dy = 0; if (srcIR.fLeft > 0) { dx = SkIntToScalar(srcIR.fLeft); } if (srcIR.fTop > 0) { dy = SkIntToScalar(srcIR.fTop); } if (dx || dy) { matrix.preTranslate(dx, dy); } SkRect extractedBitmapBounds; extractedBitmapBounds.isetWH(bitmapPtr->width(), bitmapPtr->height()); if (extractedBitmapBounds == tmpSrc) { // no fractional part in src, we can just call drawBitmap goto USE_DRAWBITMAP; } } else { USE_DRAWBITMAP: // We can go faster by just calling drawBitmap, which will concat the // matrix with the CTM, and try to call drawSprite if it can. If not, // it will make a shader and call drawRect, as we do below. this->drawBitmap(draw, *bitmapPtr, matrix, paint); return; } // construct a shader, so we can call drawRect with the dst SkShader* s = SkShader::CreateBitmapShader(*bitmapPtr, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode); if (NULL == s) { return; } s->setLocalMatrix(matrix); SkPaint paintWithShader(paint); paintWithShader.setStyle(SkPaint::kFill_Style); paintWithShader.setShader(s)->unref(); // Call ourself, in case the subclass wanted to share this setup code // but handle the drawRect code themselves. this->drawRect(draw, *dstPtr, paintWithShader); }
Bitmap_GrTextureMaker(const SkBitmap& bitmap) : INHERITED(bitmap.width(), bitmap.height()) , fBitmap(bitmap) {}
/** * First, make sure that writing an 8-bit RGBA KTX file and then * reading it produces the same bitmap. */ DEF_TEST(KtxReadWrite, reporter) { // Random number generator with explicit seed for reproducibility SkRandom rand(0x1005cbad); SkBitmap bm8888; bm8888.allocN32Pixels(128, 128); uint8_t *pixels = reinterpret_cast<uint8_t*>(bm8888.getPixels()); REPORTER_ASSERT(reporter, pixels); if (nullptr == pixels) { return; } uint8_t *row = pixels; for (int y = 0; y < bm8888.height(); ++y) { for (int x = 0; x < bm8888.width(); ++x) { uint8_t a = rand.nextRangeU(0, 255); uint8_t r = rand.nextRangeU(0, 255); uint8_t g = rand.nextRangeU(0, 255); uint8_t b = rand.nextRangeU(0, 255); SkPMColor &pixel = *(reinterpret_cast<SkPMColor*>(row + x*sizeof(SkPMColor))); pixel = SkPreMultiplyARGB(a, r, g, b); } row += bm8888.rowBytes(); } REPORTER_ASSERT(reporter, !(bm8888.empty())); SkAutoDataUnref encodedData(SkImageEncoder::EncodeData(bm8888, SkImageEncoder::kKTX_Type, 0)); if (nullptr == encodedData.get()) { ERRORF(reporter, "failed to encode the bitmap to KTX"); return; } SkAutoTDelete<SkMemoryStream> stream(new SkMemoryStream(encodedData)); REPORTER_ASSERT(reporter, stream); SkBitmap decodedBitmap; bool imageDecodeSuccess = SkImageDecoder::DecodeStream(stream, &decodedBitmap); if (!imageDecodeSuccess) { ERRORF(reporter, "failed to decode the KTX stream"); return; } REPORTER_ASSERT(reporter, decodedBitmap.colorType() == bm8888.colorType()); REPORTER_ASSERT(reporter, decodedBitmap.alphaType() == bm8888.alphaType()); REPORTER_ASSERT(reporter, decodedBitmap.width() == bm8888.width()); REPORTER_ASSERT(reporter, decodedBitmap.height() == bm8888.height()); REPORTER_ASSERT(reporter, !(decodedBitmap.empty())); uint8_t *decodedPixels = reinterpret_cast<uint8_t*>(decodedBitmap.getPixels()); REPORTER_ASSERT(reporter, decodedPixels); REPORTER_ASSERT(reporter, decodedBitmap.getSize() == bm8888.getSize()); if (nullptr == decodedPixels) { return; } REPORTER_ASSERT(reporter, memcmp(decodedPixels, pixels, decodedBitmap.getSize()) == 0); }
static void dilateX(const SkBitmap& src, SkBitmap* dst, int radiusX) { dilate(src.getAddr32(0, 0), dst->getAddr32(0, 0), radiusX, src.width(), src.height(), 1, src.rowBytesAsPixels(), 1, dst->rowBytesAsPixels()); }
void SkBitmapDevice::replaceBitmapBackendForRasterSurface(const SkBitmap& bm) { SkASSERT(bm.width() == fBitmap.width()); SkASSERT(bm.height() == fBitmap.height()); fBitmap = bm; // intent is to use bm's pixelRef (and rowbytes/config) fBitmap.lockPixels(); }
// Using known images, test that decoding into unpremul and premul behave as expected. DEF_TEST(ImageDecoding_unpremul, reporter) { SkString resourcePath = GetResourcePath(); if (resourcePath.isEmpty()) { SkDebugf("Could not run unpremul test because resourcePath not specified."); return; } const char* root = "half-transparent-white-pixel"; const char* suffixes[] = { ".png", ".webp" }; for (size_t i = 0; i < SK_ARRAY_COUNT(suffixes); ++i) { SkString basename = SkStringPrintf("%s%s", root, suffixes[i]); SkString fullName = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str()); SkBitmap bm; SkFILEStream stream(fullName.c_str()); if (!stream.isValid()) { SkDebugf("file %s missing from resource directoy %s\n", basename.c_str(), resourcePath.c_str()); continue; } // This should never fail since we know the images we're decoding. SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream)); REPORTER_ASSERT(reporter, NULL != decoder.get()); if (NULL == decoder.get()) { continue; } // Test unpremultiplied. We know what color this should result in. decoder->setRequireUnpremultipliedColors(true); bool success = decoder->decode(&stream, &bm, kN32_SkColorType, SkImageDecoder::kDecodePixels_Mode); REPORTER_ASSERT(reporter, success); if (!success) { continue; } REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1); { SkAutoLockPixels alp(bm); REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7fffffff); } success = stream.rewind(); REPORTER_ASSERT(reporter, success); if (!success) { continue; } // Test premultiplied. Once again, we know which color this should // result in. decoder->setRequireUnpremultipliedColors(false); success = decoder->decode(&stream, &bm, kN32_SkColorType, SkImageDecoder::kDecodePixels_Mode); REPORTER_ASSERT(reporter, success); if (!success) { continue; } REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1); { SkAutoLockPixels alp(bm); REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7f7f7f7f); } } }