Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Unified Diff: src/codec/SkScaledCodec.cpp

Issue 1395183003: Add scaled subset API to SkCodec (Closed) Base URL: https://skia.googlesource.com/skia.git@split0
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: src/codec/SkScaledCodec.cpp
diff --git a/src/codec/SkScaledCodec.cpp b/src/codec/SkScaledCodec.cpp
index 6b50a09f268c3889e99831098980486aef235af7..0a5265404e5457c77783b56fa2acd1f21abbbedb 100644
--- a/src/codec/SkScaledCodec.cpp
+++ b/src/codec/SkScaledCodec.cpp
@@ -51,16 +51,28 @@ bool SkScaledCodec::onRewind() {
return fCodec->onRewind();
}
-static SkISize best_scaled_dimensions(const SkISize& origDims, const SkISize& nativeDims,
- const SkISize& scaledCodecDims, float desiredScale) {
- if (nativeDims == scaledCodecDims) {
- // does not matter which to return if equal. Return here to skip below calculations
- return nativeDims;
+static bool is_in_subset(int coord, int offset, int length) {
scroggo 2015/10/12 20:47:07 nit: It seems like this is a more generic function
+ if (coord < offset || coord >= offset + length) {
scroggo 2015/10/12 20:47:07 nit: This can just be: return coord >= offset &
+ return false;
}
+ return true;
+}
+
+static int get_sample_size(float scale) {
+ return SkScalarRoundToInt(1.0f / scale);
+}
+
+static bool use_native_scaling(const SkISize& origDims, const SkISize& nativeDims,
+ const SkISize& sampledDims, float desiredScale) {
+ if (nativeDims == sampledDims) {
+ // If both options are the same, choose native scaling.
+ return true;
+ }
+
float idealWidth = origDims.width() * desiredScale;
float idealHeight = origDims.height() * desiredScale;
- // calculate difference between native dimensions and ideal dimensions
+ // Calculate difference between native dimensions and ideal dimensions.
float nativeWDiff = SkTAbs(idealWidth - nativeDims.width());
float nativeHDiff = SkTAbs(idealHeight - nativeDims.height());
float nativeDiff = nativeWDiff + nativeHDiff;
@@ -68,42 +80,76 @@ static SkISize best_scaled_dimensions(const SkISize& origDims, const SkISize& na
// Native scaling is preferred to sampling. If we can scale natively to
// within one of the ideal value, we should choose to scale natively.
if (nativeWDiff < 1.0f && nativeHDiff < 1.0f) {
- return nativeDims;
+ return true;
}
- // calculate difference between scaledCodec dimensions and ideal dimensions
- float scaledCodecWDiff = SkTAbs(idealWidth - scaledCodecDims.width());
- float scaledCodecHDiff = SkTAbs(idealHeight - scaledCodecDims.height());
- float scaledCodecDiff = scaledCodecWDiff + scaledCodecHDiff;
+ // Calculate difference between sampled dimensions and ideal dimensions.
+ float sampledWDiff = SkTAbs(idealWidth - sampledDims.width());
+ float sampledHDiff = SkTAbs(idealHeight - sampledDims.height());
+ float sampledDiff = sampledWDiff + sampledHDiff;
- // return dimensions closest to ideal dimensions.
- // If the differences are equal, return nativeDims, as native scaling is more efficient.
- return nativeDiff > scaledCodecDiff ? scaledCodecDims : nativeDims;
+ // Use native scaling if it is closer to the ideal scale.
+ return nativeDiff <= sampledDiff;
}
/*
* Return a valid set of output dimensions for this decoder, given an input scale
*/
SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const {
- SkISize nativeDimensions = fCodec->getScaledDimensions(desiredScale);
- // support scaling down by integer numbers. Ex: 1/2, 1/3, 1/4 ...
- SkISize scaledCodecDimensions;
- if (desiredScale > 0.5f) {
- // sampleSize = 1
- scaledCodecDimensions = fCodec->getInfo().dimensions();
+ SkISize nativeDims = fCodec->getScaledDimensions(desiredScale);
+ int sampleSize = get_sample_size(desiredScale);
+ SkISize sampledDims = SkISize::Make(
+ get_scaled_dimension(this->getInfo().width(), sampleSize),
+ get_scaled_dimension(this->getInfo().height(), sampleSize));
+
+ if (use_native_scaling(this->getInfo().dimensions(), nativeDims, sampledDims, desiredScale)) {
+ return nativeDims;
}
- // sampleSize determines the step size between samples
- // Ex: sampleSize = 2, sample every second pixel in x and y directions
- int sampleSize = int ((1.0f / desiredScale) + 0.5f);
- int scaledWidth = get_scaled_dimension(this->getInfo().width(), sampleSize);
- int scaledHeight = get_scaled_dimension(this->getInfo().height(), sampleSize);
+ return sampledDims;
+}
+
+bool SkScaledCodec::onGetScaledSubsetDimensions(float desiredScale, const Options& options) const {
+ SkISize nativeDims = fCodec->getScaledDimensions(desiredScale);
+ int sampleSize = get_sample_size(desiredScale);
+ SkISize sampledDims = SkISize::Make(
+ get_scaled_dimension(this->getInfo().width(), sampleSize),
+ get_scaled_dimension(this->getInfo().height(), sampleSize));
+
+ if (use_native_scaling(this->getInfo().dimensions(), nativeDims, sampledDims, desiredScale)) {
+ // Set the scaled dimensions and calculate subset size using native scaling.
+ *(options.fScaledDimensions) = nativeDims;
+ float widthScale = ((float) nativeDims.width()) / ((float) this->getInfo().width());
+ float heightScale = ((float) nativeDims.height()) / ((float) this->getInfo().height());
+ // Notice that we may round the size of the subset up to 1. This means that we must
+ // floor the left and top offsets to ensure that we do not suggest a subset that is
+ // off the edge of the image.
+ *(options.fScaledSubset) = SkIRect::MakeXYWH(
+ int (((float) options.fSubset->left()) * widthScale),
+ int (((float) options.fSubset->top()) * heightScale),
+ SkTMax(1, SkScalarRoundToInt(((float) options.fSubset->width()) * widthScale)),
+ SkTMax(1, SkScalarRoundToInt(((float) options.fSubset->height()) * heightScale)));
+ return true;
+ }
- // Return the calculated output dimensions for the given scale
- scaledCodecDimensions = SkISize::Make(scaledWidth, scaledHeight);
+ // Set the scaled dimensions and calculate subset size using sampling.
+ *(options.fScaledDimensions) = sampledDims;
+ *(options.fScaledSubset) = SkIRect::MakeXYWH(
+ options.fSubset->left() / sampleSize,
+ options.fSubset->top() / sampleSize,
+ get_scaled_dimension(options.fSubset->width(), sampleSize),
+ get_scaled_dimension(options.fSubset->height(), sampleSize));
+
+ // FIXME (msarett): Despite our best efforts, one pixel scaled subsets may be off
+ // the edge of the image because we round down when calculating
+ // sampledDims and always round the subset size up to one. Maybe
+ // we should always return false when the sampleSize is greater
+ // than the width or height?
+ if (!is_valid_subset(*(options.fScaledSubset), *(options.fScaledDimensions))) {
+ return false;
+ }
- return best_scaled_dimensions(this->getInfo().dimensions(), nativeDimensions,
- scaledCodecDimensions, desiredScale);
+ return true;
}
// check if scaling to dstInfo size from srcInfo size using sampleSize is possible
@@ -114,6 +160,7 @@ static bool scaling_supported(const SkISize& dstDim, const SkISize& srcDim,
const int dstHeight = dstDim.height();
const int srcWidth = srcDim.width();
const int srcHeight = srcDim.height();
+
// only support down sampling, not up sampling
if (dstWidth > srcWidth || dstHeight > srcHeight) {
return false;
@@ -153,6 +200,29 @@ bool SkScaledCodec::onDimensionsSupported(const SkISize& dim) {
return scaling_supported(dim, this->getInfo().dimensions(), &sampleX, &sampleY);
}
+bool SkScaledCodec::onScaledSubsetSupported(const Options& options, bool isScanlineDecode) {
+ if (fCodec->dimensionsSupported(*options.fScaledDimensions)) {
+ return true;
+ }
+
+ int sampleX;
+ int sampleY;
+ if (!scaling_supported(*options.fScaledDimensions, this->getInfo().dimensions(), &sampleX,
+ &sampleY)) {
+ return false;
+ }
+
+ int subsetSampleX;
+ int subsetSampleY;
+ if (!scaling_supported(options.fScaledSubset->size(), options.fSubset->size(), &subsetSampleX,
+ &subsetSampleY)) {
+ return false;
+ }
+
+ // TODO (msarett): Is it necessary/correct to be this strict?
+ return sampleX == subsetSampleX && sampleY == subsetSampleY;
+}
+
// calculates sampleSize in x and y direction
void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim,
int* sampleXPtr, int* sampleYPtr) {
@@ -186,6 +256,7 @@ void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcD
} else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) {
sampleY = sampleX;
}
+ // FIXME (msarett): Should this never be reached?
}
}
@@ -197,47 +268,114 @@ void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcD
}
}
-// TODO: Implement subsetting in onGetPixels which works when and when not sampling
+SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& scaledSubsetInfo, void* dst,
+ size_t rowBytes, const Options& options, SkPMColor ctable[], int* ctableCount,
+ int* rowsDecoded) {
+
+ // There are various values of Rect, Size, and Info used in this function. I think it is
+ // useful to go ahead and define what they mean.
+ // orig_: Refers to the original image size.
+ // scaledSubset_: Refers to the size of the final output. This can match the original
+ // dimensions, be a subset of the original dimensions, be a scaled version
+ // of the original dimensions, or be a scaled subset of the original dimensions.
+ // subset_: Refers to the size of the unscaled subset in terms of the original image
+ // dimensions. If this is not a subset decode, this will match the original
+ // image dimensions.
+ // scaled_: Refers to the scaled size of the original image, ignoring any subsetting.
+ // If we are not scaling, this will match the original dimensions.
+ SkISize origSize = this->getInfo().dimensions();
+ SkIRect subsetRect;
+ SkISize scaledSize;
+ SkIRect scaledSubsetRect;
+ if (nullptr == options.fSubset) {
+ // This is not a subset decode.
+ SkASSERT(!options.fScaledDimensions);
+ SkASSERT(!options.fScaledSubset);
+
+ // Set the "subset" to the full image dimensions.
+ subsetRect = SkIRect::MakeSize(origSize);
+
+ // This may be scaled or unscaled, depending on if scaledSize matches origSize.
+ scaledSize = scaledSubsetInfo.dimensions();
+ scaledSubsetRect = SkIRect::MakeSize(scaledSize);
+ } else {
+ // This is a subset decode.
+ if (!is_valid_subset(*options.fSubset, origSize)) {
+ return kInvalidParameters;
+ }
-SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst,
- size_t rowBytes, const Options& options,
- SkPMColor ctable[], int* ctableCount,
- int* rowsDecoded) {
+ subsetRect = *(options.fSubset);
+ if (!options.fScaledDimensions) {
+ // This is an unscaled subset decode.
+ SkASSERT(!options.fScaledSubset);
+ SkASSERT(scaledSubsetInfo.dimensions() == subsetRect.size());
+
+ scaledSize = origSize;
+ scaledSubsetRect = subsetRect;
+ } else {
+ // This is a scaled subset decode.
+ SkASSERT(options.fScaledSubset);
+ SkASSERT(scaledSubsetInfo.dimensions() == options.fScaledSubset->size());
+ if (!is_valid_subset(*options.fScaledSubset, *options.fScaledDimensions)) {
+ return kInvalidParameters;
+ }
- if (options.fSubset) {
- // Subsets are not supported.
- return kUnimplemented;
+ scaledSize = *options.fScaledDimensions;
+ scaledSubsetRect = *options.fScaledSubset;
+ }
}
- if (fCodec->dimensionsSupported(requestedInfo.dimensions())) {
- // Make sure that the parent class does not fill on an incomplete decode, since
- // fCodec will take care of filling the uninitialized lines.
- *rowsDecoded = requestedInfo.height();
- return fCodec->getPixels(requestedInfo, dst, rowBytes, &options, ctable, ctableCount);
+ // The native decoder needs the scaled size of the entire image to check if it can decode to
+ // the requested scale.
+ SkImageInfo scaledInfo = scaledSubsetInfo.makeWH(scaledSize.width(), scaledSize.height());
+
+ // Create options for the native decoder.
+ Options nativeOptions = options;
+ // The scanline decoder is not aware of any subsetting in the y-dimension.
+ SkIRect nativeScanlineSubset = SkIRect::MakeXYWH(scaledSubsetRect.left(), 0,
+ scaledSubsetRect.width(), scaledSize.height());
+ nativeOptions.fSubset = &nativeScanlineSubset;
+ nativeOptions.fScaledDimensions = &scaledSize;
+ nativeOptions.fScaledSubset = &scaledSubsetRect;
+
+ Result result = fCodec->startScanlineDecode(scaledInfo, &nativeOptions, ctable, ctableCount);
+ switch (result) {
+ case kSuccess:
+ return this->nativeDecode(scaledInfo, dst, rowBytes, scaledSubsetRect,
+ options.fZeroInitialized, rowsDecoded);
+ case kInvalidScale:
+ // We will attempt to scale by sampling below.
+ break;
+ default:
+ return result;
}
- // scaling requested
+ // Try to provide the scale using sampling.
int sampleX;
int sampleY;
- if (!scaling_supported(requestedInfo.dimensions(), fCodec->getInfo().dimensions(),
- &sampleX, &sampleY)) {
- // onDimensionsSupported would have returned false, meaning we should never reach here.
+ if (!scaling_supported(scaledSubsetInfo.dimensions(), subsetRect.size(), &sampleX, &sampleY)) {
+ // onScaledSubsetSupported would have returned false, meaning we should never reach here.
SkASSERT(false);
return kInvalidScale;
}
- // set first sample pixel in y direction
- const int Y0 = get_start_coord(sampleY);
-
- const int dstHeight = requestedInfo.height();
- const int srcWidth = fCodec->getInfo().width();
- const int srcHeight = fCodec->getInfo().height();
-
- const SkImageInfo info = requestedInfo.makeWH(srcWidth, srcHeight);
-
- Result result = fCodec->startScanlineDecode(info, &options, ctable, ctableCount);
-
+ // Create the image info that will be passed to fCodec. The native codec has no knowledge that
+ // we are sampling, so we pass the original width and the original height.
+ const SkImageInfo decodeInfo = scaledSubsetInfo.makeWH(this->getInfo().width(),
+ this->getInfo().height());
+
+ // Create default options for the native decoder.
+ Options sampledOptions = options;
+ // The scanline decoder is not aware of any subsetting in the y-dimension.
+ SkIRect sampledScanlineSubset = SkIRect::MakeXYWH(subsetRect.left(), 0, subsetRect.width(),
+ this->getInfo().height());
+ sampledOptions.fSubset = &sampledScanlineSubset;
+ sampledOptions.fScaledDimensions = nullptr;
+ sampledOptions.fScaledSubset = nullptr;
+
+ result = fCodec->startScanlineDecode(decodeInfo, &sampledOptions, ctable, ctableCount);
if (kSuccess != result) {
+ SkASSERT(kInvalidScale != result);
return result;
}
@@ -245,18 +383,73 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi
if (!sampler) {
return kUnimplemented;
}
-
- if (sampler->setSampleX(sampleX) != requestedInfo.width()) {
+ if (sampler->setSampleX(sampleX) != scaledSubsetInfo.width()) {
+ SkASSERT(false);
return kInvalidScale;
}
+ return this->sampledDecode(scaledSubsetInfo, dst, rowBytes, subsetRect, sampleX, sampleY,
+ options.fZeroInitialized, rowsDecoded);
+}
+
+SkCodec::Result SkScaledCodec::nativeDecode(const SkImageInfo& scaledInfo, void* dst,
+ size_t rowBytes, const SkIRect& scaledSubsetRect, ZeroInitialized zeroInit,
+ int* rowsDecoded) {
+
+ int scaledSubsetTop = scaledSubsetRect.top();
+ int scaledSubsetHeight = scaledSubsetRect.height();
+ switch (fCodec->getScanlineOrder()) {
+ case SkCodec::kTopDown_SkScanlineOrder:
+ case SkCodec::kBottomUp_SkScanlineOrder:
+ case SkCodec::kNone_SkScanlineOrder: {
+ if (!fCodec->skipScanlines(scaledSubsetTop)) {
+ *rowsDecoded = 0;
+ return kIncompleteInput;
+ }
+
+ uint32_t decodedLines = fCodec->getScanlines(dst, scaledSubsetHeight,
+ rowBytes);
+ if (decodedLines != scaledSubsetHeight) {
+ *rowsDecoded = decodedLines;
+ return kIncompleteInput;
+ }
+ return kSuccess;
+ }
+ case SkCodec::kOutOfOrder_SkScanlineOrder: {
+ for (int y = 0; y < scaledInfo.height(); y++) {
+ int dstY = fCodec->nextScanline();
+ if (is_in_subset(dstY, scaledSubsetTop, scaledSubsetHeight)) {
+ void* dstPtr = SkTAddOffset<void>(dst, rowBytes * (dstY - scaledSubsetTop));
+ if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) {
+ *rowsDecoded = y + 1;
+ return kIncompleteInput;
+ }
+ } else {
+ if (!fCodec->skipScanlines(1)) {
+ *rowsDecoded = y + 1;
+ return kIncompleteInput;
+ }
+ }
+ }
+ return kSuccess;
+ }
+ }
+}
+
+SkCodec::Result SkScaledCodec::sampledDecode(const SkImageInfo& scaledSubsetInfo, void* dst,
+ size_t rowBytes, const SkIRect& subsetRect, int sampleX, int sampleY,
+ ZeroInitialized zeroInit, int* rowsDecoded) {
+
+ // Set first sample pixel in y direction.
+ int y0 = get_start_coord(sampleY);
+ int scaledSubsetHeight = scaledSubsetInfo.height();
switch(fCodec->getScanlineOrder()) {
- case SkCodec::kTopDown_SkScanlineOrder: {
- if (!fCodec->skipScanlines(Y0)) {
+ case SkCodec::kTopDown_SkScanlineOrder:
+ if (!fCodec->skipScanlines(y0 + subsetRect.top())) {
*rowsDecoded = 0;
return kIncompleteInput;
}
- for (int y = 0; y < dstHeight; y++) {
+ for (int y = 0; y < scaledSubsetHeight; y++) {
if (1 != fCodec->getScanlines(dst, 1, rowBytes)) {
// The failed call to getScanlines() will take care of
// filling the failed row, so we indicate that we have
@@ -264,7 +457,7 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi
*rowsDecoded = y + 1;
return kIncompleteInput;
}
- if (y < dstHeight - 1) {
+ if (y < scaledSubsetHeight - 1) {
if (!fCodec->skipScanlines(sampleY - 1)) {
*rowsDecoded = y + 1;
return kIncompleteInput;
@@ -273,14 +466,13 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi
dst = SkTAddOffset<void>(dst, rowBytes);
}
return kSuccess;
- }
case SkCodec::kBottomUp_SkScanlineOrder:
case SkCodec::kOutOfOrder_SkScanlineOrder: {
Result result = kSuccess;
int y;
- for (y = 0; y < srcHeight; y++) {
+ for (y = 0; y < this->getInfo().height(); y++) {
int srcY = fCodec->nextScanline();
- if (is_coord_necessary(srcY, sampleY, dstHeight)) {
+ if (is_coord_necessary(srcY, sampleY, scaledSubsetHeight, subsetRect.top())) {
void* dstPtr = SkTAddOffset<void>(dst, rowBytes * get_dst_coord(srcY, sampleY));
if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) {
result = kIncompleteInput;
@@ -297,38 +489,42 @@ SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, voi
// We handle filling uninitialized memory here instead of in the parent class.
// The parent class does not know that we are sampling.
if (kIncompleteInput == result) {
- const uint32_t fillValue = fCodec->getFillValue(requestedInfo.colorType(),
- requestedInfo.alphaType());
- for (; y < srcHeight; y++) {
+ const uint32_t fillValue = fCodec->getFillValue(scaledSubsetInfo.colorType(),
+ scaledSubsetInfo.alphaType());
+ for (; y < this->getInfo().height(); y++) {
int srcY = fCodec->outputScanline(y);
- if (is_coord_necessary(srcY, sampleY, dstHeight)) {
+ if (is_coord_necessary(srcY, sampleY, scaledSubsetHeight, subsetRect.top())) {
void* dstRow = SkTAddOffset<void>(dst,
rowBytes * get_dst_coord(srcY, sampleY));
- SkSampler::Fill(requestedInfo.makeWH(requestedInfo.width(), 1), dstRow,
- rowBytes, fillValue, options.fZeroInitialized);
+ SkSampler::Fill(scaledSubsetInfo.makeWH(scaledSubsetInfo.width(), 1),
+ dstRow, rowBytes, fillValue, zeroInit);
}
}
- *rowsDecoded = dstHeight;
+ *rowsDecoded = scaledSubsetInfo.height();
}
return result;
}
case SkCodec::kNone_SkScanlineOrder: {
- SkAutoMalloc storage(srcHeight * rowBytes);
+ SkAutoMalloc storage(subsetRect.height() * rowBytes);
uint8_t* storagePtr = static_cast<uint8_t*>(storage.get());
- int scanlines = fCodec->getScanlines(storagePtr, srcHeight, rowBytes);
- storagePtr += Y0 * rowBytes;
- scanlines -= Y0;
+ if (!fCodec->skipScanlines(subsetRect.top())) {
+ *rowsDecoded = 0;
+ return kIncompleteInput;
+ }
+ int scanlines = fCodec->getScanlines(storagePtr, subsetRect.height(), rowBytes);
+ scanlines -= y0;
+ storagePtr += y0 * rowBytes;
int y = 0;
- while (y < dstHeight && scanlines > 0) {
- memcpy(dst, storagePtr, rowBytes);
+ while (y < scaledSubsetHeight && scanlines > 0) {
+ memcpy(dst, storagePtr, scaledSubsetInfo.minRowBytes());
storagePtr += sampleY * rowBytes;
dst = SkTAddOffset<void>(dst, rowBytes);
scanlines -= sampleY;
y++;
}
- if (y < dstHeight) {
+ if (y < scaledSubsetHeight) {
// fCodec has already handled filling uninitialized memory.
- *rowsDecoded = dstHeight;
+ *rowsDecoded = scaledSubsetInfo.height();
return kIncompleteInput;
}
return kSuccess;

Powered by Google App Engine
This is Rietveld 408576698