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; |