static CGDataProviderRef SkStreamToDataProvider(SkStream* stream) { // TODO: use callbacks, so we don't have to load all the data into RAM SkAutoMalloc storage; const size_t len = CopyStreamToStorage(&storage, stream); void* data = storage.detach(); return CGDataProviderCreateWithData(data, data, len, malloc_release_proc); }
GrGLvoid* GR_GL_FUNCTION_TYPE nullGLMapBuffer(GrGLenum target, GrGLenum access) { // We just reserve 32MB of RAM for all locks and hope its big enough static SkAutoMalloc gBufferData(32 * (1 << 20)); GrGLuint buf = 0; switch (target) { case GR_GL_ARRAY_BUFFER: buf = gCurrArrayBuffer; break; case GR_GL_ELEMENT_ARRAY_BUFFER: buf = gCurrElementArrayBuffer; break; } if (buf) { *gMappedBuffers.append() = buf; } return gBufferData.get(); }
SkImageDecoder::Result SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { // First read the entire stream, so that all of the data can be passed to // the BmpDecoderHelper. // Allocated space used to hold the data. SkAutoMalloc storage; // Byte length of all of the data. const size_t length = SkCopyStreamToStorage(&storage, stream); if (0 == length) { return kFailure; } const bool justBounds = SkImageDecoder::kDecodeBounds_Mode == mode; SkBmpDecoderCallback callback(justBounds); // Now decode the BMP into callback's rgb() array [r,g,b, r,g,b, ...] { image_codec::BmpDecoderHelper helper; const int max_pixels = 16383*16383; // max width*height if (!helper.DecodeImage((const char*)storage.get(), length, max_pixels, &callback)) { return kFailure; } } // we don't need this anymore, so free it now (before we try to allocate // the bitmap's pixels) rather than waiting for its destructor storage.free(); int width = callback.width(); int height = callback.height(); SkColorType colorType = this->getPrefColorType(k32Bit_SrcDepth, false); // only accept prefConfig if it makes sense for us if (kARGB_4444_SkColorType != colorType && kRGB_565_SkColorType != colorType) { colorType = kN32_SkColorType; } SkScaledBitmapSampler sampler(width, height, getSampleSize()); bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(), colorType, kOpaque_SkAlphaType)); if (justBounds) { return kSuccess; } if (!this->allocPixelRef(bm, NULL)) { return kFailure; } SkAutoLockPixels alp(*bm); if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) { return kFailure; } const int srcRowBytes = width * 3; const int dstHeight = sampler.scaledHeight(); const uint8_t* srcRow = callback.rgb(); srcRow += sampler.srcY0() * srcRowBytes; for (int y = 0; y < dstHeight; y++) { sampler.next(srcRow); srcRow += sampler.srcDY() * srcRowBytes; } return kSuccess; }
bool SkJPEGImageDecoder::onDecodeRegion(SkBitmap* bm, SkIRect region) { if (index == NULL) { return false; } int startX = region.fLeft; int startY = region.fTop; int width = region.width(); int height = region.height(); jpeg_decompress_struct *cinfo = index->cinfo; SkAutoMalloc srcStorage; skjpeg_error_mgr sk_err; cinfo->err = jpeg_std_error(&sk_err); sk_err.error_exit = skjpeg_error_exit; if (setjmp(sk_err.fJmpBuf)) { return false; } int requestedSampleSize = this->getSampleSize(); cinfo->scale_denom = requestedSampleSize; if (this->getPreferQualityOverSpeed()) { cinfo->dct_method = JDCT_ISLOW; } else { cinfo->dct_method = JDCT_IFAST; } SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false); if (config != SkBitmap::kARGB_8888_Config && config != SkBitmap::kARGB_4444_Config && config != SkBitmap::kRGB_565_Config) { config = SkBitmap::kARGB_8888_Config; } /* default format is RGB */ cinfo->out_color_space = JCS_RGB; #ifdef ANDROID_RGB cinfo->dither_mode = JDITHER_NONE; if (config == SkBitmap::kARGB_8888_Config) { cinfo->out_color_space = JCS_RGBA_8888; } else if (config == SkBitmap::kRGB_565_Config) { cinfo->out_color_space = JCS_RGB_565; if (this->getDitherImage()) { cinfo->dither_mode = JDITHER_ORDERED; } } #endif int oriStartX = startX; int oriStartY = startY; int oriWidth = width; int oriHeight = height; jpeg_init_read_tile_scanline(cinfo, index->index, &startX, &startY, &width, &height); int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo); int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size); SkBitmap *bitmap = new SkBitmap; SkAutoTDelete<SkBitmap> adb(bitmap); #ifdef ANDROID_RGB /* short-circuit the SkScaledBitmapSampler when possible, as this gives a significant performance boost. */ if (skiaSampleSize == 1 && ((config == SkBitmap::kARGB_8888_Config && cinfo->out_color_space == JCS_RGBA_8888) || (config == SkBitmap::kRGB_565_Config && cinfo->out_color_space == JCS_RGB_565))) { bitmap->setConfig(config, cinfo->output_width, height); bitmap->setIsOpaque(true); if (!this->allocPixelRef(bitmap, NULL)) { return return_false(*cinfo, *bitmap, "allocPixelRef"); } SkAutoLockPixels alp(*bitmap); JSAMPLE* rowptr = (JSAMPLE*)bitmap->getPixels(); INT32 const bpr = bitmap->rowBytes(); int row_total_count = 0; while (row_total_count < height) { int row_count = jpeg_read_tile_scanline(cinfo, index->index, &rowptr); // if row_count == 0, then we didn't get a scanline, so abort. // if we supported partial images, we might return true in this case if (0 == row_count) { return return_false(*cinfo, *bitmap, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(*cinfo, *bitmap, "shouldCancelDecode"); } row_total_count += row_count; rowptr += bpr; } cropBitmap(bm, bitmap, actualSampleSize, oriStartX, oriStartY, oriWidth, oriHeight, startX, startY); return true; } #endif // check for supported formats SkScaledBitmapSampler::SrcConfig sc; if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGB; #ifdef ANDROID_RGB } else if (JCS_RGBA_8888 == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGBX; } else if (JCS_RGB_565 == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGB_565; #endif } else if (1 == cinfo->out_color_components && JCS_GRAYSCALE == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kGray; } else { return return_false(*cinfo, *bm, "jpeg colorspace"); } SkScaledBitmapSampler sampler(width, height, skiaSampleSize); bitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); bitmap->setIsOpaque(true); if (!this->allocPixelRef(bitmap, NULL)) { return return_false(*cinfo, *bitmap, "allocPixelRef"); } SkAutoLockPixels alp(*bitmap); if (!sampler.begin(bitmap, sc, this->getDitherImage())) { return return_false(*cinfo, *bitmap, "sampler.begin"); } uint8_t* srcRow = (uint8_t*)srcStorage.reset(width * 4); // Possibly skip initial rows [sampler.srcY0] if (!skip_src_rows_tile(cinfo, index->index, srcRow, sampler.srcY0())) { return return_false(*cinfo, *bitmap, "skip rows"); } // now loop through scanlines until y == bitmap->height() - 1 for (int y = 0;; y++) { JSAMPLE* rowptr = (JSAMPLE*)srcRow; int row_count = jpeg_read_tile_scanline(cinfo, index->index, &rowptr); if (0 == row_count) { return return_false(*cinfo, *bitmap, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(*cinfo, *bitmap, "shouldCancelDecode"); } sampler.next(srcRow); if (bitmap->height() - 1 == y) { // we're done break; } if (!skip_src_rows_tile(cinfo, index->index, srcRow, sampler.srcDY() - 1)) { return return_false(*cinfo, *bitmap, "skip rows"); } } cropBitmap(bm, bitmap, actualSampleSize, oriStartX, oriStartY, oriWidth, oriHeight, startX, startY); return true; }
bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { #ifdef TIME_DECODE AutoTimeMillis atm("JPEG Decode"); #endif SkAutoMalloc srcStorage; JPEGAutoClean autoClean; jpeg_decompress_struct cinfo; skjpeg_error_mgr sk_err; skjpeg_source_mgr sk_stream(stream, this, false); cinfo.err = jpeg_std_error(&sk_err); sk_err.error_exit = skjpeg_error_exit; // All objects need to be instantiated before this setjmp call so that // they will be cleaned up properly if an error occurs. if (setjmp(sk_err.fJmpBuf)) { return return_false(cinfo, *bm, "setjmp"); } jpeg_create_decompress(&cinfo); autoClean.set(&cinfo); #ifdef SK_BUILD_FOR_ANDROID overwrite_mem_buffer_size(&cinfo); #endif //jpeg_stdio_src(&cinfo, file); cinfo.src = &sk_stream; int status = jpeg_read_header(&cinfo, true); if (status != JPEG_HEADER_OK) { return return_false(cinfo, *bm, "read_header"); } /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it can) much faster that we, just use their num/denom api to approximate the size. */ int sampleSize = this->getSampleSize(); if (this->getPreferQualityOverSpeed()) { cinfo.dct_method = JDCT_ISLOW; } else { cinfo.dct_method = JDCT_IFAST; } cinfo.scale_num = 1; cinfo.scale_denom = sampleSize; /* this gives about 30% performance improvement. In theory it may reduce the visual quality, in practice I'm not seeing a difference */ cinfo.do_fancy_upsampling = 0; /* this gives another few percents */ cinfo.do_block_smoothing = 0; /* default format is RGB */ cinfo.out_color_space = JCS_RGB; SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false); // only these make sense for jpegs if (config != SkBitmap::kARGB_8888_Config && config != SkBitmap::kARGB_4444_Config && config != SkBitmap::kRGB_565_Config) { config = SkBitmap::kARGB_8888_Config; } #ifdef ANDROID_RGB cinfo.dither_mode = JDITHER_NONE; if (config == SkBitmap::kARGB_8888_Config) { cinfo.out_color_space = JCS_RGBA_8888; } else if (config == SkBitmap::kRGB_565_Config) { cinfo.out_color_space = JCS_RGB_565; if (this->getDitherImage()) { cinfo.dither_mode = JDITHER_ORDERED; } } #endif if (sampleSize == 1 && mode == SkImageDecoder::kDecodeBounds_Mode) { bm->setConfig(config, cinfo.image_width, cinfo.image_height); bm->setIsOpaque(true); return true; } /* image_width and image_height are the original dimensions, available after jpeg_read_header(). To see the scaled dimensions, we have to call jpeg_start_decompress(), and then read output_width and output_height. */ if (!jpeg_start_decompress(&cinfo)) { /* If we failed here, we may still have enough information to return to the caller if they just wanted (subsampled bounds). If sampleSize was 1, then we would have already returned. Thus we just check if we're in kDecodeBounds_Mode, and that we have valid output sizes. One reason to fail here is that we have insufficient stream data to complete the setup. However, output dimensions seem to get computed very early, which is why this special check can pay off. */ if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) { SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height, recompute_sampleSize(sampleSize, cinfo)); bm->setConfig(config, smpl.scaledWidth(), smpl.scaledHeight()); bm->setIsOpaque(true); return true; } else { return return_false(cinfo, *bm, "start_decompress"); } } sampleSize = recompute_sampleSize(sampleSize, cinfo); // should we allow the Chooser (if present) to pick a config for us??? if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) { return return_false(cinfo, *bm, "chooseFromOneChoice"); } #ifdef ANDROID_RGB /* short-circuit the SkScaledBitmapSampler when possible, as this gives a significant performance boost. */ if (sampleSize == 1 && ((config == SkBitmap::kARGB_8888_Config && cinfo.out_color_space == JCS_RGBA_8888) || (config == SkBitmap::kRGB_565_Config && cinfo.out_color_space == JCS_RGB_565))) { bm->lockPixels(); JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels(); bm->unlockPixels(); bool reuseBitmap = (rowptr != NULL); if (reuseBitmap && ((int) cinfo.output_width != bm->width() || (int) cinfo.output_height != bm->height())) { // Dimensions must match return false; } if (!reuseBitmap) { bm->setConfig(config, cinfo.output_width, cinfo.output_height); bm->setIsOpaque(true); if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; } if (!this->allocPixelRef(bm, NULL)) { return return_false(cinfo, *bm, "allocPixelRef"); } } else if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; } SkAutoLockPixels alp(*bm); rowptr = (JSAMPLE*)bm->getPixels(); INT32 const bpr = bm->rowBytes(); while (cinfo.output_scanline < cinfo.output_height) { int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); // if row_count == 0, then we didn't get a scanline, so abort. // if we supported partial images, we might return true in this case if (0 == row_count) { return return_false(cinfo, *bm, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(cinfo, *bm, "shouldCancelDecode"); } rowptr += bpr; } if (reuseBitmap) { bm->notifyPixelsChanged(); } jpeg_finish_decompress(&cinfo); return true; } #endif // check for supported formats SkScaledBitmapSampler::SrcConfig sc; if (3 == cinfo.out_color_components && JCS_RGB == cinfo.out_color_space) { sc = SkScaledBitmapSampler::kRGB; #ifdef ANDROID_RGB } else if (JCS_RGBA_8888 == cinfo.out_color_space) { sc = SkScaledBitmapSampler::kRGBX; } else if (JCS_RGB_565 == cinfo.out_color_space) { sc = SkScaledBitmapSampler::kRGB_565; #endif } else if (1 == cinfo.out_color_components && JCS_GRAYSCALE == cinfo.out_color_space) { sc = SkScaledBitmapSampler::kGray; } else { return return_false(cinfo, *bm, "jpeg colorspace"); } SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize); bm->lockPixels(); JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels(); bool reuseBitmap = (rowptr != NULL); bm->unlockPixels(); if (reuseBitmap && (sampler.scaledWidth() != bm->width() || sampler.scaledHeight() != bm->height())) { // Dimensions must match return false; } if (!reuseBitmap) { bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); // jpegs are always opaque (i.e. have no per-pixel alpha) bm->setIsOpaque(true); if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; } if (!this->allocPixelRef(bm, NULL)) { return return_false(cinfo, *bm, "allocPixelRef"); } } else if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; } SkAutoLockPixels alp(*bm); if (!sampler.begin(bm, sc, this->getDitherImage())) { return return_false(cinfo, *bm, "sampler.begin"); } uint8_t* srcRow = (uint8_t*)srcStorage.reset(cinfo.output_width * 4); // Possibly skip initial rows [sampler.srcY0] if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) { return return_false(cinfo, *bm, "skip rows"); } // now loop through scanlines until y == bm->height() - 1 for (int y = 0;; y++) { JSAMPLE* rowptr = (JSAMPLE*)srcRow; int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); if (0 == row_count) { return return_false(cinfo, *bm, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(cinfo, *bm, "shouldCancelDecode"); } sampler.next(srcRow); if (bm->height() - 1 == y) { // we're done break; } if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) { return return_false(cinfo, *bm, "skip rows"); } } // we formally skip the rest, so we don't get a complaint from libjpeg if (!skip_src_rows(&cinfo, srcRow, cinfo.output_height - cinfo.output_scanline)) { return return_false(cinfo, *bm, "skip rows"); } if (reuseBitmap) { bm->notifyPixelsChanged(); } jpeg_finish_decompress(&cinfo); // SkDebugf("------------------- bm2 size %d [%d %d] %d\n", bm->getSize(), bm->width(), bm->height(), bm->config()); return true; }
void SkScalerContext::getImage(const SkGlyph& origGlyph) { const SkGlyph* glyph = &origGlyph; SkGlyph tmpGlyph; // in case we need to call generateImage on a mask-format that is different // (i.e. larger) than what our caller allocated by looking at origGlyph. SkAutoMalloc tmpGlyphImageStorage; // If we are going to draw-from-path, then we cannot generate color, since // the path only makes a mask. This case should have been caught up in // generateMetrics(). SkASSERT(!fGenerateImageFromPath || SkMask::kARGB32_Format != origGlyph.fMaskFormat); if (fMaskFilter) { // restore the prefilter bounds tmpGlyph.initGlyphIdFrom(origGlyph); // need the original bounds, sans our maskfilter SkMaskFilter* mf = fMaskFilter; fMaskFilter = nullptr; // temp disable this->getMetrics(&tmpGlyph); fMaskFilter = mf; // restore // we need the prefilter bounds to be <= filter bounds SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth); SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight); if (tmpGlyph.fMaskFormat == origGlyph.fMaskFormat) { tmpGlyph.fImage = origGlyph.fImage; } else { tmpGlyphImageStorage.reset(tmpGlyph.computeImageSize()); tmpGlyph.fImage = tmpGlyphImageStorage.get(); } glyph = &tmpGlyph; } if (fGenerateImageFromPath) { SkPath devPath, fillPath; SkMatrix fillToDevMatrix; SkMask mask; this->internalGetPath(*glyph, &fillPath, &devPath, &fillToDevMatrix); glyph->toMask(&mask); if (fRasterizer) { mask.fFormat = SkMask::kA8_Format; sk_bzero(glyph->fImage, mask.computeImageSize()); if (!fRasterizer->rasterize(fillPath, fillToDevMatrix, nullptr, fMaskFilter, &mask, SkMask::kJustRenderImage_CreateMode)) { return; } if (fPreBlend.isApplicable()) { applyLUTToA8Mask(mask, fPreBlend.fG); } } else { SkASSERT(SkMask::kARGB32_Format != mask.fFormat); generateMask(mask, devPath, fPreBlend); } } else { generateImage(*glyph); } if (fMaskFilter) { SkMask srcM, dstM; SkMatrix matrix; // the src glyph image shouldn't be 3D SkASSERT(SkMask::k3D_Format != glyph->fMaskFormat); SkAutoSMalloc<32*32> a8storage; glyph->toMask(&srcM); if (SkMask::kARGB32_Format == srcM.fFormat) { // now we need to extract the alpha-channel from the glyph's image // and copy it into a temp buffer, and then point srcM at that temp. srcM.fFormat = SkMask::kA8_Format; srcM.fRowBytes = SkAlign4(srcM.fBounds.width()); size_t size = srcM.computeImageSize(); a8storage.reset(size); srcM.fImage = (uint8_t*)a8storage.get(); extract_alpha(srcM, (const SkPMColor*)glyph->fImage, glyph->rowBytes()); } fRec.getMatrixFrom2x2(&matrix); if (fMaskFilter->filterMask(&dstM, srcM, matrix, nullptr)) { int width = SkFastMin32(origGlyph.fWidth, dstM.fBounds.width()); int height = SkFastMin32(origGlyph.fHeight, dstM.fBounds.height()); int dstRB = origGlyph.rowBytes(); int srcRB = dstM.fRowBytes; const uint8_t* src = (const uint8_t*)dstM.fImage; uint8_t* dst = (uint8_t*)origGlyph.fImage; if (SkMask::k3D_Format == dstM.fFormat) { // we have to copy 3 times as much height *= 3; } // clean out our glyph, since it may be larger than dstM //sk_bzero(dst, height * dstRB); while (--height >= 0) { memcpy(dst, src, width); src += srcRB; dst += dstRB; } SkMask::FreeImage(dstM.fImage); if (fPreBlendForFilter.isApplicable()) { applyLUTToA8Mask(srcM, fPreBlendForFilter.fG); } } } }
static GrTexture* load_yuv_texture(GrContext* ctx, const GrUniqueKey& optionalKey, const SkBitmap& bm, const GrSurfaceDesc& desc) { // Subsets are not supported, the whole pixelRef is loaded when using YUV decoding SkPixelRef* pixelRef = bm.pixelRef(); if ((NULL == pixelRef) || (pixelRef->info().width() != bm.info().width()) || (pixelRef->info().height() != bm.info().height())) { return NULL; } const bool useCache = optionalKey.isValid(); SkYUVPlanesCache::Info yuvInfo; SkAutoTUnref<SkCachedData> cachedData; SkAutoMalloc storage; if (useCache) { cachedData.reset(SkYUVPlanesCache::FindAndRef(pixelRef->getGenerationID(), &yuvInfo)); } void* planes[3]; if (cachedData.get()) { planes[0] = (void*)cachedData->data(); planes[1] = (uint8_t*)planes[0] + yuvInfo.fSizeInMemory[0]; planes[2] = (uint8_t*)planes[1] + yuvInfo.fSizeInMemory[1]; } else { // Fetch yuv plane sizes for memory allocation. Here, width and height can be // rounded up to JPEG block size and be larger than the image's width and height. if (!pixelRef->getYUV8Planes(yuvInfo.fSize, NULL, NULL, NULL)) { return NULL; } // Allocate the memory for YUV size_t totalSize(0); for (int i = 0; i < 3; ++i) { yuvInfo.fRowBytes[i] = yuvInfo.fSize[i].fWidth; yuvInfo.fSizeInMemory[i] = yuvInfo.fRowBytes[i] * yuvInfo.fSize[i].fHeight; totalSize += yuvInfo.fSizeInMemory[i]; } if (useCache) { cachedData.reset(SkResourceCache::NewCachedData(totalSize)); planes[0] = cachedData->writable_data(); } else { storage.reset(totalSize); planes[0] = storage.get(); } planes[1] = (uint8_t*)planes[0] + yuvInfo.fSizeInMemory[0]; planes[2] = (uint8_t*)planes[1] + yuvInfo.fSizeInMemory[1]; // Get the YUV planes and update plane sizes to actual image size if (!pixelRef->getYUV8Planes(yuvInfo.fSize, planes, yuvInfo.fRowBytes, &yuvInfo.fColorSpace)) { return NULL; } if (useCache) { // Decoding is done, cache the resulting YUV planes SkYUVPlanesCache::Add(pixelRef->getGenerationID(), cachedData, &yuvInfo); } } GrSurfaceDesc yuvDesc; yuvDesc.fConfig = kAlpha_8_GrPixelConfig; SkAutoTUnref<GrTexture> yuvTextures[3]; for (int i = 0; i < 3; ++i) { yuvDesc.fWidth = yuvInfo.fSize[i].fWidth; yuvDesc.fHeight = yuvInfo.fSize[i].fHeight; bool needsExactTexture = (yuvDesc.fWidth != yuvInfo.fSize[0].fWidth) || (yuvDesc.fHeight != yuvInfo.fSize[0].fHeight); if (needsExactTexture) { yuvTextures[i].reset(ctx->textureProvider()->createTexture(yuvDesc, true)); } else { yuvTextures[i].reset(ctx->textureProvider()->createApproxTexture(yuvDesc)); } if (!yuvTextures[i] || !yuvTextures[i]->writePixels(0, 0, yuvDesc.fWidth, yuvDesc.fHeight, yuvDesc.fConfig, planes[i], yuvInfo.fRowBytes[i])) { return NULL; } } GrSurfaceDesc rtDesc = desc; rtDesc.fFlags = rtDesc.fFlags | kRenderTarget_GrSurfaceFlag; GrTexture* result = create_texture_for_bmp(ctx, optionalKey, rtDesc, pixelRef, NULL, 0); if (!result) { return NULL; } GrRenderTarget* renderTarget = result->asRenderTarget(); SkASSERT(renderTarget); GrPaint paint; SkAutoTUnref<GrFragmentProcessor> yuvToRgbProcessor(GrYUVtoRGBEffect::Create(paint.getProcessorDataManager(), yuvTextures[0], yuvTextures[1], yuvTextures[2], yuvInfo.fSize, yuvInfo.fColorSpace)); paint.addColorProcessor(yuvToRgbProcessor); SkRect r = SkRect::MakeWH(SkIntToScalar(yuvInfo.fSize[0].fWidth), SkIntToScalar(yuvInfo.fSize[0].fHeight)); GrDrawContext* drawContext = ctx->drawContext(); if (!drawContext) { return NULL; } drawContext->drawRect(renderTarget, GrClip::WideOpen(), paint, SkMatrix::I(), r); return result; }
DEF_TEST(BitmapCopy, reporter) { static const bool isExtracted[] = { false, true }; for (size_t i = 0; i < SK_ARRAY_COUNT(gPairs); i++) { SkBitmap srcOpaque, srcPremul; setup_src_bitmaps(&srcOpaque, &srcPremul, gPairs[i].fColorType); for (size_t j = 0; j < SK_ARRAY_COUNT(gPairs); j++) { SkBitmap dst; bool success = srcPremul.copyTo(&dst, gPairs[j].fColorType); bool expected = gPairs[i].fValid[j] != '0'; if (success != expected) { ERRORF(reporter, "SkBitmap::copyTo from %s to %s. expected %s " "returned %s", gColorTypeName[i], gColorTypeName[j], boolStr(expected), boolStr(success)); } bool canSucceed = srcPremul.canCopyTo(gPairs[j].fColorType); if (success != canSucceed) { ERRORF(reporter, "SkBitmap::copyTo from %s to %s. returned %s " "canCopyTo %s", gColorTypeName[i], gColorTypeName[j], boolStr(success), boolStr(canSucceed)); } if (success) { REPORTER_ASSERT(reporter, srcPremul.width() == dst.width()); REPORTER_ASSERT(reporter, srcPremul.height() == dst.height()); REPORTER_ASSERT(reporter, dst.colorType() == gPairs[j].fColorType); test_isOpaque(reporter, srcOpaque, srcPremul, dst.colorType()); if (srcPremul.colorType() == dst.colorType()) { SkAutoLockPixels srcLock(srcPremul); SkAutoLockPixels dstLock(dst); REPORTER_ASSERT(reporter, srcPremul.readyToDraw()); REPORTER_ASSERT(reporter, dst.readyToDraw()); const char* srcP = (const char*)srcPremul.getAddr(0, 0); const char* dstP = (const char*)dst.getAddr(0, 0); REPORTER_ASSERT(reporter, srcP != dstP); REPORTER_ASSERT(reporter, !memcmp(srcP, dstP, srcPremul.getSize())); REPORTER_ASSERT(reporter, srcPremul.getGenerationID() == dst.getGenerationID()); } else { REPORTER_ASSERT(reporter, srcPremul.getGenerationID() != dst.getGenerationID()); } } else { // dst should be unchanged from its initial state REPORTER_ASSERT(reporter, dst.colorType() == kUnknown_SkColorType); REPORTER_ASSERT(reporter, dst.width() == 0); REPORTER_ASSERT(reporter, dst.height() == 0); } } // for (size_t j = ... // Tests for getSafeSize(), getSafeSize64(), copyPixelsTo(), // copyPixelsFrom(). // for (size_t copyCase = 0; copyCase < SK_ARRAY_COUNT(isExtracted); ++copyCase) { // Test copying to/from external buffer. // Note: the tests below have hard-coded values --- // Please take care if modifying. // Tests for getSafeSize64(). // Test with a very large configuration without pixel buffer // attached. SkBitmap tstSafeSize; tstSafeSize.setConfig(SkImageInfo::Make(100000000U, 100000000U, gPairs[i].fColorType, kPremul_SkAlphaType)); int64_t safeSize = tstSafeSize.computeSafeSize64(); if (safeSize < 0) { ERRORF(reporter, "getSafeSize64() negative: %s", gColorTypeName[tstSafeSize.colorType()]); } bool sizeFail = false; // Compare against hand-computed values. switch (gPairs[i].fColorType) { case kUnknown_SkColorType: break; case kAlpha_8_SkColorType: case kIndex_8_SkColorType: if (safeSize != 0x2386F26FC10000LL) { sizeFail = true; } break; case kRGB_565_SkColorType: case kARGB_4444_SkColorType: if (safeSize != 0x470DE4DF820000LL) { sizeFail = true; } break; case kN32_SkColorType: if (safeSize != 0x8E1BC9BF040000LL) { sizeFail = true; } break; default: break; } if (sizeFail) { ERRORF(reporter, "computeSafeSize64() wrong size: %s", gColorTypeName[tstSafeSize.colorType()]); } int subW = 2; int subH = 2; // Create bitmap to act as source for copies and subsets. SkBitmap src, subset; SkColorTable* ct = NULL; if (kIndex_8_SkColorType == src.colorType()) { ct = init_ctable(kPremul_SkAlphaType); } if (isExtracted[copyCase]) { // A larger image to extract from. src.allocPixels(SkImageInfo::Make(2 * subW + 1, subH, gPairs[i].fColorType, kPremul_SkAlphaType)); } else { // Tests expect a 2x2 bitmap, so make smaller. src.allocPixels(SkImageInfo::Make(subW, subH, gPairs[i].fColorType, kPremul_SkAlphaType)); } SkSafeUnref(ct); // Either copy src or extract into 'subset', which is used // for subsequent calls to copyPixelsTo/From. bool srcReady = false; // Test relies on older behavior that extractSubset will fail on // kUnknown_SkColorType if (kUnknown_SkColorType != src.colorType() && isExtracted[copyCase]) { // The extractedSubset() test case allows us to test copy- // ing when src and dst mave possibly different strides. SkIRect r; r.set(1, 0, 1 + subW, subH); // 2x2 extracted bitmap srcReady = src.extractSubset(&subset, r); } else { srcReady = src.copyTo(&subset); } // Not all configurations will generate a valid 'subset'. if (srcReady) { // Allocate our target buffer 'buf' for all copies. // To simplify verifying correctness of copies attach // buf to a SkBitmap, but copies are done using the // raw buffer pointer. const size_t bufSize = subH * SkColorTypeMinRowBytes(src.colorType(), subW) * 2; SkAutoMalloc autoBuf (bufSize); uint8_t* buf = static_cast<uint8_t*>(autoBuf.get()); SkBitmap bufBm; // Attach buf to this bitmap. bool successExpected; // Set up values for each pixel being copied. Coordinates coords(subW * subH); for (int x = 0; x < subW; ++x) for (int y = 0; y < subH; ++y) { int index = y * subW + x; SkASSERT(index < coords.length); coords[index]->fX = x; coords[index]->fY = y; } writeCoordPixels(subset, coords); // Test #1 //////////////////////////////////////////// const SkImageInfo info = SkImageInfo::Make(subW, subH, gPairs[i].fColorType, kPremul_SkAlphaType); // Before/after comparisons easier if we attach buf // to an appropriately configured SkBitmap. memset(buf, 0xFF, bufSize); // Config with stride greater than src but that fits in buf. bufBm.installPixels(info, buf, info.minRowBytes() * 2); successExpected = false; // Then attempt to copy with a stride that is too large // to fit in the buffer. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes() * 3) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, 1.5*maxRowBytes)", reporter); // Test #2 //////////////////////////////////////////// // This test should always succeed, but in the case // of extracted bitmaps only because we handle the // issue of getSafeSize(). Without getSafeSize() // buffer overrun/read would occur. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, subset.rowBytes()); successExpected = subset.getSafeSize() <= bufSize; REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize)", reporter); // Test #3 //////////////////////////////////////////// // Copy with different stride between src and dst. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, subset.rowBytes()+1); successExpected = true; // Should always work. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, subset.rowBytes()+1) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, rowBytes+1)", reporter); // Test #4 //////////////////////////////////////////// // Test copy with stride too small. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, info.minRowBytes()); successExpected = false; // Request copy with stride too small. REPORTER_ASSERT(reporter, subset.copyPixelsTo(buf, bufSize, bufBm.rowBytes()-1) == successExpected); if (successExpected) reportCopyVerification(subset, bufBm, coords, "copyPixelsTo(buf, bufSize, rowBytes()-1)", reporter); #if 0 // copyPixelsFrom is gone // Test #5 //////////////////////////////////////////// // Tests the case where the source stride is too small // for the source configuration. memset(buf, 0xFF, bufSize); bufBm.installPixels(info, buf, info.minRowBytes()); writeCoordPixels(bufBm, coords); REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, bufSize, 1) == false); // Test #6 /////////////////////////////////////////// // Tests basic copy from an external buffer to the bitmap. // If the bitmap is "extracted", this also tests the case // where the source stride is different from the dest. // stride. // We've made the buffer large enough to always succeed. bufBm.installPixels(info, buf, info.minRowBytes()); writeCoordPixels(bufBm, coords); REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, bufSize, bufBm.rowBytes()) == true); reportCopyVerification(bufBm, subset, coords, "copyPixelsFrom(buf, bufSize)", reporter); // Test #7 //////////////////////////////////////////// // Tests the case where the source buffer is too small // for the transfer. REPORTER_ASSERT(reporter, subset.copyPixelsFrom(buf, 1, subset.rowBytes()) == false); #endif } } // for (size_t copyCase ... } }
SkImageDecoder::Result SkASTCImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) { SkAutoMalloc autoMal; const size_t length = SkCopyStreamToStorage(&autoMal, stream); if (0 == length) { return kFailure; } unsigned char* buf = (unsigned char*)autoMal.get(); // Make sure that the magic header is there... SkASSERT(SkEndian_SwapLE32(*(reinterpret_cast<uint32_t*>(buf))) == kASTCMagicNumber); // Advance past the magic header buf += 4; const int blockDimX = buf[0]; const int blockDimY = buf[1]; const int blockDimZ = buf[2]; if (1 != blockDimZ) { // We don't support decoding 3D return kFailure; } // Choose the proper ASTC format SkTextureCompressor::Format astcFormat; if (4 == blockDimX && 4 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_4x4_Format; } else if (5 == blockDimX && 4 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_5x4_Format; } else if (5 == blockDimX && 5 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_5x5_Format; } else if (6 == blockDimX && 5 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_6x5_Format; } else if (6 == blockDimX && 6 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_6x6_Format; } else if (8 == blockDimX && 5 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_8x5_Format; } else if (8 == blockDimX && 6 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_8x6_Format; } else if (8 == blockDimX && 8 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_8x8_Format; } else if (10 == blockDimX && 5 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_10x5_Format; } else if (10 == blockDimX && 6 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_10x6_Format; } else if (10 == blockDimX && 8 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_10x8_Format; } else if (10 == blockDimX && 10 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_10x10_Format; } else if (12 == blockDimX && 10 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_12x10_Format; } else if (12 == blockDimX && 12 == blockDimY) { astcFormat = SkTextureCompressor::kASTC_12x12_Format; } else { // We don't support any other block dimensions.. return kFailure; } // Advance buf past the block dimensions buf += 3; // Read the width/height/depth from the buffer... const int width = read_24bit(buf); const int height = read_24bit(buf + 3); const int depth = read_24bit(buf + 6); if (1 != depth) { // We don't support decoding 3D. return kFailure; } // Advance the buffer past the image dimensions buf += 9; // Setup the sampler... SkScaledBitmapSampler sampler(width, height, this->getSampleSize()); // Determine the alpha of the bitmap... SkAlphaType alphaType = kOpaque_SkAlphaType; if (this->getRequireUnpremultipliedColors()) { alphaType = kUnpremul_SkAlphaType; } else { alphaType = kPremul_SkAlphaType; } // Set the config... bm->setInfo(SkImageInfo::MakeN32(sampler.scaledWidth(), sampler.scaledHeight(), alphaType)); if (SkImageDecoder::kDecodeBounds_Mode == mode) { return kSuccess; } if (!this->allocPixelRef(bm, NULL)) { return kFailure; } // Lock the pixels, since we're about to write to them... SkAutoLockPixels alp(*bm); if (!sampler.begin(bm, SkScaledBitmapSampler::kRGBA, *this)) { return kFailure; } // ASTC Data is encoded as RGBA pixels, so we should extract it as such int nPixels = width * height; SkAutoMalloc outRGBAData(nPixels * 4); uint8_t *outRGBADataPtr = reinterpret_cast<uint8_t *>(outRGBAData.get()); // Decode ASTC if (!SkTextureCompressor::DecompressBufferFromFormat( outRGBADataPtr, width*4, buf, width, height, astcFormat)) { return kFailure; } // Set each of the pixels... const int srcRowBytes = width * 4; const int dstHeight = sampler.scaledHeight(); const uint8_t *srcRow = reinterpret_cast<uint8_t *>(outRGBADataPtr); srcRow += sampler.srcY0() * srcRowBytes; for (int y = 0; y < dstHeight; ++y) { sampler.next(srcRow); srcRow += sampler.srcDY() * srcRowBytes; } return kSuccess; }
bool SkJPEGImageDecoder::onDecodeRegion(SkBitmap* bm, SkIRect region) { if (index == NULL) { return false; } jpeg_decompress_struct *cinfo = index->cinfo; SkIRect rect = SkIRect::MakeWH(this->imageWidth, this->imageHeight); if (!rect.intersect(region)) { // If the requested region is entirely outsides the image, just // returns false return false; } SkAutoMalloc srcStorage; skjpeg_error_mgr sk_err; cinfo->err = jpeg_std_error(&sk_err); sk_err.error_exit = skjpeg_error_exit; if (setjmp(sk_err.fJmpBuf)) { return false; } int requestedSampleSize = this->getSampleSize(); cinfo->scale_denom = requestedSampleSize; if (this->getPreferQualityOverSpeed()) { cinfo->dct_method = JDCT_ISLOW; } else { cinfo->dct_method = JDCT_IFAST; } SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, false); if (config != SkBitmap::kARGB_8888_Config && config != SkBitmap::kARGB_4444_Config && config != SkBitmap::kRGB_565_Config) { config = SkBitmap::kARGB_8888_Config; } /* default format is RGB */ cinfo->out_color_space = JCS_RGB; #ifdef ANDROID_RGB cinfo->dither_mode = JDITHER_NONE; if (config == SkBitmap::kARGB_8888_Config) { cinfo->out_color_space = JCS_RGBA_8888; } else if (config == SkBitmap::kRGB_565_Config) { cinfo->out_color_space = JCS_RGB_565; if (this->getDitherImage()) { cinfo->dither_mode = JDITHER_ORDERED; } } #endif int startX = rect.fLeft; int startY = rect.fTop; int width = rect.width(); int height = rect.height(); jpeg_init_read_tile_scanline(cinfo, index->index, &startX, &startY, &width, &height); int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo); int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size); SkBitmap *bitmap = new SkBitmap; SkAutoTDelete<SkBitmap> adb(bitmap); #ifdef ANDROID_RGB /* short-circuit the SkScaledBitmapSampler when possible, as this gives a significant performance boost. */ if (skiaSampleSize == 1 && ((config == SkBitmap::kARGB_8888_Config && cinfo->out_color_space == JCS_RGBA_8888) || (config == SkBitmap::kRGB_565_Config && cinfo->out_color_space == JCS_RGB_565))) { bitmap->setConfig(config, cinfo->output_width, height); bitmap->setIsOpaque(true); // Check ahead of time if the swap(dest, src) is possible or not. // If yes, then we will stick to AllocPixelRef since it's cheaper // with the swap happening. If no, then we will use alloc to allocate // pixels to prevent garbage collection. // // Not using a recycled-bitmap and the output rect is same as the // decoded region. int w = rect.width() / actualSampleSize; int h = rect.height() / actualSampleSize; bool swapOnly = (rect == region) && bm->isNull() && (w == bitmap->width()) && (h == bitmap->height()) && ((startX - rect.x()) / actualSampleSize == 0) && ((startY - rect.y()) / actualSampleSize == 0); if (swapOnly) { if (!this->allocPixelRef(bitmap, NULL)) { return return_false(*cinfo, *bitmap, "allocPixelRef"); } } else { if (!bitmap->allocPixels()) { return return_false(*cinfo, *bitmap, "allocPixels"); } } SkAutoLockPixels alp(*bitmap); JSAMPLE* rowptr = (JSAMPLE*)bitmap->getPixels(); INT32 const bpr = bitmap->rowBytes(); int row_total_count = 0; while (row_total_count < height) { int row_count = jpeg_read_tile_scanline(cinfo, index->index, &rowptr); // if row_count == 0, then we didn't get a scanline, so abort. // if we supported partial images, we might return true in this case if (0 == row_count) { return return_false(*cinfo, *bitmap, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(*cinfo, *bitmap, "shouldCancelDecode"); } row_total_count += row_count; rowptr += bpr; } if (swapOnly) { bm->swap(*bitmap); } else { cropBitmap(bm, bitmap, actualSampleSize, region.x(), region.y(), region.width(), region.height(), startX, startY); } return true; } #endif // check for supported formats SkScaledBitmapSampler::SrcConfig sc; if (3 == cinfo->out_color_components && JCS_RGB == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGB; #ifdef ANDROID_RGB } else if (JCS_RGBA_8888 == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGBX; } else if (JCS_RGB_565 == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kRGB_565; #endif } else if (1 == cinfo->out_color_components && JCS_GRAYSCALE == cinfo->out_color_space) { sc = SkScaledBitmapSampler::kGray; } else { return return_false(*cinfo, *bm, "jpeg colorspace"); } SkScaledBitmapSampler sampler(width, height, skiaSampleSize); bitmap->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); bitmap->setIsOpaque(true); // Check ahead of time if the swap(dest, src) is possible or not. // If yes, then we will stick to AllocPixelRef since it's cheaper with the // swap happening. If no, then we will use alloc to allocate pixels to // prevent garbage collection. int w = rect.width() / actualSampleSize; int h = rect.height() / actualSampleSize; bool swapOnly = (rect == region) && bm->isNull() && (w == bitmap->width()) && (h == bitmap->height()) && ((startX - rect.x()) / actualSampleSize == 0) && ((startY - rect.y()) / actualSampleSize == 0); if (swapOnly) { if (!this->allocPixelRef(bitmap, NULL)) { return return_false(*cinfo, *bitmap, "allocPixelRef"); } } else { if (!bitmap->allocPixels()) { return return_false(*cinfo, *bitmap, "allocPixels"); } } SkAutoLockPixels alp(*bitmap); if (!sampler.begin(bitmap, sc, this->getDitherImage())) { return return_false(*cinfo, *bitmap, "sampler.begin"); } uint8_t* srcRow = (uint8_t*)srcStorage.reset(width * 4); // Possibly skip initial rows [sampler.srcY0] if (!skip_src_rows_tile(cinfo, index->index, srcRow, sampler.srcY0())) { return return_false(*cinfo, *bitmap, "skip rows"); } // now loop through scanlines until y == bitmap->height() - 1 for (int y = 0;; y++) { JSAMPLE* rowptr = (JSAMPLE*)srcRow; int row_count = jpeg_read_tile_scanline(cinfo, index->index, &rowptr); if (0 == row_count) { return return_false(*cinfo, *bitmap, "read_scanlines"); } if (this->shouldCancelDecode()) { return return_false(*cinfo, *bitmap, "shouldCancelDecode"); } sampler.next(srcRow); if (bitmap->height() - 1 == y) { // we're done break; } if (!skip_src_rows_tile(cinfo, index->index, srcRow, sampler.srcDY() - 1)) { return return_false(*cinfo, *bitmap, "skip rows"); } } if (swapOnly) { bm->swap(*bitmap); } else { cropBitmap(bm, bitmap, actualSampleSize, region.x(), region.y(), region.width(), region.height(), startX, startY); } return true; }
bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, SkBitmap::Config prefConfig, Mode mode) { #ifdef TIME_DECODE AutoTimeMillis atm("JPEG Decode"); #endif SkAutoMalloc srcStorage; JPEGAutoClean autoClean; jpeg_decompress_struct cinfo; sk_error_mgr sk_err; sk_source_mgr sk_stream(stream); cinfo.err = jpeg_std_error(&sk_err); sk_err.error_exit = sk_error_exit; // All objects need to be instantiated before this setjmp call so that // they will be cleaned up properly if an error occurs. if (setjmp(sk_err.fJmpBuf)) { return false; } jpeg_create_decompress(&cinfo); autoClean.set(&cinfo); //jpeg_stdio_src(&cinfo, file); cinfo.src = &sk_stream; jpeg_read_header(&cinfo, true); /* Try to fulfill the requested sampleSize. Since jpeg can do it (when it can) much faster that we, just use their num/denom api to approximate the size. */ int sampleSize = this->getSampleSize(); cinfo.dct_method = JDCT_IFAST; cinfo.scale_num = 1; cinfo.scale_denom = sampleSize; /* image_width and image_height are the original dimensions, available after jpeg_read_header(). To see the scaled dimensions, we have to call jpeg_start_decompress(), and then read output_width and output_height. */ jpeg_start_decompress(&cinfo); /* If we need to better match the request, we might examine the image and output dimensions, and determine if the downsampling jpeg provided is not sufficient. If so, we can recompute a modified sampleSize value to make up the difference. To skip this additional scaling, just set sampleSize = 1; below. */ sampleSize = sampleSize * cinfo.output_width / cinfo.image_width; // check for supported formats bool isRGB; // as opposed to gray8 if (3 == cinfo.num_components && JCS_RGB == cinfo.out_color_space) { isRGB = true; } else if (1 == cinfo.num_components && JCS_GRAYSCALE == cinfo.out_color_space) { isRGB = false; // could use Index8 config if we want... } else { SkDEBUGF(("SkJPEGImageDecoder: unsupported jpeg colorspace %d with %d components\n", cinfo.jpeg_color_space, cinfo.num_components)); return false; } SkBitmap::Config config = prefConfig; // if no user preference, see what the device recommends if (config == SkBitmap::kNo_Config) config = SkImageDecoder::GetDeviceConfig(); // only these make sense for jpegs if (config != SkBitmap::kARGB_8888_Config && config != SkBitmap::kARGB_4444_Config && config != SkBitmap::kRGB_565_Config) { config = SkBitmap::kARGB_8888_Config; } // should we allow the Chooser (if present) to pick a config for us??? if (!this->chooseFromOneChoice(config, cinfo.output_width, cinfo.output_height)) { return false; } SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize); bm->setConfig(config, sampler.scaledWidth(), sampler.scaledHeight()); // jpegs are always opauqe (i.e. have no per-pixel alpha) bm->setIsOpaque(true); if (SkImageDecoder::kDecodeBounds_Mode == mode) { return true; } if (!this->allocPixelRef(bm, NULL)) { return false; } SkAutoLockPixels alp(*bm); if (!sampler.begin(bm, isRGB ? SkScaledBitmapSampler::kRGB : SkScaledBitmapSampler::kGray, this->getDitherImage())) { return false; } uint8_t* srcRow = (uint8_t*)srcStorage.alloc(cinfo.output_width * 3); skip_src_rows(&cinfo, srcRow, sampler.srcY0()); for (int y = 0;; y++) { JSAMPLE* rowptr = (JSAMPLE*)srcRow; int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1); SkASSERT(row_count == 1); sampler.next(srcRow); if (bm->height() - 1 == y) { break; } skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1); } // ??? If I don't do this, I get an error from finish_decompress skip_src_rows(&cinfo, srcRow, cinfo.output_height - cinfo.output_scanline); jpeg_finish_decompress(&cinfo); return true; }