Index: src/codec/SkScaledCodec.cpp |
diff --git a/src/codec/SkScaledCodec.cpp b/src/codec/SkScaledCodec.cpp |
index 6b50a09f268c3889e99831098980486aef235af7..c2be2d6ede8a5525db44b21c84c2315c63fe8b99 100644 |
--- a/src/codec/SkScaledCodec.cpp |
+++ b/src/codec/SkScaledCodec.cpp |
@@ -5,344 +5,210 @@ |
* found in the LICENSE file. |
*/ |
+#include "SkCodec.h" |
#include "SkCodecPriv.h" |
-#include "SkScaledCodec.h" |
-#include "SkStream.h" |
-#include "SkWebpCodec.h" |
+#include "SkSampledCodec.h" |
+// FIXME: Rename this file to SkSampledCodec.cpp |
-SkCodec* SkScaledCodec::NewFromStream(SkStream* stream) { |
- SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream)); |
- if (nullptr == codec) { |
- return nullptr; |
- } |
- |
- switch (codec->getEncodedFormat()) { |
- case kWEBP_SkEncodedFormat: |
- // Webp codec supports scaling and subsetting natively |
- return codec.detach(); |
- case kPNG_SkEncodedFormat: |
- case kJPEG_SkEncodedFormat: |
- // wrap in new SkScaledCodec |
- return new SkScaledCodec(codec.detach()); |
- default: |
- // FIXME: SkScaledCodec is temporarily disabled for other formats |
- // while focusing on the formats that are supported by |
- // BitmapRegionDecoder. |
- return nullptr; |
- } |
-} |
- |
-SkCodec* SkScaledCodec::NewFromData(SkData* data) { |
- if (!data) { |
- return nullptr; |
- } |
- return NewFromStream(new SkMemoryStream(data)); |
-} |
- |
-SkScaledCodec::SkScaledCodec(SkCodec* codec) |
- : INHERITED(codec->getInfo(), nullptr) |
+SkSampledCodec::SkSampledCodec(SkCodec* codec) |
+ : INHERITED(codec->getInfo()) |
, fCodec(codec) |
{} |
-SkScaledCodec::~SkScaledCodec() {} |
- |
-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; |
+SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const { |
+ // Fast path for when we are not scaling. |
+ if (1 == sampleSize) { |
+ return fCodec->getInfo().dimensions(); |
} |
- float idealWidth = origDims.width() * desiredScale; |
- float idealHeight = origDims.height() * desiredScale; |
- // calculate difference between native dimensions and ideal dimensions |
- float nativeWDiff = SkTAbs(idealWidth - nativeDims.width()); |
- float nativeHDiff = SkTAbs(idealHeight - nativeDims.height()); |
- float nativeDiff = nativeWDiff + nativeHDiff; |
+ const int width = fCodec->getInfo().width(); |
+ const int height = fCodec->getInfo().height(); |
+ // Check if the codec can provide the scaling natively. |
+ float scale = get_scale_from_sample_size(sampleSize); |
+ SkSize idealSize = SkSize::Make(scale * (float) width, scale * (float) height); |
+ SkISize nativeSize = fCodec->getScaledDimensions(scale); |
+ float widthDiff = SkTAbs(((float) nativeSize.width()) - idealSize.width()); |
+ float heightDiff = SkTAbs(((float) nativeSize.height()) - idealSize.height()); |
// 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; |
+ if (widthDiff < 1.0f && heightDiff < 1.0f) { |
+ return nativeSize; |
} |
- // calculate difference between scaledCodec dimensions and ideal dimensions |
- float scaledCodecWDiff = SkTAbs(idealWidth - scaledCodecDims.width()); |
- float scaledCodecHDiff = SkTAbs(idealHeight - scaledCodecDims.height()); |
- float scaledCodecDiff = scaledCodecWDiff + scaledCodecHDiff; |
- |
- // return dimensions closest to ideal dimensions. |
- // If the differences are equal, return nativeDims, as native scaling is more efficient. |
- return nativeDiff > scaledCodecDiff ? scaledCodecDims : nativeDims; |
+ // Provide the scaling by sampling. |
+ return SkISize::Make(get_scaled_dimension(width, sampleSize), |
+ get_scaled_dimension(height, sampleSize)); |
} |
-/* |
- * 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(); |
- } |
- // 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 the calculated output dimensions for the given scale |
- scaledCodecDimensions = SkISize::Make(scaledWidth, scaledHeight); |
- |
- return best_scaled_dimensions(this->getInfo().dimensions(), nativeDimensions, |
- scaledCodecDimensions, desiredScale); |
-} |
- |
-// check if scaling to dstInfo size from srcInfo size using sampleSize is possible |
-static bool scaling_supported(const SkISize& dstDim, const SkISize& srcDim, |
- int* sampleX, int* sampleY) { |
- SkScaledCodec::ComputeSampleSize(dstDim, srcDim, sampleX, sampleY); |
- const int dstWidth = dstDim.width(); |
- 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; |
- } |
- // check that srcWidth is scaled down by an integer value |
- if (get_scaled_dimension(srcWidth, *sampleX) != dstWidth) { |
- return false; |
- } |
- // check that src height is scaled down by an integer value |
- if (get_scaled_dimension(srcHeight, *sampleY) != dstHeight) { |
- return false; |
- } |
- // sampleX and sampleY should be equal unless the original sampleSize requested was larger |
- // than srcWidth or srcHeight. If so, the result of this is dstWidth or dstHeight = 1. |
- // This functionality allows for tall thin images to still be scaled down by scaling factors. |
- if (*sampleX != *sampleY){ |
- if (1 != dstWidth && 1 != dstHeight) { |
- return false; |
+SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, |
+ size_t rowBytes, AndroidOptions& options) { |
+ // Create an Options struct for the codec. |
+ SkCodec::Options codecOptions; |
+ codecOptions.fZeroInitialized = options.fZeroInitialized; |
+ |
+ SkIRect* subset = options.fSubset; |
+ if (!subset || subset->size() == fCodec->getInfo().dimensions()) { |
+ if (fCodec->dimensionsSupported(info.dimensions())) { |
+ return fCodec->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr, |
+ options.fColorCount); |
} |
- } |
- return true; |
-} |
-bool SkScaledCodec::onDimensionsSupported(const SkISize& dim) { |
- // Check with fCodec first. No need to call the non-virtual version, which |
- // just checks if it matches the original, since a match means this method |
- // will not be called. |
- if (fCodec->onDimensionsSupported(dim)) { |
- return true; |
+ // If the native codec does not support the requested scale, scale by sampling. |
+ return this->sampledDecode(info, pixels, rowBytes, options); |
} |
- // FIXME: These variables are unused, but are needed by scaling_supported. |
- // This class could also cache these values, and avoid calling this in |
- // onGetPixels (since getPixels already calls it). |
- int sampleX; |
- int sampleY; |
- return scaling_supported(dim, this->getInfo().dimensions(), &sampleX, &sampleY); |
-} |
- |
-// calculates sampleSize in x and y direction |
-void SkScaledCodec::ComputeSampleSize(const SkISize& dstDim, const SkISize& srcDim, |
- int* sampleXPtr, int* sampleYPtr) { |
- int srcWidth = srcDim.width(); |
- int dstWidth = dstDim.width(); |
- int srcHeight = srcDim.height(); |
- int dstHeight = dstDim.height(); |
- |
- int sampleX = srcWidth / dstWidth; |
- int sampleY = srcHeight / dstHeight; |
- |
- // only support down sampling, not up sampling |
- SkASSERT(dstWidth <= srcWidth); |
- SkASSERT(dstHeight <= srcHeight); |
+ // We are performing a subset decode. |
+ int sampleSize = options.fSampleSize; |
+ SkISize scaledSize = this->onGetSampledDimensions(sampleSize); |
+ if (!fCodec->dimensionsSupported(scaledSize)) { |
+ // If the native codec does not support the requested scale, scale by sampling. |
+ return this->sampledDecode(info, pixels, rowBytes, options); |
+ } |
- // sampleX and sampleY should be equal unless the original sampleSize requested was |
- // larger than srcWidth or srcHeight. |
- // If so, the result of this is dstWidth or dstHeight = 1. This functionality |
- // allows for tall thin images to still be scaled down by scaling factors. |
+ // Calculate the scaled subset bounds. |
+ int scaledSubsetX = subset->x() / sampleSize; |
+ int scaledSubsetY = subset->y() / sampleSize; |
+ int scaledSubsetWidth = info.width(); |
+ int scaledSubsetHeight = info.height(); |
- if (sampleX != sampleY){ |
- if (1 != dstWidth && 1 != dstHeight) { |
+ // Start the scanline decode. |
+ SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth, |
+ scaledSize.height()); |
+ codecOptions.fSubset = &scanlineSubset; |
+ SkCodec::Result result = fCodec->startScanlineDecode(info.makeWH(scaledSize.width(), |
+ scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount); |
+ if (SkCodec::kSuccess != result) { |
+ return result; |
+ } |
- // rounding during onGetScaledDimensions can cause different sampleSizes |
- // Ex: srcWidth = 79, srcHeight = 20, sampleSize = 10 |
- // dstWidth = 7, dstHeight = 2, sampleX = 79/7 = 11, sampleY = 20/2 = 10 |
- // correct for this rounding by comparing width to sampleY and height to sampleX |
+ // At this point, we are only concerned with subsetting. Either no scale was |
+ // requested, or the fCodec is handling the scale. |
+ switch (fCodec->getScanlineOrder()) { |
+ case SkCodec::kTopDown_SkScanlineOrder: |
+ case SkCodec::kNone_SkScanlineOrder: { |
+ if (!fCodec->skipScanlines(scaledSubsetY)) { |
+ fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, |
+ scaledSubsetHeight, 0); |
+ return SkCodec::kIncompleteInput; |
+ } |
- if (get_scaled_dimension(srcWidth, sampleY) == dstWidth) { |
- sampleX = sampleY; |
- } else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) { |
- sampleY = sampleX; |
+ int decodedLines = fCodec->getScanlines(pixels, scaledSubsetHeight, rowBytes); |
+ if (decodedLines != scaledSubsetHeight) { |
+ return SkCodec::kIncompleteInput; |
} |
+ return SkCodec::kSuccess; |
} |
- } |
- |
- if (sampleXPtr) { |
- *sampleXPtr = sampleX; |
- } |
- if (sampleYPtr) { |
- *sampleYPtr = sampleY; |
+ default: |
+ SkASSERT(false); |
+ return SkCodec::kUnimplemented; |
} |
} |
-// TODO: Implement subsetting in onGetPixels which works when and when not sampling |
-SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst, |
- size_t rowBytes, const Options& options, |
- SkPMColor ctable[], int* ctableCount, |
- int* rowsDecoded) { |
+SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, |
+ size_t rowBytes, AndroidOptions& options) { |
+ // Create options struct for the codec. |
+ SkCodec::Options sampledOptions; |
+ sampledOptions.fZeroInitialized = options.fZeroInitialized; |
+ // Check if there is a subset. |
+ SkIRect subset; |
+ int subsetY = 0; |
+ int subsetWidth = fCodec->getInfo().width(); |
+ int subsetHeight = fCodec->getInfo().height(); |
if (options.fSubset) { |
- // Subsets are not supported. |
- return kUnimplemented; |
- } |
- |
- 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); |
- } |
- |
- // scaling requested |
- 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. |
- 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); |
- |
- if (kSuccess != result) { |
+ // We will need to know about subsetting in the y-dimension in order to use the |
+ // scanline decoder. |
+ SkIRect* subsetPtr = options.fSubset; |
+ subsetY = subsetPtr->y(); |
+ subsetWidth = subsetPtr->width(); |
+ subsetHeight = subsetPtr->height(); |
+ |
+ // The scanline decoder only needs to be aware of subsetting in the x-dimension. |
+ subset.setXYWH(subsetPtr->x(), 0, subsetWidth, fCodec->getInfo().height()); |
+ sampledOptions.fSubset = ⊂ |
+ } |
+ |
+ // Start the scanline decode. |
+ SkCodec::Result result = fCodec->startScanlineDecode( |
+ info.makeWH(fCodec->getInfo().width(), fCodec->getInfo().height()), &sampledOptions, |
+ options.fColorPtr, options.fColorCount); |
+ if (SkCodec::kSuccess != result) { |
return result; |
} |
SkSampler* sampler = fCodec->getSampler(true); |
if (!sampler) { |
- return kUnimplemented; |
+ return SkCodec::kUnimplemented; |
} |
- if (sampler->setSampleX(sampleX) != requestedInfo.width()) { |
- return kInvalidScale; |
+ // Since we guarantee that output dimensions are always at least one (even if the sampleSize |
+ // is greater than a given dimension), the input sampleSize is not always the sampleSize that |
+ // we use in practice. |
+ const int sampleX = subsetWidth / info.width(); |
+ const int sampleY = subsetHeight / info.height(); |
+ if (sampler->setSampleX(sampleX) != info.width()) { |
+ return SkCodec::kInvalidScale; |
+ } |
+ if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) { |
+ return SkCodec::kInvalidScale; |
} |
+ const int samplingOffsetY = get_start_coord(sampleY); |
+ const int startY = samplingOffsetY + subsetY; |
+ int dstHeight = info.height(); |
switch(fCodec->getScanlineOrder()) { |
case SkCodec::kTopDown_SkScanlineOrder: { |
- if (!fCodec->skipScanlines(Y0)) { |
- *rowsDecoded = 0; |
- return kIncompleteInput; |
+ if (!fCodec->skipScanlines(startY)) { |
+ fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, |
+ dstHeight, 0); |
+ return SkCodec::kIncompleteInput; |
} |
+ void* pixelPtr = pixels; |
for (int y = 0; y < dstHeight; 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 |
- // decoded (y + 1) rows. |
- *rowsDecoded = y + 1; |
- return kIncompleteInput; |
- } |
- if (y < dstHeight - 1) { |
- if (!fCodec->skipScanlines(sampleY - 1)) { |
- *rowsDecoded = y + 1; |
- return kIncompleteInput; |
- } |
- } |
- 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++) { |
- int srcY = fCodec->nextScanline(); |
- if (is_coord_necessary(srcY, sampleY, dstHeight)) { |
- void* dstPtr = SkTAddOffset<void>(dst, rowBytes * get_dst_coord(srcY, sampleY)); |
- if (1 != fCodec->getScanlines(dstPtr, 1, rowBytes)) { |
- result = kIncompleteInput; |
- break; |
- } |
- } else { |
- if (!fCodec->skipScanlines(1)) { |
- result = kIncompleteInput; |
- break; |
- } |
+ if (1 != fCodec->getScanlines(pixelPtr, 1, rowBytes)) { |
+ fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, |
+ dstHeight, y + 1); |
+ return SkCodec::kIncompleteInput; |
} |
- } |
- |
- // 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++) { |
- int srcY = fCodec->outputScanline(y); |
- if (is_coord_necessary(srcY, sampleY, dstHeight)) { |
- void* dstRow = SkTAddOffset<void>(dst, |
- rowBytes * get_dst_coord(srcY, sampleY)); |
- SkSampler::Fill(requestedInfo.makeWH(requestedInfo.width(), 1), dstRow, |
- rowBytes, fillValue, options.fZeroInitialized); |
- } |
+ int linesToSkip = SkTMin(sampleY - 1, dstHeight - y - 1); |
+ if (!fCodec->skipScanlines(linesToSkip)) { |
+ fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, |
+ dstHeight, y + 1); |
+ return SkCodec::kIncompleteInput; |
} |
- *rowsDecoded = dstHeight; |
+ pixelPtr = SkTAddOffset<void>(pixelPtr, rowBytes); |
} |
- return result; |
+ return SkCodec::kSuccess; |
} |
case SkCodec::kNone_SkScanlineOrder: { |
- SkAutoMalloc storage(srcHeight * rowBytes); |
+ const int linesNeeded = subsetHeight - samplingOffsetY; |
+ SkAutoMalloc storage(linesNeeded * rowBytes); |
uint8_t* storagePtr = static_cast<uint8_t*>(storage.get()); |
- int scanlines = fCodec->getScanlines(storagePtr, srcHeight, rowBytes); |
- storagePtr += Y0 * rowBytes; |
- scanlines -= Y0; |
- int y = 0; |
- while (y < dstHeight && scanlines > 0) { |
- memcpy(dst, storagePtr, rowBytes); |
+ |
+ if (!fCodec->skipScanlines(startY)) { |
+ fCodec->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, |
+ dstHeight, 0); |
+ return SkCodec::kIncompleteInput; |
+ } |
+ int scanlines = fCodec->getScanlines(storagePtr, linesNeeded, rowBytes); |
+ |
+ for (int y = 0; y < dstHeight; y++) { |
+ memcpy(pixels, storagePtr, info.minRowBytes()); |
storagePtr += sampleY * rowBytes; |
- dst = SkTAddOffset<void>(dst, rowBytes); |
- scanlines -= sampleY; |
- y++; |
+ pixels = SkTAddOffset<void>(pixels, rowBytes); |
} |
- if (y < dstHeight) { |
+ |
+ if (scanlines < dstHeight) { |
// fCodec has already handled filling uninitialized memory. |
- *rowsDecoded = dstHeight; |
- return kIncompleteInput; |
+ return SkCodec::kIncompleteInput; |
} |
- return kSuccess; |
+ return SkCodec::kSuccess; |
} |
default: |
SkASSERT(false); |
- return kUnimplemented; |
+ return SkCodec::kUnimplemented; |
} |
} |
- |
-uint32_t SkScaledCodec::onGetFillValue(SkColorType colorType, SkAlphaType alphaType) const { |
- return fCodec->onGetFillValue(colorType, alphaType); |
-} |
- |
-SkCodec::SkScanlineOrder SkScaledCodec::onGetScanlineOrder() const { |
- return fCodec->onGetScanlineOrder(); |
-} |