diff options
author | msarett <msarett@google.com> | 2015-09-08 15:35:32 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-09-08 15:35:32 -0700 |
commit | a5783aeff042ccaf517e50dee3660a4925f5f694 (patch) | |
tree | 17144d306e184bd3d6178e863769c5febbd59e22 | |
parent | 036fd8e6f66b53cf87a5f91083cae82f0aeb3635 (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.cpp | 127 | ||||
-rw-r--r-- | dm/DMSrcSink.cpp | 160 | ||||
-rw-r--r-- | dm/DMSrcSink.h | 29 | ||||
-rw-r--r-- | gyp/dm.gypi | 1 | ||||
-rw-r--r-- | gyp/tools.gyp | 16 | ||||
-rw-r--r-- | tools/SkBitmapRegionCanvas.cpp | 189 | ||||
-rw-r--r-- | tools/SkBitmapRegionCanvas.h | 43 | ||||
-rw-r--r-- | tools/SkBitmapRegionDecoderInterface.cpp | 51 | ||||
-rw-r--r-- | tools/SkBitmapRegionDecoderInterface.h | 77 | ||||
-rw-r--r-- | tools/SkBitmapRegionSampler.cpp | 50 | ||||
-rw-r--r-- | tools/SkBitmapRegionSampler.h | 41 |
11 files changed, 775 insertions, 9 deletions
@@ -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; + +}; |