From 76f755e6d54a32f9887ad254ce59a3a62f28bde4 Mon Sep 17 00:00:00 2001 From: msarett Date: Fri, 4 Sep 2015 13:00:49 -0700 Subject: Provides various implementations of Android's SkBitmapRegionDecoder. Implements testing in DM for these implementations. nanobench testing will follow after this. BUG=skia: Review URL: https://codereview.chromium.org/1288963002 --- src/utils/SkBitmapRegionCanvas.cpp | 189 +++++++++++++++++++++++++++ src/utils/SkBitmapRegionCanvas.h | 43 ++++++ src/utils/SkBitmapRegionDecoderInterface.cpp | 51 ++++++++ src/utils/SkBitmapRegionDecoderInterface.h | 77 +++++++++++ src/utils/SkBitmapRegionSampler.cpp | 50 +++++++ src/utils/SkBitmapRegionSampler.h | 41 ++++++ 6 files changed, 451 insertions(+) create mode 100644 src/utils/SkBitmapRegionCanvas.cpp create mode 100644 src/utils/SkBitmapRegionCanvas.h create mode 100644 src/utils/SkBitmapRegionDecoderInterface.cpp create mode 100644 src/utils/SkBitmapRegionDecoderInterface.h create mode 100644 src/utils/SkBitmapRegionSampler.cpp create mode 100644 src/utils/SkBitmapRegionSampler.h (limited to 'src') diff --git a/src/utils/SkBitmapRegionCanvas.cpp b/src/utils/SkBitmapRegionCanvas.cpp new file mode 100644 index 0000000000..2344d9020f --- /dev/null +++ b/src/utils/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 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/src/utils/SkBitmapRegionCanvas.h b/src/utils/SkBitmapRegionCanvas.h new file mode 100644 index 0000000000..96631d70d3 --- /dev/null +++ b/src/utils/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 fDecoder; + + typedef SkBitmapRegionDecoderInterface INHERITED; + +}; diff --git a/src/utils/SkBitmapRegionDecoderInterface.cpp b/src/utils/SkBitmapRegionDecoderInterface.cpp new file mode 100644 index 0000000000..090f042ce3 --- /dev/null +++ b/src/utils/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/src/utils/SkBitmapRegionDecoderInterface.h b/src/utils/SkBitmapRegionDecoderInterface.h new file mode 100644 index 0000000000..bc28c2b2af --- /dev/null +++ b/src/utils/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/src/utils/SkBitmapRegionSampler.cpp b/src/utils/SkBitmapRegionSampler.cpp new file mode 100644 index 0000000000..98c183daac --- /dev/null +++ b/src/utils/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 bitmap(new SkBitmap()); + if (!fDecoder->decodeSubset(bitmap.get(), region, prefColorType)) { + SkDebugf("Error: decodeRegion failed.\n"); + return nullptr; + } + return bitmap.detach(); +} diff --git a/src/utils/SkBitmapRegionSampler.h b/src/utils/SkBitmapRegionSampler.h new file mode 100644 index 0000000000..d2f738d3b5 --- /dev/null +++ b/src/utils/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 fDecoder; + + typedef SkBitmapRegionDecoderInterface INHERITED; + +}; -- cgit v1.2.3