// check if scaling to dstInfo size from srcInfo size using sampleSize is possible static bool scaling_supported(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo, int* sampleX, int* sampleY) { SkScaledCodec::ComputeSampleSize(dstInfo, srcInfo, sampleX, sampleY); const int dstWidth = dstInfo.width(); const int dstHeight = dstInfo.height(); const int srcWidth = srcInfo.width(); const int srcHeight = srcInfo.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; } } return true; }
// calculates sampleSize in x and y direction void SkScaledCodec::ComputeSampleSize(const SkImageInfo& dstInfo, const SkImageInfo& srcInfo, int* sampleXPtr, int* sampleYPtr) { int srcWidth = srcInfo.width(); int dstWidth = dstInfo.width(); int srcHeight = srcInfo.height(); int dstHeight = dstInfo.height(); int sampleX = srcWidth / dstWidth; int sampleY = srcHeight / dstHeight; // only support down sampling, not up sampling SkASSERT(dstWidth <= srcWidth); SkASSERT(dstHeight <= srcHeight); // 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) { // 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 if (get_scaled_dimension(srcWidth, sampleY) == dstWidth) { sampleX = sampleY; } else if (get_scaled_dimension(srcHeight, sampleX) == dstHeight) { sampleY = sampleX; } } } if (sampleXPtr) { *sampleXPtr = sampleX; } if (sampleYPtr) { *sampleYPtr = sampleY; } }
/* * Return a valid set of output dimensions for this decoder, given an input scale */ SkISize SkScaledCodec::onGetScaledDimensions(float desiredScale) const { SkISize nativeDimensions = fScanlineDecoder->getScaledDimensions(desiredScale); // support scaling down by integer numbers. Ex: 1/2, 1/3, 1/4 ... SkISize scaledCodecDimensions; if (desiredScale > 0.5f) { // sampleSize = 1 scaledCodecDimensions = fScanlineDecoder->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 / desiredScale); 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); }
SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const { if (!is_valid_sample_size(sampleSize)) { return SkISize::Make(0, 0); } // We require that the input subset is a subset that is supported by SkAndroidCodec. // We test this by calling getSupportedSubset() and verifying that no modifications // are made to the subset. SkIRect copySubset = subset; if (!this->getSupportedSubset(©Subset) || copySubset != subset) { return SkISize::Make(0, 0); } // If the subset is the entire image, for consistency, use getSampledDimensions(). if (fInfo.dimensions() == subset.size()) { return this->getSampledDimensions(sampleSize); } // This should perhaps call a virtual function, but currently both of our subclasses // want the same implementation. return SkISize::Make(get_scaled_dimension(subset.width(), sampleSize), get_scaled_dimension(subset.height(), sampleSize)); }
SkISize SkSampledCodec::onGetSampledDimensions(int sampleSize) const { const SkISize size = this->accountForNativeScaling(&sampleSize); return SkISize::Make(get_scaled_dimension(size.width(), sampleSize), get_scaled_dimension(size.height(), sampleSize)); }
SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, const AndroidOptions& options) { // We should only call this function when sampling. SkASSERT(options.fSampleSize > 1); // Create options struct for the codec. SkCodec::Options sampledOptions; sampledOptions.fZeroInitialized = options.fZeroInitialized; sampledOptions.fPremulBehavior = SkTransferFunctionBehavior::kIgnore; // FIXME: This was already called by onGetAndroidPixels. Can we reduce that? int sampleSize = options.fSampleSize; int nativeSampleSize; SkISize nativeSize = this->accountForNativeScaling(&sampleSize, &nativeSampleSize); // Check if there is a subset. SkIRect subset; int subsetY = 0; int subsetWidth = nativeSize.width(); int subsetHeight = nativeSize.height(); if (options.fSubset) { // We will need to know about subsetting in the y-dimension in order to use the // scanline decoder. // Update the subset to account for scaling done by this->codec(). const SkIRect* subsetPtr = options.fSubset; // Do the divide ourselves, instead of calling get_scaled_dimension. If // X and Y are 0, they should remain 0, rather than being upgraded to 1 // due to being smaller than the sampleSize. const int subsetX = subsetPtr->x() / nativeSampleSize; subsetY = subsetPtr->y() / nativeSampleSize; subsetWidth = get_scaled_dimension(subsetPtr->width(), nativeSampleSize); subsetHeight = get_scaled_dimension(subsetPtr->height(), nativeSampleSize); // The scanline decoder only needs to be aware of subsetting in the x-dimension. subset.setXYWH(subsetX, 0, subsetWidth, nativeSize.height()); sampledOptions.fSubset = ⊂ } // 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(); const int samplingOffsetY = get_start_coord(sampleY); const int startY = samplingOffsetY + subsetY; const int dstHeight = info.height(); const SkImageInfo nativeInfo = info.makeWH(nativeSize.width(), nativeSize.height()); { // Although startScanlineDecode expects the bottom and top to match the // SkImageInfo, startIncrementalDecode uses them to determine which rows to // decode. SkCodec::Options incrementalOptions = sampledOptions; SkIRect incrementalSubset; if (sampledOptions.fSubset) { incrementalSubset.fTop = subsetY; incrementalSubset.fBottom = subsetY + subsetHeight; incrementalSubset.fLeft = sampledOptions.fSubset->fLeft; incrementalSubset.fRight = sampledOptions.fSubset->fRight; incrementalOptions.fSubset = &incrementalSubset; } const SkCodec::Result startResult = this->codec()->startIncrementalDecode(nativeInfo, pixels, rowBytes, &incrementalOptions); if (SkCodec::kSuccess == startResult) { SkSampler* sampler = this->codec()->getSampler(true); if (!sampler) { return SkCodec::kUnimplemented; } if (sampler->setSampleX(sampleX) != info.width()) { return SkCodec::kInvalidScale; } if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) { return SkCodec::kInvalidScale; } sampler->setSampleY(sampleY); int rowsDecoded; const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded); if (incResult == SkCodec::kSuccess) { return SkCodec::kSuccess; } SkASSERT(incResult == SkCodec::kIncompleteInput || incResult == SkCodec::kErrorInInput); SkASSERT(rowsDecoded <= info.height()); this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, info.height(), rowsDecoded); return incResult; } else if (startResult != SkCodec::kUnimplemented) { return startResult; } // kUnimplemented means use the old method. } // Start the scanline decode. SkCodec::Result result = this->codec()->startScanlineDecode(nativeInfo, &sampledOptions); if (SkCodec::kSuccess != result) { return result; } SkSampler* sampler = this->codec()->getSampler(true); if (!sampler) { return SkCodec::kUnimplemented; } if (sampler->setSampleX(sampleX) != info.width()) { return SkCodec::kInvalidScale; } if (get_scaled_dimension(subsetHeight, sampleY) != info.height()) { return SkCodec::kInvalidScale; } switch(this->codec()->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: { if (!this->codec()->skipScanlines(startY)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, 0); return SkCodec::kIncompleteInput; } void* pixelPtr = pixels; for (int y = 0; y < dstHeight; y++) { if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, y + 1); return SkCodec::kIncompleteInput; } if (y < dstHeight - 1) { if (!this->codec()->skipScanlines(sampleY - 1)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, y + 1); return SkCodec::kIncompleteInput; } } pixelPtr = SkTAddOffset<void>(pixelPtr, rowBytes); } return SkCodec::kSuccess; } case SkCodec::kBottomUp_SkScanlineOrder: { // Note that these modes do not support subsetting. SkASSERT(0 == subsetY && nativeSize.height() == subsetHeight); int y; for (y = 0; y < nativeSize.height(); y++) { int srcY = this->codec()->nextScanline(); if (is_coord_necessary(srcY, sampleY, dstHeight)) { void* pixelPtr = SkTAddOffset<void>(pixels, rowBytes * get_dst_coord(srcY, sampleY)); if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { break; } } else { if (!this->codec()->skipScanlines(1)) { break; } } } if (nativeSize.height() == y) { return SkCodec::kSuccess; } // We handle filling uninitialized memory here instead of using this->codec(). // this->codec() does not know that we are sampling. const uint64_t fillValue = this->codec()->getFillValue(info); const SkImageInfo fillInfo = info.makeWH(info.width(), 1); for (; y < nativeSize.height(); y++) { int srcY = this->codec()->outputScanline(y); if (!is_coord_necessary(srcY, sampleY, dstHeight)) { continue; } void* rowPtr = SkTAddOffset<void>(pixels, rowBytes * get_dst_coord(srcY, sampleY)); SkSampler::Fill(fillInfo, rowPtr, rowBytes, fillValue, options.fZeroInitialized); } return SkCodec::kIncompleteInput; } default: SkASSERT(false); return SkCodec::kUnimplemented; } }
/* * 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* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, int inputWidth, int inputHeight, int sampleSize, SkColorType dstColorType) { // Reject color types not supported by this method if (kIndex_8_SkColorType == dstColorType || kGray_8_SkColorType == dstColorType) { SkDebugf("Error: Color type not supported.\n"); 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; // 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. 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; 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; set_subset_region(inputY, inputHeight, this->height(), &imageSubsetY, &outY, &imageSubsetHeight); if (imageSubsetWidth <= 0 || imageSubsetHeight <= 0) { SkDebugf("Error: Region must intersect part of the image.\n"); return nullptr; } // Create the image info for the decode SkAlphaType dstAlphaType = fDecoder->getInfo().alphaType(); if (kUnpremul_SkAlphaType == dstAlphaType) { dstAlphaType = kPremul_SkAlphaType; } SkImageInfo decodeInfo = SkImageInfo::Make(this->width(), this->height(), dstColorType, dstAlphaType); // Start the scanline decoder SkCodec::Result r = fDecoder->startScanlineDecode(decodeInfo); if (SkCodec::kSuccess != r) { SkDebugf("Error: Could not start scanline decoder.\n"); return nullptr; } // Allocate a bitmap for the unscaled decode SkBitmap tmp; SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), imageSubsetHeight); if (!tmp.tryAllocPixels(tmpInfo)) { SkDebugf("Error: Could not allocate pixels.\n"); return nullptr; } // Skip the unneeded rows if (!fDecoder->skipScanlines(imageSubsetY)) { SkDebugf("Error: Failed to skip scanlines.\n"); return nullptr; } // Decode the necessary rows fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight, tmp.rowBytes()); // Calculate the size of the output const int outWidth = get_scaled_dimension(inputWidth, sampleSize); const int outHeight = get_scaled_dimension(inputHeight, sampleSize); // Initialize the destination bitmap SkAutoTDelete<SkBitmap> bitmap(new SkBitmap()); SkImageInfo dstInfo = decodeInfo.makeWH(outWidth, outHeight); if (!bitmap->tryAllocPixels(dstInfo)) { SkDebugf("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 (0 != outX || 0 != outY || inputX + inputWidth > this->width() || inputY + inputHeight > this->height()) { 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 dst = SkRect::MakeXYWH((SkScalar) (outX / sampleSize), (SkScalar) (outY / sampleSize), (SkScalar) get_scaled_dimension(imageSubsetWidth, sampleSize), (SkScalar) get_scaled_dimension(imageSubsetHeight, sampleSize)); SkPaint paint; // Overwrite the dst with the src pixels paint.setXfermodeMode(SkXfermode::kSrc_Mode); // TODO (msarett): Test multiple filter qualities. kNone is the default. canvas.drawBitmapRect(tmp, src, dst, &paint); return bitmap.detach(); }
/* * Performs the bitmap decoding for RLE input format * RLE decoding is performed all at once, rather than a one row at a time */ int SkBmpRLECodec::decodeRows(const SkImageInfo& info, void* dst, size_t dstRowBytes, const Options& opts) { // Set RLE flags static const uint8_t RLE_ESCAPE = 0; static const uint8_t RLE_EOL = 0; static const uint8_t RLE_EOF = 1; static const uint8_t RLE_DELTA = 2; const int width = this->getInfo().width(); int height = info.height(); // Account for sampling. SkImageInfo dstInfo = info.makeWH(get_scaled_dimension(width, fSampleX), height); // Set the background as transparent. Then, if the RLE code skips pixels, // the skipped pixels will be transparent. // Because of the need for transparent pixels, kN32 is the only color // type that makes sense for the destination format. SkASSERT(kRGBA_8888_SkColorType == dstInfo.colorType() || kBGRA_8888_SkColorType == dstInfo.colorType()); if (dst) { SkSampler::Fill(dstInfo, dst, dstRowBytes, SK_ColorTRANSPARENT, opts.fZeroInitialized); } // Adjust the height and the dst if the previous call to decodeRows() left us // with lines that need to be skipped. if (height > fLinesToSkip) { height -= fLinesToSkip; dst = SkTAddOffset<void>(dst, fLinesToSkip * dstRowBytes); fLinesToSkip = 0; } else { fLinesToSkip -= height; return height; } // Destination parameters int x = 0; int y = 0; while (true) { // If we have reached a row that is beyond the requested height, we have // succeeded. if (y >= height) { // It would be better to check for the EOF marker before indicating // success, but we may be performing a scanline decode, which // would require us to stop before decoding the full height. return height; } // Every entry takes at least two bytes if ((int) fRLEBytes - fCurrRLEByte < 2) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < 2) { return y; } } // Read the next two bytes. These bytes have different meanings // depending on their values. In the first interpretation, the first // byte is an escape flag and the second byte indicates what special // task to perform. const uint8_t flag = fStreamBuffer.get()[fCurrRLEByte++]; const uint8_t task = fStreamBuffer.get()[fCurrRLEByte++]; // Perform decoding if (RLE_ESCAPE == flag) { switch (task) { case RLE_EOL: x = 0; y++; break; case RLE_EOF: return height; case RLE_DELTA: { // Two bytes are needed to specify delta if ((int) fRLEBytes - fCurrRLEByte < 2) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < 2) { return y; } } // Modify x and y const uint8_t dx = fStreamBuffer.get()[fCurrRLEByte++]; const uint8_t dy = fStreamBuffer.get()[fCurrRLEByte++]; x += dx; y += dy; if (x > width) { SkCodecPrintf("Warning: invalid RLE input.\n"); return y - dy; } else if (y > height) { fLinesToSkip = y - height; return height; } break; } default: { // If task does not match any of the above signals, it // indicates that we have a sequence of non-RLE pixels. // Furthermore, the value of task is equal to the number // of pixels to interpret. uint8_t numPixels = task; const size_t rowBytes = compute_row_bytes(numPixels, this->bitsPerPixel()); // Abort if setting numPixels moves us off the edge of the // image. if (x + numPixels > width) { SkCodecPrintf("Warning: invalid RLE input.\n"); return y; } // Also abort if there are not enough bytes // remaining in the stream to set numPixels. if ((int) fRLEBytes - fCurrRLEByte < SkAlign2(rowBytes)) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < SkAlign2(rowBytes)) { return y; } } // Set numPixels number of pixels while (numPixels > 0) { switch(this->bitsPerPixel()) { case 4: { SkASSERT(fCurrRLEByte < fRLEBytes); uint8_t val = fStreamBuffer.get()[fCurrRLEByte++]; setPixel(dst, dstRowBytes, dstInfo, x++, y, val >> 4); numPixels--; if (numPixels != 0) { setPixel(dst, dstRowBytes, dstInfo, x++, y, val & 0xF); numPixels--; } break; } case 8: SkASSERT(fCurrRLEByte < fRLEBytes); setPixel(dst, dstRowBytes, dstInfo, x++, y, fStreamBuffer.get()[fCurrRLEByte++]); numPixels--; break; case 24: { SkASSERT(fCurrRLEByte + 2 < fRLEBytes); uint8_t blue = fStreamBuffer.get()[fCurrRLEByte++]; uint8_t green = fStreamBuffer.get()[fCurrRLEByte++]; uint8_t red = fStreamBuffer.get()[fCurrRLEByte++]; setRGBPixel(dst, dstRowBytes, dstInfo, x++, y, red, green, blue); numPixels--; break; } default: SkASSERT(false); return y; } } // Skip a byte if necessary to maintain alignment if (!SkIsAlign2(rowBytes)) { fCurrRLEByte++; } break; } } } else { // If the first byte read is not a flag, it indicates the number of // pixels to set in RLE mode. const uint8_t numPixels = flag; const int endX = SkTMin<int>(x + numPixels, width); if (24 == this->bitsPerPixel()) { // In RLE24, the second byte read is part of the pixel color. // There are two more required bytes to finish encoding the // color. if ((int) fRLEBytes - fCurrRLEByte < 2) { SkCodecPrintf("Warning: might be incomplete RLE input.\n"); if (this->checkForMoreData() < 2) { return y; } } // Fill the pixels up to endX with the specified color uint8_t blue = task; uint8_t green = fStreamBuffer.get()[fCurrRLEByte++]; uint8_t red = fStreamBuffer.get()[fCurrRLEByte++]; while (x < endX) { setRGBPixel(dst, dstRowBytes, dstInfo, x++, y, red, green, blue); } } else { // In RLE8 or RLE4, the second byte read gives the index in the // color table to look up the pixel color. // RLE8 has one color index that gets repeated // RLE4 has two color indexes in the upper and lower 4 bits of // the bytes, which are alternated uint8_t indices[2] = { task, task }; if (4 == this->bitsPerPixel()) { indices[0] >>= 4; indices[1] &= 0xf; } // Set the indicated number of pixels for (int which = 0; x < endX; x++) { setPixel(dst, dstRowBytes, dstInfo, x, y, indices[which]); which = !which; } } }
SkCodec::Result SkSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, const AndroidOptions& options) { // We should only call this function when sampling. SkASSERT(options.fSampleSize > 1); // Create options struct for the codec. SkCodec::Options sampledOptions; sampledOptions.fZeroInitialized = options.fZeroInitialized; // FIXME: This was already called by onGetAndroidPixels. Can we reduce that? int sampleSize = options.fSampleSize; int nativeSampleSize; SkISize nativeSize = this->accountForNativeScaling(&sampleSize, &nativeSampleSize); // Check if there is a subset. SkIRect subset; int subsetY = 0; int subsetWidth = nativeSize.width(); int subsetHeight = nativeSize.height(); if (options.fSubset) { // We will need to know about subsetting in the y-dimension in order to use the // scanline decoder. // Update the subset to account for scaling done by this->codec(). SkIRect* subsetPtr = options.fSubset; // Do the divide ourselves, instead of calling get_scaled_dimension. If // X and Y are 0, they should remain 0, rather than being upgraded to 1 // due to being smaller than the sampleSize. const int subsetX = subsetPtr->x() / nativeSampleSize; subsetY = subsetPtr->y() / nativeSampleSize; subsetWidth = get_scaled_dimension(subsetPtr->width(), nativeSampleSize); subsetHeight = get_scaled_dimension(subsetPtr->height(), nativeSampleSize); // The scanline decoder only needs to be aware of subsetting in the x-dimension. subset.setXYWH(subsetX, 0, subsetWidth, nativeSize.height()); sampledOptions.fSubset = ⊂ } // Start the scanline decode. SkCodec::Result result = this->codec()->startScanlineDecode( info.makeWH(nativeSize.width(), nativeSize.height()), &sampledOptions, options.fColorPtr, options.fColorCount); if (SkCodec::kSuccess != result) { return result; } SkSampler* sampler = this->codec()->getSampler(true); if (!sampler) { return SkCodec::kUnimplemented; } // 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(this->codec()->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: { if (!this->codec()->skipScanlines(startY)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, 0); return SkCodec::kIncompleteInput; } void* pixelPtr = pixels; for (int y = 0; y < dstHeight; y++) { if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, y + 1); return SkCodec::kIncompleteInput; } if (y < dstHeight - 1) { if (!this->codec()->skipScanlines(sampleY - 1)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, y + 1); return SkCodec::kIncompleteInput; } } pixelPtr = SkTAddOffset<void>(pixelPtr, rowBytes); } return SkCodec::kSuccess; } case SkCodec::kOutOfOrder_SkScanlineOrder: case SkCodec::kBottomUp_SkScanlineOrder: { // Note that these modes do not support subsetting. SkASSERT(0 == subsetY && nativeSize.height() == subsetHeight); int y; for (y = 0; y < nativeSize.height(); y++) { int srcY = this->codec()->nextScanline(); if (is_coord_necessary(srcY, sampleY, dstHeight)) { void* pixelPtr = SkTAddOffset<void>(pixels, rowBytes * get_dst_coord(srcY, sampleY)); if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { break; } } else { if (!this->codec()->skipScanlines(1)) { break; } } } if (nativeSize.height() == y) { return SkCodec::kSuccess; } // We handle filling uninitialized memory here instead of using this->codec(). // this->codec() does not know that we are sampling. const uint32_t fillValue = this->codec()->getFillValue(info.colorType()); const SkImageInfo fillInfo = info.makeWH(info.width(), 1); for (; y < nativeSize.height(); y++) { int srcY = this->codec()->outputScanline(y); if (!is_coord_necessary(srcY, sampleY, dstHeight)) { continue; } void* rowPtr = SkTAddOffset<void>(pixels, rowBytes * get_dst_coord(srcY, sampleY)); SkSampler::Fill(fillInfo, rowPtr, rowBytes, fillValue, options.fZeroInitialized); } return SkCodec::kIncompleteInput; } case SkCodec::kNone_SkScanlineOrder: { const int linesNeeded = subsetHeight - samplingOffsetY; SkAutoTMalloc<uint8_t> storage(linesNeeded * rowBytes); uint8_t* storagePtr = storage.get(); if (!this->codec()->skipScanlines(startY)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, dstHeight, 0); return SkCodec::kIncompleteInput; } int scanlines = this->codec()->getScanlines(storagePtr, linesNeeded, rowBytes); for (int y = 0; y < dstHeight; y++) { memcpy(pixels, storagePtr, info.minRowBytes()); storagePtr += sampleY * rowBytes; pixels = SkTAddOffset<void>(pixels, rowBytes); } if (scanlines < dstHeight) { // this->codec() has already handled filling uninitialized memory. return SkCodec::kIncompleteInput; } return SkCodec::kSuccess; } default: SkASSERT(false); return SkCodec::kUnimplemented; } }