Chromium Code Reviews| 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; |