Implementation of SkBitmapRegionDecoder using SkAndroidCodec

Includes testing in DM and nanobench

BUG=skia:

Review URL: https://codereview.chromium.org/1402863002
This commit is contained in:
msarett 2015-10-22 07:29:19 -07:00 committed by Commit bot
parent ed0935a28a
commit 26ad17b8f8
11 changed files with 305 additions and 96 deletions

View File

@ -30,6 +30,9 @@ BitmapRegionDecoderBench::BitmapRegionDecoderBench(const char* baseName, SkData*
case SkBitmapRegionDecoderInterface::kCanvas_Strategy:
strategyName = "Canvas";
break;
case SkBitmapRegionDecoderInterface::kAndroidCodec_Strategy:
strategyName = "AndroidCodec";
break;
default:
SkASSERT(false);
strategyName = "";
@ -54,8 +57,7 @@ bool BitmapRegionDecoderBench::isSuitableFor(Backend backend) {
}
void BitmapRegionDecoderBench::onDelayedSetup() {
SkStreamRewindable* stream = new SkMemoryStream(fData);
fBRD.reset(SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(stream, fStrategy));
fBRD.reset(SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(fData, fStrategy));
}
void BitmapRegionDecoderBench::onDraw(int n, SkCanvas* canvas) {

View File

@ -596,9 +596,8 @@ static bool valid_subset_bench(const SkString& path, SkColorType colorType, bool
static bool valid_brd_bench(SkData* encoded, SkBitmapRegionDecoderInterface::Strategy strategy,
SkColorType colorType, uint32_t sampleSize, uint32_t minOutputSize, int* width,
int* height) {
SkStreamRewindable* stream = new SkMemoryStream(encoded);
SkAutoTDelete<SkBitmapRegionDecoderInterface> brd(
SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(stream, strategy));
SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(encoded, strategy));
if (nullptr == brd.get()) {
// This is indicates that subset decoding is not supported for a particular image format.
return false;
@ -962,8 +961,9 @@ public:
SkBitmapRegionDecoderInterface::Strategy fStrategy;
const char* fName;
} strategies[] = {
{ SkBitmapRegionDecoderInterface::kOriginal_Strategy, "BRD" },
{ SkBitmapRegionDecoderInterface::kCanvas_Strategy, "BRD_canvas" },
{ SkBitmapRegionDecoderInterface::kOriginal_Strategy, "BRD" },
{ SkBitmapRegionDecoderInterface::kCanvas_Strategy, "BRD_canvas" },
{ SkBitmapRegionDecoderInterface::kAndroidCodec_Strategy, "BRD_android_codec" },
};
// We intend to create benchmarks that model the use cases in

View File

@ -392,6 +392,15 @@ static bool brd_color_type_supported(SkBitmapRegionDecoderInterface::Strategy st
default:
return false;
}
case SkBitmapRegionDecoderInterface::kAndroidCodec_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;
@ -408,6 +417,9 @@ static void push_brd_src(Path path, SkBitmapRegionDecoderInterface::Strategy str
case SkBitmapRegionDecoderInterface::kOriginal_Strategy:
folder.append("brd_sample");
break;
case SkBitmapRegionDecoderInterface::kAndroidCodec_Strategy:
folder.append("brd_android_codec");
break;
default:
SkASSERT(false);
return;
@ -450,7 +462,8 @@ static void push_brd_srcs(Path path) {
const SkBitmapRegionDecoderInterface::Strategy strategies[] = {
SkBitmapRegionDecoderInterface::kCanvas_Strategy,
SkBitmapRegionDecoderInterface::kOriginal_Strategy
SkBitmapRegionDecoderInterface::kOriginal_Strategy,
SkBitmapRegionDecoderInterface::kAndroidCodec_Strategy,
};
const uint32_t sampleSizes[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
@ -458,14 +471,14 @@ static void push_brd_srcs(Path path) {
// 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,
CodecSrc::kGetFromCanvas_DstColorType,
CodecSrc::kIndex8_Always_DstColorType,
CodecSrc::kGrayscale_Always_DstColorType,
};
const BRDSrc::Mode modes[] = {
BRDSrc::kFullImage_Mode,
BRDSrc::kDivisor_Mode
BRDSrc::kFullImage_Mode,
BRDSrc::kDivisor_Mode,
};
for (SkBitmapRegionDecoderInterface::Strategy strategy : strategies) {

View File

@ -88,8 +88,7 @@ static SkBitmapRegionDecoderInterface* create_brd(Path path,
if (!encoded) {
return NULL;
}
return SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(new SkMemoryStream(encoded),
strategy);
return SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(encoded, strategy);
}
Error BRDSrc::draw(SkCanvas* canvas) const {

View File

@ -52,6 +52,7 @@
'type': 'static_library',
'sources': [
'../tools/SkBitmapRegionCanvas.cpp',
'../tools/SkBitmapRegionCodec.cpp',
'../tools/SkBitmapRegionDecoderInterface.cpp',
'../tools/SkBitmapRegionSampler.cpp',
],

View File

@ -15,30 +15,6 @@ SkBitmapRegionCanvas::SkBitmapRegionCanvas(SkCodec* decoder)
, fDecoder(decoder)
{}
/*
* Chooses the correct image subset offsets and dimensions for the partial decode.
*
* @return true if the subset is completely contained within the image
* false otherwise
*/
static bool 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);
return (*outOffset == 0) && (*imageSubsetDimension == inputDimension);
}
/*
* Three differences from the Android version:
* Returns a Skia bitmap instead of an Android bitmap.
@ -56,48 +32,25 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY,
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;
// Fix the input sampleSize if necessary.
if (sampleSize < 1) {
sampleSize = 1;
}
// 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.
// requested subset, not by the size of the intersection of the subset
// and the image dimensions.
// If inputX is negative, we will need to place decoded pixels into the
// output bitmap starting at a left offset. Call this outX.
// If outX is non-zero, subsetX must be zero.
// If inputY is negative, we will need to place decoded pixels into the
// output bitmap starting at a top offset. Call this outY.
// If outY is non-zero, subsetY 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;
bool imageContainsEntireSubset = 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;
imageContainsEntireSubset &= set_subset_region(inputY, inputHeight, this->height(),
&imageSubsetY, &outY, &imageSubsetHeight);
if (imageSubsetWidth <= 0 || imageSubsetHeight <= 0) {
SkCodecPrintf("Error: Region must intersect part of the image.\n");
SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
SubsetType type = adjust_subset_rect(fDecoder->getInfo().dimensions(), &subset, &outX, &outY);
if (SubsetType::kOutside_SubsetType == type) {
return nullptr;
}
@ -108,7 +61,7 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY,
}
SkImageInfo decodeInfo = SkImageInfo::Make(this->width(), this->height(),
dstColorType, dstAlphaType);
// Start the scanline decoder
SkCodec::Result r = fDecoder->startScanlineDecode(decodeInfo);
if (SkCodec::kSuccess != r) {
@ -118,20 +71,20 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY,
// Allocate a bitmap for the unscaled decode
SkBitmap tmp;
SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), imageSubsetHeight);
SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), subset.height());
if (!tmp.tryAllocPixels(tmpInfo)) {
SkCodecPrintf("Error: Could not allocate pixels.\n");
return nullptr;
}
// Skip the unneeded rows
if (!fDecoder->skipScanlines(imageSubsetY)) {
if (!fDecoder->skipScanlines(subset.y())) {
SkCodecPrintf("Error: Failed to skip scanlines.\n");
return nullptr;
}
// Decode the necessary rows
fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight, tmp.rowBytes());
fDecoder->getScanlines(tmp.getAddr(0, 0), subset.height(), tmp.rowBytes());
// Calculate the size of the output
const int outWidth = get_scaled_dimension(inputWidth, sampleSize);
@ -152,18 +105,18 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY,
// 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 (!imageContainsEntireSubset) {
if (SubsetType::kPartiallyInside_SubsetType == type) {
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 src = SkRect::MakeXYWH((SkScalar) subset.x(), (SkScalar) 0,
(SkScalar) subset.width(), (SkScalar) subset.height());
SkRect dst = SkRect::MakeXYWH((SkScalar) (outX / sampleSize), (SkScalar) (outY / sampleSize),
(SkScalar) get_scaled_dimension(imageSubsetWidth, sampleSize),
(SkScalar) get_scaled_dimension(imageSubsetHeight, sampleSize));
(SkScalar) get_scaled_dimension(subset.width(), sampleSize),
(SkScalar) get_scaled_dimension(subset.height(), sampleSize));
SkPaint paint;
// Overwrite the dst with the src pixels
paint.setXfermodeMode(SkXfermode::kSrc_Mode);

View File

@ -0,0 +1,143 @@
/*
* 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 "SkBitmapRegionCodec.h"
#include "SkAndroidCodec.h"
#include "SkCodecPriv.h"
#include "SkCodecTools.h"
SkBitmapRegionCodec::SkBitmapRegionCodec(SkAndroidCodec* codec)
: INHERITED(codec->getInfo().width(), codec->getInfo().height())
, fCodec(codec)
{}
/*
* 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.
*/
// FIXME: Should this function should take in SkIRect?
SkBitmap* SkBitmapRegionCodec::decodeRegion(int inputX, int inputY, int inputWidth, int inputHeight,
int sampleSize, SkColorType dstColorType) {
// Fix the input sampleSize if necessary.
if (sampleSize < 1) {
sampleSize = 1;
}
// The size of the output bitmap is determined by the size of the
// requested subset, not by the size of the intersection of the subset
// and the image dimensions.
// If inputX is negative, we will need to place decoded pixels into the
// output bitmap starting at a left offset. Call this outX.
// If outX is non-zero, subsetX must be zero.
// If inputY is negative, we will need to place decoded pixels into the
// output bitmap starting at a top offset. Call this outY.
// If outY is non-zero, subsetY must be zero.
int outX;
int outY;
SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
SubsetType type = adjust_subset_rect(fCodec->getInfo().dimensions(), &subset, &outX, &outY);
if (SubsetType::kOutside_SubsetType == type) {
return nullptr;
}
// Ask the codec for a scaled subset
if (!fCodec->getSupportedSubset(&subset)) {
SkCodecPrintf("Error: Could not get subset.\n");
return nullptr;
}
SkISize scaledSize = fCodec->getSampledSubsetDimensions(sampleSize, subset);
// Create the image info for the decode
SkAlphaType dstAlphaType = fCodec->getInfo().alphaType();
if (kUnpremul_SkAlphaType == dstAlphaType) {
dstAlphaType = kPremul_SkAlphaType;
}
SkImageInfo decodeInfo = SkImageInfo::Make(scaledSize.width(), scaledSize.height(),
dstColorType, dstAlphaType);
// Construct a color table for the decode if necessary
SkAutoTUnref<SkColorTable> colorTable(nullptr);
SkPMColor* colorPtr = nullptr;
int* colorCountPtr = nullptr;
int maxColors = 256;
SkPMColor colors[256];
if (kIndex_8_SkColorType == dstColorType) {
// TODO (msarett): This performs a copy that is unnecessary since
// we have not yet initialized the color table.
// And then we need to use a const cast to get
// a pointer to the color table that we can
// modify during the decode. We could alternatively
// perform the decode before creating the bitmap and
// the color table. We still would need to copy the
// colors into the color table after the decode.
colorTable.reset(new SkColorTable(colors, maxColors));
colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
colorCountPtr = &maxColors;
}
// Initialize the destination bitmap
SkAutoTDelete<SkBitmap> bitmap(new SkBitmap());
int scaledOutX = 0;
int scaledOutY = 0;
int scaledOutWidth = scaledSize.width();
int scaledOutHeight = scaledSize.height();
if (SubsetType::kPartiallyInside_SubsetType == type) {
scaledOutX = outX / sampleSize;
scaledOutY = outY / sampleSize;
// We need to be safe here because getSupportedSubset() may have modified the subset.
const int extraX = SkTMax(0, inputWidth - outX - subset.width());
const int extraY = SkTMax(0, inputHeight - outY - subset.height());
const int scaledExtraX = extraX / sampleSize;
const int scaledExtraY = extraY / sampleSize;
scaledOutWidth += scaledOutX + scaledExtraX;
scaledOutHeight += scaledOutY + scaledExtraY;
}
SkImageInfo outInfo = decodeInfo.makeWH(scaledOutWidth, scaledOutHeight);
if (!bitmap->tryAllocPixels(outInfo, nullptr, colorTable.get())) {
SkCodecPrintf("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 (SubsetType::kPartiallyInside_SubsetType == type) {
void* pixels = bitmap->getPixels();
size_t bytes = outInfo.getSafeSize(bitmap->rowBytes());
memset(pixels, 0, bytes);
}
// Decode into the destination bitmap
SkAndroidCodec::AndroidOptions options;
options.fSampleSize = sampleSize;
options.fSubset = &subset;
options.fColorPtr = colorPtr;
options.fColorCount = colorCountPtr;
void* dst = bitmap->getAddr(scaledOutX, scaledOutY);
size_t rowBytes = bitmap->rowBytes();
SkCodec::Result result = fCodec->getAndroidPixels(decodeInfo, dst, rowBytes, &options);
if (SkCodec::kSuccess != result && SkCodec::kIncompleteInput != result) {
SkCodecPrintf("Error: Could not get pixels.\n");
return nullptr;
}
return bitmap.detach();
}
bool SkBitmapRegionCodec::conversionSupported(SkColorType colorType) {
// FIXME: Call virtual function when it lands.
SkImageInfo info = SkImageInfo::Make(0, 0, colorType, fCodec->getInfo().alphaType(),
fCodec->getInfo().profileType());
return conversion_possible(info, fCodec->getInfo());
}

View File

@ -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 "SkAndroidCodec.h"
/*
* This class implements SkBitmapRegionDecoder using an SkAndroidCodec.
*/
class SkBitmapRegionCodec : public SkBitmapRegionDecoderInterface {
public:
/*
* Takes ownership of pointer to codec
*/
SkBitmapRegionCodec(SkAndroidCodec* codec);
/*
* 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;
bool conversionSupported(SkColorType colorType) override;
private:
SkAutoTDelete<SkAndroidCodec> fCodec;
typedef SkBitmapRegionDecoderInterface INHERITED;
};

View File

@ -6,24 +6,26 @@
*/
#include "SkBitmapRegionCanvas.h"
#include "SkBitmapRegionCodec.h"
#include "SkBitmapRegionDecoderInterface.h"
#include "SkBitmapRegionSampler.h"
#include "SkAndroidCodec.h"
#include "SkCodec.h"
#include "SkCodecPriv.h"
#include "SkImageDecoder.h"
SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder(
SkStreamRewindable* stream, Strategy strategy) {
SkAutoTDelete<SkStreamRewindable> streamDeleter(stream);
SkData* data, Strategy strategy) {
switch (strategy) {
case kOriginal_Strategy: {
SkAutoTDelete<SkStreamRewindable> stream(new SkMemoryStream(data));
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
int width, height;
if (nullptr == decoder) {
SkCodecPrintf("Error: Could not create image decoder.\n");
return nullptr;
}
if (!decoder->buildTileIndex(streamDeleter.detach(), &width, &height)) {
if (!decoder->buildTileIndex(stream.detach(), &width, &height)) {
SkCodecPrintf("Error: Could not build tile index.\n");
delete decoder;
return nullptr;
@ -31,7 +33,7 @@ SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegi
return new SkBitmapRegionSampler(decoder, width, height);
}
case kCanvas_Strategy: {
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(streamDeleter.detach()));
SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data));
if (nullptr == codec) {
SkCodecPrintf("Error: Failed to create decoder.\n");
return nullptr;
@ -46,6 +48,14 @@ SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegi
}
return new SkBitmapRegionCanvas(codec.detach());
}
case kAndroidCodec_Strategy: {
SkAutoTDelete<SkAndroidCodec> codec = SkAndroidCodec::NewFromData(data);
if (NULL == codec) {
SkCodecPrintf("Error: Failed to create codec.\n");
return NULL;
}
return new SkBitmapRegionCodec(codec.detach());
}
default:
SkASSERT(false);
return nullptr;

View File

@ -19,19 +19,18 @@ class SkBitmapRegionDecoderInterface {
public:
enum Strategy {
kCanvas_Strategy, // Draw to the canvas, uses SkCodec
kOriginal_Strategy, // Sampling, uses SkImageDecoder
// TODO (msarett): Add strategy for SkScaledCodec
kCanvas_Strategy, // Draw to the canvas, uses SkCodec
kOriginal_Strategy, // Sampling, uses SkImageDecoder
kAndroidCodec_Strategy, // Uses SkAndroidCodec for scaling and subsetting
};
/*
* @param stream Encoded image stream, takes ownership
* @param data Refs the data while this object exists, unrefs on destruction
* @param strategy Strategy used for scaling and subsetting
* @return Tries to create an SkBitmapRegionDecoder, returns NULL
* on failure
* @return Tries to create an SkBitmapRegionDecoder, returns NULL on failure
*/
static SkBitmapRegionDecoderInterface* CreateBitmapRegionDecoder(
SkStreamRewindable* stream, Strategy strategy);
SkData* data, Strategy strategy);
/*
* Decode a scaled region of the encoded image stream

View File

@ -12,4 +12,52 @@ inline float get_scale_from_sample_size(uint32_t sampleSize) {
return 1.0f / (float) sampleSize;
}
enum SubsetType {
kFullyInside_SubsetType,
kPartiallyInside_SubsetType,
kOutside_SubsetType,
};
/*
* Corrects image subset offsets and dimensions in order to perform a valid decode.
* Also indicates if the image subset should be placed at an offset within the
* output bitmap.
*
* Values of output variables are undefined if the SubsetType is kInvalid.
*
* @param imageDims Original image dimensions.
* @param subset As input, the subset that the client requested.
* As output, the image subset that we will decode.
* @param outX The left offset of the image subset within the output bitmap.
* @param outY The top offset of the image subset within the output bitmap.
*
* @return An indication of how the subset is contained in the image.
* If the return value is kInvalid, values of output variables are undefined.
*/
inline SubsetType adjust_subset_rect(const SkISize& imageDims, SkIRect* subset, int* outX,
int* outY) {
// These must be at least zero, we can't start decoding the image at a negative coordinate.
int left = SkTMax(0, subset->fLeft);
int top = SkTMax(0, subset->fTop);
// If input offsets are less than zero, we decode to an offset location in the output bitmap.
*outX = left - subset->fLeft;
*outY = top - subset->fTop;
// Make sure we don't decode pixels past the edge of the image or past the edge of the subset.
int width = SkTMin(imageDims.width() - left, subset->width() - *outX);
int height = SkTMin(imageDims.height() - top, subset->height() - *outY);
if (width <= 0 || height <= 0) {
return SubsetType::kOutside_SubsetType;
}
subset->setXYWH(left, top, width, height);
if ((*outX != 0) || (*outY != 0) || (width != subset->width()) ||
(height != subset->height())) {
return SubsetType::kPartiallyInside_SubsetType;
}
return SubsetType::kFullyInside_SubsetType;
}
#endif // SkCodecTools_DEFINED