aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar msarett <msarett@google.com>2015-09-08 15:35:32 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2015-09-08 15:35:32 -0700
commita5783aeff042ccaf517e50dee3660a4925f5f694 (patch)
tree17144d306e184bd3d6178e863769c5febbd59e22
parent036fd8e6f66b53cf87a5f91083cae82f0aeb3635 (diff)
Provides various implementations of Android's SkBitmapRegionDecoder.
Implements testing in DM for these implementations. nanobench testing will follow after this. TBR=scroggo BUG=skia: Committed: https://skia.googlesource.com/skia/+/76f755e6d54a32f9887ad254ce59a3a62f28bde4 Review URL: https://codereview.chromium.org/1288963002
-rw-r--r--dm/DM.cpp127
-rw-r--r--dm/DMSrcSink.cpp160
-rw-r--r--dm/DMSrcSink.h29
-rw-r--r--gyp/dm.gypi1
-rw-r--r--gyp/tools.gyp16
-rw-r--r--tools/SkBitmapRegionCanvas.cpp189
-rw-r--r--tools/SkBitmapRegionCanvas.h43
-rw-r--r--tools/SkBitmapRegionDecoderInterface.cpp51
-rw-r--r--tools/SkBitmapRegionDecoderInterface.h77
-rw-r--r--tools/SkBitmapRegionSampler.cpp50
-rw-r--r--tools/SkBitmapRegionSampler.h41
11 files changed, 775 insertions, 9 deletions
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 55651d9cda..66cc272f24 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -327,12 +327,123 @@ static void push_codec_srcs(Path path) {
}
}
-static bool codec_supported(const char* ext) {
- // FIXME: Once other versions of SkCodec are available, we can add them to this
- // list (and eventually we can remove this check once they are all supported).
+static bool brd_color_type_supported(SkBitmapRegionDecoderInterface::Strategy strategy,
+ CodecSrc::DstColorType dstColorType) {
+ switch (strategy) {
+ case SkBitmapRegionDecoderInterface::kCanvas_Strategy:
+ if (CodecSrc::kGetFromCanvas_DstColorType == dstColorType) {
+ return true;
+ }
+ return false;
+ case SkBitmapRegionDecoderInterface::kOriginal_Strategy:
+ switch (dstColorType) {
+ case CodecSrc::kGetFromCanvas_DstColorType:
+ case CodecSrc::kIndex8_Always_DstColorType:
+ case CodecSrc::kGrayscale_Always_DstColorType:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ SkASSERT(false);
+ return false;
+ }
+}
+
+static void push_brd_src(Path path, SkBitmapRegionDecoderInterface::Strategy strategy,
+ CodecSrc::DstColorType dstColorType, BRDSrc::Mode mode, uint32_t sampleSize) {
+ SkString folder;
+ switch (strategy) {
+ case SkBitmapRegionDecoderInterface::kCanvas_Strategy:
+ folder.append("brd_canvas");
+ break;
+ case SkBitmapRegionDecoderInterface::kOriginal_Strategy:
+ folder.append("brd_sample");
+ break;
+ default:
+ SkASSERT(false);
+ return;
+ }
+
+ switch (mode) {
+ case BRDSrc::kFullImage_Mode:
+ break;
+ case BRDSrc::kDivisor_Mode:
+ folder.append("_divisor");
+ break;
+ default:
+ SkASSERT(false);
+ return;
+ }
+
+ switch (dstColorType) {
+ case CodecSrc::kGetFromCanvas_DstColorType:
+ break;
+ case CodecSrc::kIndex8_Always_DstColorType:
+ folder.append("_kIndex");
+ break;
+ case CodecSrc::kGrayscale_Always_DstColorType:
+ folder.append("_kGray");
+ break;
+ default:
+ SkASSERT(false);
+ return;
+ }
+
+ if (1 != sampleSize) {
+ folder.appendf("_%.3f", BRDSrc::GetScale(sampleSize));
+ }
+
+ BRDSrc* src = new BRDSrc(path, strategy, mode, dstColorType, sampleSize);
+ push_src("image", folder, src);
+}
+
+static void push_brd_srcs(Path path) {
+
+ const SkBitmapRegionDecoderInterface::Strategy strategies[] = {
+ SkBitmapRegionDecoderInterface::kCanvas_Strategy,
+ SkBitmapRegionDecoderInterface::kOriginal_Strategy
+ };
+
+ // We will only test to one backend (8888), but we will test all of the
+ // color types that we need to decode to on this backend.
+ const CodecSrc::DstColorType dstColorTypes[] = {
+ CodecSrc::kGetFromCanvas_DstColorType,
+ CodecSrc::kIndex8_Always_DstColorType,
+ CodecSrc::kGrayscale_Always_DstColorType,
+ };
+
+ const BRDSrc::Mode modes[] = {
+ BRDSrc::kFullImage_Mode,
+ BRDSrc::kDivisor_Mode
+ };
+
+ const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+
+ for (SkBitmapRegionDecoderInterface::Strategy strategy : strategies) {
+ // We disable png testing for kOriginal_Strategy because the implementation leaks
+ // memory in our forked libpng.
+ // TODO (msarett): Decide if we want to test pngs in this mode and how we might do this.
+ if (SkBitmapRegionDecoderInterface::kOriginal_Strategy == strategy &&
+ (path.endsWith(".png") || path.endsWith(".PNG"))) {
+ continue;
+ }
+ for (CodecSrc::DstColorType dstColorType : dstColorTypes) {
+ if (brd_color_type_supported(strategy, dstColorType)) {
+ for (BRDSrc::Mode mode : modes) {
+ for (uint32_t sampleSize : sampleSizes) {
+ push_brd_src(path, strategy, dstColorType, mode, sampleSize);
+ }
+ }
+ }
+ }
+ }
+}
+
+static bool brd_supported(const char* ext) {
static const char* const exts[] = {
- "bmp", "gif", "jpg", "jpeg", "png", "ico", "wbmp", "webp",
- "BMP", "GIF", "JPG", "JPEG", "PNG", "ICO", "WBMP", "WEBP",
+ "jpg", "jpeg", "png", "webp",
+ "JPG", "JPEG", "PNG", "WEBP",
};
for (uint32_t i = 0; i < SK_ARRAY_COUNT(exts); i++) {
@@ -371,8 +482,9 @@ static void gather_srcs() {
SkString path = SkOSPath::Join(flag, file.c_str());
push_src("image", "decode", new ImageSrc(path)); // Decode entire image
push_src("image", "subset", new ImageSrc(path, 2)); // Decode into 2x2 subsets
- if (codec_supported(exts[j])) {
- push_codec_srcs(path);
+ push_codec_srcs(path);
+ if (brd_supported(exts[j])) {
+ push_brd_srcs(path);
}
}
}
@@ -381,6 +493,7 @@ static void gather_srcs() {
push_src("image", "decode", new ImageSrc(flag)); // Decode entire image.
push_src("image", "subset", new ImageSrc(flag, 2)); // Decode into 2 x 2 subsets
push_codec_srcs(flag);
+ push_brd_srcs(flag);
}
}
}
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 438868dd54..981e47d6c0 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -64,6 +64,163 @@ void GMSrc::modifyGrContextOptions(GrContextOptions* options) const {
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+BRDSrc::BRDSrc(Path path, SkBitmapRegionDecoderInterface::Strategy strategy, Mode mode,
+ CodecSrc::DstColorType dstColorType, uint32_t sampleSize)
+ : fPath(path)
+ , fStrategy(strategy)
+ , fMode(mode)
+ , fDstColorType(dstColorType)
+ , fSampleSize(sampleSize)
+{}
+
+bool BRDSrc::veto(SinkFlags flags) const {
+ // No need to test to non-raster or indirect backends.
+ return flags.type != SinkFlags::kRaster
+ || flags.approach != SinkFlags::kDirect;
+}
+
+static SkBitmapRegionDecoderInterface* create_brd(Path path,
+ SkBitmapRegionDecoderInterface::Strategy strategy) {
+ SkAutoTUnref<SkData> encoded(SkData::NewFromFileName(path.c_str()));
+ if (!encoded) {
+ return NULL;
+ }
+ return SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(new SkMemoryStream(encoded),
+ strategy);
+}
+
+Error BRDSrc::draw(SkCanvas* canvas) const {
+ SkColorType colorType = canvas->imageInfo().colorType();
+ if (kRGB_565_SkColorType == colorType &&
+ CodecSrc::kGetFromCanvas_DstColorType != fDstColorType) {
+ return Error::Nonfatal("Testing non-565 to 565 is uninteresting.");
+ }
+ switch (fDstColorType) {
+ case CodecSrc::kGetFromCanvas_DstColorType:
+ break;
+ case CodecSrc::kIndex8_Always_DstColorType:
+ colorType = kIndex_8_SkColorType;
+ break;
+ case CodecSrc::kGrayscale_Always_DstColorType:
+ colorType = kGray_8_SkColorType;
+ break;
+ }
+
+ SkAutoTDelete<SkBitmapRegionDecoderInterface> brd(create_brd(fPath, fStrategy));
+ if (nullptr == brd.get()) {
+ return Error::Nonfatal(SkStringPrintf("Could not create brd for %s.", fPath.c_str()));
+ }
+
+ const uint32_t width = brd->width();
+ const uint32_t height = brd->height();
+ // Visually inspecting very small output images is not necessary.
+ if ((width / fSampleSize <= 10 || height / fSampleSize <= 10) && 1 != fSampleSize) {
+ return Error::Nonfatal("Scaling very small images is uninteresting.");
+ }
+ switch (fMode) {
+ case kFullImage_Mode: {
+ SkAutoTDelete<SkBitmap> bitmap(brd->decodeRegion(0, 0, width, height, fSampleSize,
+ colorType));
+ if (nullptr == bitmap.get() || colorType != bitmap->colorType()) {
+ return Error::Nonfatal("Cannot convert to color type.\n");
+ }
+ canvas->drawBitmap(*bitmap, 0, 0);
+ return "";
+ }
+ case kDivisor_Mode: {
+ const uint32_t divisor = 2;
+ if (width < divisor || height < divisor) {
+ return Error::Nonfatal("Divisor is larger than image dimension.\n");
+ }
+
+ // Use a border to test subsets that extend outside the image.
+ // We will not allow the border to be larger than the image dimensions. Allowing
+ // these large borders causes off by one errors that indicate a problem with the
+ // test suite, not a problem with the implementation.
+ const uint32_t maxBorder = SkTMin(width, height) / (fSampleSize * divisor);
+ const uint32_t scaledBorder = SkTMin(5u, maxBorder);
+ const uint32_t unscaledBorder = scaledBorder * fSampleSize;
+
+ // We may need to clear the canvas to avoid uninitialized memory.
+ // Assume we are scaling a 780x780 image with sampleSize = 8.
+ // The output image should be 97x97.
+ // Each subset will be 390x390.
+ // Each scaled subset be 48x48.
+ // Four scaled subsets will only fill a 96x96 image.
+ // The bottom row and last column will not be touched.
+ // This is an unfortunate result of our rounding rules when scaling.
+ // Maybe we need to consider testing scaled subsets without trying to
+ // combine them to match the full scaled image? Or maybe this is the
+ // best we can do?
+ canvas->clear(0);
+
+ for (uint32_t x = 0; x < divisor; x++) {
+ for (uint32_t y = 0; y < divisor; y++) {
+ // Calculate the subset dimensions
+ uint32_t subsetWidth = width / divisor;
+ uint32_t subsetHeight = height / divisor;
+ const int left = x * subsetWidth;
+ const int top = y * subsetHeight;
+
+ // Increase the size of the last subset in each row or column, when the
+ // divisor does not divide evenly into the image dimensions
+ subsetWidth += (x + 1 == divisor) ? (width % divisor) : 0;
+ subsetHeight += (y + 1 == divisor) ? (height % divisor) : 0;
+
+ // Increase the size of the subset in order to have a border on each side
+ const int decodeLeft = left - unscaledBorder;
+ const int decodeTop = top - unscaledBorder;
+ const uint32_t decodeWidth = subsetWidth + unscaledBorder * 2;
+ const uint32_t decodeHeight = subsetHeight + unscaledBorder * 2;
+ SkAutoTDelete<SkBitmap> bitmap(brd->decodeRegion(decodeLeft,
+ decodeTop, decodeWidth, decodeHeight, fSampleSize, colorType));
+ if (nullptr == bitmap.get() || colorType != bitmap->colorType()) {
+ return Error::Nonfatal("Cannot convert to color type.\n");
+ }
+
+ canvas->drawBitmapRect(*bitmap,
+ SkRect::MakeXYWH((SkScalar) scaledBorder, (SkScalar) scaledBorder,
+ (SkScalar) (subsetWidth / fSampleSize),
+ (SkScalar) (subsetHeight / fSampleSize)),
+ SkRect::MakeXYWH((SkScalar) (left / fSampleSize),
+ (SkScalar) (top / fSampleSize),
+ (SkScalar) (subsetWidth / fSampleSize),
+ (SkScalar) (subsetHeight / fSampleSize)),
+ nullptr);
+ }
+ }
+ return "";
+ }
+ default:
+ SkASSERT(false);
+ return "Error: Should not be reached.\n";
+ }
+}
+
+SkISize BRDSrc::size() const {
+ SkAutoTDelete<SkBitmapRegionDecoderInterface> brd(create_brd(fPath, fStrategy));
+ if (brd) {
+ return SkISize::Make(SkTMax(1, brd->width() / (int) fSampleSize),
+ SkTMax(1, brd->height() / (int) fSampleSize));
+ }
+ return SkISize::Make(0, 0);
+}
+
+static SkString get_scaled_name(const Path& path, float scale) {
+ return SkStringPrintf("%s_%.3f", SkOSPath::Basename(path.c_str()).c_str(), scale);
+}
+
+Name BRDSrc::name() const {
+ // We will replicate the names used by CodecSrc so that images can
+ // be compared in Gold.
+ if (1 == fSampleSize) {
+ return SkOSPath::Basename(fPath.c_str());
+ }
+ return get_scaled_name(fPath, BRDSrc::GetScale(fSampleSize));
+}
+
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+
CodecSrc::CodecSrc(Path path, Mode mode, DstColorType dstColorType, float scale)
: fPath(path)
, fMode(mode)
@@ -513,9 +670,8 @@ SkISize CodecSrc::size() const {
Name CodecSrc::name() const {
if (1.0f == fScale) {
return SkOSPath::Basename(fPath.c_str());
- } else {
- return SkStringPrintf("%s_%.3f", SkOSPath::Basename(fPath.c_str()).c_str(), fScale);
}
+ return get_scaled_name(fPath, fScale);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index efae87138e..fe6c91dfad 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -12,6 +12,7 @@
#include "SkBBHFactory.h"
#include "SkBBoxHierarchy.h"
#include "SkBitmap.h"
+#include "SkBitmapRegionDecoderInterface.h"
#include "SkCanvas.h"
#include "SkData.h"
#include "SkGPipe.h"
@@ -128,6 +129,34 @@ private:
float fScale;
};
+// Allows for testing of various implementations of Android's BitmapRegionDecoder
+class BRDSrc : public Src {
+public:
+ enum Mode {
+ // Decode the entire image as one region.
+ kFullImage_Mode,
+ // Splits the image into multiple regions using a divisor and decodes the regions
+ // separately. Also, this test adds a border of a few pixels to each of the regions
+ // that it is decoding. This tests the behavior when a client asks for a region that
+ // does not fully fit in the image.
+ kDivisor_Mode,
+ };
+
+ BRDSrc(Path, SkBitmapRegionDecoderInterface::Strategy, Mode, CodecSrc::DstColorType, uint32_t);
+
+ static float GetScale(uint32_t sampleSize) { return 1.0f / (float) sampleSize; }
+
+ Error draw(SkCanvas*) const override;
+ SkISize size() const override;
+ Name name() const override;
+ bool veto(SinkFlags) const override;
+private:
+ Path fPath;
+ SkBitmapRegionDecoderInterface::Strategy fStrategy;
+ Mode fMode;
+ CodecSrc::DstColorType fDstColorType;
+ uint32_t fSampleSize;
+};
class ImageSrc : public Src {
public:
diff --git a/gyp/dm.gypi b/gyp/dm.gypi
index 4ce2b4b580..ad73072bda 100644
--- a/gyp/dm.gypi
+++ b/gyp/dm.gypi
@@ -25,6 +25,7 @@
'libpng.gyp:libpng_static_when_possible',
'skia_lib.gyp:skia_lib',
'svg.gyp:svg',
+ 'tools.gyp:bitmap_region_decoder',
'tools.gyp:crash_handler',
'tools.gyp:proc_stats',
'tools.gyp:sk_tool_utils',
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index 463fa732a7..1c443229ee 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -18,6 +18,7 @@
'type': 'none',
'dependencies': [
'bench_pictures',
+ 'bitmap_region_decoder',
'chrome_fuzz',
'dump_record',
'filter',
@@ -50,6 +51,21 @@
],
},
{
+ 'target_name': 'bitmap_region_decoder',
+ 'type': 'static_library',
+ 'sources': [
+ '../tools/SkBitmapRegionCanvas.cpp',
+ '../tools/SkBitmapRegionDecoderInterface.cpp',
+ '../tools/SkBitmapRegionSampler.cpp',
+ ],
+ 'include_dirs': [
+ '../include/private'
+ ],
+ 'dependencies': [
+ 'skia_lib.gyp:skia_lib',
+ ],
+ },
+ {
'target_name': 'chrome_fuzz',
'type': 'executable',
'sources': [
diff --git a/tools/SkBitmapRegionCanvas.cpp b/tools/SkBitmapRegionCanvas.cpp
new file mode 100644
index 0000000000..2344d9020f
--- /dev/null
+++ b/tools/SkBitmapRegionCanvas.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapRegionCanvas.h"
+#include "SkCanvas.h"
+#include "SkScanlineDecoder.h"
+
+SkBitmapRegionCanvas::SkBitmapRegionCanvas(SkScanlineDecoder* decoder)
+ : INHERITED(decoder->getInfo().width(), decoder->getInfo().height())
+ , fDecoder(decoder)
+{}
+
+/*
+ * Chooses the correct image subset offsets and dimensions for the partial decode.
+ */
+static inline void set_subset_region(int inputOffset, int inputDimension,
+ int imageOriginalDimension, int* imageSubsetOffset, int* outOffset,
+ int* imageSubsetDimension) {
+
+ // This must be at least zero, we can't start decoding the image at a negative coordinate.
+ *imageSubsetOffset = SkTMax(0, inputOffset);
+
+ // If inputOffset is less than zero, we decode to an offset location in the output bitmap.
+ *outOffset = *imageSubsetOffset - inputOffset;
+
+ // Use imageSusetOffset to make sure we don't decode pixels past the edge of the image.
+ // Use outOffset to make sure we don't decode pixels past the edge of the region.
+ *imageSubsetDimension = SkTMin(imageOriginalDimension - *imageSubsetOffset,
+ inputDimension - *outOffset);
+}
+
+/*
+ * Returns a scaled dimension based on the original dimension and the sample size.
+ * TODO: Share this implementation with SkScaledCodec.
+ */
+static int get_scaled_dimension(int srcDimension, int sampleSize) {
+ if (sampleSize > srcDimension) {
+ return 1;
+ }
+ return srcDimension / sampleSize;
+}
+
+/*
+ * Three differences from the Android version:
+ * Returns a Skia bitmap instead of an Android bitmap.
+ * Android version attempts to reuse a recycled bitmap.
+ * Removed the options object and used parameters for color type and
+ * sample size.
+ */
+SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY,
+ int inputWidth, int inputHeight,
+ int sampleSize,
+ SkColorType dstColorType) {
+ // Reject color types not supported by this method
+ if (kIndex_8_SkColorType == dstColorType || kGray_8_SkColorType == dstColorType) {
+ SkDebugf("Error: Color type not supported.\n");
+ return nullptr;
+ }
+
+ // The client may not necessarily request a region that is fully within
+ // the image. We may need to do some calculation to determine what part
+ // of the image to decode.
+
+ // The left offset of the portion of the image we want, where zero
+ // indicates the left edge of the image.
+ int imageSubsetX;
+
+ // The size of the output bitmap is determined by the size of the
+ // requested region, not by the size of the intersection of the region
+ // and the image dimensions. If inputX is negative, we will need to
+ // place decoded pixels into the output bitmap starting at a left offset.
+ // If this is non-zero, imageSubsetX must be zero.
+ int outX;
+
+ // The width of the portion of the image that we will write to the output
+ // bitmap. If the region is not fully contained within the image, this
+ // will not be the same as inputWidth.
+ int imageSubsetWidth;
+ set_subset_region(inputX, inputWidth, this->width(), &imageSubsetX, &outX, &imageSubsetWidth);
+
+ // The top offset of the portion of the image we want, where zero
+ // indicates the top edge of the image.
+ int imageSubsetY;
+
+ // The size of the output bitmap is determined by the size of the
+ // requested region, not by the size of the intersection of the region
+ // and the image dimensions. If inputY is negative, we will need to
+ // place decoded pixels into the output bitmap starting at a top offset.
+ // If this is non-zero, imageSubsetY must be zero.
+ int outY;
+
+ // The height of the portion of the image that we will write to the output
+ // bitmap. If the region is not fully contained within the image, this
+ // will not be the same as inputHeight.
+ int imageSubsetHeight;
+ set_subset_region(inputY, inputHeight, this->height(), &imageSubsetY, &outY,
+ &imageSubsetHeight);
+
+ if (imageSubsetWidth <= 0 || imageSubsetHeight <= 0) {
+ SkDebugf("Error: Region must intersect part of the image.\n");
+ return nullptr;
+ }
+
+ // Create the image info for the decode
+ SkAlphaType dstAlphaType = fDecoder->getInfo().alphaType();
+ if (kUnpremul_SkAlphaType == dstAlphaType) {
+ dstAlphaType = kPremul_SkAlphaType;
+ }
+ SkImageInfo decodeInfo = SkImageInfo::Make(this->width(), this->height(),
+ dstColorType, dstAlphaType);
+
+ // Start the scanline decoder
+ SkCodec::Result r = fDecoder->start(decodeInfo);
+ if (SkCodec::kSuccess != r) {
+ SkDebugf("Error: Could not start scanline decoder.\n");
+ return nullptr;
+ }
+
+ // Allocate a bitmap for the unscaled decode
+ SkBitmap tmp;
+ SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), imageSubsetHeight);
+ if (!tmp.tryAllocPixels(tmpInfo)) {
+ SkDebugf("Error: Could not allocate pixels.\n");
+ return nullptr;
+ }
+
+ // Skip the unneeded rows
+ if (SkCodec::kSuccess != fDecoder->skipScanlines(imageSubsetY)) {
+ SkDebugf("Error: Failed to skip scanlines.\n");
+ return nullptr;
+ }
+
+ // Decode the necessary rows
+ SkCodec::Result result = fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight,
+ tmp.rowBytes());
+ switch (result) {
+ case SkCodec::kSuccess:
+ case SkCodec::kIncompleteInput:
+ break;
+ default:
+ SkDebugf("Error: Failed to get scanlines.\n");
+ return nullptr;
+ }
+
+ // Calculate the size of the output
+ const int outWidth = get_scaled_dimension(inputWidth, sampleSize);
+ const int outHeight = get_scaled_dimension(inputHeight, sampleSize);
+
+ // Initialize the destination bitmap
+ SkAutoTDelete<SkBitmap> bitmap(new SkBitmap());
+ SkImageInfo dstInfo = decodeInfo.makeWH(outWidth, outHeight);
+ if (!bitmap->tryAllocPixels(dstInfo)) {
+ SkDebugf("Error: Could not allocate pixels.\n");
+ return nullptr;
+ }
+
+ // Zero the bitmap if the region is not completely within the image.
+ // TODO (msarett): Can we make this faster by implementing it to only
+ // zero parts of the image that we won't overwrite with
+ // pixels?
+ // TODO (msarett): This could be skipped if memory is zero initialized.
+ // This would matter if this code is moved to Android and
+ // uses Android bitmaps.
+ if (0 != outX || 0 != outY ||
+ inputX + inputWidth > this->width() ||
+ inputY + inputHeight > this->height()) {
+ bitmap->eraseColor(0);
+ }
+
+ // Use a canvas to crop and scale to the destination bitmap
+ SkCanvas canvas(*bitmap);
+ // TODO (msarett): Maybe we can take advantage of the fact that SkRect uses floats?
+ SkRect src = SkRect::MakeXYWH((SkScalar) imageSubsetX, (SkScalar) 0,
+ (SkScalar) imageSubsetWidth, (SkScalar) imageSubsetHeight);
+ SkRect dst = SkRect::MakeXYWH((SkScalar) (outX / sampleSize), (SkScalar) (outY / sampleSize),
+ (SkScalar) get_scaled_dimension(imageSubsetWidth, sampleSize),
+ (SkScalar) get_scaled_dimension(imageSubsetHeight, sampleSize));
+ SkPaint paint;
+ // Overwrite the dst with the src pixels
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ // TODO (msarett): Test multiple filter qualities. kNone is the default.
+ canvas.drawBitmapRect(tmp, src, dst, &paint);
+
+ return bitmap.detach();
+}
diff --git a/tools/SkBitmapRegionCanvas.h b/tools/SkBitmapRegionCanvas.h
new file mode 100644
index 0000000000..96631d70d3
--- /dev/null
+++ b/tools/SkBitmapRegionCanvas.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapRegionDecoderInterface.h"
+#include "SkScanlineDecoder.h"
+
+/*
+ * This class implements SkBitmapRegionDecoder using an SkScanlineDecoder and
+ * an SkCanvas. It uses the scanline decoder to subset the height. It then
+ * will subset the width and scale by drawing to an SkCanvas.
+ */
+// FIXME (msarett): This implementation does not support WEBP, because WEBP
+// does not have a scanline decoder.
+class SkBitmapRegionCanvas : public SkBitmapRegionDecoderInterface {
+public:
+
+ /*
+ * Takes ownership of pointer to decoder
+ */
+ SkBitmapRegionCanvas(SkScanlineDecoder* decoder);
+
+ /*
+ * Three differences from the Android version:
+ * Returns a Skia bitmap instead of an Android bitmap.
+ * Android version attempts to reuse a recycled bitmap.
+ * Removed the options object and used parameters for color type and
+ * sample size.
+ */
+ SkBitmap* decodeRegion(int start_x, int start_y, int width, int height,
+ int sampleSize, SkColorType prefColorType) override;
+
+private:
+
+ SkAutoTDelete<SkScanlineDecoder> fDecoder;
+
+ typedef SkBitmapRegionDecoderInterface INHERITED;
+
+};
diff --git a/tools/SkBitmapRegionDecoderInterface.cpp b/tools/SkBitmapRegionDecoderInterface.cpp
new file mode 100644
index 0000000000..090f042ce3
--- /dev/null
+++ b/tools/SkBitmapRegionDecoderInterface.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapRegionCanvas.h"
+#include "SkBitmapRegionDecoderInterface.h"
+#include "SkBitmapRegionSampler.h"
+#include "SkScanlineDecoder.h"
+#include "SkImageDecoder.h"
+
+SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(
+ SkStreamRewindable* stream, Strategy strategy) {
+ switch (strategy) {
+ case kOriginal_Strategy: {
+ SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
+ int width, height;
+ if (nullptr == decoder) {
+ SkDebugf("Error: Could not create image decoder.\n");
+ return nullptr;
+ }
+ if (!decoder->buildTileIndex(stream, &width, &height)) {
+ SkDebugf("Error: Could not build tile index.\n");
+ delete decoder;
+ return nullptr;
+ }
+ return new SkBitmapRegionSampler(decoder, width, height);
+ }
+ case kCanvas_Strategy: {
+ SkScanlineDecoder* decoder = SkScanlineDecoder::NewFromStream(stream);
+ if (nullptr == decoder) {
+ SkDebugf("Error: Failed to create decoder.\n");
+ return nullptr;
+ }
+ switch (decoder->getScanlineOrder()) {
+ case SkScanlineDecoder::kTopDown_SkScanlineOrder:
+ case SkScanlineDecoder::kNone_SkScanlineOrder:
+ break;
+ default:
+ SkDebugf("Error: Scanline ordering not supported.\n");
+ return nullptr;
+ }
+ return new SkBitmapRegionCanvas(decoder);
+ }
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+}
diff --git a/tools/SkBitmapRegionDecoderInterface.h b/tools/SkBitmapRegionDecoderInterface.h
new file mode 100644
index 0000000000..bc28c2b2af
--- /dev/null
+++ b/tools/SkBitmapRegionDecoderInterface.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBitmapRegionDecoder_DEFINED
+#define SkBitmapRegionDecoder_DEFINED
+
+#include "SkBitmap.h"
+#include "SkStream.h"
+
+/*
+ * This class aims to provide an interface to test multiple implementations of
+ * SkBitmapRegionDecoder.
+ */
+class SkBitmapRegionDecoderInterface {
+public:
+
+ enum Strategy {
+ kCanvas_Strategy, // Draw to the canvas, uses SkCodec
+ kOriginal_Strategy, // Sampling, uses SkImageDecoder
+ // TODO (msarett): Add strategy for SkScaledCodec
+ };
+
+ /*
+ * @param stream Encoded image stream, takes ownership
+ * @param strategy Strategy used for scaling and subsetting
+ * @return Tries to create an SkBitmapRegionDecoder, returns NULL
+ * on failure
+ */
+ static SkBitmapRegionDecoderInterface* CreateBitmapRegionDecoder(
+ SkStreamRewindable* stream, Strategy strategy);
+
+ /*
+ * Decode a scaled region of the encoded image stream
+ *
+ * @param start_x X-coordinate of upper-left corner of region.
+ * This coordinate is unscaled, relative to the original dimensions.
+ * @param start_y Y-coordinate of upper-left corner of region.
+ * This coordinate is unscaled, relative to the original dimensions.
+ * @param width Width of the region to decode.
+ * This distance is unscaled, relative to the original dimensions.
+ * @param height Height of the region to decode.
+ * This distance is unscaled, relative to the original dimensions.
+ * @param sampleSize An integer downscaling factor for the decode.
+ * @param colorType Preferred output colorType.
+ * New implementations should return NULL if they do not support
+ * decoding to this color type.
+ * The old kOriginal_Strategy will decode to a default color type
+ * if this color type is unsupported.
+ * @return Pointer to a bitmap of the decoded region on success, NULL on
+ * failure.
+ */
+ virtual SkBitmap* decodeRegion(int start_x, int start_y, int width,
+ int height, int sampleSize,
+ SkColorType colorType) = 0;
+
+ int width() const { return fWidth; }
+ int height() const { return fHeight; }
+
+ virtual ~SkBitmapRegionDecoderInterface() {}
+
+protected:
+
+ SkBitmapRegionDecoderInterface(int width, int height)
+ : fWidth(width)
+ , fHeight(height)
+ {}
+
+private:
+ const int fWidth;
+ const int fHeight;
+};
+
+#endif
diff --git a/tools/SkBitmapRegionSampler.cpp b/tools/SkBitmapRegionSampler.cpp
new file mode 100644
index 0000000000..98c183daac
--- /dev/null
+++ b/tools/SkBitmapRegionSampler.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapRegionSampler.h"
+
+SkBitmapRegionSampler::SkBitmapRegionSampler(SkImageDecoder* decoder, int width,
+ int height)
+ : INHERITED(width, height)
+ , fDecoder(decoder)
+{}
+
+/*
+ * Three differences from the Android version:
+ * Returns a Skia bitmap instead of an Android bitmap.
+ * Android version attempts to reuse a recycled bitmap.
+ * Removed the options object and used parameters for color type and
+ * sample size.
+ */
+SkBitmap* SkBitmapRegionSampler::decodeRegion(int start_x, int start_y,
+ int width, int height,
+ int sampleSize,
+ SkColorType prefColorType) {
+ // Match Android's default settings
+ fDecoder->setDitherImage(true);
+ fDecoder->setPreferQualityOverSpeed(false);
+ fDecoder->setRequireUnpremultipliedColors(false);
+ fDecoder->setSampleSize(sampleSize);
+
+ // kAlpha8 is the legacy representation of kGray8 used by SkImageDecoder
+ if (kGray_8_SkColorType == prefColorType) {
+ prefColorType = kAlpha_8_SkColorType;
+ }
+
+ SkIRect region;
+ region.fLeft = start_x;
+ region.fTop = start_y;
+ region.fRight = start_x + width;
+ region.fBottom = start_y + height;
+
+ SkAutoTDelete<SkBitmap> bitmap(new SkBitmap());
+ if (!fDecoder->decodeSubset(bitmap.get(), region, prefColorType)) {
+ SkDebugf("Error: decodeRegion failed.\n");
+ return nullptr;
+ }
+ return bitmap.detach();
+}
diff --git a/tools/SkBitmapRegionSampler.h b/tools/SkBitmapRegionSampler.h
new file mode 100644
index 0000000000..d2f738d3b5
--- /dev/null
+++ b/tools/SkBitmapRegionSampler.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmap.h"
+#include "SkBitmapRegionDecoderInterface.h"
+#include "SkImageDecoder.h"
+#include "SkTemplates.h"
+
+/*
+ * This class aims to duplicate the current implementation of
+ * SkBitmapRegionDecoder in Android.
+ */
+class SkBitmapRegionSampler : public SkBitmapRegionDecoderInterface {
+public:
+
+ /*
+ * Takes ownership of pointer to decoder
+ */
+ SkBitmapRegionSampler(SkImageDecoder* decoder, int width, int height);
+
+ /*
+ * Three differences from the Android version:
+ * Returns a Skia bitmap instead of an Android bitmap.
+ * Android version attempts to reuse a recycled bitmap.
+ * Removed the options object and used parameters for color type and
+ * sample size.
+ */
+ SkBitmap* decodeRegion(int start_x, int start_y, int width, int height,
+ int sampleSize, SkColorType prefColorType) override;
+
+private:
+
+ SkAutoTDelete<SkImageDecoder> fDecoder;
+
+ typedef SkBitmapRegionDecoderInterface INHERITED;
+
+};