static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo, huffman_index *index, void* buffer, int count) { for (int i = 0; i < count; i++) { JSAMPLE* rowptr = (JSAMPLE*)buffer; int row_count = jpeg_read_tile_scanline(cinfo, index, &rowptr); if (row_count != 1) { return false; } } return true; }
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::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; }