SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& requestInfo, void* requestPixels, size_t requestRowBytes, const AndroidOptions* options) { if (!requestPixels) { return SkCodec::kInvalidParameters; } if (requestRowBytes < requestInfo.minRowBytes()) { return SkCodec::kInvalidParameters; } SkImageInfo adjustedInfo = fInfo; if (ExifOrientationBehavior::kRespect == fOrientationBehavior && SkPixmapPriv::ShouldSwapWidthHeight(fCodec->getOrigin())) { adjustedInfo = SkPixmapPriv::SwapWidthHeight(adjustedInfo); } AndroidOptions defaultOptions; if (!options) { options = &defaultOptions; } else if (options->fSubset) { if (!is_valid_subset(*options->fSubset, adjustedInfo.dimensions())) { return SkCodec::kInvalidParameters; } if (SkIRect::MakeSize(adjustedInfo.dimensions()) == *options->fSubset) { // The caller wants the whole thing, rather than a subset. Modify // the AndroidOptions passed to onGetAndroidPixels to not specify // a subset. defaultOptions = *options; defaultOptions.fSubset = nullptr; options = &defaultOptions; } } if (ExifOrientationBehavior::kIgnore == fOrientationBehavior) { return this->onGetAndroidPixels(requestInfo, requestPixels, requestRowBytes, *options); } SkCodec::Result result; auto decode = [this, options, &result](const SkPixmap& pm) { result = this->onGetAndroidPixels(pm.info(), pm.writable_addr(), pm.rowBytes(), *options); return acceptable_result(result); }; SkPixmap dst(requestInfo, requestPixels, requestRowBytes); if (SkPixmapPriv::Orient(dst, fCodec->getOrigin(), decode)) { return result; } // Orient returned false. If onGetAndroidPixels succeeded, then Orient failed internally. if (acceptable_result(result)) { return SkCodec::kInternalError; } return result; }
SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options* options, SkPMColor ctable[], int* ctableCount) { // Reset fCurrScanline in case of failure. fCurrScanline = -1; // Ensure that valid color ptrs are passed in for kIndex8 color type if (kIndex_8_SkColorType == dstInfo.colorType()) { if (nullptr == ctable || nullptr == ctableCount) { return SkCodec::kInvalidParameters; } } else { if (ctableCount) { *ctableCount = 0; } ctableCount = nullptr; ctable = nullptr; } if (!this->rewindIfNeeded()) { return kCouldNotRewind; } // Set options. Options optsStorage; if (nullptr == options) { options = &optsStorage; } else if (options->fSubset) { SkIRect size = SkIRect::MakeSize(dstInfo.dimensions()); if (!size.contains(*options->fSubset)) { return kInvalidInput; } // We only support subsetting in the x-dimension for scanline decoder. // Subsetting in the y-dimension can be accomplished using skipScanlines(). if (options->fSubset->top() != 0 || options->fSubset->height() != dstInfo.height()) { return kInvalidInput; } } // FIXME: Support subsets somehow? if (!this->dimensionsSupported(dstInfo.dimensions())) { return kInvalidScale; } const Result result = this->onStartScanlineDecode(dstInfo, *options, ctable, ctableCount); if (result != SkCodec::kSuccess) { return result; } fCurrScanline = 0; fDstInfo = dstInfo; fOptions = *options; return kSuccess; }
/* * Initiates the bitmap decode */ SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, int* inputColorCount) { if (!this->rewindIfNeeded()) { return kCouldNotRewind; } if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } if (dstInfo.dimensions() != this->getInfo().dimensions()) { SkCodecPrintf("Error: scaling not supported.\n"); return kInvalidScale; } if (!conversion_possible(dstInfo, this->getInfo())) { SkCodecPrintf("Error: cannot convert input type to output type.\n"); return kInvalidConversion; } // Initialize a the mask swizzler if (!this->initializeSwizzler(dstInfo)) { SkCodecPrintf("Error: cannot initialize swizzler.\n"); return kInvalidConversion; } return this->decode(dstInfo, dst, dstRowBytes, opts); }
sk_sp<SkAnimatedImage> SkAnimatedImage::Make(std::unique_ptr<SkAndroidCodec> codec, const SkImageInfo& requestedInfo, SkIRect cropRect, sk_sp<SkPicture> postProcess) { if (!codec) { return nullptr; } auto scaledSize = requestedInfo.dimensions(); auto decodeInfo = requestedInfo; if (codec->getEncodedFormat() != SkEncodedImageFormat::kWEBP || scaledSize.width() >= decodeInfo.width() || scaledSize.height() >= decodeInfo.height()) { // Only libwebp can decode to arbitrary smaller sizes. auto dims = codec->getInfo().dimensions(); decodeInfo = decodeInfo.makeWH(dims.width(), dims.height()); } auto image = sk_sp<SkAnimatedImage>(new SkAnimatedImage(std::move(codec), scaledSize, decodeInfo, cropRect, std::move(postProcess))); if (!image->fDisplayFrame.fBitmap.getPixels()) { // tryAllocPixels failed. return nullptr; } return image; }
SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options& options, SkPMColor colorTable[], int* colorCount) { if (!ico_conversion_possible(dstInfo)) { return kInvalidConversion; } int index = 0; SkCodec::Result result = kInvalidScale; while (true) { index = this->chooseCodec(dstInfo.dimensions(), index); if (index < 0) { break; } SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index); SkImageInfo decodeInfo = fix_embedded_alpha(dstInfo, embeddedCodec->getInfo().alphaType()); result = embeddedCodec->startScanlineDecode(decodeInfo, &options, colorTable, colorCount); if (kSuccess == result) { fCurrScanlineCodec = embeddedCodec; return result; } index++; } SkCodecPrintf("Error: No matching candidate image in ico.\n"); return result; }
/* * Initiates the bitmap decode */ SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } if (dstInfo.dimensions() != this->dimensions()) { SkCodecPrintf("Error: scaling not supported.\n"); return kInvalidScale; } Result result = this->prepareToDecode(dstInfo, opts); if (kSuccess != result) { return result; } int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); if (rows != dstInfo.height()) { *rowsDecoded = rows; return kIncompleteInput; } return kSuccess; }
/* * Initiates the gif decode */ SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, int* inputColorCount, int* rowsDecoded) { Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts); if (kSuccess != result) { return result; } if (dstInfo.dimensions() != this->getInfo().dimensions()) { return gif_error("Scaling not supported.\n", kInvalidScale); } // Initialize the swizzler if (fFrameIsSubset) { // Fill the background SkSampler::Fill(dstInfo, dst, dstRowBytes, this->getFillValue(dstInfo.colorType()), opts.fZeroInitialized); } // Iterate over rows of the input for (int y = fFrameRect.top(); y < fFrameRect.bottom(); y++) { if (!this->readRow()) { *rowsDecoded = y; return gif_error("Could not decode line.\n", kIncompleteInput); } void* dstRow = SkTAddOffset<void>(dst, dstRowBytes * this->outputScanline(y)); fSwizzler->swizzle(dstRow, fSrcBuffer.get()); } return kSuccess; }
/* * Initiates the Ico decode */ SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* ct, int* ptr) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } // We return invalid scale if there is no candidate image with matching // dimensions. Result result = kInvalidScale; for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) { // If the dimensions match, try to decode if (dstInfo.dimensions() == fEmbeddedCodecs->operator[](i)->getInfo().dimensions()) { // Perform the decode result = fEmbeddedCodecs->operator[](i)->getPixels(dstInfo, dst, dstRowBytes, &opts, ct, ptr); // On a fatal error, keep trying to find an image to decode if (kInvalidConversion == result || kInvalidInput == result || kInvalidScale == result) { SkCodecPrintf("Warning: Attempt to decode candidate ico failed.\n"); continue; } // On success or partial success, return the result return result; } } SkCodecPrintf("Error: No matching candidate image in ico.\n"); return result; }
/* * Initiates the bitmap decode */ SkCodec::Result SkBmpMaskCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, int* inputColorCount, int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } if (dstInfo.dimensions() != this->getInfo().dimensions()) { SkCodecPrintf("Error: scaling not supported.\n"); return kInvalidScale; } if (!conversion_possible(dstInfo, this->getInfo())) { SkCodecPrintf("Error: cannot convert input type to output type.\n"); return kInvalidConversion; } Result result = this->prepareToDecode(dstInfo, opts, inputColorPtr, inputColorCount); if (kSuccess != result) { return result; } int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); if (rows != dstInfo.height()) { *rowsDecoded = rows; return kIncompleteInput; } return kSuccess; }
void draw(SkCanvas* canvas) { const int height = 2; const int width = 2; SkImageInfo imageInfo = SkImageInfo::Make(width, height, kN32_SkColorType, kPremul_SkAlphaType); SkISize dimensions = imageInfo.dimensions(); SkIRect bounds = imageInfo.bounds(); SkIRect dimensionsAsBounds = SkIRect::MakeSize(dimensions); SkDebugf("dimensionsAsBounds %c= bounds\n", dimensionsAsBounds == bounds ? '=' : '!'); }
static void check(skiatest::Reporter* r, const char path[], SkISize size, bool supportsScanlineDecoding) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach())); if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } // This test is used primarily to verify rewinding works properly. Using kN32 allows // us to test this without the added overhead of creating different bitmaps depending // on the color type (ex: building a color table for kIndex8). DM is where we test // decodes to all possible destination color types. SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); REPORTER_ASSERT(r, info.dimensions() == size); SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkImageGenerator::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL); REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess); SkMD5::Digest digest1, digest2; md5(bm, &digest1); bm.eraseColor(SK_ColorYELLOW); result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL); REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess); // verify that re-decoding gives the same result. md5(bm, &digest2); REPORTER_ASSERT(r, digest1 == digest2); SkScanlineDecoder* scanlineDecoder = codec->getScanlineDecoder(info); if (supportsScanlineDecoding) { bm.eraseColor(SK_ColorYELLOW); REPORTER_ASSERT(r, scanlineDecoder); for (int y = 0; y < info.height(); y++) { result = scanlineDecoder->getScanlines(bm.getAddr(0, y), 1, 0); REPORTER_ASSERT(r, result == SkImageGenerator::kSuccess); } // verify that scanline decoding gives the same result. SkMD5::Digest digest3; md5(bm, &digest3); REPORTER_ASSERT(r, digest3 == digest1); } else { REPORTER_ASSERT(r, !scanlineDecoder); } }
static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info, const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) { REPORTER_ASSERT(r, info.dimensions() == size); bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, result == expectedResult); md5(bm, digest); if (goodDigest) { REPORTER_ASSERT(r, *digest == *goodDigest); } { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); SkCodec::Result expected565 = info.alphaType() == kOpaque_SkAlphaType ? expectedResult : SkCodec::kInvalidConversion; test_info(r, codec, info565, expected565, nullptr); } // Verify that re-decoding gives the same result. It is interesting to check this after // a decode to 565, since choosing to decode to 565 may result in some of the decode // options being modified. These options should return to their defaults on another // decode to kN32, so the new digest should match the old digest. test_info(r, codec, info, expectedResult, digest); { // Check alpha type conversions if (info.alphaType() == kOpaque_SkAlphaType) { test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), expectedResult, digest); test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), expectedResult, digest); } else { // Decoding to opaque should fail test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), SkCodec::kInvalidConversion, nullptr); SkAlphaType otherAt = info.alphaType(); if (kPremul_SkAlphaType == otherAt) { otherAt = kUnpremul_SkAlphaType; } else { otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr); } } }
/* * Initiates the gif decode */ SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, int* inputColorCount, int* rowsDecoded) { Result result = this->prepareToDecode(dstInfo, inputColorPtr, inputColorCount, opts); if (kSuccess != result) { return result; } if (dstInfo.dimensions() != this->getInfo().dimensions()) { return gif_error("Scaling not supported.\n", kInvalidScale); } // Initialize the swizzler if (fFrameIsSubset) { const SkImageInfo subsetDstInfo = dstInfo.makeWH(fFrameRect.width(), fFrameRect.height()); if (kSuccess != this->initializeSwizzler(subsetDstInfo, opts)) { return gif_error("Could not initialize swizzler.\n", kUnimplemented); } // Fill the background SkSampler::Fill(dstInfo, dst, dstRowBytes, this->getFillValue(dstInfo.colorType(), dstInfo.alphaType()), opts.fZeroInitialized); // Modify the dst pointer const int32_t dstBytesPerPixel = SkColorTypeBytesPerPixel(dstInfo.colorType()); dst = SkTAddOffset<void*>(dst, dstRowBytes * fFrameRect.top() + dstBytesPerPixel * fFrameRect.left()); } else { if (kSuccess != this->initializeSwizzler(dstInfo, opts)) { return gif_error("Could not initialize swizzler.\n", kUnimplemented); } } // Iterate over rows of the input uint32_t height = fFrameRect.height(); for (uint32_t y = 0; y < height; y++) { if (!this->readRow()) { *rowsDecoded = y; return gif_error("Could not decode line.\n", kIncompleteInput); } void* dstRow = SkTAddOffset<void>(dst, dstRowBytes * this->outputScanline(y)); fSwizzler->swizzle(dstRow, fSrcBuffer.get()); } return kSuccess; }
SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options* options, SkPMColor ctable[], int* ctableCount) { // Reset fCurrScanline in case of failure. fCurrScanline = -1; // Ensure that valid color ptrs are passed in for kIndex8 color type if (kIndex_8_SkColorType == dstInfo.colorType()) { if (nullptr == ctable || nullptr == ctableCount) { return SkCodec::kInvalidParameters; } } else { if (ctableCount) { *ctableCount = 0; } ctableCount = nullptr; ctable = nullptr; } if (!this->rewindIfNeeded()) { return kCouldNotRewind; } // Set options. Options optsStorage; if (nullptr == options) { options = &optsStorage; } else if (options->fSubset) { SkIRect subset(*options->fSubset); if (!this->onGetValidSubset(&subset) || subset != *options->fSubset) { // FIXME: How to differentiate between not supporting subset at all // and not supporting this particular subset? return kUnimplemented; } } // FIXME: Support subsets somehow? if (!this->dimensionsSupported(dstInfo.dimensions())) { return kInvalidScale; } const Result result = this->onStartScanlineDecode(dstInfo, *options, ctable, ctableCount); if (result != SkCodec::kSuccess) { return result; } fCurrScanline = 0; fDstInfo = dstInfo; fOptions = *options; return kSuccess; }
SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, const SkCodec::Options& options, SkPMColor* colorTable, int* colorCount) { int index = 0; while (true) { index = this->chooseCodec(dstInfo.dimensions(), index); if (index < 0) { break; } SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index); switch (embeddedCodec->startIncrementalDecode(dstInfo, pixels, rowBytes, &options, colorTable, colorCount)) { case kSuccess: fCurrIncrementalCodec = embeddedCodec; fCurrScanlineCodec = nullptr; return kSuccess; case kUnimplemented: // FIXME: embeddedCodec is a BMP. If scanline decoding would work, // return kUnimplemented so that SkSampledCodec will fall through // to use the scanline decoder. // Note that calling startScanlineDecode will require an extra // rewind. The embedded codec has an SkMemoryStream, which is // cheap to rewind, though it will do extra work re-reading the // header. // Also note that we pass nullptr for Options. This is because // Options that are valid for incremental decoding may not be // valid for scanline decoding. // Once BMP supports incremental decoding this workaround can go // away. if (embeddedCodec->startScanlineDecode(dstInfo, nullptr, colorTable, colorCount) == kSuccess) { return kUnimplemented; } // Move on to the next embedded codec. break; default: break; } index++; } SkCodecPrintf("Error: No matching candidate image in ico.\n"); return kInvalidScale; }
/* * Initiates the Ico decode */ SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* colorTable, int* colorCount, int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } if (!ico_conversion_possible(dstInfo)) { return kInvalidConversion; } int index = 0; SkCodec::Result result = kInvalidScale; while (true) { index = this->chooseCodec(dstInfo.dimensions(), index); if (index < 0) { break; } SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index); SkImageInfo decodeInfo = fix_embedded_alpha(dstInfo, embeddedCodec->getInfo().alphaType()); SkASSERT(decodeInfo.colorType() == kN32_SkColorType); result = embeddedCodec->getPixels(decodeInfo, dst, dstRowBytes, &opts, colorTable, colorCount); switch (result) { case kSuccess: case kIncompleteInput: // The embedded codec will handle filling incomplete images, so we will indicate // that all of the rows are initialized. *rowsDecoded = decodeInfo.height(); return result; default: // Continue trying to find a valid embedded codec on a failed decode. break; } index++; } SkCodecPrintf("Error: No matching candidate image in ico.\n"); return result; }
SkCodec::Result SkWebpAdapterCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const AndroidOptions& options) { // SkWebpCodec will support pretty much any dimensions that we provide, but we want // to be stricter about the type of scaling that we allow, so we will add an extra // check here. SkISize supportedSize; if (!options.fSubset) { supportedSize = this->onGetSampledDimensions(options.fSampleSize); } else { supportedSize = this->getSampledSubsetDimensions(options.fSampleSize, *options.fSubset); } if (supportedSize != info.dimensions()) { return SkCodec::kInvalidParameters; } SkCodec::Options codecOptions; codecOptions.fZeroInitialized = options.fZeroInitialized; codecOptions.fSubset = options.fSubset; return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr, options.fColorCount); }
SkCodec::Result onStart(const SkImageInfo& dstInfo, const SkCodec::Options& options, SkPMColor inputColorPtr[], int* inputColorCount) override { if (!fCodec->rewindIfNeeded()) { return SkCodec::kCouldNotRewind; } if (options.fSubset) { // Subsets are not supported. return SkCodec::kUnimplemented; } if (dstInfo.dimensions() != this->getInfo().dimensions()) { if (!SkScaledCodec::DimensionsSupportedForSampling(this->getInfo(), dstInfo)) { return SkCodec::kInvalidScale; } } if (!conversion_possible(dstInfo, this->getInfo())) { SkCodecPrintf("Error: cannot convert input type to output type.\n"); return SkCodec::kInvalidConversion; } return fCodec->prepareToDecode(dstInfo, options, inputColorPtr, inputColorCount); }
/* * Initiates the bitmap decode */ SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, SkPMColor* inputColorPtr, int* inputColorCount) { if (!this->rewindIfNeeded()) { return kCouldNotRewind; } if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } if (dstInfo.dimensions() != this->getInfo().dimensions()) { SkCodecPrintf("Error: scaling not supported.\n"); return kInvalidScale; } if (!conversion_possible(dstInfo, this->getInfo())) { SkCodecPrintf("Error: cannot convert input type to output type.\n"); return kInvalidConversion; } // Create the color table if necessary and prepare the stream for decode // Note that if it is non-NULL, inputColorCount will be modified if (!this->createColorTable(inputColorCount)) { SkCodecPrintf("Error: could not create color table.\n"); return kInvalidInput; } // Copy the color table to the client if necessary copy_color_table(dstInfo, fColorTable, inputColorPtr, inputColorCount); // Initialize a swizzler if necessary if (!this->initializeStreamBuffer()) { SkCodecPrintf("Error: cannot initialize swizzler.\n"); return kInvalidConversion; } // Perform the decode return decode(dstInfo, dst, dstRowBytes, opts); }
SkCodec::Result SkIcoCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, const SkCodec::Options& options) { int index = 0; SkCodec::Result result = kInvalidScale; while (true) { index = this->chooseCodec(dstInfo.dimensions(), index); if (index < 0) { break; } SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get(); result = embeddedCodec->startScanlineDecode(dstInfo, &options); if (kSuccess == result) { fCurrCodec = embeddedCodec; return result; } index++; } SkCodecPrintf("Error: No matching candidate image in ico.\n"); return result; }
/* * Initiates the Ico decode */ SkCodec::Result SkIcoCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& opts, int* rowsDecoded) { if (opts.fSubset) { // Subsets are not supported. return kUnimplemented; } int index = 0; SkCodec::Result result = kInvalidScale; while (true) { index = this->chooseCodec(dstInfo.dimensions(), index); if (index < 0) { break; } SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get(); result = embeddedCodec->getPixels(dstInfo, dst, dstRowBytes, &opts); switch (result) { case kSuccess: case kIncompleteInput: // The embedded codec will handle filling incomplete images, so we will indicate // that all of the rows are initialized. *rowsDecoded = dstInfo.height(); return result; default: // Continue trying to find a valid embedded codec on a failed decode. break; } index++; } SkCodecPrintf("Error: No matching candidate image in ico.\n"); return result; }
static sk_sp<SkImage> make_pict_gen(GrContext*, SkPicture* pic, const SkImageInfo& info) { return SkImage::MakeFromPicture(sk_ref_sp(pic), info.dimensions(), nullptr, nullptr); }
SkCodec::Result SkCodec::getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options* options, SkPMColor ctable[], int* ctableCount) { if (kUnknown_SkColorType == info.colorType()) { return kInvalidConversion; } if (nullptr == pixels) { return kInvalidParameters; } if (rowBytes < info.minRowBytes()) { return kInvalidParameters; } if (kIndex_8_SkColorType == info.colorType()) { if (nullptr == ctable || nullptr == ctableCount) { return kInvalidParameters; } } else { if (ctableCount) { *ctableCount = 0; } ctableCount = nullptr; ctable = nullptr; } { SkAlphaType canonical; if (!SkColorTypeValidateAlphaType(info.colorType(), info.alphaType(), &canonical) || canonical != info.alphaType()) { return kInvalidConversion; } } if (!this->rewindIfNeeded()) { return kCouldNotRewind; } // Default options. Options optsStorage; if (nullptr == options) { options = &optsStorage; } else if (options->fSubset) { SkIRect subset(*options->fSubset); if (!this->onGetValidSubset(&subset) || subset != *options->fSubset) { // FIXME: How to differentiate between not supporting subset at all // and not supporting this particular subset? return kUnimplemented; } } // FIXME: Support subsets somehow? Note that this works for SkWebpCodec // because it supports arbitrary scaling/subset combinations. if (!this->dimensionsSupported(info.dimensions())) { return kInvalidScale; } // On an incomplete decode, the subclass will specify the number of scanlines that it decoded // successfully. int rowsDecoded = 0; const Result result = this->onGetPixels(info, pixels, rowBytes, *options, ctable, ctableCount, &rowsDecoded); if ((kIncompleteInput == result || kSuccess == result) && ctableCount) { SkASSERT(*ctableCount >= 0 && *ctableCount <= 256); } // A return value of kIncompleteInput indicates a truncated image stream. // In this case, we will fill any uninitialized memory with a default value. // Some subclasses will take care of filling any uninitialized memory on // their own. They indicate that all of the memory has been filled by // setting rowsDecoded equal to the height. if (kIncompleteInput == result && rowsDecoded != info.height()) { this->fillIncompleteImage(info, pixels, rowBytes, options->fZeroInitialized, info.height(), rowsDecoded); } return result; }
bool SkPixelInfo::CopyPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRB, const SkImageInfo& srcInfo, const void* srcPixels, size_t srcRB, SkColorTable* ctable) { if (srcInfo.dimensions() != dstInfo.dimensions()) { return false; } const int width = srcInfo.width(); const int height = srcInfo.height(); // Handle fancy alpha swizzling if both are ARGB32 if (4 == srcInfo.bytesPerPixel() && 4 == dstInfo.bytesPerPixel()) { SkDstPixelInfo dstPI; dstPI.fColorType = dstInfo.colorType(); dstPI.fAlphaType = dstInfo.alphaType(); dstPI.fPixels = dstPixels; dstPI.fRowBytes = dstRB; SkSrcPixelInfo srcPI; srcPI.fColorType = srcInfo.colorType(); srcPI.fAlphaType = srcInfo.alphaType(); srcPI.fPixels = srcPixels; srcPI.fRowBytes = srcRB; return srcPI.convertPixelsTo(&dstPI, width, height); } // If they agree on colorType and the alphaTypes are compatible, then we just memcpy. // Note: we've already taken care of 32bit colortypes above. if (srcInfo.colorType() == dstInfo.colorType()) { switch (srcInfo.colorType()) { case kRGB_565_SkColorType: case kAlpha_8_SkColorType: break; case kIndex_8_SkColorType: case kARGB_4444_SkColorType: if (srcInfo.alphaType() != dstInfo.alphaType()) { return false; } break; default: return false; } rect_memcpy(dstPixels, dstRB, srcPixels, srcRB, width * srcInfo.bytesPerPixel(), height); return true; } /* * Begin section where we try to change colorTypes along the way. Not all combinations * are supported. */ // Can no longer draw directly into 4444, but we can manually whack it for a few combinations if (kARGB_4444_SkColorType == dstInfo.colorType() && (kN32_SkColorType == srcInfo.colorType() || kIndex_8_SkColorType == srcInfo.colorType())) { if (srcInfo.alphaType() == kUnpremul_SkAlphaType) { // Our method for converting to 4444 assumes premultiplied. return false; } const SkPMColor* table = NULL; if (kIndex_8_SkColorType == srcInfo.colorType()) { if (NULL == ctable) { return false; } table = ctable->readColors(); } for (int y = 0; y < height; ++y) { DITHER_4444_SCAN(y); SkPMColor16* SK_RESTRICT dstRow = (SkPMColor16*)dstPixels; if (table) { const uint8_t* SK_RESTRICT srcRow = (const uint8_t*)srcPixels; for (int x = 0; x < width; ++x) { dstRow[x] = SkDitherARGB32To4444(table[srcRow[x]], DITHER_VALUE(x)); } } else { const SkPMColor* SK_RESTRICT srcRow = (const SkPMColor*)srcPixels; for (int x = 0; x < width; ++x) { dstRow[x] = SkDitherARGB32To4444(srcRow[x], DITHER_VALUE(x)); } } dstPixels = (char*)dstPixels + dstRB; srcPixels = (const char*)srcPixels + srcRB; } return true; }
static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info, const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) { REPORTER_ASSERT(r, info.dimensions() == size); bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes()); REPORTER_ASSERT(r, result == expectedResult); md5(bm, digest); if (goodDigest) { REPORTER_ASSERT(r, *digest == *goodDigest); } { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); if (info.alphaType() == kOpaque_SkAlphaType) { // Decoding to 565 should succeed. SkBitmap bm565; bm565.allocPixels(info565); SkAutoLockPixels alp(bm565); // This will allow comparison even if the image is incomplete. bm565.eraseColor(SK_ColorBLACK); REPORTER_ASSERT(r, expectedResult == codec->getPixels(info565, bm565.getPixels(), bm565.rowBytes())); SkMD5::Digest digest565; md5(bm565, &digest565); // A dumb client's request for non-opaque should also succeed. for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) { info565 = info565.makeAlphaType(alpha); test_info(r, codec, info565, expectedResult, &digest565); } } else { test_info(r, codec, info565, SkCodec::kInvalidConversion, nullptr); } } if (codec->getInfo().colorType() == kGray_8_SkColorType) { SkImageInfo grayInfo = codec->getInfo(); SkBitmap grayBm; grayBm.allocPixels(grayInfo); SkAutoLockPixels alp(grayBm); grayBm.eraseColor(SK_ColorBLACK); REPORTER_ASSERT(r, expectedResult == codec->getPixels(grayInfo, grayBm.getPixels(), grayBm.rowBytes())); SkMD5::Digest grayDigest; md5(grayBm, &grayDigest); for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) { grayInfo = grayInfo.makeAlphaType(alpha); test_info(r, codec, grayInfo, expectedResult, &grayDigest); } } // Verify that re-decoding gives the same result. It is interesting to check this after // a decode to 565, since choosing to decode to 565 may result in some of the decode // options being modified. These options should return to their defaults on another // decode to kN32, so the new digest should match the old digest. test_info(r, codec, info, expectedResult, digest); { // Check alpha type conversions if (info.alphaType() == kOpaque_SkAlphaType) { test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), expectedResult, digest); test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), expectedResult, digest); } else { // Decoding to opaque should fail test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), SkCodec::kInvalidConversion, nullptr); SkAlphaType otherAt = info.alphaType(); if (kPremul_SkAlphaType == otherAt) { otherAt = kUnpremul_SkAlphaType; } else { otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. test_info(r, codec, info.makeAlphaType(otherAt), expectedResult, nullptr); } } }
SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const AndroidOptions& options) { // Create an Options struct for the codec. SkCodec::Options codecOptions; codecOptions.fZeroInitialized = options.fZeroInitialized; codecOptions.fPremulBehavior = SkTransferFunctionBehavior::kIgnore; SkIRect* subset = options.fSubset; if (!subset || subset->size() == this->codec()->getInfo().dimensions()) { if (this->codec()->dimensionsSupported(info.dimensions())) { return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions); } // If the native codec does not support the requested scale, scale by sampling. return this->sampledDecode(info, pixels, rowBytes, options); } // We are performing a subset decode. int sampleSize = options.fSampleSize; SkISize scaledSize = this->getSampledDimensions(sampleSize); if (!this->codec()->dimensionsSupported(scaledSize)) { // If the native codec does not support the requested scale, scale by sampling. return this->sampledDecode(info, pixels, rowBytes, options); } // Calculate the scaled subset bounds. int scaledSubsetX = subset->x() / sampleSize; int scaledSubsetY = subset->y() / sampleSize; int scaledSubsetWidth = info.width(); int scaledSubsetHeight = info.height(); const SkImageInfo scaledInfo = info.makeWH(scaledSize.width(), scaledSize.height()); { // Although startScanlineDecode expects the bottom and top to match the // SkImageInfo, startIncrementalDecode uses them to determine which rows to // decode. SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY, scaledSubsetWidth, scaledSubsetHeight); codecOptions.fSubset = &incrementalSubset; const SkCodec::Result startResult = this->codec()->startIncrementalDecode( scaledInfo, pixels, rowBytes, &codecOptions); if (SkCodec::kSuccess == startResult) { int rowsDecoded; const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded); if (incResult == SkCodec::kSuccess) { return SkCodec::kSuccess; } SkASSERT(SkCodec::kIncompleteInput == incResult); // FIXME: Can zero initialized be read from SkCodec::fOptions? this->codec()->fillIncompleteImage(scaledInfo, pixels, rowBytes, options.fZeroInitialized, scaledSubsetHeight, rowsDecoded); return SkCodec::kIncompleteInput; } else if (startResult != SkCodec::kUnimplemented) { return startResult; } // Otherwise fall down to use the old scanline decoder. // codecOptions.fSubset will be reset below, so it will not continue to // point to the object that is no longer on the stack. } // Start the scanline decode. SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth, scaledSize.height()); codecOptions.fSubset = &scanlineSubset; SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo, &codecOptions); if (SkCodec::kSuccess != result) { return result; } // At this point, we are only concerned with subsetting. Either no scale was // requested, or the this->codec() is handling the scale. // Note that subsetting is only supported for kTopDown, so this code will not be // reached for other orders. SkASSERT(this->codec()->getScanlineOrder() == SkCodec::kTopDown_SkScanlineOrder); if (!this->codec()->skipScanlines(scaledSubsetY)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, scaledSubsetHeight, 0); return SkCodec::kIncompleteInput; } int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes); if (decodedLines != scaledSubsetHeight) { return SkCodec::kIncompleteInput; } return SkCodec::kSuccess; }
SkCodec::Result SkSampledCodec::onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const AndroidOptions& options) { // Create an Options struct for the codec. SkCodec::Options codecOptions; codecOptions.fZeroInitialized = options.fZeroInitialized; SkIRect* subset = options.fSubset; if (!subset || subset->size() == this->codec()->getInfo().dimensions()) { if (this->codec()->dimensionsSupported(info.dimensions())) { return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions, options.fColorPtr, options.fColorCount); } // If the native codec does not support the requested scale, scale by sampling. return this->sampledDecode(info, pixels, rowBytes, options); } // We are performing a subset decode. int sampleSize = options.fSampleSize; SkISize scaledSize = this->getSampledDimensions(sampleSize); if (!this->codec()->dimensionsSupported(scaledSize)) { // If the native codec does not support the requested scale, scale by sampling. return this->sampledDecode(info, pixels, rowBytes, options); } // Calculate the scaled subset bounds. int scaledSubsetX = subset->x() / sampleSize; int scaledSubsetY = subset->y() / sampleSize; int scaledSubsetWidth = info.width(); int scaledSubsetHeight = info.height(); // Start the scanline decode. SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth, scaledSize.height()); codecOptions.fSubset = &scanlineSubset; SkCodec::Result result = this->codec()->startScanlineDecode(info.makeWH(scaledSize.width(), scaledSize.height()), &codecOptions, options.fColorPtr, options.fColorCount); if (SkCodec::kSuccess != result) { return result; } // At this point, we are only concerned with subsetting. Either no scale was // requested, or the this->codec() is handling the scale. switch (this->codec()->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: case SkCodec::kNone_SkScanlineOrder: { if (!this->codec()->skipScanlines(scaledSubsetY)) { this->codec()->fillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, scaledSubsetHeight, 0); return SkCodec::kIncompleteInput; } int decodedLines = this->codec()->getScanlines(pixels, scaledSubsetHeight, rowBytes); if (decodedLines != scaledSubsetHeight) { return SkCodec::kIncompleteInput; } return SkCodec::kSuccess; } default: SkASSERT(false); return SkCodec::kUnimplemented; } }
SkCodec::Result SkScaledCodec::onGetPixels(const SkImageInfo& requestedInfo, void* dst, size_t rowBytes, const Options& options, SkPMColor ctable[], int* ctableCount, int* rowsDecoded) { 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) { return result; } SkSampler* sampler = fCodec->getSampler(true); if (!sampler) { return kUnimplemented; } if (sampler->setSampleX(sampleX) != requestedInfo.width()) { return kInvalidScale; } switch(fCodec->getScanlineOrder()) { case SkCodec::kTopDown_SkScanlineOrder: { if (!fCodec->skipScanlines(Y0)) { *rowsDecoded = 0; return kIncompleteInput; } 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; } } } // 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); } } *rowsDecoded = dstHeight; } return result; } case SkCodec::kNone_SkScanlineOrder: { SkAutoMalloc storage(srcHeight * 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); storagePtr += sampleY * rowBytes; dst = SkTAddOffset<void>(dst, rowBytes); scanlines -= sampleY; y++; } if (y < dstHeight) { // fCodec has already handled filling uninitialized memory. *rowsDecoded = dstHeight; return kIncompleteInput; } return kSuccess; } default: SkASSERT(false); return kUnimplemented; } }
static void check(skiatest::Reporter* r, const char path[], SkISize size, bool supportsScanlineDecoding, bool supportsSubsetDecoding, bool supports565 = true) { SkAutoTDelete<SkStream> stream(resource(path)); if (!stream) { SkDebugf("Missing resource '%s'\n", path); return; } SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(stream.detach())); if (!codec) { ERRORF(r, "Unable to decode '%s'", path); return; } // This test is used primarily to verify rewinding works properly. Using kN32 allows // us to test this without the added overhead of creating different bitmaps depending // on the color type (ex: building a color table for kIndex8). DM is where we test // decodes to all possible destination color types. SkImageInfo info = codec->getInfo().makeColorType(kN32_SkColorType); REPORTER_ASSERT(r, info.dimensions() == size); { // Test decoding to 565 SkImageInfo info565 = info.makeColorType(kRGB_565_SkColorType); SkCodec::Result expected = (supports565 && info.alphaType() == kOpaque_SkAlphaType) ? SkCodec::kSuccess : SkCodec::kInvalidConversion; test_info(r, codec, info565, expected, NULL); } SkBitmap bm; bm.allocPixels(info); SkAutoLockPixels autoLockPixels(bm); SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes(), NULL, NULL, NULL); REPORTER_ASSERT(r, result == SkCodec::kSuccess); SkMD5::Digest digest; md5(bm, &digest); // verify that re-decoding gives the same result. test_info(r, codec, info, SkCodec::kSuccess, &digest); { // Check alpha type conversions if (info.alphaType() == kOpaque_SkAlphaType) { test_info(r, codec, info.makeAlphaType(kUnpremul_SkAlphaType), SkCodec::kInvalidConversion, NULL); test_info(r, codec, info.makeAlphaType(kPremul_SkAlphaType), SkCodec::kInvalidConversion, NULL); } else { // Decoding to opaque should fail test_info(r, codec, info.makeAlphaType(kOpaque_SkAlphaType), SkCodec::kInvalidConversion, NULL); SkAlphaType otherAt = info.alphaType(); if (kPremul_SkAlphaType == otherAt) { otherAt = kUnpremul_SkAlphaType; } else { otherAt = kPremul_SkAlphaType; } // The other non-opaque alpha type should always succeed, but not match. test_info(r, codec, info.makeAlphaType(otherAt), SkCodec::kSuccess, NULL); } } // Scanline decoding follows. stream.reset(resource(path)); SkAutoTDelete<SkScanlineDecoder> scanlineDecoder( SkScanlineDecoder::NewFromStream(stream.detach())); if (supportsScanlineDecoding) { bm.eraseColor(SK_ColorYELLOW); REPORTER_ASSERT(r, scanlineDecoder); REPORTER_ASSERT(r, scanlineDecoder->start(info) == SkCodec::kSuccess); for (int y = 0; y < info.height(); y++) { result = scanlineDecoder->getScanlines(bm.getAddr(0, y), 1, 0); REPORTER_ASSERT(r, result == SkCodec::kSuccess); } // verify that scanline decoding gives the same result. compare_to_good_digest(r, digest, bm); } else { REPORTER_ASSERT(r, !scanlineDecoder); } // The rest of this function tests decoding subsets, and will decode an arbitrary number of // random subsets. // Do not attempt to decode subsets of an image of only once pixel, since there is no // meaningful subset. if (size.width() * size.height() == 1) { return; } SkRandom rand; SkIRect subset; SkCodec::Options opts; opts.fSubset = ⊂ for (int i = 0; i < 5; i++) { subset = generate_random_subset(&rand, size.width(), size.height()); SkASSERT(!subset.isEmpty()); const bool supported = codec->getValidSubset(&subset); REPORTER_ASSERT(r, supported == supportsSubsetDecoding); SkImageInfo subsetInfo = info.makeWH(subset.width(), subset.height()); SkBitmap bm; bm.allocPixels(subsetInfo); const SkCodec::Result result = codec->getPixels(bm.info(), bm.getPixels(), bm.rowBytes(), &opts, NULL, NULL); if (supportsSubsetDecoding) { REPORTER_ASSERT(r, result == SkCodec::kSuccess); // Webp is the only codec that supports subsets, and it will have modified the subset // to have even left/top. REPORTER_ASSERT(r, SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); } else { // No subsets will work. REPORTER_ASSERT(r, result == SkCodec::kUnimplemented); } } }