diff options
Diffstat (limited to '')
62 files changed, 14842 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/codec/SkAndroidCodec.cpp b/gfx/skia/skia/src/codec/SkAndroidCodec.cpp new file mode 100644 index 0000000000..b90dbe7af0 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkAndroidCodec.cpp @@ -0,0 +1,416 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" +#include "include/core/SkPixmap.h" +#include "src/codec/SkAndroidCodecAdapter.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSampledCodec.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkPixmapPriv.h" + +static bool is_valid_sample_size(int sampleSize) { + // FIXME: As Leon has mentioned elsewhere, surely there is also a maximum sampleSize? + return sampleSize > 0; +} + +/** + * Loads the gamut as a set of three points (triangle). + */ +static void load_gamut(SkPoint rgb[], const skcms_Matrix3x3& xyz) { + // rx = rX / (rX + rY + rZ) + // ry = rY / (rX + rY + rZ) + // gx, gy, bx, and gy are calulcated similarly. + for (int rgbIdx = 0; rgbIdx < 3; rgbIdx++) { + float sum = xyz.vals[rgbIdx][0] + xyz.vals[rgbIdx][1] + xyz.vals[rgbIdx][2]; + rgb[rgbIdx].fX = xyz.vals[rgbIdx][0] / sum; + rgb[rgbIdx].fY = xyz.vals[rgbIdx][1] / sum; + } +} + +/** + * Calculates the area of the triangular gamut. + */ +static float calculate_area(SkPoint abc[]) { + SkPoint a = abc[0]; + SkPoint b = abc[1]; + SkPoint c = abc[2]; + return 0.5f * SkTAbs(a.fX*b.fY + b.fX*c.fY - a.fX*c.fY - c.fX*b.fY - b.fX*a.fY); +} + +static constexpr float kSRGB_D50_GamutArea = 0.084f; + +static bool is_wide_gamut(const skcms_ICCProfile& profile) { + // Determine if the source image has a gamut that is wider than sRGB. If so, we + // will use P3 as the output color space to avoid clipping the gamut. + if (profile.has_toXYZD50) { + SkPoint rgb[3]; + load_gamut(rgb, profile.toXYZD50); + return calculate_area(rgb) > kSRGB_D50_GamutArea; + } + + return false; +} + +static inline SkImageInfo adjust_info(SkCodec* codec, + SkAndroidCodec::ExifOrientationBehavior orientationBehavior) { + auto info = codec->getInfo(); + if (orientationBehavior == SkAndroidCodec::ExifOrientationBehavior::kIgnore + || !SkPixmapPriv::ShouldSwapWidthHeight(codec->getOrigin())) { + return info; + } + return SkPixmapPriv::SwapWidthHeight(info); +} + +SkAndroidCodec::SkAndroidCodec(SkCodec* codec, ExifOrientationBehavior orientationBehavior) + : fInfo(adjust_info(codec, orientationBehavior)) + , fOrientationBehavior(orientationBehavior) + , fCodec(codec) +{} + +SkAndroidCodec::~SkAndroidCodec() {} + +std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + SkPngChunkReader* chunkReader) { + auto codec = SkCodec::MakeFromStream(std::move(stream), nullptr, chunkReader); + return MakeFromCodec(std::move(codec)); +} + +std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromCodec(std::unique_ptr<SkCodec> codec, + ExifOrientationBehavior orientationBehavior) { + if (nullptr == codec) { + return nullptr; + } + + switch ((SkEncodedImageFormat)codec->getEncodedFormat()) { + case SkEncodedImageFormat::kPNG: + case SkEncodedImageFormat::kICO: + case SkEncodedImageFormat::kJPEG: +#ifndef SK_HAS_WUFFS_LIBRARY + case SkEncodedImageFormat::kGIF: +#endif + case SkEncodedImageFormat::kBMP: + case SkEncodedImageFormat::kWBMP: + case SkEncodedImageFormat::kHEIF: + return skstd::make_unique<SkSampledCodec>(codec.release(), orientationBehavior); +#ifdef SK_HAS_WUFFS_LIBRARY + case SkEncodedImageFormat::kGIF: +#endif +#ifdef SK_HAS_WEBP_LIBRARY + case SkEncodedImageFormat::kWEBP: +#endif +#ifdef SK_CODEC_DECODES_RAW + case SkEncodedImageFormat::kDNG: +#endif +#if defined(SK_HAS_WEBP_LIBRARY) || defined(SK_CODEC_DECODES_RAW) || defined(SK_HAS_WUFFS_LIBRARY) + return skstd::make_unique<SkAndroidCodecAdapter>(codec.release(), orientationBehavior); +#endif + + default: + return nullptr; + } +} + +std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromData(sk_sp<SkData> data, + SkPngChunkReader* chunkReader) { + if (!data) { + return nullptr; + } + + return MakeFromStream(SkMemoryStream::Make(std::move(data)), chunkReader); +} + +SkColorType SkAndroidCodec::computeOutputColorType(SkColorType requestedColorType) { + bool highPrecision = fCodec->getEncodedInfo().bitsPerComponent() > 8; + switch (requestedColorType) { + case kARGB_4444_SkColorType: + return kN32_SkColorType; + case kN32_SkColorType: + break; + case kAlpha_8_SkColorType: + // Fall through to kGray_8. Before kGray_8_SkColorType existed, + // we allowed clients to request kAlpha_8 when they wanted a + // grayscale decode. + case kGray_8_SkColorType: + if (kGray_8_SkColorType == this->getInfo().colorType()) { + return kGray_8_SkColorType; + } + break; + case kRGB_565_SkColorType: + if (kOpaque_SkAlphaType == this->getInfo().alphaType()) { + return kRGB_565_SkColorType; + } + break; + case kRGBA_F16_SkColorType: + return kRGBA_F16_SkColorType; + default: + break; + } + + // F16 is the Android default for high precision images. + return highPrecision ? kRGBA_F16_SkColorType : kN32_SkColorType; +} + +SkAlphaType SkAndroidCodec::computeOutputAlphaType(bool requestedUnpremul) { + if (kOpaque_SkAlphaType == this->getInfo().alphaType()) { + return kOpaque_SkAlphaType; + } + return requestedUnpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; +} + +sk_sp<SkColorSpace> SkAndroidCodec::computeOutputColorSpace(SkColorType outputColorType, + sk_sp<SkColorSpace> prefColorSpace) { + switch (outputColorType) { + case kRGBA_F16_SkColorType: + case kRGB_565_SkColorType: + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + // If |prefColorSpace| is supplied, choose it. + if (prefColorSpace) { + return prefColorSpace; + } + + const skcms_ICCProfile* encodedProfile = fCodec->getEncodedInfo().profile(); + if (encodedProfile) { + if (auto encodedSpace = SkColorSpace::Make(*encodedProfile)) { + // Leave the pixels in the encoded color space. Color space conversion + // will be handled after decode time. + return encodedSpace; + } + + if (is_wide_gamut(*encodedProfile)) { + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); + } + } + + return SkColorSpace::MakeSRGB(); + } + default: + // Color correction not supported for kGray. + return nullptr; + } +} + +static bool supports_any_down_scale(const SkCodec* codec) { + return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP; +} + +// There are a variety of ways two SkISizes could be compared. This method +// returns true if either dimensions of a is < that of b. +// computeSampleSize also uses the opposite, which means that both +// dimensions of a >= b. +static inline bool smaller_than(const SkISize& a, const SkISize& b) { + return a.width() < b.width() || a.height() < b.height(); +} + +// Both dimensions of a > that of b. +static inline bool strictly_bigger_than(const SkISize& a, const SkISize& b) { + return a.width() > b.width() && a.height() > b.height(); +} + +int SkAndroidCodec::computeSampleSize(SkISize* desiredSize) const { + SkASSERT(desiredSize); + + if (!desiredSize || *desiredSize == fInfo.dimensions()) { + return 1; + } + + if (smaller_than(fInfo.dimensions(), *desiredSize)) { + *desiredSize = fInfo.dimensions(); + return 1; + } + + // Handle bad input: + if (desiredSize->width() < 1 || desiredSize->height() < 1) { + *desiredSize = SkISize::Make(std::max(1, desiredSize->width()), + std::max(1, desiredSize->height())); + } + + if (supports_any_down_scale(fCodec.get())) { + return 1; + } + + int sampleX = fInfo.width() / desiredSize->width(); + int sampleY = fInfo.height() / desiredSize->height(); + int sampleSize = std::min(sampleX, sampleY); + auto computedSize = this->getSampledDimensions(sampleSize); + if (computedSize == *desiredSize) { + return sampleSize; + } + + if (computedSize == fInfo.dimensions() || sampleSize == 1) { + // Cannot downscale + *desiredSize = computedSize; + return 1; + } + + if (strictly_bigger_than(computedSize, *desiredSize)) { + // See if there is a tighter fit. + while (true) { + auto smaller = this->getSampledDimensions(sampleSize + 1); + if (smaller == *desiredSize) { + return sampleSize + 1; + } + if (smaller == computedSize || smaller_than(smaller, *desiredSize)) { + // Cannot get any smaller without being smaller than desired. + *desiredSize = computedSize; + return sampleSize; + } + + sampleSize++; + computedSize = smaller; + } + + SkASSERT(false); + } + + if (!smaller_than(computedSize, *desiredSize)) { + // This means one of the computed dimensions is equal to desired, and + // the other is bigger. This is as close as we can get. + *desiredSize = computedSize; + return sampleSize; + } + + // computedSize is too small. Make it larger. + while (sampleSize > 2) { + auto bigger = this->getSampledDimensions(sampleSize - 1); + if (bigger == *desiredSize || !smaller_than(bigger, *desiredSize)) { + *desiredSize = bigger; + return sampleSize - 1; + } + sampleSize--; + } + + *desiredSize = fInfo.dimensions(); + return 1; +} + +SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const { + if (!is_valid_sample_size(sampleSize)) { + return {0, 0}; + } + + // Fast path for when we are not scaling. + if (1 == sampleSize) { + return fInfo.dimensions(); + } + + auto dims = this->onGetSampledDimensions(sampleSize); + if (fOrientationBehavior == SkAndroidCodec::ExifOrientationBehavior::kIgnore + || !SkPixmapPriv::ShouldSwapWidthHeight(fCodec->getOrigin())) { + return dims; + } + + return { dims.height(), dims.width() }; +} + +bool SkAndroidCodec::getSupportedSubset(SkIRect* desiredSubset) const { + if (!desiredSubset || !is_valid_subset(*desiredSubset, fInfo.dimensions())) { + return false; + } + + return this->onGetSupportedSubset(desiredSubset); +} + +SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const { + if (!is_valid_sample_size(sampleSize)) { + return {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 {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 {get_scaled_dimension(subset.width(), sampleSize), + get_scaled_dimension(subset.height(), sampleSize)}; +} + +static bool acceptable_result(SkCodec::Result result) { + switch (result) { + // These results mean a partial or complete image. They should be considered + // a success by SkPixmapPriv. + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + return false; + } +} + +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 SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes) { + return this->getAndroidPixels(info, pixels, rowBytes, nullptr); +} diff --git a/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp new file mode 100644 index 0000000000..5551136fa6 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkAndroidCodecAdapter.h" +#include "src/codec/SkCodecPriv.h" + +SkAndroidCodecAdapter::SkAndroidCodecAdapter(SkCodec* codec, ExifOrientationBehavior behavior) + : INHERITED(codec, behavior) +{} + +SkISize SkAndroidCodecAdapter::onGetSampledDimensions(int sampleSize) const { + float scale = get_scale_from_sample_size(sampleSize); + return this->codec()->getScaledDimensions(scale); +} + +bool SkAndroidCodecAdapter::onGetSupportedSubset(SkIRect* desiredSubset) const { + return this->codec()->getValidSubset(desiredSubset); +} + +SkCodec::Result SkAndroidCodecAdapter::onGetAndroidPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const AndroidOptions& options) { + SkCodec::Options codecOptions; + codecOptions.fZeroInitialized = options.fZeroInitialized; + codecOptions.fSubset = options.fSubset; + return this->codec()->getPixels(info, pixels, rowBytes, &codecOptions); +} diff --git a/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h new file mode 100644 index 0000000000..7a5d093d5b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkAndroidCodecAdapter_DEFINED +#define SkAndroidCodecAdapter_DEFINED + +#include "include/codec/SkAndroidCodec.h" + +/** + * This class wraps SkCodec to implement the functionality of SkAndroidCodec. + * The underlying SkCodec implements sampled decodes. SkCodec's that do not + * implement that are wrapped with SkSampledCodec instead. + */ +class SkAndroidCodecAdapter : public SkAndroidCodec { +public: + + explicit SkAndroidCodecAdapter(SkCodec*, ExifOrientationBehavior); + + ~SkAndroidCodecAdapter() override {} + +protected: + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override; + + SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions& options) override; + +private: + + typedef SkAndroidCodec INHERITED; +}; +#endif // SkAndroidCodecAdapter_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp b/gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp new file mode 100644 index 0000000000..32a1d37384 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "include/private/SkMalloc.h" +#include "src/codec/SkBmpBaseCodec.h" + +SkBmpBaseCodec::~SkBmpBaseCodec() {} + +SkBmpBaseCodec::SkBmpBaseCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fSrcBuffer(sk_malloc_canfail(this->srcRowBytes())) +{} diff --git a/gfx/skia/skia/src/codec/SkBmpBaseCodec.h b/gfx/skia/skia/src/codec/SkBmpBaseCodec.h new file mode 100644 index 0000000000..1c57ce6de4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpBaseCodec.h @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpBaseCodec_DEFINED +#define SkBmpBaseCodec_DEFINED + +#include "include/private/SkTemplates.h" +#include "src/codec/SkBmpCodec.h" + +/* + * Common base class for SkBmpStandardCodec and SkBmpMaskCodec. + */ +class SkBmpBaseCodec : public SkBmpCodec { +public: + ~SkBmpBaseCodec() override; + + /* + * Whether fSrcBuffer was successfully created. + * + * If false, this Codec must not be used. + */ + bool didCreateSrcBuffer() const { return fSrcBuffer != nullptr; } + +protected: + SkBmpBaseCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream>, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder); + + uint8_t* srcBuffer() { return reinterpret_cast<uint8_t*>(fSrcBuffer.get()); } + +private: + SkAutoFree fSrcBuffer; + + typedef SkBmpCodec INHERITED; +}; +#endif // SkBmpBaseCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpCodec.cpp b/gfx/skia/skia/src/codec/SkBmpCodec.cpp new file mode 100644 index 0000000000..615c8a4212 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpCodec.cpp @@ -0,0 +1,650 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkBmpMaskCodec.h" +#include "src/codec/SkBmpRLECodec.h" +#include "src/codec/SkBmpStandardCodec.h" +#include "src/codec/SkCodecPriv.h" + +/* + * Defines the version and type of the second bitmap header + */ +enum BmpHeaderType { + kInfoV1_BmpHeaderType, + kInfoV2_BmpHeaderType, + kInfoV3_BmpHeaderType, + kInfoV4_BmpHeaderType, + kInfoV5_BmpHeaderType, + kOS2V1_BmpHeaderType, + kOS2VX_BmpHeaderType, + kUnknown_BmpHeaderType +}; + +/* + * Possible bitmap compression types + */ +enum BmpCompressionMethod { + kNone_BmpCompressionMethod = 0, + k8BitRLE_BmpCompressionMethod = 1, + k4BitRLE_BmpCompressionMethod = 2, + kBitMasks_BmpCompressionMethod = 3, + kJpeg_BmpCompressionMethod = 4, + kPng_BmpCompressionMethod = 5, + kAlphaBitMasks_BmpCompressionMethod = 6, + kCMYK_BmpCompressionMethod = 11, + kCMYK8BitRLE_BmpCompressionMethod = 12, + kCMYK4BitRLE_BmpCompressionMethod = 13 +}; + +/* + * Used to define the input format of the bmp + */ +enum BmpInputFormat { + kStandard_BmpInputFormat, + kRLE_BmpInputFormat, + kBitMask_BmpInputFormat, + kUnknown_BmpInputFormat +}; + +/* + * Checks the start of the stream to see if the image is a bitmap + */ +bool SkBmpCodec::IsBmp(const void* buffer, size_t bytesRead) { + // TODO: Support "IC", "PT", "CI", "CP", "BA" + const char bmpSig[] = { 'B', 'M' }; + return bytesRead >= sizeof(bmpSig) && !memcmp(buffer, bmpSig, sizeof(bmpSig)); +} + +/* + * Assumes IsBmp was called and returned true + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ +std::unique_ptr<SkCodec> SkBmpCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + return SkBmpCodec::MakeFromStream(std::move(stream), result, false); +} + +/* + * Creates a bmp decoder for a bmp embedded in ico + * Reads enough of the stream to determine the image format + */ +std::unique_ptr<SkCodec> SkBmpCodec::MakeFromIco(std::unique_ptr<SkStream> stream, Result* result) { + return SkBmpCodec::MakeFromStream(std::move(stream), result, true); +} + +// Header size constants +static constexpr uint32_t kBmpHeaderBytes = 14; +static constexpr uint32_t kBmpHeaderBytesPlusFour = kBmpHeaderBytes + 4; +static constexpr uint32_t kBmpOS2V1Bytes = 12; +static constexpr uint32_t kBmpOS2V2Bytes = 64; +static constexpr uint32_t kBmpInfoBaseBytes = 16; +static constexpr uint32_t kBmpInfoV1Bytes = 40; +static constexpr uint32_t kBmpInfoV2Bytes = 52; +static constexpr uint32_t kBmpInfoV3Bytes = 56; +static constexpr uint32_t kBmpInfoV4Bytes = 108; +static constexpr uint32_t kBmpInfoV5Bytes = 124; +static constexpr uint32_t kBmpMaskBytes = 12; + +static BmpHeaderType get_header_type(size_t infoBytes) { + if (infoBytes >= kBmpInfoBaseBytes) { + // Check the version of the header + switch (infoBytes) { + case kBmpInfoV1Bytes: + return kInfoV1_BmpHeaderType; + case kBmpInfoV2Bytes: + return kInfoV2_BmpHeaderType; + case kBmpInfoV3Bytes: + return kInfoV3_BmpHeaderType; + case kBmpInfoV4Bytes: + return kInfoV4_BmpHeaderType; + case kBmpInfoV5Bytes: + return kInfoV5_BmpHeaderType; + case 16: + case 20: + case 24: + case 28: + case 32: + case 36: + case 42: + case 46: + case 48: + case 60: + case kBmpOS2V2Bytes: + return kOS2VX_BmpHeaderType; + default: + SkCodecPrintf("Error: unknown bmp header format.\n"); + return kUnknown_BmpHeaderType; + } + } if (infoBytes >= kBmpOS2V1Bytes) { + // The OS2V1 is treated separately because it has a unique format + return kOS2V1_BmpHeaderType; + } else { + // There are no valid bmp headers + SkCodecPrintf("Error: second bitmap header size is invalid.\n"); + return kUnknown_BmpHeaderType; + } +} + +SkCodec::Result SkBmpCodec::ReadHeader(SkStream* stream, bool inIco, + std::unique_ptr<SkCodec>* codecOut) { + // The total bytes in the bmp file + // We only need to use this value for RLE decoding, so we will only + // check that it is valid in the RLE case. + uint32_t totalBytes; + // The offset from the start of the file where the pixel data begins + uint32_t offset; + // The size of the second (info) header in bytes + uint32_t infoBytes; + + // Bmps embedded in Icos skip the first Bmp header + if (!inIco) { + // Read the first header and the size of the second header + uint8_t hBuffer[kBmpHeaderBytesPlusFour]; + if (stream->read(hBuffer, kBmpHeaderBytesPlusFour) != + kBmpHeaderBytesPlusFour) { + SkCodecPrintf("Error: unable to read first bitmap header.\n"); + return kIncompleteInput; + } + + totalBytes = get_int(hBuffer, 2); + offset = get_int(hBuffer, 10); + if (offset < kBmpHeaderBytes + kBmpOS2V1Bytes) { + SkCodecPrintf("Error: invalid starting location for pixel data\n"); + return kInvalidInput; + } + + // The size of the second (info) header in bytes + // The size is the first field of the second header, so we have already + // read the first four infoBytes. + infoBytes = get_int(hBuffer, 14); + if (infoBytes < kBmpOS2V1Bytes) { + SkCodecPrintf("Error: invalid second header size.\n"); + return kInvalidInput; + } + } else { + // This value is only used by RLE compression. Bmp in Ico files do not + // use RLE. If the compression field is incorrectly signaled as RLE, + // we will catch this and signal an error below. + totalBytes = 0; + + // Bmps in Ico cannot specify an offset. We will always assume that + // pixel data begins immediately after the color table. This value + // will be corrected below. + offset = 0; + + // Read the size of the second header + uint8_t hBuffer[4]; + if (stream->read(hBuffer, 4) != 4) { + SkCodecPrintf("Error: unable to read size of second bitmap header.\n"); + return kIncompleteInput; + } + infoBytes = get_int(hBuffer, 0); + if (infoBytes < kBmpOS2V1Bytes) { + SkCodecPrintf("Error: invalid second header size.\n"); + return kInvalidInput; + } + } + + // Determine image information depending on second header format + const BmpHeaderType headerType = get_header_type(infoBytes); + if (kUnknown_BmpHeaderType == headerType) { + return kInvalidInput; + } + + // We already read the first four bytes of the info header to get the size + const uint32_t infoBytesRemaining = infoBytes - 4; + + // Read the second header + std::unique_ptr<uint8_t[]> iBuffer(new uint8_t[infoBytesRemaining]); + if (stream->read(iBuffer.get(), infoBytesRemaining) != infoBytesRemaining) { + SkCodecPrintf("Error: unable to read second bitmap header.\n"); + return kIncompleteInput; + } + + // The number of bits used per pixel in the pixel data + uint16_t bitsPerPixel; + + // The compression method for the pixel data + uint32_t compression = kNone_BmpCompressionMethod; + + // Number of colors in the color table, defaults to 0 or max (see below) + uint32_t numColors = 0; + + // Bytes per color in the color table, early versions use 3, most use 4 + uint32_t bytesPerColor; + + // The image width and height + int width, height; + + switch (headerType) { + case kInfoV1_BmpHeaderType: + case kInfoV2_BmpHeaderType: + case kInfoV3_BmpHeaderType: + case kInfoV4_BmpHeaderType: + case kInfoV5_BmpHeaderType: + case kOS2VX_BmpHeaderType: + // We check the size of the header before entering the if statement. + // We should not reach this point unless the size is large enough for + // these required fields. + SkASSERT(infoBytesRemaining >= 12); + width = get_int(iBuffer.get(), 0); + height = get_int(iBuffer.get(), 4); + bitsPerPixel = get_short(iBuffer.get(), 10); + + // Some versions do not have these fields, so we check before + // overwriting the default value. + if (infoBytesRemaining >= 16) { + compression = get_int(iBuffer.get(), 12); + if (infoBytesRemaining >= 32) { + numColors = get_int(iBuffer.get(), 28); + } + } + + // All of the headers that reach this point, store color table entries + // using 4 bytes per pixel. + bytesPerColor = 4; + break; + case kOS2V1_BmpHeaderType: + // The OS2V1 is treated separately because it has a unique format + width = (int) get_short(iBuffer.get(), 0); + height = (int) get_short(iBuffer.get(), 2); + bitsPerPixel = get_short(iBuffer.get(), 6); + bytesPerColor = 3; + break; + case kUnknown_BmpHeaderType: + // We'll exit above in this case. + SkASSERT(false); + return kInvalidInput; + } + + // Check for valid dimensions from header + SkCodec::SkScanlineOrder rowOrder = SkCodec::kBottomUp_SkScanlineOrder; + if (height < 0) { + // We can't negate INT32_MIN. + if (height == INT32_MIN) { + return kInvalidInput; + } + + height = -height; + rowOrder = SkCodec::kTopDown_SkScanlineOrder; + } + // The height field for bmp in ico is double the actual height because they + // contain an XOR mask followed by an AND mask + if (inIco) { + height /= 2; + } + + // Arbitrary maximum. Matches Chromium. + constexpr int kMaxDim = 1 << 16; + if (width <= 0 || height <= 0 || width >= kMaxDim || height >= kMaxDim) { + SkCodecPrintf("Error: invalid bitmap dimensions.\n"); + return kInvalidInput; + } + + // Create mask struct + SkMasks::InputMasks inputMasks; + memset(&inputMasks, 0, sizeof(SkMasks::InputMasks)); + + // Determine the input compression format and set bit masks if necessary + uint32_t maskBytes = 0; + BmpInputFormat inputFormat = kUnknown_BmpInputFormat; + switch (compression) { + case kNone_BmpCompressionMethod: + inputFormat = kStandard_BmpInputFormat; + + // In addition to more standard pixel compression formats, bmp supports + // the use of bit masks to determine pixel components. The standard + // format for representing 16-bit colors is 555 (XRRRRRGGGGGBBBBB), + // which does not map well to any Skia color formats. For this reason, + // we will always enable mask mode with 16 bits per pixel. + if (16 == bitsPerPixel) { + inputMasks.red = 0x7C00; + inputMasks.green = 0x03E0; + inputMasks.blue = 0x001F; + inputFormat = kBitMask_BmpInputFormat; + } + break; + case k8BitRLE_BmpCompressionMethod: + if (bitsPerPixel != 8) { + SkCodecPrintf("Warning: correcting invalid bitmap format.\n"); + bitsPerPixel = 8; + } + inputFormat = kRLE_BmpInputFormat; + break; + case k4BitRLE_BmpCompressionMethod: + if (bitsPerPixel != 4) { + SkCodecPrintf("Warning: correcting invalid bitmap format.\n"); + bitsPerPixel = 4; + } + inputFormat = kRLE_BmpInputFormat; + break; + case kAlphaBitMasks_BmpCompressionMethod: + case kBitMasks_BmpCompressionMethod: + // Load the masks + inputFormat = kBitMask_BmpInputFormat; + switch (headerType) { + case kInfoV1_BmpHeaderType: { + // The V1 header stores the bit masks after the header + uint8_t buffer[kBmpMaskBytes]; + if (stream->read(buffer, kBmpMaskBytes) != kBmpMaskBytes) { + SkCodecPrintf("Error: unable to read bit inputMasks.\n"); + return kIncompleteInput; + } + maskBytes = kBmpMaskBytes; + inputMasks.red = get_int(buffer, 0); + inputMasks.green = get_int(buffer, 4); + inputMasks.blue = get_int(buffer, 8); + break; + } + case kInfoV2_BmpHeaderType: + case kInfoV3_BmpHeaderType: + case kInfoV4_BmpHeaderType: + case kInfoV5_BmpHeaderType: + // Header types are matched based on size. If the header + // is V2+, we are guaranteed to be able to read at least + // this size. + SkASSERT(infoBytesRemaining >= 48); + inputMasks.red = get_int(iBuffer.get(), 36); + inputMasks.green = get_int(iBuffer.get(), 40); + inputMasks.blue = get_int(iBuffer.get(), 44); + + if (kInfoV2_BmpHeaderType == headerType || + (kInfoV3_BmpHeaderType == headerType && !inIco)) { + break; + } + + // V3+ bmp files introduce an alpha mask and allow the creator of the image + // to use the alpha channels. However, many of these images leave the + // alpha channel blank and expect to be rendered as opaque. This is the + // case for almost all V3 images, so we ignore the alpha mask. For V4+ + // images in kMask mode, we will use the alpha mask. Additionally, V3 + // bmp-in-ico expect us to use the alpha mask. + // + // skbug.com/4116: We should perhaps also apply the alpha mask in kStandard + // mode. We just haven't seen any images that expect this + // behavior. + // + // Header types are matched based on size. If the header is + // V3+, we are guaranteed to be able to read at least this size. + SkASSERT(infoBytesRemaining >= 52); + inputMasks.alpha = get_int(iBuffer.get(), 48); + break; + case kOS2VX_BmpHeaderType: + // TODO: Decide if we intend to support this. + // It is unsupported in the previous version and + // in chromium. I have not come across a test case + // that uses this format. + SkCodecPrintf("Error: huffman format unsupported.\n"); + return kUnimplemented; + default: + SkCodecPrintf("Error: invalid bmp bit masks header.\n"); + return kInvalidInput; + } + break; + case kJpeg_BmpCompressionMethod: + if (24 == bitsPerPixel) { + inputFormat = kRLE_BmpInputFormat; + break; + } + // Fall through + case kPng_BmpCompressionMethod: + // TODO: Decide if we intend to support this. + // It is unsupported in the previous version and + // in chromium. I think it is used mostly for printers. + SkCodecPrintf("Error: compression format not supported.\n"); + return kUnimplemented; + case kCMYK_BmpCompressionMethod: + case kCMYK8BitRLE_BmpCompressionMethod: + case kCMYK4BitRLE_BmpCompressionMethod: + // TODO: Same as above. + SkCodecPrintf("Error: CMYK not supported for bitmap decoding.\n"); + return kUnimplemented; + default: + SkCodecPrintf("Error: invalid format for bitmap decoding.\n"); + return kInvalidInput; + } + iBuffer.reset(); + + // Calculate the number of bytes read so far + const uint32_t bytesRead = kBmpHeaderBytes + infoBytes + maskBytes; + if (!inIco && offset < bytesRead) { + // TODO (msarett): Do we really want to fail if the offset in the header is invalid? + // Seems like we can just assume that the offset is zero and try to decode? + // Maybe we don't want to try to decode corrupt images? + SkCodecPrintf("Error: pixel data offset less than header size.\n"); + return kInvalidInput; + } + + + + switch (inputFormat) { + case kStandard_BmpInputFormat: { + // BMPs are generally opaque, however BMPs-in-ICOs may contain + // a transparency mask after the image. Therefore, we mark the + // alpha as kBinary if the BMP is contained in an ICO. + // We use |isOpaque| to indicate if the BMP itself is opaque. + SkEncodedInfo::Alpha alpha = inIco ? SkEncodedInfo::kBinary_Alpha : + SkEncodedInfo::kOpaque_Alpha; + bool isOpaque = true; + + SkEncodedInfo::Color color; + uint8_t bitsPerComponent; + switch (bitsPerPixel) { + // Palette formats + case 1: + case 2: + case 4: + case 8: + // In the case of ICO, kBGRA is actually the closest match, + // since we will need to apply a transparency mask. + if (inIco) { + color = SkEncodedInfo::kBGRA_Color; + bitsPerComponent = 8; + } else { + color = SkEncodedInfo::kPalette_Color; + bitsPerComponent = (uint8_t) bitsPerPixel; + } + break; + case 24: + // In the case of ICO, kBGRA is actually the closest match, + // since we will need to apply a transparency mask. + color = inIco ? SkEncodedInfo::kBGRA_Color : SkEncodedInfo::kBGR_Color; + bitsPerComponent = 8; + break; + case 32: + // 32-bit BMP-in-ICOs actually use the alpha channel in place of a + // transparency mask. + if (inIco) { + isOpaque = false; + alpha = SkEncodedInfo::kUnpremul_Alpha; + color = SkEncodedInfo::kBGRA_Color; + } else { + color = SkEncodedInfo::kBGRX_Color; + } + bitsPerComponent = 8; + break; + default: + SkCodecPrintf("Error: invalid input value for bits per pixel.\n"); + return kInvalidInput; + } + + if (codecOut) { + // We require streams to have a memory base for Bmp-in-Ico decodes. + SkASSERT(!inIco || nullptr != stream->getMemoryBase()); + + // Set the image info and create a codec. + auto info = SkEncodedInfo::Make(width, height, color, alpha, bitsPerComponent); + codecOut->reset(new SkBmpStandardCodec(std::move(info), + std::unique_ptr<SkStream>(stream), + bitsPerPixel, numColors, bytesPerColor, + offset - bytesRead, rowOrder, isOpaque, + inIco)); + return static_cast<SkBmpStandardCodec*>(codecOut->get())->didCreateSrcBuffer() + ? kSuccess : kInvalidInput; + } + return kSuccess; + } + + case kBitMask_BmpInputFormat: { + // Bmp-in-Ico must be standard mode + if (inIco) { + SkCodecPrintf("Error: Icos may not use bit mask format.\n"); + return kInvalidInput; + } + + switch (bitsPerPixel) { + case 16: + case 24: + case 32: + break; + default: + SkCodecPrintf("Error: invalid input value for bits per pixel.\n"); + return kInvalidInput; + } + + // Skip to the start of the pixel array. + // We can do this here because there is no color table to read + // in bit mask mode. + if (stream->skip(offset - bytesRead) != offset - bytesRead) { + SkCodecPrintf("Error: unable to skip to image data.\n"); + return kIncompleteInput; + } + + if (codecOut) { + // Check that input bit masks are valid and create the masks object + SkASSERT(bitsPerPixel % 8 == 0); + std::unique_ptr<SkMasks> masks(SkMasks::CreateMasks(inputMasks, bitsPerPixel/8)); + if (nullptr == masks) { + SkCodecPrintf("Error: invalid input masks.\n"); + return kInvalidInput; + } + + // Masked bmps are not a great fit for SkEncodedInfo, since they have + // arbitrary component orderings and bits per component. Here we choose + // somewhat reasonable values - it's ok that we don't match exactly + // because SkBmpMaskCodec has its own mask swizzler anyway. + SkEncodedInfo::Color color; + SkEncodedInfo::Alpha alpha; + if (masks->getAlphaMask()) { + color = SkEncodedInfo::kBGRA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } else { + color = SkEncodedInfo::kBGR_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + auto info = SkEncodedInfo::Make(width, height, color, alpha, 8); + codecOut->reset(new SkBmpMaskCodec(std::move(info), + std::unique_ptr<SkStream>(stream), bitsPerPixel, + masks.release(), rowOrder)); + return static_cast<SkBmpMaskCodec*>(codecOut->get())->didCreateSrcBuffer() + ? kSuccess : kInvalidInput; + } + return kSuccess; + } + + case kRLE_BmpInputFormat: { + // We should not reach this point without a valid value of bitsPerPixel. + SkASSERT(4 == bitsPerPixel || 8 == bitsPerPixel || 24 == bitsPerPixel); + + // Check for a valid number of total bytes when in RLE mode + if (totalBytes <= offset) { + SkCodecPrintf("Error: RLE requires valid input size.\n"); + return kInvalidInput; + } + + // Bmp-in-Ico must be standard mode + // When inIco is true, this line cannot be reached, since we + // require that RLE Bmps have a valid number of totalBytes, and + // Icos skip the header that contains totalBytes. + SkASSERT(!inIco); + + if (codecOut) { + // RLE inputs may skip pixels, leaving them as transparent. This + // is uncommon, but we cannot be certain that an RLE bmp will be + // opaque or that we will be able to represent it with a palette. + // For that reason, we always indicate that we are kBGRA. + auto info = SkEncodedInfo::Make(width, height, SkEncodedInfo::kBGRA_Color, + SkEncodedInfo::kBinary_Alpha, 8); + codecOut->reset(new SkBmpRLECodec(std::move(info), + std::unique_ptr<SkStream>(stream), bitsPerPixel, + numColors, bytesPerColor, offset - bytesRead, + rowOrder)); + } + return kSuccess; + } + default: + SkASSERT(false); + return kInvalidInput; + } +} + +/* + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ +std::unique_ptr<SkCodec> SkBmpCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result, bool inIco) { + std::unique_ptr<SkCodec> codec; + *result = ReadHeader(stream.get(), inIco, &codec); + if (codec) { + // codec has taken ownership of stream, so we do not need to delete it. + stream.release(); + } + return kSuccess == *result ? std::move(codec) : nullptr; +} + +SkBmpCodec::SkBmpCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), kXformSrcColorFormat, std::move(stream)) + , fBitsPerPixel(bitsPerPixel) + , fRowOrder(rowOrder) + , fSrcRowBytes(SkAlign4(compute_row_bytes(this->dimensions().width(), fBitsPerPixel))) + , fXformBuffer(nullptr) +{} + +bool SkBmpCodec::onRewind() { + return SkBmpCodec::ReadHeader(this->stream(), this->inIco(), nullptr) == kSuccess; +} + +int32_t SkBmpCodec::getDstRow(int32_t y, int32_t height) const { + if (SkCodec::kTopDown_SkScanlineOrder == fRowOrder) { + return y; + } + SkASSERT(SkCodec::kBottomUp_SkScanlineOrder == fRowOrder); + return height - y - 1; +} + +SkCodec::Result SkBmpCodec::prepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + return this->onPrepareToDecode(dstInfo, options); +} + +SkCodec::Result SkBmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + return prepareToDecode(dstInfo, options); +} + +int SkBmpCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { + // Create a new image info representing the portion of the image to decode + SkImageInfo rowInfo = this->dstInfo().makeWH(this->dstInfo().width(), count); + + // Decode the requested rows + return this->decodeRows(rowInfo, dst, rowBytes, this->options()); +} + +bool SkBmpCodec::skipRows(int count) { + const size_t bytesToSkip = count * fSrcRowBytes; + return this->stream()->skip(bytesToSkip) == bytesToSkip; +} + +bool SkBmpCodec::onSkipScanlines(int count) { + return this->skipRows(count); +} diff --git a/gfx/skia/skia/src/codec/SkBmpCodec.h b/gfx/skia/skia/src/codec/SkBmpCodec.h new file mode 100644 index 0000000000..97a15b6d23 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpCodec.h @@ -0,0 +1,151 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpCodec_DEFINED +#define SkBmpCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +/* + * This class enables code sharing between its bmp codec subclasses. The + * subclasses actually do the work. + */ +class SkBmpCodec : public SkCodec { +public: + static bool IsBmp(const void*, size_t); + + /* + * Assumes IsBmp was called and returned true + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + + /* + * Creates a bmp decoder for a bmp embedded in ico + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr<SkCodec> MakeFromIco(std::unique_ptr<SkStream>, Result*); + +protected: + + SkBmpCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream>, + uint16_t bitsPerPixel, SkCodec::SkScanlineOrder rowOrder); + + SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kBMP; } + + /* + * Read enough of the stream to initialize the SkBmpCodec. + * On kSuccess, if codecOut is not nullptr, it will be set to a new SkBmpCodec. + */ + static Result ReadHeader(SkStream*, bool inIco, std::unique_ptr<SkCodec>* codecOut); + + bool onRewind() override; + + /* + * Returns whether this BMP is part of an ICO image. + */ + bool inIco() const { + return this->onInIco(); + } + + virtual bool onInIco() const { + return false; + } + + /* + * Get the destination row number corresponding to the encoded row number. + * For kTopDown, we simply return y, but for kBottomUp, the rows will be + * decoded in reverse order. + * + * @param y Iterates from 0 to height, indicating the current row. + * @param height The height of the current subset of the image that we are + * decoding. This is generally equal to the full height + * when we want to decode the full or one when we are + * sampling. + */ + int32_t getDstRow(int32_t y, int32_t height) const; + + /* + * Accessors used by subclasses + */ + uint16_t bitsPerPixel() const { return fBitsPerPixel; } + SkScanlineOrder onGetScanlineOrder() const override { return fRowOrder; } + size_t srcRowBytes() const { return fSrcRowBytes; } + + /* + * To be overriden by bmp subclasses, which provide unique implementations. + * Performs subclass specific setup. + * + * @param dstInfo Contains output information. Height specifies + * the total number of rows that will be decoded. + * @param options Additonal options to pass to the decoder. + */ + virtual SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) = 0; + SkCodec::Result prepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options); + + uint32_t* xformBuffer() const { return fXformBuffer.get(); } + void resetXformBuffer(int count) { fXformBuffer.reset(new uint32_t[count]); } + + /* + * BMPs are typically encoded as BGRA/BGR so this is a more efficient choice + * than RGBA. + */ + static constexpr SkColorType kXformSrcColorType = kBGRA_8888_SkColorType; + static constexpr auto kXformSrcColorFormat = skcms_PixelFormat_BGRA_8888; + +private: + + /* + * Creates a bmp decoder + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*, bool inIco); + + /* + * Decodes the next dstInfo.height() lines. + * + * onGetPixels() uses this for full image decodes. + * SkScaledCodec::onGetPixels() uses the scanline decoder to call this with + * dstInfo.height() = 1, in order to implement sampling. + * A potential future use is to allow the caller to decode a subset of the + * lines in the image. + * + * @param dstInfo Contains output information. Height specifies the + * number of rows to decode at this time. + * @param dst Memory location to store output pixels + * @param dstRowBytes Bytes in a row of the destination + * @return Number of rows successfully decoded + */ + virtual int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) = 0; + + virtual bool skipRows(int count); + + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options&) override; + + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + + bool onSkipScanlines(int count) override; + + const uint16_t fBitsPerPixel; + const SkScanlineOrder fRowOrder; + const size_t fSrcRowBytes; + std::unique_ptr<uint32_t[]> fXformBuffer; + + typedef SkCodec INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp b/gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp new file mode 100644 index 0000000000..874056a08d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp @@ -0,0 +1,105 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpMaskCodec.h" +#include "src/codec/SkCodecPriv.h" + +/* + * Creates an instance of the decoder + */ +SkBmpMaskCodec::SkBmpMaskCodec(SkEncodedInfo&& info, + std::unique_ptr<SkStream> stream, + uint16_t bitsPerPixel, SkMasks* masks, + SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fMasks(masks) + , fMaskSwizzler(nullptr) +{} + +/* + * 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; +} + +SkCodec::Result SkBmpMaskCodec::onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + if (this->colorXform()) { + this->resetXformBuffer(dstInfo.width()); + } + + SkImageInfo swizzlerInfo = dstInfo; + if (this->colorXform()) { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + } + + bool srcIsOpaque = this->getEncodedInfo().opaque(); + fMaskSwizzler.reset(SkMaskSwizzler::CreateMaskSwizzler(swizzlerInfo, srcIsOpaque, + fMasks.get(), this->bitsPerPixel(), options)); + SkASSERT(fMaskSwizzler); + + return SkCodec::kSuccess; +} + +/* + * Performs the decoding + */ +int SkBmpMaskCodec::decodeRows(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts) { + // Iterate over rows of the image + uint8_t* srcRow = this->srcBuffer(); + const int height = dstInfo.height(); + for (int y = 0; y < height; y++) { + // Read a row of the input + if (this->stream()->read(srcRow, this->srcRowBytes()) != this->srcRowBytes()) { + SkCodecPrintf("Warning: incomplete input stream.\n"); + return y; + } + + // Decode the row in destination format + uint32_t row = this->getDstRow(y, height); + void* dstRow = SkTAddOffset<void>(dst, row * dstRowBytes); + + if (this->colorXform()) { + fMaskSwizzler->swizzle(this->xformBuffer(), srcRow); + this->applyColorXform(dstRow, this->xformBuffer(), fMaskSwizzler->swizzleWidth()); + } else { + fMaskSwizzler->swizzle(dstRow, srcRow); + } + } + + // Finished decoding the entire image + return height; +} diff --git a/gfx/skia/skia/src/codec/SkBmpMaskCodec.h b/gfx/skia/skia/src/codec/SkBmpMaskCodec.h new file mode 100644 index 0000000000..eaef50d20b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpMaskCodec.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkBmpMaskCodec_DEFINED +#define SkBmpMaskCodec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkBmpBaseCodec.h" +#include "src/codec/SkMaskSwizzler.h" + +/* + * This class implements the decoding for bmp images using bit masks + */ +class SkBmpMaskCodec : public SkBmpBaseCodec { +public: + + /* + * Creates an instance of the decoder + * + * Called only by SkBmpCodec::MakeFromStream + * There should be no other callers despite this being public + * + * @param info contains properties of the encoded data + * @param stream the stream of encoded image data + * @param bitsPerPixel the number of bits used to store each pixel + * @param masks color masks for certain bmp formats + * @param rowOrder indicates whether rows are ordered top-down or bottom-up + */ + SkBmpMaskCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream>, + uint16_t bitsPerPixel, SkMasks* masks, + SkCodec::SkScanlineOrder rowOrder); + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options&, + int*) override; + + SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + +private: + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fMaskSwizzler); + return fMaskSwizzler.get(); + } + + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; + + std::unique_ptr<SkMasks> fMasks; + std::unique_ptr<SkMaskSwizzler> fMaskSwizzler; + + typedef SkBmpBaseCodec INHERITED; +}; +#endif // SkBmpMaskCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpRLECodec.cpp b/gfx/skia/skia/src/codec/SkBmpRLECodec.cpp new file mode 100644 index 0000000000..fc5d298e2f --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpRLECodec.cpp @@ -0,0 +1,568 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpRLECodec.h" +#include "src/codec/SkCodecPriv.h" + +/* + * Creates an instance of the decoder + * Called only by NewFromStream + */ +SkBmpRLECodec::SkBmpRLECodec(SkEncodedInfo&& info, + std::unique_ptr<SkStream> stream, + uint16_t bitsPerPixel, uint32_t numColors, + uint32_t bytesPerColor, uint32_t offset, + SkCodec::SkScanlineOrder rowOrder) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fColorTable(nullptr) + , fNumColors(numColors) + , fBytesPerColor(bytesPerColor) + , fOffset(offset) + , fBytesBuffered(0) + , fCurrRLEByte(0) + , fSampleX(1) +{} + +/* + * Initiates the bitmap decode + */ +SkCodec::Result SkBmpRLECodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + if (opts.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + Result result = this->prepareToDecode(dstInfo, opts); + if (kSuccess != result) { + return result; + } + + // Perform the decode + int rows = this->decodeRows(dstInfo, dst, dstRowBytes, opts); + if (rows != dstInfo.height()) { + // We set rowsDecoded equal to the height because the background has already + // been filled. RLE encodings sometimes skip pixels, so we always start by + // filling the background. + *rowsDecoded = dstInfo.height(); + return kIncompleteInput; + } + + return kSuccess; +} + +/* + * Process the color table for the bmp input + */ + bool SkBmpRLECodec::createColorTable(SkColorType dstColorType) { + // Allocate memory for color table + uint32_t colorBytes = 0; + SkPMColor colorTable[256]; + if (this->bitsPerPixel() <= 8) { + // Inform the caller of the number of colors + uint32_t maxColors = 1 << this->bitsPerPixel(); + // Don't bother reading more than maxColors. + const uint32_t numColorsToRead = + fNumColors == 0 ? maxColors : SkTMin(fNumColors, maxColors); + + // Read the color table from the stream + colorBytes = numColorsToRead * fBytesPerColor; + std::unique_ptr<uint8_t[]> cBuffer(new uint8_t[colorBytes]); + if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) { + SkCodecPrintf("Error: unable to read color table.\n"); + return false; + } + + // Fill in the color table + PackColorProc packARGB = choose_pack_color_proc(false, dstColorType); + uint32_t i = 0; + for (; i < numColorsToRead; i++) { + uint8_t blue = get_byte(cBuffer.get(), i*fBytesPerColor); + uint8_t green = get_byte(cBuffer.get(), i*fBytesPerColor + 1); + uint8_t red = get_byte(cBuffer.get(), i*fBytesPerColor + 2); + colorTable[i] = packARGB(0xFF, red, green, blue); + } + + // To avoid segmentation faults on bad pixel data, fill the end of the + // color table with black. This is the same the behavior as the + // chromium decoder. + for (; i < maxColors; i++) { + colorTable[i] = SkPackARGB32NoCheck(0xFF, 0, 0, 0); + } + + // Set the color table + fColorTable.reset(new SkColorTable(colorTable, maxColors)); + } + + // Check that we have not read past the pixel array offset + if(fOffset < colorBytes) { + // This may occur on OS 2.1 and other old versions where the color + // table defaults to max size, and the bmp tries to use a smaller + // color table. This is invalid, and our decision is to indicate + // an error, rather than try to guess the intended size of the + // color table. + SkCodecPrintf("Error: pixel data offset less than color table size.\n"); + return false; + } + + // After reading the color table, skip to the start of the pixel array + if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) { + SkCodecPrintf("Error: unable to skip to image data.\n"); + return false; + } + + // Return true on success + return true; +} + +bool SkBmpRLECodec::initializeStreamBuffer() { + fBytesBuffered = this->stream()->read(fStreamBuffer, kBufferSize); + if (fBytesBuffered == 0) { + SkCodecPrintf("Error: could not read RLE image data.\n"); + return false; + } + fCurrRLEByte = 0; + return true; +} + +/* + * @return the number of bytes remaining in the stream buffer after + * attempting to read more bytes from the stream + */ +size_t SkBmpRLECodec::checkForMoreData() { + const size_t remainingBytes = fBytesBuffered - fCurrRLEByte; + uint8_t* buffer = fStreamBuffer; + + // We will be reusing the same buffer, starting over from the beginning. + // Move any remaining bytes to the start of the buffer. + // We use memmove() instead of memcpy() because there is risk that the dst + // and src memory will overlap in corrupt images. + memmove(buffer, SkTAddOffset<uint8_t>(buffer, fCurrRLEByte), remainingBytes); + + // Adjust the buffer ptr to the start of the unfilled data. + buffer += remainingBytes; + + // Try to read additional bytes from the stream. There are fCurrRLEByte + // bytes of additional space remaining in the buffer, assuming that we + // have already copied remainingBytes to the start of the buffer. + size_t additionalBytes = this->stream()->read(buffer, fCurrRLEByte); + + // Update counters and return the number of bytes we currently have + // available. We are at the start of the buffer again. + fCurrRLEByte = 0; + fBytesBuffered = remainingBytes + additionalBytes; + return fBytesBuffered; +} + +/* + * Set an RLE pixel using the color table + */ +void SkBmpRLECodec::setPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, uint32_t y, + uint8_t index) { + if (dst && is_coord_necessary(x, fSampleX, dstInfo.width())) { + // Set the row + uint32_t row = this->getDstRow(y, dstInfo.height()); + + // Set the pixel based on destination color type + const int dstX = get_dst_coord(x, fSampleX); + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + SkPMColor* dstRow = SkTAddOffset<SkPMColor>(dst, row * (int) dstRowBytes); + dstRow[dstX] = fColorTable->operator[](index); + break; + } + case kRGB_565_SkColorType: { + uint16_t* dstRow = SkTAddOffset<uint16_t>(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPixel32ToPixel16(fColorTable->operator[](index)); + break; + } + default: + // This case should not be reached. We should catch an invalid + // color type when we check that the conversion is possible. + SkASSERT(false); + break; + } + } +} + +/* + * Set an RLE pixel from R, G, B values + */ +void SkBmpRLECodec::setRGBPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, + uint32_t y, uint8_t red, uint8_t green, + uint8_t blue) { + if (dst && is_coord_necessary(x, fSampleX, dstInfo.width())) { + // Set the row + uint32_t row = this->getDstRow(y, dstInfo.height()); + + // Set the pixel based on destination color type + const int dstX = get_dst_coord(x, fSampleX); + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: { + SkPMColor* dstRow = SkTAddOffset<SkPMColor>(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + break; + } + case kBGRA_8888_SkColorType: { + SkPMColor* dstRow = SkTAddOffset<SkPMColor>(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + break; + } + case kRGB_565_SkColorType: { + uint16_t* dstRow = SkTAddOffset<uint16_t>(dst, row * (int) dstRowBytes); + dstRow[dstX] = SkPack888ToRGB16(red, green, blue); + break; + } + default: + // This case should not be reached. We should catch an invalid + // color type when we check that the conversion is possible. + SkASSERT(false); + break; + } + } +} + +SkCodec::Result SkBmpRLECodec::onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + // FIXME: Support subsets for scanline decodes. + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + // Reset fSampleX. If it needs to be a value other than 1, it will get modified by + // the sampler. + fSampleX = 1; + fLinesToSkip = 0; + + SkColorType colorTableColorType = dstInfo.colorType(); + if (this->colorXform()) { + // Just set a known colorType for the colorTable. No need to actually transform + // the colors in the colorTable. + colorTableColorType = kBGRA_8888_SkColorType; + } + + // 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(colorTableColorType)) { + SkCodecPrintf("Error: could not create color table.\n"); + return SkCodec::kInvalidInput; + } + + // Initialize a buffer for encoded RLE data + if (!this->initializeStreamBuffer()) { + SkCodecPrintf("Error: cannot initialize stream buffer.\n"); + return SkCodec::kInvalidInput; + } + + return SkCodec::kSuccess; +} + +/* + * 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) { + int height = info.height(); + + // Account for sampling. + SkImageInfo dstInfo = info.makeWH(this->fillWidth(), height); + + // Set the background as transparent. Then, if the RLE code skips pixels, + // the skipped pixels will be transparent. + if (dst) { + SkSampler::Fill(dstInfo, dst, dstRowBytes, 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; + if (dst) { + dst = SkTAddOffset<void>(dst, fLinesToSkip * dstRowBytes); + } + fLinesToSkip = 0; + + dstInfo = dstInfo.makeWH(dstInfo.width(), height); + } else { + fLinesToSkip -= height; + return height; + } + + void* decodeDst = dst; + size_t decodeRowBytes = dstRowBytes; + SkImageInfo decodeInfo = dstInfo; + if (decodeDst) { + if (this->colorXform()) { + decodeInfo = decodeInfo.makeColorType(kXformSrcColorType); + if (kRGBA_F16_SkColorType == dstInfo.colorType()) { + int count = height * dstInfo.width(); + this->resetXformBuffer(count); + sk_bzero(this->xformBuffer(), count * sizeof(uint32_t)); + decodeDst = this->xformBuffer(); + decodeRowBytes = dstInfo.width() * sizeof(uint32_t); + } + } + } + + int decodedHeight = this->decodeRLE(decodeInfo, decodeDst, decodeRowBytes); + if (this->colorXform() && decodeDst) { + for (int y = 0; y < decodedHeight; y++) { + this->applyColorXform(dst, decodeDst, dstInfo.width()); + decodeDst = SkTAddOffset<void>(decodeDst, decodeRowBytes); + dst = SkTAddOffset<void>(dst, dstRowBytes); + } + } + + return decodedHeight; +} + +int SkBmpRLECodec::decodeRLE(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes) { + // Use the original width to count the number of pixels in each row. + const int width = this->dimensions().width(); + + // This tells us the number of rows that we are meant to decode. + const int height = dstInfo.height(); + + // Set RLE flags + constexpr uint8_t RLE_ESCAPE = 0; + constexpr uint8_t RLE_EOL = 0; + constexpr uint8_t RLE_EOF = 1; + constexpr uint8_t RLE_DELTA = 2; + + // 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) fBytesBuffered - fCurrRLEByte < 2) { + 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[fCurrRLEByte++]; + const uint8_t task = fStreamBuffer[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) fBytesBuffered - fCurrRLEByte < 2) { + if (this->checkForMoreData() < 2) { + return y; + } + } + // Modify x and y + const uint8_t dx = fStreamBuffer[fCurrRLEByte++]; + const uint8_t dy = fStreamBuffer[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. + + // At most, alignedRowBytes can be 255 (max uint8_t) * + // 3 (max bytes per pixel) + 1 (aligned) = 766. If + // fStreamBuffer was smaller than this, + // checkForMoreData would never succeed for some bmps. + static_assert(255 * 3 + 1 < kBufferSize, + "kBufferSize needs to be larger!"); + const size_t alignedRowBytes = SkAlign2(rowBytes); + if ((int) fBytesBuffered - fCurrRLEByte < alignedRowBytes) { + SkASSERT(alignedRowBytes < kBufferSize); + if (this->checkForMoreData() < alignedRowBytes) { + return y; + } + } + // Set numPixels number of pixels + while (numPixels > 0) { + switch(this->bitsPerPixel()) { + case 4: { + SkASSERT(fCurrRLEByte < fBytesBuffered); + uint8_t val = fStreamBuffer[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 < fBytesBuffered); + setPixel(dst, dstRowBytes, dstInfo, x++, + y, fStreamBuffer[fCurrRLEByte++]); + numPixels--; + break; + case 24: { + SkASSERT(fCurrRLEByte + 2 < fBytesBuffered); + uint8_t blue = fStreamBuffer[fCurrRLEByte++]; + uint8_t green = fStreamBuffer[fCurrRLEByte++]; + uint8_t red = fStreamBuffer[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) fBytesBuffered - fCurrRLEByte < 2) { + if (this->checkForMoreData() < 2) { + return y; + } + } + + // Fill the pixels up to endX with the specified color + uint8_t blue = task; + uint8_t green = fStreamBuffer[fCurrRLEByte++]; + uint8_t red = fStreamBuffer[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; + } + } + } + } +} + +bool SkBmpRLECodec::skipRows(int count) { + const SkImageInfo rowInfo = SkImageInfo::Make(this->dimensions().width(), count, + kN32_SkColorType, kUnpremul_SkAlphaType); + return count == this->decodeRows(rowInfo, nullptr, 0, this->options()); +} + +// FIXME: Make SkBmpRLECodec have no knowledge of sampling. +// Or it should do all sampling natively. +// It currently is a hybrid that needs to know what SkScaledCodec is doing. +class SkBmpRLESampler : public SkSampler { +public: + SkBmpRLESampler(SkBmpRLECodec* codec) + : fCodec(codec) + { + SkASSERT(fCodec); + } + + int fillWidth() const override { + return fCodec->fillWidth(); + } + +private: + int onSetSampleX(int sampleX) override { + return fCodec->setSampleX(sampleX); + } + + // Unowned pointer. fCodec will delete this class in its destructor. + SkBmpRLECodec* fCodec; +}; + +SkSampler* SkBmpRLECodec::getSampler(bool createIfNecessary) { + if (!fSampler && createIfNecessary) { + fSampler.reset(new SkBmpRLESampler(this)); + } + + return fSampler.get(); +} + +int SkBmpRLECodec::setSampleX(int sampleX) { + fSampleX = sampleX; + return this->fillWidth(); +} + +int SkBmpRLECodec::fillWidth() const { + return get_scaled_dimension(this->dimensions().width(), fSampleX); +} diff --git a/gfx/skia/skia/src/codec/SkBmpRLECodec.h b/gfx/skia/skia/src/codec/SkBmpRLECodec.h new file mode 100644 index 0000000000..80acf423ce --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpRLECodec.h @@ -0,0 +1,120 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpRLECodec_DEFINED +#define SkBmpRLECodec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSampler.h" + +/* + * This class implements the decoding for bmp images that use an RLE encoding + */ +class SkBmpRLECodec : public SkBmpCodec { +public: + + /* + * Creates an instance of the decoder + * + * Called only by SkBmpCodec::MakeFromStream + * There should be no other callers despite this being public + * + * @param info contains properties of the encoded data + * @param stream the stream of encoded image data + * @param bitsPerPixel the number of bits used to store each pixel + * @param numColors the number of colors in the color table + * @param bytesPerColor the number of bytes in the stream used to represent + each color in the color table + * @param offset the offset of the image pixel data from the end of the + * headers + * @param rowOrder indicates whether rows are ordered top-down or bottom-up + */ + SkBmpRLECodec(SkEncodedInfo&& info, std::unique_ptr<SkStream>, + uint16_t bitsPerPixel, uint32_t numColors, uint32_t bytesPerColor, + uint32_t offset, SkCodec::SkScanlineOrder rowOrder); + + int setSampleX(int); + + int fillWidth() const; + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options&, + int*) override; + + SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + +private: + + /* + * Creates the color table + * Sets colorCount to the new color count if it is non-nullptr + */ + bool createColorTable(SkColorType dstColorType); + + bool initializeStreamBuffer(); + + /* + * Before signalling kIncompleteInput, we should attempt to load the + * stream buffer with additional data. + * + * @return the number of bytes remaining in the stream buffer after + * attempting to read more bytes from the stream + */ + size_t checkForMoreData(); + + /* + * Set an RLE pixel using the color table + */ + void setPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, uint32_t y, + uint8_t index); + /* + * Set an RLE24 pixel from R, G, B values + */ + void setRGBPixel(void* dst, size_t dstRowBytes, + const SkImageInfo& dstInfo, uint32_t x, uint32_t y, + uint8_t red, uint8_t green, uint8_t blue); + + /* + * If dst is NULL, this is a signal to skip the rows. + */ + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; + int decodeRLE(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes); + + bool skipRows(int count) override; + + SkSampler* getSampler(bool createIfNecessary) override; + + sk_sp<SkColorTable> fColorTable; + // fNumColors is the number specified in the header, or 0 if not present in the header. + const uint32_t fNumColors; + const uint32_t fBytesPerColor; + const uint32_t fOffset; + + static constexpr size_t kBufferSize = 4096; + uint8_t fStreamBuffer[kBufferSize]; + size_t fBytesBuffered; + + uint32_t fCurrRLEByte; + int fSampleX; + std::unique_ptr<SkSampler> fSampler; + + // Scanline decodes allow the client to ask for a single scanline at a time. + // This can be tricky when the RLE encoding instructs the decoder to jump down + // multiple lines. This field keeps track of lines that need to be skipped + // on subsequent calls to decodeRows(). + int fLinesToSkip; + + typedef SkBmpCodec INHERITED; +}; +#endif // SkBmpRLECodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp b/gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp new file mode 100644 index 0000000000..e60100a50f --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp @@ -0,0 +1,341 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkBmpStandardCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/core/SkMathPriv.h" + +/* + * Creates an instance of the decoder + * Called only by NewFromStream + */ +SkBmpStandardCodec::SkBmpStandardCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + uint16_t bitsPerPixel, uint32_t numColors, + uint32_t bytesPerColor, uint32_t offset, + SkCodec::SkScanlineOrder rowOrder, + bool isOpaque, bool inIco) + : INHERITED(std::move(info), std::move(stream), bitsPerPixel, rowOrder) + , fColorTable(nullptr) + , fNumColors(numColors) + , fBytesPerColor(bytesPerColor) + , fOffset(offset) + , fSwizzler(nullptr) + , fIsOpaque(isOpaque) + , fInIco(inIco) + , fAndMaskRowBytes(fInIco ? SkAlign4(compute_row_bytes(this->dimensions().width(), 1)) : 0) +{} + +/* + * Initiates the bitmap decode + */ +SkCodec::Result SkBmpStandardCodec::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; +} + +/* + * Process the color table for the bmp input + */ + bool SkBmpStandardCodec::createColorTable(SkColorType dstColorType, SkAlphaType dstAlphaType) { + // Allocate memory for color table + uint32_t colorBytes = 0; + SkPMColor colorTable[256]; + if (this->bitsPerPixel() <= 8) { + // Inform the caller of the number of colors + uint32_t maxColors = 1 << this->bitsPerPixel(); + // Don't bother reading more than maxColors. + const uint32_t numColorsToRead = + fNumColors == 0 ? maxColors : SkTMin(fNumColors, maxColors); + + // Read the color table from the stream + colorBytes = numColorsToRead * fBytesPerColor; + std::unique_ptr<uint8_t[]> cBuffer(new uint8_t[colorBytes]); + if (stream()->read(cBuffer.get(), colorBytes) != colorBytes) { + SkCodecPrintf("Error: unable to read color table.\n"); + return false; + } + + SkColorType packColorType = dstColorType; + SkAlphaType packAlphaType = dstAlphaType; + if (this->colorXform()) { + packColorType = kBGRA_8888_SkColorType; + packAlphaType = kUnpremul_SkAlphaType; + } + + // Choose the proper packing function + bool isPremul = (kPremul_SkAlphaType == packAlphaType) && !fIsOpaque; + PackColorProc packARGB = choose_pack_color_proc(isPremul, packColorType); + + // Fill in the color table + uint32_t i = 0; + for (; i < numColorsToRead; i++) { + uint8_t blue = get_byte(cBuffer.get(), i*fBytesPerColor); + uint8_t green = get_byte(cBuffer.get(), i*fBytesPerColor + 1); + uint8_t red = get_byte(cBuffer.get(), i*fBytesPerColor + 2); + uint8_t alpha; + if (fIsOpaque) { + alpha = 0xFF; + } else { + alpha = get_byte(cBuffer.get(), i*fBytesPerColor + 3); + } + colorTable[i] = packARGB(alpha, red, green, blue); + } + + // To avoid segmentation faults on bad pixel data, fill the end of the + // color table with black. This is the same the behavior as the + // chromium decoder. + for (; i < maxColors; i++) { + colorTable[i] = SkPackARGB32NoCheck(0xFF, 0, 0, 0); + } + + if (this->colorXform() && !this->xformOnDecode()) { + this->applyColorXform(colorTable, colorTable, maxColors); + } + + // Set the color table + fColorTable.reset(new SkColorTable(colorTable, maxColors)); + } + + // Bmp-in-Ico files do not use an offset to indicate where the pixel data + // begins. Pixel data always begins immediately after the color table. + if (!fInIco) { + // Check that we have not read past the pixel array offset + if(fOffset < colorBytes) { + // This may occur on OS 2.1 and other old versions where the color + // table defaults to max size, and the bmp tries to use a smaller + // color table. This is invalid, and our decision is to indicate + // an error, rather than try to guess the intended size of the + // color table. + SkCodecPrintf("Error: pixel data offset less than color table size.\n"); + return false; + } + + // After reading the color table, skip to the start of the pixel array + if (stream()->skip(fOffset - colorBytes) != fOffset - colorBytes) { + SkCodecPrintf("Error: unable to skip to image data.\n"); + return false; + } + } + + // Return true on success + return true; +} + +static SkEncodedInfo make_info(SkEncodedInfo::Color color, + SkEncodedInfo::Alpha alpha, int bitsPerPixel) { + // This is just used for the swizzler, which does not need the width or height. + return SkEncodedInfo::Make(0, 0, color, alpha, bitsPerPixel); +} + +SkEncodedInfo SkBmpStandardCodec::swizzlerInfo() const { + const auto& info = this->getEncodedInfo(); + if (fInIco) { + if (this->bitsPerPixel() <= 8) { + return make_info(SkEncodedInfo::kPalette_Color, + info.alpha(), this->bitsPerPixel()); + } + if (this->bitsPerPixel() == 24) { + return make_info(SkEncodedInfo::kBGR_Color, + SkEncodedInfo::kOpaque_Alpha, 8); + } + } + + return make_info(info.color(), info.alpha(), info.bitsPerComponent()); +} + +void SkBmpStandardCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& opts) { + // In the case of bmp-in-icos, we will report BGRA to the client, + // since we may be required to apply an alpha mask after the decode. + // However, the swizzler needs to know the actual format of the bmp. + SkEncodedInfo encodedInfo = this->swizzlerInfo(); + + // Get a pointer to the color table if it exists + const SkPMColor* colorPtr = get_color_ptr(fColorTable.get()); + + SkImageInfo swizzlerInfo = dstInfo; + SkCodec::Options swizzlerOptions = opts; + if (this->colorXform()) { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + + swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; + } + + fSwizzler = SkSwizzler::Make(encodedInfo, colorPtr, swizzlerInfo, swizzlerOptions); + SkASSERT(fSwizzler); +} + +SkCodec::Result SkBmpStandardCodec::onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + if (this->xformOnDecode()) { + this->resetXformBuffer(dstInfo.width()); + } + + // 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(dstInfo.colorType(), dstInfo.alphaType())) { + SkCodecPrintf("Error: could not create color table.\n"); + return SkCodec::kInvalidInput; + } + + // Initialize a swizzler + this->initializeSwizzler(dstInfo, options); + return SkCodec::kSuccess; +} + +/* + * Performs the bitmap decoding for standard input format + */ +int SkBmpStandardCodec::decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) { + // Iterate over rows of the image + const int height = dstInfo.height(); + for (int y = 0; y < height; y++) { + // Read a row of the input + if (this->stream()->read(this->srcBuffer(), this->srcRowBytes()) != this->srcRowBytes()) { + SkCodecPrintf("Warning: incomplete input stream.\n"); + return y; + } + + // Decode the row in destination format + uint32_t row = this->getDstRow(y, dstInfo.height()); + + void* dstRow = SkTAddOffset<void>(dst, row * dstRowBytes); + + if (this->xformOnDecode()) { + SkASSERT(this->colorXform()); + fSwizzler->swizzle(this->xformBuffer(), this->srcBuffer()); + this->applyColorXform(dstRow, this->xformBuffer(), fSwizzler->swizzleWidth()); + } else { + fSwizzler->swizzle(dstRow, this->srcBuffer()); + } + } + + if (fInIco && fIsOpaque) { + const int startScanline = this->currScanline(); + if (startScanline < 0) { + // We are not performing a scanline decode. + // Just decode the entire ICO mask and return. + decodeIcoMask(this->stream(), dstInfo, dst, dstRowBytes); + return height; + } + + // In order to perform a scanline ICO decode, we must be able + // to skip ahead in the stream in order to apply the AND mask + // to the requested scanlines. + // We will do this by taking advantage of the fact that + // SkIcoCodec always uses a SkMemoryStream as its underlying + // representation of the stream. + const void* memoryBase = this->stream()->getMemoryBase(); + SkASSERT(nullptr != memoryBase); + SkASSERT(this->stream()->hasLength()); + SkASSERT(this->stream()->hasPosition()); + + const size_t length = this->stream()->getLength(); + const size_t currPosition = this->stream()->getPosition(); + + // Calculate how many bytes we must skip to reach the AND mask. + const int remainingScanlines = this->dimensions().height() - startScanline - height; + const size_t bytesToSkip = remainingScanlines * this->srcRowBytes() + + startScanline * fAndMaskRowBytes; + const size_t subStreamStartPosition = currPosition + bytesToSkip; + if (subStreamStartPosition >= length) { + // FIXME: How can we indicate that this decode was actually incomplete? + return height; + } + + // Create a subStream to pass to decodeIcoMask(). It is useful to encapsulate + // the memory base into a stream in order to safely handle incomplete images + // without reading out of bounds memory. + const void* subStreamMemoryBase = SkTAddOffset<const void>(memoryBase, + subStreamStartPosition); + const size_t subStreamLength = length - subStreamStartPosition; + // This call does not transfer ownership of the subStreamMemoryBase. + SkMemoryStream subStream(subStreamMemoryBase, subStreamLength, false); + + // FIXME: If decodeIcoMask does not succeed, is there a way that we can + // indicate the decode was incomplete? + decodeIcoMask(&subStream, dstInfo, dst, dstRowBytes); + } + + return height; +} + +void SkBmpStandardCodec::decodeIcoMask(SkStream* stream, const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes) { + // BMP in ICO have transparency, so this cannot be 565. The below code depends + // on the output being an SkPMColor. + SkASSERT(kRGBA_8888_SkColorType == dstInfo.colorType() || + kBGRA_8888_SkColorType == dstInfo.colorType() || + kRGBA_F16_SkColorType == dstInfo.colorType()); + + // If we are sampling, make sure that we only mask the sampled pixels. + // We do not need to worry about sampling in the y-dimension because that + // should be handled by SkSampledCodec. + const int sampleX = fSwizzler->sampleX(); + const int sampledWidth = get_scaled_dimension(this->dimensions().width(), sampleX); + const int srcStartX = get_start_coord(sampleX); + + + SkPMColor* dstPtr = (SkPMColor*) dst; + for (int y = 0; y < dstInfo.height(); y++) { + // The srcBuffer will at least be large enough + if (stream->read(this->srcBuffer(), fAndMaskRowBytes) != fAndMaskRowBytes) { + SkCodecPrintf("Warning: incomplete AND mask for bmp-in-ico.\n"); + return; + } + + auto applyMask = [dstInfo](void* dstRow, int x, uint64_t bit) { + if (kRGBA_F16_SkColorType == dstInfo.colorType()) { + uint64_t* dst64 = (uint64_t*) dstRow; + dst64[x] &= bit - 1; + } else { + uint32_t* dst32 = (uint32_t*) dstRow; + dst32[x] &= bit - 1; + } + }; + + int row = this->getDstRow(y, dstInfo.height()); + + void* dstRow = SkTAddOffset<SkPMColor>(dstPtr, row * dstRowBytes); + + int srcX = srcStartX; + for (int dstX = 0; dstX < sampledWidth; dstX++) { + int quotient; + int modulus; + SkTDivMod(srcX, 8, "ient, &modulus); + uint32_t shift = 7 - modulus; + uint64_t alphaBit = (this->srcBuffer()[quotient] >> shift) & 0x1; + applyMask(dstRow, dstX, alphaBit); + srcX += sampleX; + } + } +} diff --git a/gfx/skia/skia/src/codec/SkBmpStandardCodec.h b/gfx/skia/skia/src/codec/SkBmpStandardCodec.h new file mode 100644 index 0000000000..966330ef4a --- /dev/null +++ b/gfx/skia/skia/src/codec/SkBmpStandardCodec.h @@ -0,0 +1,92 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkBmpStandardCodec_DEFINED +#define SkBmpStandardCodec_DEFINED + +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkBmpBaseCodec.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +/* + * This class implements the decoding for bmp images that use "standard" modes, + * which essentially means they do not contain bit masks or RLE codes. + */ +class SkBmpStandardCodec : public SkBmpBaseCodec { +public: + + /* + * Creates an instance of the decoder + * + * Called only by SkBmpCodec::MakeFromStream + * There should be no other callers despite this being public + * + * @param info contains properties of the encoded data + * @param stream the stream of encoded image data + * @param bitsPerPixel the number of bits used to store each pixel + * @param numColors the number of colors in the color table + * @param bytesPerColor the number of bytes in the stream used to represent + each color in the color table + * @param offset the offset of the image pixel data from the end of the + * headers + * @param rowOrder indicates whether rows are ordered top-down or bottom-up + * @param isOpaque indicates if the bmp itself is opaque (before applying + * the icp mask, if there is one) + * @param inIco indicates if the bmp is embedded in an ico file + */ + SkBmpStandardCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + uint16_t bitsPerPixel, uint32_t numColors, uint32_t bytesPerColor, + uint32_t offset, SkCodec::SkScanlineOrder rowOrder, + bool isOpaque, bool inIco); + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options&, + int*) override; + + bool onInIco() const override { + return fInIco; + } + + SkCodec::Result onPrepareToDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler.get(); + } + +private: + bool createColorTable(SkColorType colorType, SkAlphaType alphaType); + SkEncodedInfo swizzlerInfo() const; + void initializeSwizzler(const SkImageInfo& dstInfo, const Options& opts); + + int decodeRows(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, + const Options& opts) override; + + /* + * @param stream This may be a pointer to the stream owned by the parent SkCodec + * or a sub-stream of the stream owned by the parent SkCodec. + * Either way, this stream is unowned. + */ + void decodeIcoMask(SkStream* stream, const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes); + + sk_sp<SkColorTable> fColorTable; + // fNumColors is the number specified in the header, or 0 if not present in the header. + const uint32_t fNumColors; + const uint32_t fBytesPerColor; + const uint32_t fOffset; + std::unique_ptr<SkSwizzler> fSwizzler; + const bool fIsOpaque; + const bool fInIco; + const size_t fAndMaskRowBytes; // only used for fInIco decodes + + typedef SkBmpBaseCodec INHERITED; +}; +#endif // SkBmpStandardCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkCodec.cpp b/gfx/skia/skia/src/codec/SkCodec.cpp new file mode 100644 index 0000000000..cc5b9bff6e --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodec.cpp @@ -0,0 +1,867 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkData.h" +#include "include/private/SkHalf.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkFrameHolder.h" +#ifdef SK_HAS_HEIF_LIBRARY +#include "src/codec/SkHeifCodec.h" +#endif +#include "src/codec/SkIcoCodec.h" +#include "src/codec/SkJpegCodec.h" +#ifdef SK_HAS_PNG_LIBRARY +#include "src/codec/SkPngCodec.h" +#endif +#include "include/core/SkStream.h" +#include "src/codec/SkRawCodec.h" +#include "src/codec/SkWbmpCodec.h" +#include "src/codec/SkWebpCodec.h" +#ifdef SK_HAS_WUFFS_LIBRARY +#include "src/codec/SkWuffsCodec.h" +#else +#include "src/codec/SkGifCodec.h" +#endif + +struct DecoderProc { + bool (*IsFormat)(const void*, size_t); + std::unique_ptr<SkCodec> (*MakeFromStream)(std::unique_ptr<SkStream>, SkCodec::Result*); +}; + +static std::vector<DecoderProc>* decoders() { + static auto* decoders = new std::vector<DecoderProc> { + #ifdef SK_HAS_JPEG_LIBRARY + { SkJpegCodec::IsJpeg, SkJpegCodec::MakeFromStream }, + #endif + #ifdef SK_HAS_WEBP_LIBRARY + { SkWebpCodec::IsWebp, SkWebpCodec::MakeFromStream }, + #endif + #ifdef SK_HAS_WUFFS_LIBRARY + { SkWuffsCodec_IsFormat, SkWuffsCodec_MakeFromStream }, + #else + { SkGifCodec::IsGif, SkGifCodec::MakeFromStream }, + #endif + #ifdef SK_HAS_PNG_LIBRARY + { SkIcoCodec::IsIco, SkIcoCodec::MakeFromStream }, + #endif + { SkBmpCodec::IsBmp, SkBmpCodec::MakeFromStream }, + { SkWbmpCodec::IsWbmp, SkWbmpCodec::MakeFromStream }, + }; + return decoders; +} + +void SkCodec::Register( + bool (*peek)(const void*, size_t), + std::unique_ptr<SkCodec> (*make)(std::unique_ptr<SkStream>, SkCodec::Result*)) { + decoders()->push_back(DecoderProc{peek, make}); +} + +std::unique_ptr<SkCodec> SkCodec::MakeFromStream( + std::unique_ptr<SkStream> stream, Result* outResult, + SkPngChunkReader* chunkReader, SelectionPolicy selectionPolicy) { + Result resultStorage; + if (!outResult) { + outResult = &resultStorage; + } + + if (!stream) { + *outResult = kInvalidInput; + return nullptr; + } + + if (selectionPolicy != SelectionPolicy::kPreferStillImage + && selectionPolicy != SelectionPolicy::kPreferAnimation) { + *outResult = kInvalidParameters; + return nullptr; + } + + constexpr size_t bytesToRead = MinBufferedBytesNeeded(); + + char buffer[bytesToRead]; + size_t bytesRead = stream->peek(buffer, bytesToRead); + + // It is also possible to have a complete image less than bytesToRead bytes + // (e.g. a 1 x 1 wbmp), meaning peek() would return less than bytesToRead. + // Assume that if bytesRead < bytesToRead, but > 0, the stream is shorter + // than bytesToRead, so pass that directly to the decoder. + // It also is possible the stream uses too small a buffer for peeking, but + // we trust the caller to use a large enough buffer. + + if (0 == bytesRead) { + // TODO: After implementing peek in CreateJavaOutputStreamAdaptor.cpp, this + // printf could be useful to notice failures. + // SkCodecPrintf("Encoded image data failed to peek!\n"); + + // It is possible the stream does not support peeking, but does support + // rewinding. + // Attempt to read() and pass the actual amount read to the decoder. + bytesRead = stream->read(buffer, bytesToRead); + if (!stream->rewind()) { + SkCodecPrintf("Encoded image data could not peek or rewind to determine format!\n"); + *outResult = kCouldNotRewind; + return nullptr; + } + } + + // PNG is special, since we want to be able to supply an SkPngChunkReader. + // But this code follows the same pattern as the loop. +#ifdef SK_HAS_PNG_LIBRARY + if (SkPngCodec::IsPng(buffer, bytesRead)) { + return SkPngCodec::MakeFromStream(std::move(stream), outResult, chunkReader); + } else +#endif + { + for (DecoderProc proc : *decoders()) { + if (proc.IsFormat(buffer, bytesRead)) { + return proc.MakeFromStream(std::move(stream), outResult); + } + } + +#ifdef SK_HAS_HEIF_LIBRARY + if (SkHeifCodec::IsHeif(buffer, bytesRead)) { + return SkHeifCodec::MakeFromStream(std::move(stream), selectionPolicy, outResult); + } +#endif + +#ifdef SK_CODEC_DECODES_RAW + // Try to treat the input as RAW if all the other checks failed. + return SkRawCodec::MakeFromStream(std::move(stream), outResult); +#endif + } + + if (bytesRead < bytesToRead) { + *outResult = kIncompleteInput; + } else { + *outResult = kUnimplemented; + } + + return nullptr; +} + +std::unique_ptr<SkCodec> SkCodec::MakeFromData(sk_sp<SkData> data, SkPngChunkReader* reader) { + if (!data) { + return nullptr; + } + return MakeFromStream(SkMemoryStream::Make(std::move(data)), nullptr, reader); +} + +SkCodec::SkCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr<SkStream> stream, + SkEncodedOrigin origin) + : fEncodedInfo(std::move(info)) + , fSrcXformFormat(srcFormat) + , fStream(std::move(stream)) + , fNeedsRewind(false) + , fOrigin(origin) + , fDstInfo() + , fOptions() + , fCurrScanline(-1) + , fStartedIncrementalDecode(false) +{} + +SkCodec::~SkCodec() {} + +bool SkCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, bool needsColorXform) { + if (!valid_alpha(dst.alphaType(), srcIsOpaque)) { + return false; + } + + switch (dst.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + return true; + case kRGBA_F16_SkColorType: + return dst.colorSpace(); + case kRGB_565_SkColorType: + return srcIsOpaque; + case kGray_8_SkColorType: + return SkEncodedInfo::kGray_Color == fEncodedInfo.color() && srcIsOpaque; + case kAlpha_8_SkColorType: + // conceptually we can convert anything into alpha_8, but we haven't actually coded + // all of those other conversions yet. + return SkEncodedInfo::kXAlpha_Color == fEncodedInfo.color(); + default: + return false; + } +} + +bool SkCodec::rewindIfNeeded() { + // Store the value of fNeedsRewind so we can update it. Next read will + // require a rewind. + const bool needsRewind = fNeedsRewind; + fNeedsRewind = true; + if (!needsRewind) { + return true; + } + + // startScanlineDecode will need to be called before decoding scanlines. + fCurrScanline = -1; + // startIncrementalDecode will need to be called before incrementalDecode. + fStartedIncrementalDecode = false; + + // Some codecs do not have a stream. They may hold onto their own data or another codec. + // They must handle rewinding themselves. + if (fStream && !fStream->rewind()) { + return false; + } + + return this->onRewind(); +} + +static SkIRect frame_rect_on_screen(SkIRect frameRect, + const SkIRect& screenRect) { + if (!frameRect.intersect(screenRect)) { + return SkIRect::MakeEmpty(); + } + + return frameRect; +} + +bool zero_rect(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + SkISize srcDimensions, SkIRect prevRect) { + prevRect = frame_rect_on_screen(prevRect, SkIRect::MakeSize(srcDimensions)); + if (prevRect.isEmpty()) { + return true; + } + const auto dimensions = dstInfo.dimensions(); + if (dimensions != srcDimensions) { + SkRect src = SkRect::Make(srcDimensions); + SkRect dst = SkRect::Make(dimensions); + SkMatrix map = SkMatrix::MakeRectToRect(src, dst, SkMatrix::kCenter_ScaleToFit); + SkRect asRect = SkRect::Make(prevRect); + if (!map.mapRect(&asRect)) { + return false; + } + asRect.roundIn(&prevRect); + if (prevRect.isEmpty()) { + // Down-scaling shrank the empty portion to nothing, + // so nothing to zero. + return true; + } + } + + const SkImageInfo info = dstInfo.makeDimensions(prevRect.size()); + const size_t bpp = dstInfo.bytesPerPixel(); + const size_t offset = prevRect.x() * bpp + prevRect.y() * rowBytes; + void* eraseDst = SkTAddOffset<void>(pixels, offset); + SkSampler::Fill(info, eraseDst, rowBytes, SkCodec::kNo_ZeroInitialized); + return true; +} + +SkCodec::Result SkCodec::handleFrameIndex(const SkImageInfo& info, void* pixels, size_t rowBytes, + const Options& options) { + const int index = options.fFrameIndex; + if (0 == index) { + return this->initializeColorXform(info, fEncodedInfo.alpha(), fEncodedInfo.opaque()) + ? kSuccess : kInvalidConversion; + } + + if (index < 0) { + return kInvalidParameters; + } + + if (options.fSubset) { + // If we add support for this, we need to update the code that zeroes + // a kRestoreBGColor frame. + return kInvalidParameters; + } + + if (index >= this->onGetFrameCount()) { + return kIncompleteInput; + } + + const auto* frameHolder = this->getFrameHolder(); + SkASSERT(frameHolder); + + const auto* frame = frameHolder->getFrame(index); + SkASSERT(frame); + + const int requiredFrame = frame->getRequiredFrame(); + if (requiredFrame != kNoFrame) { + if (options.fPriorFrame != kNoFrame) { + // Check for a valid frame as a starting point. Alternatively, we could + // treat an invalid frame as not providing one, but rejecting it will + // make it easier to catch the mistake. + if (options.fPriorFrame < requiredFrame || options.fPriorFrame >= index) { + return kInvalidParameters; + } + const auto* prevFrame = frameHolder->getFrame(options.fPriorFrame); + switch (prevFrame->getDisposalMethod()) { + case SkCodecAnimation::DisposalMethod::kRestorePrevious: + return kInvalidParameters; + case SkCodecAnimation::DisposalMethod::kRestoreBGColor: + // If a frame after the required frame is provided, there is no + // need to clear, since it must be covered by the desired frame. + if (options.fPriorFrame == requiredFrame) { + SkIRect prevRect = prevFrame->frameRect(); + if (!zero_rect(info, pixels, rowBytes, this->dimensions(), prevRect)) { + return kInternalError; + } + } + break; + default: + break; + } + } else { + Options prevFrameOptions(options); + prevFrameOptions.fFrameIndex = requiredFrame; + prevFrameOptions.fZeroInitialized = kNo_ZeroInitialized; + const Result result = this->getPixels(info, pixels, rowBytes, &prevFrameOptions); + if (result != kSuccess) { + return result; + } + const auto* prevFrame = frameHolder->getFrame(requiredFrame); + const auto disposalMethod = prevFrame->getDisposalMethod(); + if (disposalMethod == SkCodecAnimation::DisposalMethod::kRestoreBGColor) { + auto prevRect = prevFrame->frameRect(); + if (!zero_rect(info, pixels, rowBytes, this->dimensions(), prevRect)) { + return kInternalError; + } + } + } + } + + return this->initializeColorXform(info, frame->reportedAlpha(), !frame->hasAlpha()) + ? kSuccess : kInvalidConversion; +} + +SkCodec::Result SkCodec::getPixels(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const Options* options) { + SkImageInfo info = dstInfo; + if (!info.colorSpace()) { + info = info.makeColorSpace(SkColorSpace::MakeSRGB()); + } + + if (kUnknown_SkColorType == info.colorType()) { + return kInvalidConversion; + } + if (nullptr == pixels) { + return kInvalidParameters; + } + if (rowBytes < info.minRowBytes()) { + return kInvalidParameters; + } + + 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; + } + } + } + + const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, + *options); + if (frameIndexResult != kSuccess) { + return frameIndexResult; + } + + // FIXME: Support subsets somehow? Note that this works for SkWebpCodec + // because it supports arbitrary scaling/subset combinations. + if (!this->dimensionsSupported(info.dimensions())) { + return kInvalidScale; + } + + fDstInfo = info; + fOptions = *options; + + // 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, &rowsDecoded); + + // 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 || kErrorInInput == result) && rowsDecoded != info.height()) { + // FIXME: (skbug.com/5772) fillIncompleteImage will fill using the swizzler's width, unless + // there is a subset. In that case, it will use the width of the subset. From here, the + // subset will only be non-null in the case of SkWebpCodec, but it treats the subset + // differenty from the other codecs, and it needs to use the width specified by the info. + // Set the subset to null so SkWebpCodec uses the correct width. + fOptions.fSubset = nullptr; + this->fillIncompleteImage(info, pixels, rowBytes, options->fZeroInitialized, info.height(), + rowsDecoded); + } + + return result; +} + +SkCodec::Result SkCodec::startIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, + size_t rowBytes, const SkCodec::Options* options) { + fStartedIncrementalDecode = false; + + SkImageInfo info = dstInfo; + if (!info.colorSpace()) { + info = info.makeColorSpace(SkColorSpace::MakeSRGB()); + } + if (kUnknown_SkColorType == info.colorType()) { + return kInvalidConversion; + } + if (nullptr == pixels) { + return kInvalidParameters; + } + + // FIXME: If the rows come after the rows of a previous incremental decode, + // we might be able to skip the rewind, but only the implementation knows + // that. (e.g. PNG will always need to rewind, since we called longjmp, but + // a bottom-up BMP could skip rewinding if the new rows are above the old + // rows.) + if (!this->rewindIfNeeded()) { + return kCouldNotRewind; + } + + // Set options. + Options optsStorage; + if (nullptr == options) { + options = &optsStorage; + } else { + if (options->fSubset) { + SkIRect size = SkIRect::MakeSize(info.dimensions()); + if (!size.contains(*options->fSubset)) { + return kInvalidParameters; + } + + const int top = options->fSubset->top(); + const int bottom = options->fSubset->bottom(); + if (top < 0 || top >= info.height() || top >= bottom || bottom > info.height()) { + return kInvalidParameters; + } + } + } + + const Result frameIndexResult = this->handleFrameIndex(info, pixels, rowBytes, + *options); + if (frameIndexResult != kSuccess) { + return frameIndexResult; + } + + if (!this->dimensionsSupported(info.dimensions())) { + return kInvalidScale; + } + + fDstInfo = info; + fOptions = *options; + + const Result result = this->onStartIncrementalDecode(info, pixels, rowBytes, fOptions); + if (kSuccess == result) { + fStartedIncrementalDecode = true; + } else if (kUnimplemented == result) { + // FIXME: This is temporarily necessary, until we transition SkCodec + // implementations from scanline decoding to incremental decoding. + // SkAndroidCodec will first attempt to use incremental decoding, but + // will fall back to scanline decoding if incremental returns + // kUnimplemented. rewindIfNeeded(), above, set fNeedsRewind to true + // (after potentially rewinding), but we do not want the next call to + // startScanlineDecode() to do a rewind. + fNeedsRewind = false; + } + return result; +} + + +SkCodec::Result SkCodec::startScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options* options) { + // Reset fCurrScanline in case of failure. + fCurrScanline = -1; + + SkImageInfo info = dstInfo; + if (!info.colorSpace()) { + info = info.makeColorSpace(SkColorSpace::MakeSRGB()); + } + + if (!this->rewindIfNeeded()) { + return kCouldNotRewind; + } + + // Set options. + Options optsStorage; + if (nullptr == options) { + options = &optsStorage; + } else if (options->fSubset) { + SkIRect size = SkIRect::MakeSize(info.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() != info.height()) { + return kInvalidInput; + } + } + + // Scanline decoding only supports decoding the first frame. + if (options->fFrameIndex != 0) { + return kUnimplemented; + } + + // The void* dst and rowbytes in handleFrameIndex or only used for decoding prior + // frames, which is not supported here anyway, so it is safe to pass nullptr/0. + const Result frameIndexResult = this->handleFrameIndex(info, nullptr, 0, *options); + if (frameIndexResult != kSuccess) { + return frameIndexResult; + } + + // FIXME: Support subsets somehow? + if (!this->dimensionsSupported(info.dimensions())) { + return kInvalidScale; + } + + const Result result = this->onStartScanlineDecode(info, *options); + if (result != SkCodec::kSuccess) { + return result; + } + + fCurrScanline = 0; + fDstInfo = info; + fOptions = *options; + return kSuccess; +} + +int SkCodec::getScanlines(void* dst, int countLines, size_t rowBytes) { + if (fCurrScanline < 0) { + return 0; + } + + SkASSERT(!fDstInfo.isEmpty()); + if (countLines <= 0 || fCurrScanline + countLines > fDstInfo.height()) { + return 0; + } + + const int linesDecoded = this->onGetScanlines(dst, countLines, rowBytes); + if (linesDecoded < countLines) { + this->fillIncompleteImage(this->dstInfo(), dst, rowBytes, this->options().fZeroInitialized, + countLines, linesDecoded); + } + fCurrScanline += countLines; + return linesDecoded; +} + +bool SkCodec::skipScanlines(int countLines) { + if (fCurrScanline < 0) { + return false; + } + + SkASSERT(!fDstInfo.isEmpty()); + if (countLines < 0 || fCurrScanline + countLines > fDstInfo.height()) { + // Arguably, we could just skip the scanlines which are remaining, + // and return true. We choose to return false so the client + // can catch their bug. + return false; + } + + bool result = this->onSkipScanlines(countLines); + fCurrScanline += countLines; + return result; +} + +int SkCodec::outputScanline(int inputScanline) const { + SkASSERT(0 <= inputScanline && inputScanline < fEncodedInfo.height()); + return this->onOutputScanline(inputScanline); +} + +int SkCodec::onOutputScanline(int inputScanline) const { + switch (this->getScanlineOrder()) { + case kTopDown_SkScanlineOrder: + return inputScanline; + case kBottomUp_SkScanlineOrder: + return fEncodedInfo.height() - inputScanline - 1; + default: + // This case indicates an interlaced gif and is implemented by SkGifCodec. + SkASSERT(false); + return 0; + } +} + +void SkCodec::fillIncompleteImage(const SkImageInfo& info, void* dst, size_t rowBytes, + ZeroInitialized zeroInit, int linesRequested, int linesDecoded) { + if (kYes_ZeroInitialized == zeroInit) { + return; + } + + const int linesRemaining = linesRequested - linesDecoded; + SkSampler* sampler = this->getSampler(false); + + const int fillWidth = sampler ? sampler->fillWidth() : + fOptions.fSubset ? fOptions.fSubset->width() : + info.width() ; + void* fillDst = this->getScanlineOrder() == kBottomUp_SkScanlineOrder ? dst : + SkTAddOffset<void>(dst, linesDecoded * rowBytes); + const auto fillInfo = info.makeWH(fillWidth, linesRemaining); + SkSampler::Fill(fillInfo, fillDst, rowBytes, kNo_ZeroInitialized); +} + +bool sk_select_xform_format(SkColorType colorType, bool forColorTable, + skcms_PixelFormat* outFormat) { + SkASSERT(outFormat); + + switch (colorType) { + case kRGBA_8888_SkColorType: + *outFormat = skcms_PixelFormat_RGBA_8888; + break; + case kBGRA_8888_SkColorType: + *outFormat = skcms_PixelFormat_BGRA_8888; + break; + case kRGB_565_SkColorType: + if (forColorTable) { +#ifdef SK_PMCOLOR_IS_RGBA + *outFormat = skcms_PixelFormat_RGBA_8888; +#else + *outFormat = skcms_PixelFormat_BGRA_8888; +#endif + break; + } + *outFormat = skcms_PixelFormat_BGR_565; + break; + case kRGBA_F16_SkColorType: + *outFormat = skcms_PixelFormat_RGBA_hhhh; + break; + case kGray_8_SkColorType: + *outFormat = skcms_PixelFormat_G_8; + break; + default: + return false; + } + return true; +} + +bool SkCodec::initializeColorXform(const SkImageInfo& dstInfo, SkEncodedInfo::Alpha encodedAlpha, + bool srcIsOpaque) { + fXformTime = kNo_XformTime; + bool needsColorXform = false; + if (this->usesColorXform() && dstInfo.colorSpace()) { + dstInfo.colorSpace()->toProfile(&fDstProfile); + if (kRGBA_F16_SkColorType == dstInfo.colorType()) { + needsColorXform = true; + } else { + const auto* srcProfile = fEncodedInfo.profile(); + if (!srcProfile) { + srcProfile = skcms_sRGB_profile(); + } + if (!skcms_ApproximatelyEqualProfiles(srcProfile, &fDstProfile) ) { + needsColorXform = true; + } + } + } + + if (!this->conversionSupported(dstInfo, srcIsOpaque, needsColorXform)) { + return false; + } + + if (needsColorXform) { + fXformTime = SkEncodedInfo::kPalette_Color != fEncodedInfo.color() + || kRGBA_F16_SkColorType == dstInfo.colorType() + ? kDecodeRow_XformTime : kPalette_XformTime; + if (!sk_select_xform_format(dstInfo.colorType(), fXformTime == kPalette_XformTime, + &fDstXformFormat)) { + return false; + } + if (encodedAlpha == SkEncodedInfo::kUnpremul_Alpha + && dstInfo.alphaType() == kPremul_SkAlphaType) { + fDstXformAlphaFormat = skcms_AlphaFormat_PremulAsEncoded; + } else { + fDstXformAlphaFormat = skcms_AlphaFormat_Unpremul; + } + } + return true; +} + +void SkCodec::applyColorXform(void* dst, const void* src, int count) const { + // It is okay for srcProfile to be null. This will use sRGB. + const auto* srcProfile = fEncodedInfo.profile(); + SkAssertResult(skcms_Transform(src, fSrcXformFormat, skcms_AlphaFormat_Unpremul, srcProfile, + dst, fDstXformFormat, fDstXformAlphaFormat, &fDstProfile, + count)); +} + +std::vector<SkCodec::FrameInfo> SkCodec::getFrameInfo() { + const int frameCount = this->getFrameCount(); + SkASSERT(frameCount >= 0); + if (frameCount <= 0) { + return std::vector<FrameInfo>{}; + } + + if (frameCount == 1 && !this->onGetFrameInfo(0, nullptr)) { + // Not animated. + return std::vector<FrameInfo>{}; + } + + std::vector<FrameInfo> result(frameCount); + for (int i = 0; i < frameCount; ++i) { + SkAssertResult(this->onGetFrameInfo(i, &result[i])); + } + return result; +} + +const char* SkCodec::ResultToString(Result result) { + switch (result) { + case kSuccess: + return "success"; + case kIncompleteInput: + return "incomplete input"; + case kErrorInInput: + return "error in input"; + case kInvalidConversion: + return "invalid conversion"; + case kInvalidScale: + return "invalid scale"; + case kInvalidParameters: + return "invalid parameters"; + case kInvalidInput: + return "invalid input"; + case kCouldNotRewind: + return "could not rewind"; + case kInternalError: + return "internal error"; + case kUnimplemented: + return "unimplemented"; + default: + SkASSERT(false); + return "bogus result value"; + } +} + +static bool independent(const SkFrame& frame) { + return frame.getRequiredFrame() == SkCodec::kNoFrame; +} + +static bool restore_bg(const SkFrame& frame) { + return frame.getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestoreBGColor; +} + +// As its name suggests, this method computes a frame's alpha (e.g. completely +// opaque, unpremul, binary) and its required frame (a preceding frame that +// this frame depends on, to draw the complete image at this frame's point in +// the animation stream), and calls this frame's setter methods with that +// computed information. +// +// A required frame of kNoFrame means that this frame is independent: drawing +// the complete image at this frame's point in the animation stream does not +// require first preparing the pixel buffer based on another frame. Instead, +// drawing can start from an uninitialized pixel buffer. +// +// "Uninitialized" is from the SkCodec's caller's point of view. In the SkCodec +// implementation, for independent frames, first party Skia code (in src/codec) +// will typically fill the buffer with a uniform background color (e.g. +// transparent black) before calling into third party codec-specific code (e.g. +// libjpeg or libpng). Pixels outside of the frame's rect will remain this +// background color after drawing this frame. For incomplete decodes, pixels +// inside that rect may be (at least temporarily) set to that background color. +// In an incremental decode, later passes may then overwrite that background +// color. +// +// Determining kNoFrame or otherwise involves testing a number of conditions +// sequentially. The first satisfied condition results in setting the required +// frame to kNoFrame (an "INDx" condition) or to a non-negative frame number (a +// "DEPx" condition), and the function returning early. Those "INDx" and "DEPx" +// labels also map to comments in the function body. +// +// - IND1: this frame is the first frame. +// - IND2: this frame fills out the whole image, and it is completely opaque +// or it overwrites (not blends with) the previous frame. +// - IND3: all preceding frames' disposals are kRestorePrevious. +// - IND4: the prevFrame's disposal is kRestoreBGColor, and it fills out the +// whole image or it is itself otherwise independent. +// - DEP5: this frame reports alpha (it is not completely opaque) and it +// blends with (not overwrites) the previous frame. +// - IND6: this frame's rect covers the rects of all preceding frames back to +// and including the most recent independent frame before this frame. +// - DEP7: unconditional. +// +// The "prevFrame" variable initially points to the previous frame (also known +// as the prior frame), but that variable may iterate further backwards over +// the course of this computation. +void SkFrameHolder::setAlphaAndRequiredFrame(SkFrame* frame) { + const bool reportsAlpha = frame->reportedAlpha() != SkEncodedInfo::kOpaque_Alpha; + const auto screenRect = SkIRect::MakeWH(fScreenWidth, fScreenHeight); + const auto frameRect = frame_rect_on_screen(frame->frameRect(), screenRect); + + const int i = frame->frameId(); + if (0 == i) { + frame->setHasAlpha(reportsAlpha || frameRect != screenRect); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND1 + return; + } + + + const bool blendWithPrevFrame = frame->getBlend() == SkCodecAnimation::Blend::kPriorFrame; + if ((!reportsAlpha || !blendWithPrevFrame) && frameRect == screenRect) { + frame->setHasAlpha(reportsAlpha); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND2 + return; + } + + const SkFrame* prevFrame = this->getFrame(i-1); + while (prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kRestorePrevious) { + const int prevId = prevFrame->frameId(); + if (0 == prevId) { + frame->setHasAlpha(true); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND3 + return; + } + + prevFrame = this->getFrame(prevId - 1); + } + + const bool clearPrevFrame = restore_bg(*prevFrame); + auto prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect); + + if (clearPrevFrame) { + if (prevFrameRect == screenRect || independent(*prevFrame)) { + frame->setHasAlpha(true); + frame->setRequiredFrame(SkCodec::kNoFrame); // IND4 + return; + } + } + + if (reportsAlpha && blendWithPrevFrame) { + // Note: We could be more aggressive here. If prevFrame clears + // to background color and covers its required frame (and that + // frame is independent), prevFrame could be marked independent. + // Would this extra complexity be worth it? + frame->setRequiredFrame(prevFrame->frameId()); // DEP5 + frame->setHasAlpha(prevFrame->hasAlpha() || clearPrevFrame); + return; + } + + while (frameRect.contains(prevFrameRect)) { + const int prevRequiredFrame = prevFrame->getRequiredFrame(); + if (prevRequiredFrame == SkCodec::kNoFrame) { + frame->setRequiredFrame(SkCodec::kNoFrame); // IND6 + frame->setHasAlpha(true); + return; + } + + prevFrame = this->getFrame(prevRequiredFrame); + prevFrameRect = frame_rect_on_screen(prevFrame->frameRect(), screenRect); + } + + frame->setRequiredFrame(prevFrame->frameId()); // DEP7 + if (restore_bg(*prevFrame)) { + frame->setHasAlpha(true); + return; + } + SkASSERT(prevFrame->getDisposalMethod() == SkCodecAnimation::DisposalMethod::kKeep); + frame->setHasAlpha(prevFrame->hasAlpha() || (reportsAlpha && !blendWithPrevFrame)); +} + diff --git a/gfx/skia/skia/src/codec/SkCodecAnimationPriv.h b/gfx/skia/skia/src/codec/SkCodecAnimationPriv.h new file mode 100644 index 0000000000..233a79b211 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecAnimationPriv.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodecAnimationPriv_DEFINED +#define SkCodecAnimationPriv_DEFINED + +namespace SkCodecAnimation { + /** + * How to blend the current frame. + */ + enum class Blend { + /** + * Blend with the prior frame. This is the typical case, supported + * by all animated image types. + */ + kPriorFrame, + + /** + * Do not blend. + * + * This frames pixels overwrite previous pixels "blending" with + * the background color of transparent. + */ + kBG, + }; + +} +#endif // SkCodecAnimationPriv_DEFINED diff --git a/gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp b/gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp new file mode 100644 index 0000000000..b7909479a1 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp @@ -0,0 +1,99 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkYUVAIndex.h" +#include "src/codec/SkCodecImageGenerator.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkPixmapPriv.h" + +std::unique_ptr<SkImageGenerator> SkCodecImageGenerator::MakeFromEncodedCodec(sk_sp<SkData> data) { + auto codec = SkCodec::MakeFromData(data); + if (nullptr == codec) { + return nullptr; + } + + return std::unique_ptr<SkImageGenerator>(new SkCodecImageGenerator(std::move(codec), data)); +} + +std::unique_ptr<SkImageGenerator> +SkCodecImageGenerator::MakeFromCodec(std::unique_ptr<SkCodec> codec) { + return codec + ? std::unique_ptr<SkImageGenerator>(new SkCodecImageGenerator(std::move(codec), nullptr)) + : nullptr; +} + +static SkImageInfo adjust_info(SkCodec* codec) { + SkImageInfo info = codec->getInfo(); + if (kUnpremul_SkAlphaType == info.alphaType()) { + info = info.makeAlphaType(kPremul_SkAlphaType); + } + if (SkPixmapPriv::ShouldSwapWidthHeight(codec->getOrigin())) { + info = SkPixmapPriv::SwapWidthHeight(info); + } + return info; +} + +SkCodecImageGenerator::SkCodecImageGenerator(std::unique_ptr<SkCodec> codec, sk_sp<SkData> data) + : INHERITED(adjust_info(codec.get())) + , fCodec(std::move(codec)) + , fData(std::move(data)) +{} + +sk_sp<SkData> SkCodecImageGenerator::onRefEncodedData() { + return fData; +} + +bool SkCodecImageGenerator::onGetPixels(const SkImageInfo& requestInfo, void* requestPixels, + size_t requestRowBytes, const Options&) { + SkPixmap dst(requestInfo, requestPixels, requestRowBytes); + + auto decode = [this](const SkPixmap& pm) { + SkCodec::Result result = fCodec->getPixels(pm); + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + return false; + } + }; + + return SkPixmapPriv::Orient(dst, fCodec->getOrigin(), decode); +} + +bool SkCodecImageGenerator::onQueryYUVA8(SkYUVASizeInfo* sizeInfo, + SkYUVAIndex yuvaIndices[SkYUVAIndex::kIndexCount], + SkYUVColorSpace* colorSpace) const { + // This image generator always returns 3 separate non-interleaved planes + yuvaIndices[SkYUVAIndex::kY_Index].fIndex = 0; + yuvaIndices[SkYUVAIndex::kY_Index].fChannel = SkColorChannel::kR; + yuvaIndices[SkYUVAIndex::kU_Index].fIndex = 1; + yuvaIndices[SkYUVAIndex::kU_Index].fChannel = SkColorChannel::kR; + yuvaIndices[SkYUVAIndex::kV_Index].fIndex = 2; + yuvaIndices[SkYUVAIndex::kV_Index].fChannel = SkColorChannel::kR; + yuvaIndices[SkYUVAIndex::kA_Index].fIndex = -1; + yuvaIndices[SkYUVAIndex::kA_Index].fChannel = SkColorChannel::kR; + + return fCodec->queryYUV8(sizeInfo, colorSpace); +} + +bool SkCodecImageGenerator::onGetYUVA8Planes(const SkYUVASizeInfo& sizeInfo, + const SkYUVAIndex indices[SkYUVAIndex::kIndexCount], + void* planes[]) { + SkCodec::Result result = fCodec->getYUV8Planes(sizeInfo, planes); + // TODO: check indices + + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + return true; + default: + return false; + } +} diff --git a/gfx/skia/skia/src/codec/SkCodecImageGenerator.h b/gfx/skia/skia/src/codec/SkCodecImageGenerator.h new file mode 100644 index 0000000000..3d8404f2dc --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecImageGenerator.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkCodecImageGenerator_DEFINED +#define SkCodecImageGenerator_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkImageGenerator.h" + +class SkCodecImageGenerator : public SkImageGenerator { +public: + /* + * If this data represents an encoded image that we know how to decode, + * return an SkCodecImageGenerator. Otherwise return nullptr. + */ + static std::unique_ptr<SkImageGenerator> MakeFromEncodedCodec(sk_sp<SkData>); + + static std::unique_ptr<SkImageGenerator> MakeFromCodec(std::unique_ptr<SkCodec>); + +protected: + sk_sp<SkData> onRefEncodedData() override; + + bool onGetPixels( + const SkImageInfo& info, void* pixels, size_t rowBytes, const Options& opts) override; + + bool onQueryYUVA8( + SkYUVASizeInfo*, SkYUVAIndex[SkYUVAIndex::kIndexCount], SkYUVColorSpace*) const override; + + bool onGetYUVA8Planes(const SkYUVASizeInfo&, const SkYUVAIndex[SkYUVAIndex::kIndexCount], + void* planes[]) override; + +private: + /* + * Takes ownership of codec + */ + SkCodecImageGenerator(std::unique_ptr<SkCodec>, sk_sp<SkData>); + + std::unique_ptr<SkCodec> fCodec; + sk_sp<SkData> fData; + + typedef SkImageGenerator INHERITED; +}; +#endif // SkCodecImageGenerator_DEFINED diff --git a/gfx/skia/skia/src/codec/SkCodecPriv.h b/gfx/skia/skia/src/codec/SkCodecPriv.h new file mode 100644 index 0000000000..d2f2a37f31 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkCodecPriv.h @@ -0,0 +1,251 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodecPriv_DEFINED +#define SkCodecPriv_DEFINED + +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkEncodedInfo.h" +#include "src/codec/SkColorTable.h" +#include "src/core/SkEndian.h" + +#ifdef SK_PRINT_CODEC_MESSAGES + #define SkCodecPrintf SkDebugf +#else + #define SkCodecPrintf(...) +#endif + +// Defined in SkCodec.cpp +bool sk_select_xform_format(SkColorType colorType, bool forColorTable, + skcms_PixelFormat* outFormat); + +// FIXME: Consider sharing with dm, nanbench, and tools. +static inline float get_scale_from_sample_size(int sampleSize) { + return 1.0f / ((float) sampleSize); +} + +static inline bool is_valid_subset(const SkIRect& subset, const SkISize& imageDims) { + return SkIRect::MakeSize(imageDims).contains(subset); +} + +/* + * returns a scaled dimension based on the original dimension and the sampleSize + * NOTE: we round down here for scaled dimension to match the behavior of SkImageDecoder + * FIXME: I think we should call this get_sampled_dimension(). + */ +static inline int get_scaled_dimension(int srcDimension, int sampleSize) { + if (sampleSize > srcDimension) { + return 1; + } + return srcDimension / sampleSize; +} + +/* + * Returns the first coordinate that we will keep during a scaled decode. + * The output can be interpreted as an x-coordinate or a y-coordinate. + * + * This does not need to be called and is not called when sampleFactor == 1. + */ +static inline int get_start_coord(int sampleFactor) { return sampleFactor / 2; }; + +/* + * Given a coordinate in the original image, this returns the corresponding + * coordinate in the scaled image. This function is meaningless if + * IsCoordNecessary returns false. + * The output can be interpreted as an x-coordinate or a y-coordinate. + * + * This does not need to be called and is not called when sampleFactor == 1. + */ +static inline int get_dst_coord(int srcCoord, int sampleFactor) { return srcCoord / sampleFactor; }; + +/* + * When scaling, we will discard certain y-coordinates (rows) and + * x-coordinates (columns). This function returns true if we should keep the + * coordinate and false otherwise. + * The inputs may be x-coordinates or y-coordinates. + * + * This does not need to be called and is not called when sampleFactor == 1. + */ +static inline bool is_coord_necessary(int srcCoord, int sampleFactor, int scaledDim) { + // Get the first coordinate that we want to keep + int startCoord = get_start_coord(sampleFactor); + + // Return false on edge cases + if (srcCoord < startCoord || get_dst_coord(srcCoord, sampleFactor) >= scaledDim) { + return false; + } + + // Every sampleFactor rows are necessary + return ((srcCoord - startCoord) % sampleFactor) == 0; +} + +static inline bool valid_alpha(SkAlphaType dstAlpha, bool srcIsOpaque) { + if (kUnknown_SkAlphaType == dstAlpha) { + return false; + } + + if (srcIsOpaque) { + if (kOpaque_SkAlphaType != dstAlpha) { + SkCodecPrintf("Warning: an opaque image should be decoded as opaque " + "- it is being decoded as non-opaque, which will draw slower\n"); + } + return true; + } + + return dstAlpha != kOpaque_SkAlphaType; +} + +/* + * If there is a color table, get a pointer to the colors, otherwise return nullptr + */ +static inline const SkPMColor* get_color_ptr(SkColorTable* colorTable) { + return nullptr != colorTable ? colorTable->readColors() : nullptr; +} + +/* + * Compute row bytes for an image using pixels per byte + */ +static inline size_t compute_row_bytes_ppb(int width, uint32_t pixelsPerByte) { + return (width + pixelsPerByte - 1) / pixelsPerByte; +} + +/* + * Compute row bytes for an image using bytes per pixel + */ +static inline size_t compute_row_bytes_bpp(int width, uint32_t bytesPerPixel) { + return width * bytesPerPixel; +} + +/* + * Compute row bytes for an image + */ +static inline size_t compute_row_bytes(int width, uint32_t bitsPerPixel) { + if (bitsPerPixel < 16) { + SkASSERT(0 == 8 % bitsPerPixel); + const uint32_t pixelsPerByte = 8 / bitsPerPixel; + return compute_row_bytes_ppb(width, pixelsPerByte); + } else { + SkASSERT(0 == bitsPerPixel % 8); + const uint32_t bytesPerPixel = bitsPerPixel / 8; + return compute_row_bytes_bpp(width, bytesPerPixel); + } +} + +/* + * Get a byte from a buffer + * This method is unsafe, the caller is responsible for performing a check + */ +static inline uint8_t get_byte(uint8_t* buffer, uint32_t i) { + return buffer[i]; +} + +/* + * Get a short from a buffer + * This method is unsafe, the caller is responsible for performing a check + */ +static inline uint16_t get_short(uint8_t* buffer, uint32_t i) { + uint16_t result; + memcpy(&result, &(buffer[i]), 2); +#ifdef SK_CPU_BENDIAN + return SkEndianSwap16(result); +#else + return result; +#endif +} + +/* + * Get an int from a buffer + * This method is unsafe, the caller is responsible for performing a check + */ +static inline uint32_t get_int(uint8_t* buffer, uint32_t i) { + uint32_t result; + memcpy(&result, &(buffer[i]), 4); +#ifdef SK_CPU_BENDIAN + return SkEndianSwap32(result); +#else + return result; +#endif +} + +/* + * @param data Buffer to read bytes from + * @param isLittleEndian Output parameter + * Indicates if the data is little endian + * Is unaffected on false returns + */ +static inline bool is_valid_endian_marker(const uint8_t* data, bool* isLittleEndian) { + // II indicates Intel (little endian) and MM indicates motorola (big endian). + if (('I' != data[0] || 'I' != data[1]) && ('M' != data[0] || 'M' != data[1])) { + return false; + } + + *isLittleEndian = ('I' == data[0]); + return true; +} + +static inline uint16_t get_endian_short(const uint8_t* data, bool littleEndian) { + if (littleEndian) { + return (data[1] << 8) | (data[0]); + } + + return (data[0] << 8) | (data[1]); +} + +static inline SkPMColor premultiply_argb_as_rgba(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + + return SkPackARGB_as_RGBA(a, r, g, b); +} + +static inline SkPMColor premultiply_argb_as_bgra(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { + if (a != 255) { + r = SkMulDiv255Round(r, a); + g = SkMulDiv255Round(g, a); + b = SkMulDiv255Round(b, a); + } + + return SkPackARGB_as_BGRA(a, r, g, b); +} + +static inline bool is_rgba(SkColorType colorType) { +#ifdef SK_PMCOLOR_IS_RGBA + return (kBGRA_8888_SkColorType != colorType); +#else + return (kRGBA_8888_SkColorType == colorType); +#endif +} + +// Method for coverting to a 32 bit pixel. +typedef uint32_t (*PackColorProc)(U8CPU a, U8CPU r, U8CPU g, U8CPU b); + +static inline PackColorProc choose_pack_color_proc(bool isPremul, SkColorType colorType) { + bool isRGBA = is_rgba(colorType); + if (isPremul) { + if (isRGBA) { + return &premultiply_argb_as_rgba; + } else { + return &premultiply_argb_as_bgra; + } + } else { + if (isRGBA) { + return &SkPackARGB_as_RGBA; + } else { + return &SkPackARGB_as_BGRA; + } + } +} + +bool is_orientation_marker(const uint8_t* data, size_t data_length, SkEncodedOrigin* orientation); + +#endif // SkCodecPriv_DEFINED diff --git a/gfx/skia/skia/src/codec/SkColorTable.cpp b/gfx/skia/skia/src/codec/SkColorTable.cpp new file mode 100644 index 0000000000..c15c452478 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkColorTable.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2009 The Android Open Source Project + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkMalloc.h" +#include "src/codec/SkColorTable.h" + +SkColorTable::SkColorTable(const SkPMColor colors[], int count) { + SkASSERT(0 == count || colors); + SkASSERT(count >= 0 && count <= 256); + + fCount = count; + fColors = reinterpret_cast<SkPMColor*>(sk_malloc_throw(count * sizeof(SkPMColor))); + + memcpy(fColors, colors, count * sizeof(SkPMColor)); +} + +SkColorTable::~SkColorTable() { + sk_free(fColors); +} diff --git a/gfx/skia/skia/src/codec/SkColorTable.h b/gfx/skia/skia/src/codec/SkColorTable.h new file mode 100644 index 0000000000..e83498ead8 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkColorTable.h @@ -0,0 +1,50 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkColorTable_DEFINED +#define SkColorTable_DEFINED + +#include "include/core/SkColor.h" +#include "include/core/SkRefCnt.h" + +/** \class SkColorTable + + SkColorTable holds an array SkPMColors (premultiplied 32-bit colors) used by + 8-bit bitmaps, where the bitmap bytes are interpreted as indices into the colortable. + + SkColorTable is thread-safe. +*/ +class SkColorTable : public SkRefCnt { +public: + /** Copy up to 256 colors into a new SkColorTable. + */ + SkColorTable(const SkPMColor colors[], int count); + ~SkColorTable() override; + + /** Returns the number of colors in the table. + */ + int count() const { return fCount; } + + /** Returns the specified color from the table. In the debug build, this asserts that + * the index is in range (0 <= index < count). + */ + SkPMColor operator[](int index) const { + SkASSERT(fColors != nullptr && (unsigned)index < (unsigned)fCount); + return fColors[index]; + } + + /** Return the array of colors for reading. */ + const SkPMColor* readColors() const { return fColors; } + +private: + SkPMColor* fColors; + int fCount; + + typedef SkRefCnt INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkEncodedInfo.cpp b/gfx/skia/skia/src/codec/SkEncodedInfo.cpp new file mode 100644 index 0000000000..75c4d3061d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkEncodedInfo.cpp @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkEncodedInfo.h" + +std::unique_ptr<SkEncodedInfo::ICCProfile> SkEncodedInfo::ICCProfile::Make(sk_sp<SkData> data) { + if (data) { + skcms_ICCProfile profile; + if (skcms_Parse(data->data(), data->size(), &profile)) { + return std::unique_ptr<ICCProfile>(new ICCProfile(profile, std::move(data))); + } + } + return nullptr; +} + +std::unique_ptr<SkEncodedInfo::ICCProfile> SkEncodedInfo::ICCProfile::Make( + const skcms_ICCProfile& profile) { + return std::unique_ptr<ICCProfile>(new ICCProfile(profile)); +} + +SkEncodedInfo::ICCProfile::ICCProfile(const skcms_ICCProfile& profile, sk_sp<SkData> data) + : fProfile(profile) + , fData(std::move(data)) +{} diff --git a/gfx/skia/skia/src/codec/SkFrameHolder.h b/gfx/skia/skia/src/codec/SkFrameHolder.h new file mode 100644 index 0000000000..c44d2e048c --- /dev/null +++ b/gfx/skia/skia/src/codec/SkFrameHolder.h @@ -0,0 +1,201 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkFrameHolder_DEFINED +#define SkFrameHolder_DEFINED + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkRect.h" +#include "include/core/SkTypes.h" +#include "include/private/SkEncodedInfo.h" +#include "include/private/SkNoncopyable.h" +#include "src/codec/SkCodecAnimationPriv.h" + +/** + * Base class for a single frame of an animated image. + * + * Separate from SkCodec::FrameInfo, which is a pared down + * interface that only contains the info the client needs. + */ +class SkFrame : public SkNoncopyable { +public: + SkFrame(int id) + : fId(id) + , fHasAlpha(false) + , fRequiredFrame(kUninitialized) + , fDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep) + , fDuration(0) + , fBlend(SkCodecAnimation::Blend::kPriorFrame) + { + fRect.setEmpty(); + } + + virtual ~SkFrame() {} + + /** + * An explicit move constructor, as + * https://en.cppreference.com/w/cpp/language/move_constructor says that + * there is no implicit move constructor if there are user-declared + * destructors, and we have one, immediately above. + * + * Without a move constructor, it is harder to use an SkFrame, or an + * SkFrame subclass, inside a std::vector. + */ + SkFrame(SkFrame&&) = default; + + /** + * 0-based index of the frame in the image sequence. + */ + int frameId() const { return fId; } + + /** + * How this frame reports its alpha. + * + * This only considers the rectangle of this frame, and + * considers it to have alpha even if it is opaque once + * blended with the frame behind it. + */ + SkEncodedInfo::Alpha reportedAlpha() const { + return this->onReportedAlpha(); + } + + /** + * Cached value representing whether the frame has alpha, + * after compositing with the prior frame. + */ + bool hasAlpha() const { return fHasAlpha; } + + /** + * Cache whether the finished frame has alpha. + */ + void setHasAlpha(bool alpha) { fHasAlpha = alpha; } + + /** + * Whether enough of the frame has been read to determine + * fRequiredFrame and fHasAlpha. + */ + bool reachedStartOfData() const { return fRequiredFrame != kUninitialized; } + + /** + * The frame this one depends on. + * + * Must not be called until fRequiredFrame has been set properly. + */ + int getRequiredFrame() const { + SkASSERT(this->reachedStartOfData()); + return fRequiredFrame; + } + + /** + * Set the frame that this frame depends on. + */ + void setRequiredFrame(int req) { fRequiredFrame = req; } + + /** + * Set the rectangle that is updated by this frame. + */ + void setXYWH(int x, int y, int width, int height) { + fRect.setXYWH(x, y, width, height); + } + + /** + * The rectangle that is updated by this frame. + */ + SkIRect frameRect() const { return fRect; } + + int xOffset() const { return fRect.x(); } + int yOffset() const { return fRect.y(); } + int width() const { return fRect.width(); } + int height() const { return fRect.height(); } + + SkCodecAnimation::DisposalMethod getDisposalMethod() const { + return fDisposalMethod; + } + + void setDisposalMethod(SkCodecAnimation::DisposalMethod disposalMethod) { + fDisposalMethod = disposalMethod; + } + + /** + * Set the duration (in ms) to show this frame. + */ + void setDuration(int duration) { + fDuration = duration; + } + + /** + * Duration in ms to show this frame. + */ + int getDuration() const { + return fDuration; + } + + void setBlend(SkCodecAnimation::Blend blend) { + fBlend = blend; + } + + SkCodecAnimation::Blend getBlend() const { + return fBlend; + } + +protected: + virtual SkEncodedInfo::Alpha onReportedAlpha() const = 0; + +private: + static constexpr int kUninitialized = -2; + + const int fId; + bool fHasAlpha; + int fRequiredFrame; + SkIRect fRect; + SkCodecAnimation::DisposalMethod fDisposalMethod; + int fDuration; + SkCodecAnimation::Blend fBlend; +}; + +/** + * Base class for an object which holds the SkFrames of an + * image sequence. + */ +class SkFrameHolder : public SkNoncopyable { +public: + SkFrameHolder() + : fScreenWidth(0) + , fScreenHeight(0) + {} + + virtual ~SkFrameHolder() {} + + /** + * Size of the image. Each frame will be contained in + * these dimensions (possibly after clipping). + */ + int screenWidth() const { return fScreenWidth; } + int screenHeight() const { return fScreenHeight; } + + /** + * Compute the opacity and required frame, based on + * the frame's reportedAlpha and how it blends + * with prior frames. + */ + void setAlphaAndRequiredFrame(SkFrame*); + + /** + * Return the frame with frameId i. + */ + const SkFrame* getFrame(int i) const { + return this->onGetFrame(i); + } + +protected: + int fScreenWidth; + int fScreenHeight; + + virtual const SkFrame* onGetFrame(int i) const = 0; +}; + +#endif // SkFrameHolder_DEFINED diff --git a/gfx/skia/skia/src/codec/SkGifCodec.cpp b/gfx/skia/skia/src/codec/SkGifCodec.cpp new file mode 100644 index 0000000000..b2185d184e --- /dev/null +++ b/gfx/skia/skia/src/codec/SkGifCodec.cpp @@ -0,0 +1,533 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkGifCodec.h" +#include "src/codec/SkSwizzler.h" +#include "src/core/SkMakeUnique.h" + +#include <algorithm> + +#define GIF87_STAMP "GIF87a" +#define GIF89_STAMP "GIF89a" +#define GIF_STAMP_LEN 6 + +/* + * Checks the start of the stream to see if the image is a gif + */ +bool SkGifCodec::IsGif(const void* buf, size_t bytesRead) { + if (bytesRead >= GIF_STAMP_LEN) { + if (memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 || + memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) + { + return true; + } + } + return false; +} + +/* + * Error function + */ +static SkCodec::Result gif_error(const char* msg, SkCodec::Result result = SkCodec::kInvalidInput) { + SkCodecPrintf("Gif Error: %s\n", msg); + return result; +} + +std::unique_ptr<SkCodec> SkGifCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + std::unique_ptr<SkGifImageReader> reader(new SkGifImageReader(std::move(stream))); + *result = reader->parse(SkGifImageReader::SkGIFSizeQuery); + if (*result != kSuccess) { + return nullptr; + } + + // If no images are in the data, or the first header is not yet defined, we cannot + // create a codec. In either case, the width and height are not yet known. + auto* frame = reader->frameContext(0); + if (!frame || !frame->isHeaderDefined()) { + *result = kInvalidInput; + return nullptr; + } + + // isHeaderDefined() will not return true if the screen size is empty. + SkASSERT(reader->screenHeight() > 0 && reader->screenWidth() > 0); + + const auto alpha = reader->firstFrameHasAlpha() ? SkEncodedInfo::kBinary_Alpha + : SkEncodedInfo::kOpaque_Alpha; + // Use kPalette since Gifs are encoded with a color table. + // FIXME: Gifs can actually be encoded with 4-bits per pixel. Using 8 works, but we could skip + // expanding to 8 bits and take advantage of the SkSwizzler to work from 4. + auto encodedInfo = SkEncodedInfo::Make(reader->screenWidth(), reader->screenHeight(), + SkEncodedInfo::kPalette_Color, alpha, 8); + return std::unique_ptr<SkCodec>(new SkGifCodec(std::move(encodedInfo), reader.release())); +} + +bool SkGifCodec::onRewind() { + fReader->clearDecodeState(); + return true; +} + +SkGifCodec::SkGifCodec(SkEncodedInfo&& encodedInfo, SkGifImageReader* reader) + : INHERITED(std::move(encodedInfo), skcms_PixelFormat_RGBA_8888, nullptr) + , fReader(reader) + , fTmpBuffer(nullptr) + , fSwizzler(nullptr) + , fCurrColorTable(nullptr) + , fCurrColorTableIsReal(false) + , fFilledBackground(false) + , fFirstCallToIncrementalDecode(false) + , fDst(nullptr) + , fDstRowBytes(0) + , fRowsDecoded(0) +{ + reader->setClient(this); +} + +int SkGifCodec::onGetFrameCount() { + fReader->parse(SkGifImageReader::SkGIFFrameCountQuery); + return fReader->imagesCount(); +} + +bool SkGifCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const { + if (i >= fReader->imagesCount()) { + return false; + } + + const SkGIFFrameContext* frameContext = fReader->frameContext(i); + SkASSERT(frameContext->reachedStartOfData()); + + if (frameInfo) { + frameInfo->fDuration = frameContext->getDuration(); + frameInfo->fRequiredFrame = frameContext->getRequiredFrame(); + frameInfo->fFullyReceived = frameContext->isComplete(); + frameInfo->fAlphaType = frameContext->hasAlpha() ? kUnpremul_SkAlphaType + : kOpaque_SkAlphaType; + frameInfo->fDisposalMethod = frameContext->getDisposalMethod(); + } + return true; +} + +int SkGifCodec::onGetRepetitionCount() { + fReader->parse(SkGifImageReader::SkGIFLoopCountQuery); + return fReader->loopCount(); +} + +static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; + +void SkGifCodec::initializeColorTable(const SkImageInfo& dstInfo, int frameIndex) { + SkColorType colorTableColorType = dstInfo.colorType(); + if (this->colorXform()) { + colorTableColorType = kXformSrcColorType; + } + + sk_sp<SkColorTable> currColorTable = fReader->getColorTable(colorTableColorType, frameIndex); + fCurrColorTableIsReal = static_cast<bool>(currColorTable); + if (!fCurrColorTableIsReal) { + // This is possible for an empty frame. Create a dummy with one value (transparent). + SkPMColor color = SK_ColorTRANSPARENT; + fCurrColorTable.reset(new SkColorTable(&color, 1)); + } else if (this->colorXform() && !this->xformOnDecode()) { + SkPMColor dstColors[256]; + this->applyColorXform(dstColors, currColorTable->readColors(), + currColorTable->count()); + fCurrColorTable.reset(new SkColorTable(dstColors, currColorTable->count())); + } else { + fCurrColorTable = std::move(currColorTable); + } +} + + +SkCodec::Result SkGifCodec::prepareToDecode(const SkImageInfo& dstInfo, const Options& opts) { + if (opts.fSubset) { + return gif_error("Subsets not supported.\n", kUnimplemented); + } + + const int frameIndex = opts.fFrameIndex; + if (frameIndex > 0 && kRGB_565_SkColorType == dstInfo.colorType()) { + // FIXME: In theory, we might be able to support this, but it's not clear that it + // is necessary (Chromium does not decode to 565, and Android does not decode + // frames beyond the first). Disabling it because it is somewhat difficult: + // - If there is a transparent pixel, and this frame draws on top of another frame + // (if the frame is independent with a transparent pixel, we should not decode to + // 565 anyway, since it is not opaque), we need to skip drawing the transparent + // pixels (see writeTransparentPixels in haveDecodedRow). We currently do this by + // first swizzling into temporary memory, then copying into the destination. (We + // let the swizzler handle it first because it may need to sample.) After + // swizzling to 565, we do not know which pixels in our temporary memory + // correspond to the transparent pixel, so we do not know what to skip. We could + // special case the non-sampled case (no need to swizzle), but as this is + // currently unused we can just not support it. + return gif_error("Cannot decode multiframe gif (except frame 0) as 565.\n", + kInvalidConversion); + } + + const auto* frame = fReader->frameContext(frameIndex); + SkASSERT(frame); + if (0 == frameIndex) { + // SkCodec does not have a way to just parse through frame 0, so we + // have to do so manually, here. + fReader->parse((SkGifImageReader::SkGIFParseQuery) 0); + if (!frame->reachedStartOfData()) { + // We have parsed enough to know that there is a color map, but cannot + // parse the map itself yet. Exit now, so we do not build an incorrect + // table. + return gif_error("color map not available yet\n", kIncompleteInput); + } + } else { + // Parsing happened in SkCodec::getPixels. + SkASSERT(frameIndex < fReader->imagesCount()); + SkASSERT(frame->reachedStartOfData()); + } + + if (this->xformOnDecode()) { + fXformBuffer.reset(new uint32_t[dstInfo.width()]); + sk_bzero(fXformBuffer.get(), dstInfo.width() * sizeof(uint32_t)); + } + + fTmpBuffer.reset(new uint8_t[dstInfo.minRowBytes()]); + + this->initializeColorTable(dstInfo, frameIndex); + this->initializeSwizzler(dstInfo, frameIndex); + + SkASSERT(fCurrColorTable); + return kSuccess; +} + +void SkGifCodec::initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex) { + const SkGIFFrameContext* frame = fReader->frameContext(frameIndex); + // This is only called by prepareToDecode, which ensures frameIndex is in range. + SkASSERT(frame); + + const int xBegin = frame->xOffset(); + const int xEnd = std::min(frame->frameRect().right(), fReader->screenWidth()); + + // CreateSwizzler only reads left and right of the frame. We cannot use the frame's raw + // frameRect, since it might extend beyond the edge of the frame. + SkIRect swizzleRect = SkIRect::MakeLTRB(xBegin, 0, xEnd, 0); + + SkImageInfo swizzlerInfo = dstInfo; + if (this->colorXform()) { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + } + + // The default Options should be fine: + // - we'll ignore if the memory is zero initialized - unless we're the first frame, this won't + // matter anyway. + // - subsets are not supported for gif + // - the swizzler does not need to know about the frame. + // We may not be able to use the real Options anyway, since getPixels does not store it (due to + // a bug). + fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), fCurrColorTable->readColors(), + swizzlerInfo, Options(), &swizzleRect); + SkASSERT(fSwizzler.get()); +} + +/* + * Initiates the gif decode + */ +SkCodec::Result SkGifCodec::onGetPixels(const SkImageInfo& dstInfo, + void* pixels, size_t dstRowBytes, + const Options& opts, + int* rowsDecoded) { + Result result = this->prepareToDecode(dstInfo, opts); + switch (result) { + case kSuccess: + break; + case kIncompleteInput: + // onStartIncrementalDecode treats this as incomplete, since it may + // provide more data later, but in this case, no more data will be + // provided, and there is nothing to draw. We also cannot return + // kIncompleteInput, which will make SkCodec attempt to fill + // remaining rows, but that requires an SkSwizzler, which we have + // not created. + return kInvalidInput; + default: + return result; + } + + if (dstInfo.dimensions() != this->dimensions()) { + return gif_error("Scaling not supported.\n", kInvalidScale); + } + + fDst = pixels; + fDstRowBytes = dstRowBytes; + + return this->decodeFrame(true, opts, rowsDecoded); +} + +SkCodec::Result SkGifCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* pixels, size_t dstRowBytes, + const SkCodec::Options& opts) { + Result result = this->prepareToDecode(dstInfo, opts); + if (result != kSuccess) { + return result; + } + + fDst = pixels; + fDstRowBytes = dstRowBytes; + + fFirstCallToIncrementalDecode = true; + + return kSuccess; +} + +SkCodec::Result SkGifCodec::onIncrementalDecode(int* rowsDecoded) { + // It is possible the client has appended more data. Parse, if needed. + const auto& options = this->options(); + const int frameIndex = options.fFrameIndex; + fReader->parse((SkGifImageReader::SkGIFParseQuery) frameIndex); + + const bool firstCallToIncrementalDecode = fFirstCallToIncrementalDecode; + fFirstCallToIncrementalDecode = false; + return this->decodeFrame(firstCallToIncrementalDecode, options, rowsDecoded); +} + +SkCodec::Result SkGifCodec::decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded) { + const SkImageInfo& dstInfo = this->dstInfo(); + const int scaledHeight = get_scaled_dimension(dstInfo.height(), fSwizzler->sampleY()); + + const int frameIndex = opts.fFrameIndex; + SkASSERT(frameIndex < fReader->imagesCount()); + const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex); + if (firstAttempt) { + // rowsDecoded reports how many rows have been initialized, so a layer above + // can fill the rest. In some cases, we fill the background before decoding + // (or it is already filled for us), so we report rowsDecoded to be the full + // height. + bool filledBackground = false; + if (frameContext->getRequiredFrame() == kNoFrame) { + // We may need to clear to transparent for one of the following reasons: + // - The frameRect does not cover the full bounds. haveDecodedRow will + // only draw inside the frameRect, so we need to clear the rest. + // - The frame is interlaced. There is no obvious way to fill + // afterwards for an incomplete image. (FIXME: Does the first pass + // cover all rows? If so, we do not have to fill here.) + // - There is no color table for this frame. In that case will not + // draw anything, so we need to fill. + if (frameContext->frameRect() != this->bounds() + || frameContext->interlaced() || !fCurrColorTableIsReal) { + auto fillInfo = dstInfo.makeWH(fSwizzler->fillWidth(), scaledHeight); + SkSampler::Fill(fillInfo, fDst, fDstRowBytes, opts.fZeroInitialized); + filledBackground = true; + } + } else { + // Not independent. + // SkCodec ensured that the prior frame has been decoded. + filledBackground = true; + } + + fFilledBackground = filledBackground; + if (filledBackground) { + // Report the full (scaled) height, since the client will never need to fill. + fRowsDecoded = scaledHeight; + } else { + // This will be updated by haveDecodedRow. + fRowsDecoded = 0; + } + } + + if (!fCurrColorTableIsReal) { + // Nothing to draw this frame. + return kSuccess; + } + + bool frameDecoded = false; + const bool fatalError = !fReader->decode(frameIndex, &frameDecoded); + if (fatalError || !frameDecoded || fRowsDecoded != scaledHeight) { + if (rowsDecoded) { + *rowsDecoded = fRowsDecoded; + } + if (fatalError) { + return kErrorInInput; + } + return kIncompleteInput; + } + + return kSuccess; +} + +void SkGifCodec::applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const { + if (this->xformOnDecode()) { + SkASSERT(this->colorXform()); + fSwizzler->swizzle(fXformBuffer.get(), src); + + const int xformWidth = get_scaled_dimension(dstInfo.width(), fSwizzler->sampleX()); + this->applyColorXform(dst, fXformBuffer.get(), xformWidth); + } else { + fSwizzler->swizzle(dst, src); + } +} + +template <typename T> +static void blend_line(void* dstAsVoid, const void* srcAsVoid, int width) { + T* dst = reinterpret_cast<T*>(dstAsVoid); + const T* src = reinterpret_cast<const T*>(srcAsVoid); + while (width --> 0) { + if (*src != 0) { // GIF pixels are either transparent (== 0) or opaque (!= 0). + *dst = *src; + } + src++; + dst++; + } +} + +void SkGifCodec::haveDecodedRow(int frameIndex, const unsigned char* rowBegin, + int rowNumber, int repeatCount, bool writeTransparentPixels) +{ + const SkGIFFrameContext* frameContext = fReader->frameContext(frameIndex); + // The pixel data and coordinates supplied to us are relative to the frame's + // origin within the entire image size, i.e. + // (frameContext->xOffset, frameContext->yOffset). There is no guarantee + // that width == (size().width() - frameContext->xOffset), so + // we must ensure we don't run off the end of either the source data or the + // row's X-coordinates. + const int width = frameContext->width(); + const int xBegin = frameContext->xOffset(); + const int yBegin = frameContext->yOffset() + rowNumber; + const int xEnd = std::min(xBegin + width, this->dimensions().width()); + const int yEnd = std::min(yBegin + rowNumber + repeatCount, this->dimensions().height()); + // FIXME: No need to make the checks on width/xBegin/xEnd for every row. We could instead do + // this once in prepareToDecode. + if (!width || (xBegin < 0) || (yBegin < 0) || (xEnd <= xBegin) || (yEnd <= yBegin)) + return; + + // yBegin is the first row in the non-sampled image. dstRow will be the row in the output, + // after potentially scaling it. + int dstRow = yBegin; + + const int sampleY = fSwizzler->sampleY(); + if (sampleY > 1) { + // Check to see whether this row or one that falls in the repeatCount is needed in the + // output. + bool foundNecessaryRow = false; + for (int i = 0; i < repeatCount; i++) { + const int potentialRow = yBegin + i; + if (fSwizzler->rowNeeded(potentialRow)) { + dstRow = potentialRow / sampleY; + const int scaledHeight = get_scaled_dimension(this->dstInfo().height(), sampleY); + if (dstRow >= scaledHeight) { + return; + } + + foundNecessaryRow = true; + repeatCount -= i; + + repeatCount = (repeatCount - 1) / sampleY + 1; + + // Make sure the repeatCount does not take us beyond the end of the dst + if (dstRow + repeatCount > scaledHeight) { + repeatCount = scaledHeight - dstRow; + SkASSERT(repeatCount >= 1); + } + break; + } + } + + if (!foundNecessaryRow) { + return; + } + } else { + // Make sure the repeatCount does not take us beyond the end of the dst + SkASSERT(this->dstInfo().height() >= yBegin); + repeatCount = SkTMin(repeatCount, this->dstInfo().height() - yBegin); + } + + if (!fFilledBackground) { + // At this point, we are definitely going to write the row, so count it towards the number + // of rows decoded. + // We do not consider the repeatCount, which only happens for interlaced, in which case we + // have already set fRowsDecoded to the proper value (reflecting that we have filled the + // background). + fRowsDecoded++; + } + + // decodeFrame will early exit if this is false, so this method will not be + // called. + SkASSERT(fCurrColorTableIsReal); + + // The swizzler takes care of offsetting into the dst width-wise. + void* dstLine = SkTAddOffset<void>(fDst, dstRow * fDstRowBytes); + + // We may or may not need to write transparent pixels to the buffer. + // If we're compositing against a previous image, it's wrong, but if + // we're decoding an interlaced gif and displaying it "Haeberli"-style, + // we must write these for passes beyond the first, or the initial passes + // will "show through" the later ones. + const auto dstInfo = this->dstInfo(); + if (writeTransparentPixels) { + this->applyXformRow(dstInfo, dstLine, rowBegin); + } else { + this->applyXformRow(dstInfo, fTmpBuffer.get(), rowBegin); + + size_t offsetBytes = fSwizzler->swizzleOffsetBytes(); + if (dstInfo.colorType() == kRGBA_F16_SkColorType) { + // Account for the fact that post-swizzling we converted to F16, + // which is twice as wide. + offsetBytes *= 2; + } + const void* src = SkTAddOffset<void>(fTmpBuffer.get(), offsetBytes); + void* dst = SkTAddOffset<void>(dstLine, offsetBytes); + + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + case kRGBA_8888_SkColorType: + blend_line<uint32_t>(dst, src, fSwizzler->swizzleWidth()); + break; + case kRGBA_F16_SkColorType: + blend_line<uint64_t>(dst, src, fSwizzler->swizzleWidth()); + break; + default: + SkASSERT(false); + return; + } + } + + // Tell the frame to copy the row data if need be. + if (repeatCount > 1) { + const size_t bytesPerPixel = this->dstInfo().bytesPerPixel(); + const size_t bytesToCopy = fSwizzler->swizzleWidth() * bytesPerPixel; + void* copiedLine = SkTAddOffset<void>(dstLine, fSwizzler->swizzleOffsetBytes()); + void* dst = copiedLine; + for (int i = 1; i < repeatCount; i++) { + dst = SkTAddOffset<void>(dst, fDstRowBytes); + memcpy(dst, copiedLine, bytesToCopy); + } + } +} diff --git a/gfx/skia/skia/src/codec/SkGifCodec.h b/gfx/skia/skia/src/codec/SkGifCodec.h new file mode 100644 index 0000000000..1a825fa166 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkGifCodec.h @@ -0,0 +1,156 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkGifCodec_DEFINED +#define SkGifCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +#include "third_party/gif/SkGifImageReader.h" + +/* + * + * This class implements the decoding for gif images + * + */ +class SkGifCodec : public SkCodec { +public: + static bool IsGif(const void*, size_t); + + /* + * Assumes IsGif was called and returned true + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + + // Callback for SkGifImageReader when a row is available. + void haveDecodedRow(int frameIndex, const unsigned char* rowBegin, + int rowNumber, int repeatCount, bool writeTransparentPixels); +protected: + /* + * Performs the full gif decode + */ + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, + int*) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kGIF; + } + + bool onRewind() override; + + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + + Result onStartIncrementalDecode(const SkImageInfo& /*dstInfo*/, void*, size_t, + const SkCodec::Options&) override; + + Result onIncrementalDecode(int*) override; + + const SkFrameHolder* getFrameHolder() const override { + return fReader.get(); + } + +private: + + /* + * Initializes the color table that we will use for decoding. + * + * @param dstInfo Contains the requested dst color type. + * @param frameIndex Frame whose color table to use. + */ + void initializeColorTable(const SkImageInfo& dstInfo, int frameIndex); + + /* + * Does necessary setup, including setting up the color table and swizzler. + */ + Result prepareToDecode(const SkImageInfo& dstInfo, const Options& opts); + + /* + * Initializes the swizzler. + * + * @param dstInfo Output image information. Dimensions may have been + * adjusted if the image frame size does not match the size + * indicated in the header. + * @param frameIndex Which frame we are decoding. This determines the frameRect + * to use. + */ + void initializeSwizzler(const SkImageInfo& dstInfo, int frameIndex); + + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler); + return fSwizzler.get(); + } + + /* + * Recursive function to decode a frame. + * + * @param firstAttempt Whether this is the first call to decodeFrame since + * starting. e.g. true in onGetPixels, and true in the + * first call to onIncrementalDecode after calling + * onStartIncrementalDecode. + * When true, this method may have to initialize the + * frame, for example by filling or decoding the prior + * frame. + * @param opts Options for decoding. May be different from + * this->options() for decoding prior frames. Specifies + * the frame to decode and whether the prior frame has + * already been decoded to fDst. If not, and the frame + * is not independent, this method will recursively + * decode the frame it depends on. + * @param rowsDecoded Out-parameter to report the total number of rows + * that have been decoded (or at least written to, if + * it had to fill), including rows decoded by prior + * calls to onIncrementalDecode. + * @return kSuccess if the frame is complete, kIncompleteInput + * otherwise. + */ + Result decodeFrame(bool firstAttempt, const Options& opts, int* rowsDecoded); + + /* + * Swizzles and color xforms (if necessary) into dst. + */ + void applyXformRow(const SkImageInfo& dstInfo, void* dst, const uint8_t* src) const; + + /* + * Creates an instance of the decoder + * Called only by NewFromStream + * Takes ownership of the SkGifImageReader + */ + SkGifCodec(SkEncodedInfo&&, SkGifImageReader*); + + std::unique_ptr<SkGifImageReader> fReader; + std::unique_ptr<uint8_t[]> fTmpBuffer; + std::unique_ptr<SkSwizzler> fSwizzler; + sk_sp<SkColorTable> fCurrColorTable; + // We may create a dummy table if there is not a Map in the input data. In + // that case, we set this value to false, and we can skip a lot of decoding + // work (which would not be meaningful anyway). We create a "fake"/"dummy" + // one in that case, so the client and the swizzler have something to draw. + bool fCurrColorTableIsReal; + // Whether the background was filled. + bool fFilledBackground; + // True on the first call to onIncrementalDecode. This value is passed to + // decodeFrame. + bool fFirstCallToIncrementalDecode; + + void* fDst; + size_t fDstRowBytes; + + // Updated inside haveDecodedRow when rows are decoded, unless we filled + // the background, in which case it is set once and left alone. + int fRowsDecoded; + std::unique_ptr<uint32_t[]> fXformBuffer; + + typedef SkCodec INHERITED; +}; +#endif // SkGifCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkHeifCodec.cpp b/gfx/skia/skia/src/codec/SkHeifCodec.cpp new file mode 100644 index 0000000000..de51f1b408 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkHeifCodec.cpp @@ -0,0 +1,472 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" + +#ifdef SK_HAS_HEIF_LIBRARY +#include "include/codec/SkCodec.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkHeifCodec.h" +#include "src/core/SkEndian.h" + +#define FOURCC(c1, c2, c3, c4) \ + ((c1) << 24 | (c2) << 16 | (c3) << 8 | (c4)) + +bool SkHeifCodec::IsHeif(const void* buffer, size_t bytesRead) { + // Parse the ftyp box up to bytesRead to determine if this is HEIF. + // Any valid ftyp box should have at least 8 bytes. + if (bytesRead < 8) { + return false; + } + + uint32_t* ptr = (uint32_t*)buffer; + uint64_t chunkSize = SkEndian_SwapBE32(ptr[0]); + uint32_t chunkType = SkEndian_SwapBE32(ptr[1]); + + if (chunkType != FOURCC('f', 't', 'y', 'p')) { + return false; + } + + int64_t offset = 8; + if (chunkSize == 1) { + // This indicates that the next 8 bytes represent the chunk size, + // and chunk data comes after that. + if (bytesRead < 16) { + return false; + } + auto* chunkSizePtr = SkTAddOffset<const uint64_t>(buffer, offset); + chunkSize = SkEndian_SwapBE64(*chunkSizePtr); + if (chunkSize < 16) { + // The smallest valid chunk is 16 bytes long in this case. + return false; + } + offset += 8; + } else if (chunkSize < 8) { + // The smallest valid chunk is 8 bytes long. + return false; + } + + if (chunkSize > bytesRead) { + chunkSize = bytesRead; + } + int64_t chunkDataSize = chunkSize - offset; + // It should at least have major brand (4-byte) and minor version (4-bytes). + // The rest of the chunk (if any) is a list of (4-byte) compatible brands. + if (chunkDataSize < 8) { + return false; + } + + uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4; + for (size_t i = 0; i < numCompatibleBrands + 2; ++i) { + if (i == 1) { + // Skip this index, it refers to the minorVersion, + // not a brand. + continue; + } + auto* brandPtr = SkTAddOffset<const uint32_t>(buffer, offset + 4 * i); + uint32_t brand = SkEndian_SwapBE32(*brandPtr); + if (brand == FOURCC('m', 'i', 'f', '1') || brand == FOURCC('h', 'e', 'i', 'c') + || brand == FOURCC('m', 's', 'f', '1') || brand == FOURCC('h', 'e', 'v', 'c')) { + return true; + } + } + return false; +} + +static SkEncodedOrigin get_orientation(const HeifFrameInfo& frameInfo) { + switch (frameInfo.mRotationAngle) { + case 0: return kTopLeft_SkEncodedOrigin; + case 90: return kRightTop_SkEncodedOrigin; + case 180: return kBottomRight_SkEncodedOrigin; + case 270: return kLeftBottom_SkEncodedOrigin; + } + return kDefault_SkEncodedOrigin; +} + +struct SkHeifStreamWrapper : public HeifStream { + SkHeifStreamWrapper(SkStream* stream) : fStream(stream) {} + + ~SkHeifStreamWrapper() override {} + + size_t read(void* buffer, size_t size) override { + return fStream->read(buffer, size); + } + + bool rewind() override { + return fStream->rewind(); + } + + bool seek(size_t position) override { + return fStream->seek(position); + } + + bool hasLength() const override { + return fStream->hasLength(); + } + + size_t getLength() const override { + return fStream->getLength(); + } + +private: + std::unique_ptr<SkStream> fStream; +}; + +static void releaseProc(const void* ptr, void* context) { + delete reinterpret_cast<std::vector<uint8_t>*>(context); +} + +std::unique_ptr<SkCodec> SkHeifCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + SkCodec::SelectionPolicy selectionPolicy, Result* result) { + std::unique_ptr<HeifDecoder> heifDecoder(createHeifDecoder()); + if (heifDecoder.get() == nullptr) { + *result = kInternalError; + return nullptr; + } + + HeifFrameInfo heifInfo; + if (!heifDecoder->init(new SkHeifStreamWrapper(stream.release()), &heifInfo)) { + *result = kInvalidInput; + return nullptr; + } + + size_t frameCount = 1; + if (selectionPolicy == SkCodec::SelectionPolicy::kPreferAnimation) { + HeifFrameInfo sequenceInfo; + if (heifDecoder->getSequenceInfo(&sequenceInfo, &frameCount) && + frameCount > 1) { + heifInfo = std::move(sequenceInfo); + } + } + + std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr; + if (heifInfo.mIccData.size() > 0) { + auto iccData = new std::vector<uint8_t>(std::move(heifInfo.mIccData)); + auto icc = SkData::MakeWithProc(iccData->data(), iccData->size(), releaseProc, iccData); + profile = SkEncodedInfo::ICCProfile::Make(std::move(icc)); + } + if (profile && profile->profile()->data_color_space != skcms_Signature_RGB) { + // This will result in sRGB. + profile = nullptr; + } + + SkEncodedInfo info = SkEncodedInfo::Make(heifInfo.mWidth, heifInfo.mHeight, + SkEncodedInfo::kYUV_Color, SkEncodedInfo::kOpaque_Alpha, 8, std::move(profile)); + SkEncodedOrigin orientation = get_orientation(heifInfo); + + *result = kSuccess; + return std::unique_ptr<SkCodec>(new SkHeifCodec( + std::move(info), heifDecoder.release(), orientation, frameCount > 1)); +} + +SkHeifCodec::SkHeifCodec( + SkEncodedInfo&& info, + HeifDecoder* heifDecoder, + SkEncodedOrigin origin, + bool useAnimation) + : INHERITED(std::move(info), skcms_PixelFormat_RGBA_8888, nullptr, origin) + , fHeifDecoder(heifDecoder) + , fSwizzleSrcRow(nullptr) + , fColorXformSrcRow(nullptr) + , fUseAnimation(useAnimation) +{} + +bool SkHeifCodec::conversionSupported(const SkImageInfo& dstInfo, bool srcIsOpaque, + bool needsColorXform) { + SkASSERT(srcIsOpaque); + + if (kUnknown_SkAlphaType == dstInfo.alphaType()) { + return false; + } + + if (kOpaque_SkAlphaType != dstInfo.alphaType()) { + SkCodecPrintf("Warning: an opaque image should be decoded as opaque " + "- it is being decoded as non-opaque, which will draw slower\n"); + } + + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); + + case kBGRA_8888_SkColorType: + return fHeifDecoder->setOutputColor(kHeifColorFormat_BGRA_8888); + + case kRGB_565_SkColorType: + if (needsColorXform) { + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); + } else { + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGB565); + } + + case kRGBA_F16_SkColorType: + SkASSERT(needsColorXform); + return fHeifDecoder->setOutputColor(kHeifColorFormat_RGBA_8888); + + default: + return false; + } +} + +int SkHeifCodec::readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, + const Options& opts) { + // When fSwizzleSrcRow is non-null, it means that we need to swizzle. In this case, + // we will always decode into fSwizzlerSrcRow before swizzling into the next buffer. + // We can never swizzle "in place" because the swizzler may perform sampling and/or + // subsetting. + // When fColorXformSrcRow is non-null, it means that we need to color xform and that + // we cannot color xform "in place" (many times we can, but not when the dst is F16). + // In this case, we will color xform from fColorXformSrcRow into the dst. + uint8_t* decodeDst = (uint8_t*) dst; + uint32_t* swizzleDst = (uint32_t*) dst; + size_t decodeDstRowBytes = rowBytes; + size_t swizzleDstRowBytes = rowBytes; + int dstWidth = opts.fSubset ? opts.fSubset->width() : dstInfo.width(); + if (fSwizzleSrcRow && fColorXformSrcRow) { + decodeDst = fSwizzleSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } else if (fColorXformSrcRow) { + decodeDst = (uint8_t*) fColorXformSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + } else if (fSwizzleSrcRow) { + decodeDst = fSwizzleSrcRow; + decodeDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } + + for (int y = 0; y < count; y++) { + if (!fHeifDecoder->getScanline(decodeDst)) { + return y; + } + + if (fSwizzler) { + fSwizzler->swizzle(swizzleDst, decodeDst); + } + + if (this->colorXform()) { + this->applyColorXform(dst, swizzleDst, dstWidth); + dst = SkTAddOffset<void>(dst, rowBytes); + } + + decodeDst = SkTAddOffset<uint8_t>(decodeDst, decodeDstRowBytes); + swizzleDst = SkTAddOffset<uint32_t>(swizzleDst, swizzleDstRowBytes); + } + + return count; +} + +int SkHeifCodec::onGetFrameCount() { + if (!fUseAnimation) { + return 1; + } + + if (fFrameHolder.size() == 0) { + size_t frameCount; + HeifFrameInfo frameInfo; + if (!fHeifDecoder->getSequenceInfo(&frameInfo, &frameCount) + || frameCount <= 1) { + fUseAnimation = false; + return 1; + } + fFrameHolder.reserve(frameCount); + for (size_t i = 0; i < frameCount; i++) { + Frame* frame = fFrameHolder.appendNewFrame(); + frame->setXYWH(0, 0, frameInfo.mWidth, frameInfo.mHeight); + frame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep); + // Currently we don't know the duration until the frame is actually + // decoded (onGetFrameInfo is also called before frame is decoded). + // For now, fill it base on the value reported for the sequence. + frame->setDuration(frameInfo.mDurationUs / 1000); + frame->setRequiredFrame(SkCodec::kNoFrame); + frame->setHasAlpha(false); + } + } + + return fFrameHolder.size(); +} + +const SkFrame* SkHeifCodec::FrameHolder::onGetFrame(int i) const { + return static_cast<const SkFrame*>(this->frame(i)); +} + +SkHeifCodec::Frame* SkHeifCodec::FrameHolder::appendNewFrame() { + const int i = this->size(); + fFrames.emplace_back(i); // TODO: need to handle frame duration here + return &fFrames[i]; +} + +const SkHeifCodec::Frame* SkHeifCodec::FrameHolder::frame(int i) const { + SkASSERT(i >= 0 && i < this->size()); + return &fFrames[i]; +} + +SkHeifCodec::Frame* SkHeifCodec::FrameHolder::editFrameAt(int i) { + SkASSERT(i >= 0 && i < this->size()); + return &fFrames[i]; +} + +bool SkHeifCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { + if (i >= fFrameHolder.size()) { + return false; + } + + const Frame* frame = fFrameHolder.frame(i); + if (!frame) { + return false; + } + + if (frameInfo) { + frameInfo->fRequiredFrame = SkCodec::kNoFrame; + frameInfo->fDuration = frame->getDuration(); + frameInfo->fFullyReceived = true; + frameInfo->fAlphaType = kOpaque_SkAlphaType; + frameInfo->fDisposalMethod = SkCodecAnimation::DisposalMethod::kKeep; + } + + return true; +} + +int SkHeifCodec::onGetRepetitionCount() { + return kRepetitionCountInfinite; +} + +/* + * Performs the heif decode + */ +SkCodec::Result SkHeifCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& options, + int* rowsDecoded) { + if (options.fSubset) { + // Not supporting subsets on this path for now. + // TODO: if the heif has tiles, we can support subset here, but + // need to retrieve tile config from metadata retriever first. + return kUnimplemented; + } + + bool success; + if (fUseAnimation) { + success = fHeifDecoder->decodeSequence(options.fFrameIndex, &fFrameInfo); + fFrameHolder.editFrameAt(options.fFrameIndex)->setDuration( + fFrameInfo.mDurationUs / 1000); + } else { + success = fHeifDecoder->decode(&fFrameInfo); + } + + if (!success) { + return kInvalidInput; + } + + fSwizzler.reset(nullptr); + this->allocateStorage(dstInfo); + + int rows = this->readRows(dstInfo, dst, dstRowBytes, dstInfo.height(), options); + if (rows < dstInfo.height()) { + *rowsDecoded = rows; + return kIncompleteInput; + } + + return kSuccess; +} + +void SkHeifCodec::allocateStorage(const SkImageInfo& dstInfo) { + int dstWidth = dstInfo.width(); + + size_t swizzleBytes = 0; + if (fSwizzler) { + swizzleBytes = fFrameInfo.mBytesPerPixel * fFrameInfo.mWidth; + dstWidth = fSwizzler->swizzleWidth(); + SkASSERT(!this->colorXform() || SkIsAlign4(swizzleBytes)); + } + + size_t xformBytes = 0; + if (this->colorXform() && (kRGBA_F16_SkColorType == dstInfo.colorType() || + kRGB_565_SkColorType == dstInfo.colorType())) { + xformBytes = dstWidth * sizeof(uint32_t); + } + + size_t totalBytes = swizzleBytes + xformBytes; + fStorage.reset(totalBytes); + if (totalBytes > 0) { + fSwizzleSrcRow = (swizzleBytes > 0) ? fStorage.get() : nullptr; + fColorXformSrcRow = (xformBytes > 0) ? + SkTAddOffset<uint32_t>(fStorage.get(), swizzleBytes) : nullptr; + } +} + +void SkHeifCodec::initializeSwizzler( + const SkImageInfo& dstInfo, const Options& options) { + SkImageInfo swizzlerDstInfo = dstInfo; + if (this->colorXform()) { + // The color xform will be expecting RGBA 8888 input. + swizzlerDstInfo = swizzlerDstInfo.makeColorType(kRGBA_8888_SkColorType); + } + + int srcBPP = 4; + if (dstInfo.colorType() == kRGB_565_SkColorType && !this->colorXform()) { + srcBPP = 2; + } + + fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerDstInfo, options); + SkASSERT(fSwizzler); +} + +SkSampler* SkHeifCodec::getSampler(bool createIfNecessary) { + if (!createIfNecessary || fSwizzler) { + SkASSERT(!fSwizzler || (fSwizzleSrcRow && fStorage.get() == fSwizzleSrcRow)); + return fSwizzler.get(); + } + + this->initializeSwizzler(this->dstInfo(), this->options()); + this->allocateStorage(this->dstInfo()); + return fSwizzler.get(); +} + +bool SkHeifCodec::onRewind() { + fSwizzler.reset(nullptr); + fSwizzleSrcRow = nullptr; + fColorXformSrcRow = nullptr; + fStorage.reset(); + + return true; +} + +SkCodec::Result SkHeifCodec::onStartScanlineDecode( + const SkImageInfo& dstInfo, const Options& options) { + // TODO: For now, just decode the whole thing even when there is a subset. + // If the heif image has tiles, we could potentially do this much faster, + // but the tile configuration needs to be retrieved from the metadata. + if (!fHeifDecoder->decode(&fFrameInfo)) { + return kInvalidInput; + } + + if (options.fSubset) { + this->initializeSwizzler(dstInfo, options); + } else { + fSwizzler.reset(nullptr); + } + + this->allocateStorage(dstInfo); + + return kSuccess; +} + +int SkHeifCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { + return this->readRows(this->dstInfo(), dst, dstRowBytes, count, this->options()); +} + +bool SkHeifCodec::onSkipScanlines(int count) { + return count == (int) fHeifDecoder->skipScanlines(count); +} + +#endif // SK_HAS_HEIF_LIBRARY diff --git a/gfx/skia/skia/src/codec/SkHeifCodec.h b/gfx/skia/skia/src/codec/SkHeifCodec.h new file mode 100644 index 0000000000..bbcf722295 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkHeifCodec.h @@ -0,0 +1,128 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkHeifCodec_DEFINED +#define SkHeifCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/codec/SkEncodedOrigin.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "src/codec/SkFrameHolder.h" +#include "src/codec/SkSwizzler.h" + +#if __has_include("HeifDecoderAPI.h") + #include "HeifDecoderAPI.h" +#else + #include "src/codec/SkStubHeifDecoderAPI.h" +#endif + +class SkHeifCodec : public SkCodec { +public: + static bool IsHeif(const void*, size_t); + + /* + * Assumes IsHeif was called and returned true. + */ + static std::unique_ptr<SkCodec> MakeFromStream( + std::unique_ptr<SkStream>, SkCodec::SelectionPolicy selectionPolicy, Result*); + +protected: + + Result onGetPixels( + const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& options, + int* rowsDecoded) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kHEIF; + } + + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + const SkFrameHolder* getFrameHolder() const override { + return &fFrameHolder; + } + + bool conversionSupported(const SkImageInfo&, bool, bool) override; + + bool onRewind() override; + +private: + /* + * Creates an instance of the decoder + * Called only by NewFromStream + */ + SkHeifCodec(SkEncodedInfo&&, HeifDecoder*, SkEncodedOrigin, bool animation); + + void initializeSwizzler(const SkImageInfo& dstInfo, const Options& options); + void allocateStorage(const SkImageInfo& dstInfo); + int readRows(const SkImageInfo& dstInfo, void* dst, + size_t rowBytes, int count, const Options&); + + /* + * Scanline decoding. + */ + SkSampler* getSampler(bool createIfNecessary) override; + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + bool onSkipScanlines(int count) override; + + std::unique_ptr<HeifDecoder> fHeifDecoder; + HeifFrameInfo fFrameInfo; + SkAutoTMalloc<uint8_t> fStorage; + uint8_t* fSwizzleSrcRow; + uint32_t* fColorXformSrcRow; + + std::unique_ptr<SkSwizzler> fSwizzler; + bool fUseAnimation; + + class Frame : public SkFrame { + public: + Frame(int i) : INHERITED(i) {} + + protected: + SkEncodedInfo::Alpha onReportedAlpha() const override { + return SkEncodedInfo::Alpha::kOpaque_Alpha; + } + + private: + typedef SkFrame INHERITED; + }; + + class FrameHolder : public SkFrameHolder { + public: + ~FrameHolder() override {} + void setScreenSize(int w, int h) { + fScreenWidth = w; + fScreenHeight = h; + } + Frame* appendNewFrame(); + const Frame* frame(int i) const; + Frame* editFrameAt(int i); + int size() const { + return static_cast<int>(fFrames.size()); + } + void reserve(int size) { + fFrames.reserve(size); + } + + protected: + const SkFrame* onGetFrame(int i) const override; + + private: + std::vector<Frame> fFrames; + }; + + FrameHolder fFrameHolder; + typedef SkCodec INHERITED; +}; + +#endif // SkHeifCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkIcoCodec.cpp b/gfx/skia/skia/src/codec/SkIcoCodec.cpp new file mode 100644 index 0000000000..e70a9cded4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkIcoCodec.cpp @@ -0,0 +1,384 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTDArray.h" +#include "src/codec/SkBmpCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkIcoCodec.h" +#include "src/codec/SkPngCodec.h" +#include "src/core/SkTSort.h" + +/* + * Checks the start of the stream to see if the image is an Ico or Cur + */ +bool SkIcoCodec::IsIco(const void* buffer, size_t bytesRead) { + const char icoSig[] = { '\x00', '\x00', '\x01', '\x00' }; + const char curSig[] = { '\x00', '\x00', '\x02', '\x00' }; + return bytesRead >= sizeof(icoSig) && + (!memcmp(buffer, icoSig, sizeof(icoSig)) || + !memcmp(buffer, curSig, sizeof(curSig))); +} + +std::unique_ptr<SkCodec> SkIcoCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + // Header size constants + constexpr uint32_t kIcoDirectoryBytes = 6; + constexpr uint32_t kIcoDirEntryBytes = 16; + + // Read the directory header + std::unique_ptr<uint8_t[]> dirBuffer(new uint8_t[kIcoDirectoryBytes]); + if (stream->read(dirBuffer.get(), kIcoDirectoryBytes) != kIcoDirectoryBytes) { + SkCodecPrintf("Error: unable to read ico directory header.\n"); + *result = kIncompleteInput; + return nullptr; + } + + // Process the directory header + const uint16_t numImages = get_short(dirBuffer.get(), 4); + if (0 == numImages) { + SkCodecPrintf("Error: No images embedded in ico.\n"); + *result = kInvalidInput; + return nullptr; + } + + // This structure is used to represent the vital information about entries + // in the directory header. We will obtain this information for each + // directory entry. + struct Entry { + uint32_t offset; + uint32_t size; + }; + SkAutoFree dirEntryBuffer(sk_malloc_canfail(sizeof(Entry) * numImages)); + if (!dirEntryBuffer) { + SkCodecPrintf("Error: OOM allocating ICO directory for %i images.\n", + numImages); + *result = kInternalError; + return nullptr; + } + auto* directoryEntries = reinterpret_cast<Entry*>(dirEntryBuffer.get()); + + // Iterate over directory entries + for (uint32_t i = 0; i < numImages; i++) { + uint8_t entryBuffer[kIcoDirEntryBytes]; + if (stream->read(entryBuffer, kIcoDirEntryBytes) != kIcoDirEntryBytes) { + SkCodecPrintf("Error: Dir entries truncated in ico.\n"); + *result = kIncompleteInput; + return nullptr; + } + + // The directory entry contains information such as width, height, + // bits per pixel, and number of colors in the color palette. We will + // ignore these fields since they are repeated in the header of the + // embedded image. In the event of an inconsistency, we would always + // defer to the value in the embedded header anyway. + + // Specifies the size of the embedded image, including the header + uint32_t size = get_int(entryBuffer, 8); + + // Specifies the offset of the embedded image from the start of file. + // It does not indicate the start of the pixel data, but rather the + // start of the embedded image header. + uint32_t offset = get_int(entryBuffer, 12); + + // Save the vital fields + directoryEntries[i].offset = offset; + directoryEntries[i].size = size; + } + + // Default Result, if no valid embedded codecs are found. + *result = kInvalidInput; + + // It is "customary" that the embedded images will be stored in order of + // increasing offset. However, the specification does not indicate that + // they must be stored in this order, so we will not trust that this is the + // case. Here we sort the embedded images by increasing offset. + struct EntryLessThan { + bool operator() (Entry a, Entry b) const { + return a.offset < b.offset; + } + }; + EntryLessThan lessThan; + SkTQSort(directoryEntries, &directoryEntries[numImages - 1], lessThan); + + // Now will construct a candidate codec for each of the embedded images + uint32_t bytesRead = kIcoDirectoryBytes + numImages * kIcoDirEntryBytes; + std::unique_ptr<SkTArray<std::unique_ptr<SkCodec>, true>> codecs( + new SkTArray<std::unique_ptr<SkCodec>, true>(numImages)); + for (uint32_t i = 0; i < numImages; i++) { + uint32_t offset = directoryEntries[i].offset; + uint32_t size = directoryEntries[i].size; + + // Ensure that the offset is valid + if (offset < bytesRead) { + SkCodecPrintf("Warning: invalid ico offset.\n"); + continue; + } + + // If we cannot skip, assume we have reached the end of the stream and + // stop trying to make codecs + if (stream->skip(offset - bytesRead) != offset - bytesRead) { + SkCodecPrintf("Warning: could not skip to ico offset.\n"); + break; + } + bytesRead = offset; + + // Create a new stream for the embedded codec + SkAutoFree buffer(sk_malloc_canfail(size)); + if (!buffer) { + SkCodecPrintf("Warning: OOM trying to create embedded stream.\n"); + break; + } + + if (stream->read(buffer.get(), size) != size) { + SkCodecPrintf("Warning: could not create embedded stream.\n"); + *result = kIncompleteInput; + break; + } + + sk_sp<SkData> data(SkData::MakeFromMalloc(buffer.release(), size)); + auto embeddedStream = SkMemoryStream::Make(data); + bytesRead += size; + + // Check if the embedded codec is bmp or png and create the codec + std::unique_ptr<SkCodec> codec; + Result dummyResult; + if (SkPngCodec::IsPng((const char*) data->bytes(), data->size())) { + codec = SkPngCodec::MakeFromStream(std::move(embeddedStream), &dummyResult); + } else { + codec = SkBmpCodec::MakeFromIco(std::move(embeddedStream), &dummyResult); + } + + // Save a valid codec + if (nullptr != codec) { + codecs->push_back().reset(codec.release()); + } + } + + // Recognize if there are no valid codecs + if (0 == codecs->count()) { + SkCodecPrintf("Error: could not find any valid embedded ico codecs.\n"); + return nullptr; + } + + // Use the largest codec as a "suggestion" for image info + size_t maxSize = 0; + int maxIndex = 0; + for (int i = 0; i < codecs->count(); i++) { + SkImageInfo info = codecs->operator[](i)->getInfo(); + size_t size = info.computeMinByteSize(); + + if (size > maxSize) { + maxSize = size; + maxIndex = i; + } + } + + auto maxInfo = codecs->operator[](maxIndex)->getEncodedInfo().copy(); + + *result = kSuccess; + // The original stream is no longer needed, because the embedded codecs own their + // own streams. + return std::unique_ptr<SkCodec>(new SkIcoCodec(std::move(maxInfo), codecs.release())); +} + +SkIcoCodec::SkIcoCodec(SkEncodedInfo&& info, SkTArray<std::unique_ptr<SkCodec>, true>* codecs) + // The source skcms_PixelFormat will not be used. The embedded + // codec's will be used instead. + : INHERITED(std::move(info), skcms_PixelFormat(), nullptr) + , fEmbeddedCodecs(codecs) + , fCurrCodec(nullptr) +{} + +/* + * Chooses the best dimensions given the desired scale + */ +SkISize SkIcoCodec::onGetScaledDimensions(float desiredScale) const { + // We set the dimensions to the largest candidate image by default. + // Regardless of the scale request, this is the largest image that we + // will decode. + int origWidth = this->dimensions().width(); + int origHeight = this->dimensions().height(); + float desiredSize = desiredScale * origWidth * origHeight; + // At least one image will have smaller error than this initial value + float minError = ((float) (origWidth * origHeight)) - desiredSize + 1.0f; + int32_t minIndex = -1; + for (int32_t i = 0; i < fEmbeddedCodecs->count(); i++) { + auto dimensions = fEmbeddedCodecs->operator[](i)->dimensions(); + int width = dimensions.width(); + int height = dimensions.height(); + float error = SkTAbs(((float) (width * height)) - desiredSize); + if (error < minError) { + minError = error; + minIndex = i; + } + } + SkASSERT(minIndex >= 0); + + return fEmbeddedCodecs->operator[](minIndex)->dimensions(); +} + +int SkIcoCodec::chooseCodec(const SkISize& requestedSize, int startIndex) { + SkASSERT(startIndex >= 0); + + // FIXME: Cache the index from onGetScaledDimensions? + for (int i = startIndex; i < fEmbeddedCodecs->count(); i++) { + if (fEmbeddedCodecs->operator[](i)->dimensions() == requestedSize) { + return i; + } + } + + return -1; +} + +bool SkIcoCodec::onDimensionsSupported(const SkISize& dim) { + return this->chooseCodec(dim, 0) >= 0; +} + +/* + * 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; +} + +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; +} + +int SkIcoCodec::onGetScanlines(void* dst, int count, size_t rowBytes) { + SkASSERT(fCurrCodec); + return fCurrCodec->getScanlines(dst, count, rowBytes); +} + +bool SkIcoCodec::onSkipScanlines(int count) { + SkASSERT(fCurrCodec); + return fCurrCodec->skipScanlines(count); +} + +SkCodec::Result SkIcoCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* pixels, size_t rowBytes, const SkCodec::Options& options) { + int index = 0; + while (true) { + index = this->chooseCodec(dstInfo.dimensions(), index); + if (index < 0) { + break; + } + + SkCodec* embeddedCodec = fEmbeddedCodecs->operator[](index).get(); + switch (embeddedCodec->startIncrementalDecode(dstInfo, + pixels, rowBytes, &options)) { + case kSuccess: + fCurrCodec = embeddedCodec; + 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) == 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; +} + +SkCodec::Result SkIcoCodec::onIncrementalDecode(int* rowsDecoded) { + SkASSERT(fCurrCodec); + return fCurrCodec->incrementalDecode(rowsDecoded); +} + +SkCodec::SkScanlineOrder SkIcoCodec::onGetScanlineOrder() const { + // FIXME: This function will possibly return the wrong value if it is called + // before startScanlineDecode()/startIncrementalDecode(). + if (fCurrCodec) { + return fCurrCodec->getScanlineOrder(); + } + + return INHERITED::onGetScanlineOrder(); +} + +SkSampler* SkIcoCodec::getSampler(bool createIfNecessary) { + if (fCurrCodec) { + return fCurrCodec->getSampler(createIfNecessary); + } + + return nullptr; +} diff --git a/gfx/skia/skia/src/codec/SkIcoCodec.h b/gfx/skia/skia/src/codec/SkIcoCodec.h new file mode 100644 index 0000000000..c1b27dc50d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkIcoCodec.h @@ -0,0 +1,99 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIcoCodec_DEFINED +#define SkIcoCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTArray.h" + +/* + * This class implements the decoding for bmp images + */ +class SkIcoCodec : public SkCodec { +public: + static bool IsIco(const void*, size_t); + + /* + * Assumes IsIco was called and returned true + * Creates an Ico decoder + * Reads enough of the stream to determine the image format + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + +protected: + + /* + * Chooses the best dimensions given the desired scale + */ + SkISize onGetScaledDimensions(float desiredScale) const override; + + bool onDimensionsSupported(const SkISize&) override; + + /* + * Initiates the Ico decode + */ + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + int*) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kICO; + } + + SkScanlineOrder onGetScanlineOrder() const override; + + bool conversionSupported(const SkImageInfo&, bool, bool) override { + // This will be checked by the embedded codec. + return true; + } + + // Handled by the embedded codec. + bool usesColorXform() const override { return false; } +private: + + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const SkCodec::Options& options) override; + + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + + bool onSkipScanlines(int count) override; + + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const SkCodec::Options&) override; + + Result onIncrementalDecode(int* rowsDecoded) override; + + SkSampler* getSampler(bool createIfNecessary) override; + + /* + * Searches fEmbeddedCodecs for a codec that matches requestedSize. + * The search starts at startIndex and ends when an appropriate codec + * is found, or we have reached the end of the array. + * + * @return the index of the matching codec or -1 if there is no + * matching codec between startIndex and the end of + * the array. + */ + int chooseCodec(const SkISize& requestedSize, int startIndex); + + /* + * Constructor called by NewFromStream + * @param embeddedCodecs codecs for the embedded images, takes ownership + */ + SkIcoCodec(SkEncodedInfo&& info, SkTArray<std::unique_ptr<SkCodec>, true>* embeddedCodecs); + + std::unique_ptr<SkTArray<std::unique_ptr<SkCodec>, true>> fEmbeddedCodecs; + + // fCurrCodec is owned by this class, but should not be an + // std::unique_ptr. It will be deleted by the destructor of fEmbeddedCodecs. + SkCodec* fCurrCodec; + + typedef SkCodec INHERITED; +}; +#endif // SkIcoCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkJpegCodec.cpp b/gfx/skia/skia/src/codec/SkJpegCodec.cpp new file mode 100644 index 0000000000..45bd35dd5d --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegCodec.cpp @@ -0,0 +1,974 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkJpegCodec.h" + +#include "include/codec/SkCodec.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkJpegDecoderMgr.h" +#include "src/codec/SkParseEncodedOrigin.h" +#include "src/pdf/SkJpegInfo.h" + +// stdio is needed for libjpeg-turbo +#include <stdio.h> +#include "src/codec/SkJpegUtility.h" + +// This warning triggers false postives way too often in here. +#if defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic ignored "-Wclobbered" +#endif + +extern "C" { + #include "jerror.h" + #include "jpeglib.h" +} + +bool SkJpegCodec::IsJpeg(const void* buffer, size_t bytesRead) { + constexpr uint8_t jpegSig[] = { 0xFF, 0xD8, 0xFF }; + return bytesRead >= 3 && !memcmp(buffer, jpegSig, sizeof(jpegSig)); +} + +const uint32_t kExifHeaderSize = 14; +const uint32_t kExifMarker = JPEG_APP0 + 1; + +static bool is_orientation_marker(jpeg_marker_struct* marker, SkEncodedOrigin* orientation) { + if (kExifMarker != marker->marker || marker->data_length < kExifHeaderSize) { + return false; + } + + constexpr uint8_t kExifSig[] { 'E', 'x', 'i', 'f', '\0' }; + if (memcmp(marker->data, kExifSig, sizeof(kExifSig))) { + return false; + } + + // Account for 'E', 'x', 'i', 'f', '\0', '<fill byte>'. + constexpr size_t kOffset = 6; + return SkParseEncodedOrigin(marker->data + kOffset, marker->data_length - kOffset, + orientation); +} + +static SkEncodedOrigin get_exif_orientation(jpeg_decompress_struct* dinfo) { + SkEncodedOrigin orientation; + for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) { + if (is_orientation_marker(marker, &orientation)) { + return orientation; + } + } + + return kDefault_SkEncodedOrigin; +} + +static bool is_icc_marker(jpeg_marker_struct* marker) { + if (kICCMarker != marker->marker || marker->data_length < kICCMarkerHeaderSize) { + return false; + } + + return !memcmp(marker->data, kICCSig, sizeof(kICCSig)); +} + +/* + * ICC profiles may be stored using a sequence of multiple markers. We obtain the ICC profile + * in two steps: + * (1) Discover all ICC profile markers and verify that they are numbered properly. + * (2) Copy the data from each marker into a contiguous ICC profile. + */ +static std::unique_ptr<SkEncodedInfo::ICCProfile> read_color_profile(jpeg_decompress_struct* dinfo) +{ + // Note that 256 will be enough storage space since each markerIndex is stored in 8-bits. + jpeg_marker_struct* markerSequence[256]; + memset(markerSequence, 0, sizeof(markerSequence)); + uint8_t numMarkers = 0; + size_t totalBytes = 0; + + // Discover any ICC markers and verify that they are numbered properly. + for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) { + if (is_icc_marker(marker)) { + // Verify that numMarkers is valid and consistent. + if (0 == numMarkers) { + numMarkers = marker->data[13]; + if (0 == numMarkers) { + SkCodecPrintf("ICC Profile Error: numMarkers must be greater than zero.\n"); + return nullptr; + } + } else if (numMarkers != marker->data[13]) { + SkCodecPrintf("ICC Profile Error: numMarkers must be consistent.\n"); + return nullptr; + } + + // Verify that the markerIndex is valid and unique. Note that zero is not + // a valid index. + uint8_t markerIndex = marker->data[12]; + if (markerIndex == 0 || markerIndex > numMarkers) { + SkCodecPrintf("ICC Profile Error: markerIndex is invalid.\n"); + return nullptr; + } + if (markerSequence[markerIndex]) { + SkCodecPrintf("ICC Profile Error: Duplicate value of markerIndex.\n"); + return nullptr; + } + markerSequence[markerIndex] = marker; + SkASSERT(marker->data_length >= kICCMarkerHeaderSize); + totalBytes += marker->data_length - kICCMarkerHeaderSize; + } + } + + if (0 == totalBytes) { + // No non-empty ICC profile markers were found. + return nullptr; + } + + // Combine the ICC marker data into a contiguous profile. + sk_sp<SkData> iccData = SkData::MakeUninitialized(totalBytes); + void* dst = iccData->writable_data(); + for (uint32_t i = 1; i <= numMarkers; i++) { + jpeg_marker_struct* marker = markerSequence[i]; + if (!marker) { + SkCodecPrintf("ICC Profile Error: Missing marker %d of %d.\n", i, numMarkers); + return nullptr; + } + + void* src = SkTAddOffset<void>(marker->data, kICCMarkerHeaderSize); + size_t bytes = marker->data_length - kICCMarkerHeaderSize; + memcpy(dst, src, bytes); + dst = SkTAddOffset<void>(dst, bytes); + } + + return SkEncodedInfo::ICCProfile::Make(std::move(iccData)); +} + +SkCodec::Result SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, + JpegDecoderMgr** decoderMgrOut, + std::unique_ptr<SkEncodedInfo::ICCProfile> defaultColorProfile) { + + // Create a JpegDecoderMgr to own all of the decompress information + std::unique_ptr<JpegDecoderMgr> decoderMgr(new JpegDecoderMgr(stream)); + + // libjpeg errors will be caught and reported here + skjpeg_error_mgr::AutoPushJmpBuf jmp(decoderMgr->errorMgr()); + if (setjmp(jmp)) { + return decoderMgr->returnFailure("ReadHeader", kInvalidInput); + } + + // Initialize the decompress info and the source manager + decoderMgr->init(); + auto* dinfo = decoderMgr->dinfo(); + + // Instruct jpeg library to save the markers that we care about. Since + // the orientation and color profile will not change, we can skip this + // step on rewinds. + if (codecOut) { + jpeg_save_markers(dinfo, kExifMarker, 0xFFFF); + jpeg_save_markers(dinfo, kICCMarker, 0xFFFF); + } + + // Read the jpeg header + switch (jpeg_read_header(dinfo, true)) { + case JPEG_HEADER_OK: + break; + case JPEG_SUSPENDED: + return decoderMgr->returnFailure("ReadHeader", kIncompleteInput); + default: + return decoderMgr->returnFailure("ReadHeader", kInvalidInput); + } + + if (codecOut) { + // Get the encoded color type + SkEncodedInfo::Color color; + if (!decoderMgr->getEncodedColor(&color)) { + return kInvalidInput; + } + + SkEncodedOrigin orientation = get_exif_orientation(dinfo); + auto profile = read_color_profile(dinfo); + if (profile) { + auto type = profile->profile()->data_color_space; + switch (decoderMgr->dinfo()->jpeg_color_space) { + case JCS_CMYK: + case JCS_YCCK: + if (type != skcms_Signature_CMYK) { + profile = nullptr; + } + break; + case JCS_GRAYSCALE: + if (type != skcms_Signature_Gray && + type != skcms_Signature_RGB) + { + profile = nullptr; + } + break; + default: + if (type != skcms_Signature_RGB) { + profile = nullptr; + } + break; + } + } + if (!profile) { + profile = std::move(defaultColorProfile); + } + + SkEncodedInfo info = SkEncodedInfo::Make(dinfo->image_width, dinfo->image_height, + color, SkEncodedInfo::kOpaque_Alpha, 8, + std::move(profile)); + + SkJpegCodec* codec = new SkJpegCodec(std::move(info), std::unique_ptr<SkStream>(stream), + decoderMgr.release(), orientation); + *codecOut = codec; + } else { + SkASSERT(nullptr != decoderMgrOut); + *decoderMgrOut = decoderMgr.release(); + } + return kSuccess; +} + +std::unique_ptr<SkCodec> SkJpegCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + return SkJpegCodec::MakeFromStream(std::move(stream), result, nullptr); +} + +std::unique_ptr<SkCodec> SkJpegCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result, std::unique_ptr<SkEncodedInfo::ICCProfile> defaultColorProfile) { + SkCodec* codec = nullptr; + *result = ReadHeader(stream.get(), &codec, nullptr, std::move(defaultColorProfile)); + if (kSuccess == *result) { + // Codec has taken ownership of the stream, we do not need to delete it + SkASSERT(codec); + stream.release(); + return std::unique_ptr<SkCodec>(codec); + } + return nullptr; +} + +SkJpegCodec::SkJpegCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + JpegDecoderMgr* decoderMgr, SkEncodedOrigin origin) + : INHERITED(std::move(info), skcms_PixelFormat_RGBA_8888, std::move(stream), origin) + , fDecoderMgr(decoderMgr) + , fReadyState(decoderMgr->dinfo()->global_state) + , fSwizzleSrcRow(nullptr) + , fColorXformSrcRow(nullptr) + , fSwizzlerSubset(SkIRect::MakeEmpty()) +{} + +/* + * Return the row bytes of a particular image type and width + */ +static size_t get_row_bytes(const j_decompress_ptr dinfo) { + const size_t colorBytes = (dinfo->out_color_space == JCS_RGB565) ? 2 : + dinfo->out_color_components; + return dinfo->output_width * colorBytes; + +} + +/* + * Calculate output dimensions based on the provided factors. + * + * Not to be used on the actual jpeg_decompress_struct used for decoding, since it will + * incorrectly modify num_components. + */ +void calc_output_dimensions(jpeg_decompress_struct* dinfo, unsigned int num, unsigned int denom) { + dinfo->num_components = 0; + dinfo->scale_num = num; + dinfo->scale_denom = denom; + jpeg_calc_output_dimensions(dinfo); +} + +/* + * Return a valid set of output dimensions for this decoder, given an input scale + */ +SkISize SkJpegCodec::onGetScaledDimensions(float desiredScale) const { + // libjpeg-turbo supports scaling by 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, and 1/1, so we will + // support these as well + unsigned int num; + unsigned int denom = 8; + if (desiredScale >= 0.9375) { + num = 8; + } else if (desiredScale >= 0.8125) { + num = 7; + } else if (desiredScale >= 0.6875f) { + num = 6; + } else if (desiredScale >= 0.5625f) { + num = 5; + } else if (desiredScale >= 0.4375f) { + num = 4; + } else if (desiredScale >= 0.3125f) { + num = 3; + } else if (desiredScale >= 0.1875f) { + num = 2; + } else { + num = 1; + } + + // Set up a fake decompress struct in order to use libjpeg to calculate output dimensions + jpeg_decompress_struct dinfo; + sk_bzero(&dinfo, sizeof(dinfo)); + dinfo.image_width = this->dimensions().width(); + dinfo.image_height = this->dimensions().height(); + dinfo.global_state = fReadyState; + calc_output_dimensions(&dinfo, num, denom); + + // Return the calculated output dimensions for the given scale + return SkISize::Make(dinfo.output_width, dinfo.output_height); +} + +bool SkJpegCodec::onRewind() { + JpegDecoderMgr* decoderMgr = nullptr; + if (kSuccess != ReadHeader(this->stream(), nullptr, &decoderMgr, nullptr)) { + return fDecoderMgr->returnFalse("onRewind"); + } + SkASSERT(nullptr != decoderMgr); + fDecoderMgr.reset(decoderMgr); + + fSwizzler.reset(nullptr); + fSwizzleSrcRow = nullptr; + fColorXformSrcRow = nullptr; + fStorage.reset(); + + return true; +} + +bool SkJpegCodec::conversionSupported(const SkImageInfo& dstInfo, bool srcIsOpaque, + bool needsColorXform) { + SkASSERT(srcIsOpaque); + + if (kUnknown_SkAlphaType == dstInfo.alphaType()) { + return false; + } + + if (kOpaque_SkAlphaType != dstInfo.alphaType()) { + SkCodecPrintf("Warning: an opaque image should be decoded as opaque " + "- it is being decoded as non-opaque, which will draw slower\n"); + } + + J_COLOR_SPACE encodedColorType = fDecoderMgr->dinfo()->jpeg_color_space; + + // Check for valid color types and set the output color space + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + break; + case kBGRA_8888_SkColorType: + if (needsColorXform) { + // Always using RGBA as the input format for color xforms makes the + // implementation a little simpler. + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + } else { + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_BGRA; + } + break; + case kRGB_565_SkColorType: + if (needsColorXform) { + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + } else { + fDecoderMgr->dinfo()->dither_mode = JDITHER_NONE; + fDecoderMgr->dinfo()->out_color_space = JCS_RGB565; + } + break; + case kGray_8_SkColorType: + if (JCS_GRAYSCALE != encodedColorType) { + return false; + } + + if (needsColorXform) { + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + } else { + fDecoderMgr->dinfo()->out_color_space = JCS_GRAYSCALE; + } + break; + case kRGBA_F16_SkColorType: + SkASSERT(needsColorXform); + fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; + break; + default: + return false; + } + + // Check if we will decode to CMYK. libjpeg-turbo does not convert CMYK to RGBA, so + // we must do it ourselves. + if (JCS_CMYK == encodedColorType || JCS_YCCK == encodedColorType) { + fDecoderMgr->dinfo()->out_color_space = JCS_CMYK; + } + + return true; +} + +/* + * Checks if we can natively scale to the requested dimensions and natively scales the + * dimensions if possible + */ +bool SkJpegCodec::onDimensionsSupported(const SkISize& size) { + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFalse("onDimensionsSupported"); + } + + const unsigned int dstWidth = size.width(); + const unsigned int dstHeight = size.height(); + + // Set up a fake decompress struct in order to use libjpeg to calculate output dimensions + // FIXME: Why is this necessary? + jpeg_decompress_struct dinfo; + sk_bzero(&dinfo, sizeof(dinfo)); + dinfo.image_width = this->dimensions().width(); + dinfo.image_height = this->dimensions().height(); + dinfo.global_state = fReadyState; + + // libjpeg-turbo can scale to 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, and 1/1 + unsigned int num = 8; + const unsigned int denom = 8; + calc_output_dimensions(&dinfo, num, denom); + while (dinfo.output_width != dstWidth || dinfo.output_height != dstHeight) { + + // Return a failure if we have tried all of the possible scales + if (1 == num || dstWidth > dinfo.output_width || dstHeight > dinfo.output_height) { + return false; + } + + // Try the next scale + num -= 1; + calc_output_dimensions(&dinfo, num, denom); + } + + fDecoderMgr->dinfo()->scale_num = num; + fDecoderMgr->dinfo()->scale_denom = denom; + return true; +} + +int SkJpegCodec::readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, + const Options& opts) { + // Set the jump location for libjpeg-turbo errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return 0; + } + + // When fSwizzleSrcRow is non-null, it means that we need to swizzle. In this case, + // we will always decode into fSwizzlerSrcRow before swizzling into the next buffer. + // We can never swizzle "in place" because the swizzler may perform sampling and/or + // subsetting. + // When fColorXformSrcRow is non-null, it means that we need to color xform and that + // we cannot color xform "in place" (many times we can, but not when the src and dst + // are different sizes). + // In this case, we will color xform from fColorXformSrcRow into the dst. + JSAMPLE* decodeDst = (JSAMPLE*) dst; + uint32_t* swizzleDst = (uint32_t*) dst; + size_t decodeDstRowBytes = rowBytes; + size_t swizzleDstRowBytes = rowBytes; + int dstWidth = opts.fSubset ? opts.fSubset->width() : dstInfo.width(); + if (fSwizzleSrcRow && fColorXformSrcRow) { + decodeDst = (JSAMPLE*) fSwizzleSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } else if (fColorXformSrcRow) { + decodeDst = (JSAMPLE*) fColorXformSrcRow; + swizzleDst = fColorXformSrcRow; + decodeDstRowBytes = 0; + swizzleDstRowBytes = 0; + } else if (fSwizzleSrcRow) { + decodeDst = (JSAMPLE*) fSwizzleSrcRow; + decodeDstRowBytes = 0; + dstWidth = fSwizzler->swizzleWidth(); + } + + for (int y = 0; y < count; y++) { + uint32_t lines = jpeg_read_scanlines(fDecoderMgr->dinfo(), &decodeDst, 1); + if (0 == lines) { + return y; + } + + if (fSwizzler) { + fSwizzler->swizzle(swizzleDst, decodeDst); + } + + if (this->colorXform()) { + this->applyColorXform(dst, swizzleDst, dstWidth); + dst = SkTAddOffset<void>(dst, rowBytes); + } + + decodeDst = SkTAddOffset<JSAMPLE>(decodeDst, decodeDstRowBytes); + swizzleDst = SkTAddOffset<uint32_t>(swizzleDst, swizzleDstRowBytes); + } + + return count; +} + +/* + * This is a bit tricky. We only need the swizzler to do format conversion if the jpeg is + * encoded as CMYK. + * And even then we still may not need it. If the jpeg has a CMYK color profile and a color + * xform, the color xform will handle the CMYK->RGB conversion. + */ +static inline bool needs_swizzler_to_convert_from_cmyk(J_COLOR_SPACE jpegColorType, + const skcms_ICCProfile* srcProfile, + bool hasColorSpaceXform) { + if (JCS_CMYK != jpegColorType) { + return false; + } + + bool hasCMYKColorSpace = srcProfile && srcProfile->data_color_space == skcms_Signature_CMYK; + return !hasCMYKColorSpace || !hasColorSpaceXform; +} + +/* + * Performs the jpeg decode + */ +SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, size_t dstRowBytes, + const Options& options, + int* rowsDecoded) { + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + // Get a pointer to the decompress info since we will use it quite frequently + jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); + + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFailure("setjmp", kInvalidInput); + } + + if (!jpeg_start_decompress(dinfo)) { + return fDecoderMgr->returnFailure("startDecompress", kInvalidInput); + } + + // The recommended output buffer height should always be 1 in high quality modes. + // If it's not, we want to know because it means our strategy is not optimal. + SkASSERT(1 == dinfo->rec_outbuf_height); + + if (needs_swizzler_to_convert_from_cmyk(dinfo->out_color_space, + this->getEncodedInfo().profile(), this->colorXform())) { + this->initializeSwizzler(dstInfo, options, true); + } + + this->allocateStorage(dstInfo); + + int rows = this->readRows(dstInfo, dst, dstRowBytes, dstInfo.height(), options); + if (rows < dstInfo.height()) { + *rowsDecoded = rows; + return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput); + } + + return kSuccess; +} + +void SkJpegCodec::allocateStorage(const SkImageInfo& dstInfo) { + int dstWidth = dstInfo.width(); + + size_t swizzleBytes = 0; + if (fSwizzler) { + swizzleBytes = get_row_bytes(fDecoderMgr->dinfo()); + dstWidth = fSwizzler->swizzleWidth(); + SkASSERT(!this->colorXform() || SkIsAlign4(swizzleBytes)); + } + + size_t xformBytes = 0; + + if (this->colorXform() && sizeof(uint32_t) != dstInfo.bytesPerPixel()) { + xformBytes = dstWidth * sizeof(uint32_t); + } + + size_t totalBytes = swizzleBytes + xformBytes; + if (totalBytes > 0) { + fStorage.reset(totalBytes); + fSwizzleSrcRow = (swizzleBytes > 0) ? fStorage.get() : nullptr; + fColorXformSrcRow = (xformBytes > 0) ? + SkTAddOffset<uint32_t>(fStorage.get(), swizzleBytes) : nullptr; + } +} + +void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, + bool needsCMYKToRGB) { + Options swizzlerOptions = options; + if (options.fSubset) { + // Use fSwizzlerSubset if this is a subset decode. This is necessary in the case + // where libjpeg-turbo provides a subset and then we need to subset it further. + // Also, verify that fSwizzlerSubset is initialized and valid. + SkASSERT(!fSwizzlerSubset.isEmpty() && fSwizzlerSubset.x() <= options.fSubset->x() && + fSwizzlerSubset.width() == options.fSubset->width()); + swizzlerOptions.fSubset = &fSwizzlerSubset; + } + + SkImageInfo swizzlerDstInfo = dstInfo; + if (this->colorXform()) { + // The color xform will be expecting RGBA 8888 input. + swizzlerDstInfo = swizzlerDstInfo.makeColorType(kRGBA_8888_SkColorType); + } + + if (needsCMYKToRGB) { + // The swizzler is used to convert to from CMYK. + // The swizzler does not use the width or height on SkEncodedInfo. + auto swizzlerInfo = SkEncodedInfo::Make(0, 0, SkEncodedInfo::kInvertedCMYK_Color, + SkEncodedInfo::kOpaque_Alpha, 8); + fSwizzler = SkSwizzler::Make(swizzlerInfo, nullptr, swizzlerDstInfo, swizzlerOptions); + } else { + int srcBPP = 0; + switch (fDecoderMgr->dinfo()->out_color_space) { + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_CMYK: + srcBPP = 4; + break; + case JCS_RGB565: + srcBPP = 2; + break; + case JCS_GRAYSCALE: + srcBPP = 1; + break; + default: + SkASSERT(false); + break; + } + fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerDstInfo, swizzlerOptions); + } + SkASSERT(fSwizzler); +} + +SkSampler* SkJpegCodec::getSampler(bool createIfNecessary) { + if (!createIfNecessary || fSwizzler) { + SkASSERT(!fSwizzler || (fSwizzleSrcRow && fStorage.get() == fSwizzleSrcRow)); + return fSwizzler.get(); + } + + bool needsCMYKToRGB = needs_swizzler_to_convert_from_cmyk( + fDecoderMgr->dinfo()->out_color_space, this->getEncodedInfo().profile(), + this->colorXform()); + this->initializeSwizzler(this->dstInfo(), this->options(), needsCMYKToRGB); + this->allocateStorage(this->dstInfo()); + return fSwizzler.get(); +} + +SkCodec::Result SkJpegCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) { + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + SkCodecPrintf("setjmp: Error from libjpeg\n"); + return kInvalidInput; + } + + if (!jpeg_start_decompress(fDecoderMgr->dinfo())) { + SkCodecPrintf("start decompress failed\n"); + return kInvalidInput; + } + + bool needsCMYKToRGB = needs_swizzler_to_convert_from_cmyk( + fDecoderMgr->dinfo()->out_color_space, this->getEncodedInfo().profile(), + this->colorXform()); + if (options.fSubset) { + uint32_t startX = options.fSubset->x(); + uint32_t width = options.fSubset->width(); + + // libjpeg-turbo may need to align startX to a multiple of the IDCT + // block size. If this is the case, it will decrease the value of + // startX to the appropriate alignment and also increase the value + // of width so that the right edge of the requested subset remains + // the same. + jpeg_crop_scanline(fDecoderMgr->dinfo(), &startX, &width); + + SkASSERT(startX <= (uint32_t) options.fSubset->x()); + SkASSERT(width >= (uint32_t) options.fSubset->width()); + SkASSERT(startX + width >= (uint32_t) options.fSubset->right()); + + // Instruct the swizzler (if it is necessary) to further subset the + // output provided by libjpeg-turbo. + // + // We set this here (rather than in the if statement below), so that + // if (1) we don't need a swizzler for the subset, and (2) we need a + // swizzler for CMYK, the swizzler will still use the proper subset + // dimensions. + // + // Note that the swizzler will ignore the y and height parameters of + // the subset. Since the scanline decoder (and the swizzler) handle + // one row at a time, only the subsetting in the x-dimension matters. + fSwizzlerSubset.setXYWH(options.fSubset->x() - startX, 0, + options.fSubset->width(), options.fSubset->height()); + + // We will need a swizzler if libjpeg-turbo cannot provide the exact + // subset that we request. + if (startX != (uint32_t) options.fSubset->x() || + width != (uint32_t) options.fSubset->width()) { + this->initializeSwizzler(dstInfo, options, needsCMYKToRGB); + } + } + + // Make sure we have a swizzler if we are converting from CMYK. + if (!fSwizzler && needsCMYKToRGB) { + this->initializeSwizzler(dstInfo, options, true); + } + + this->allocateStorage(dstInfo); + + return kSuccess; +} + +int SkJpegCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { + int rows = this->readRows(this->dstInfo(), dst, dstRowBytes, count, this->options()); + if (rows < count) { + // This allows us to skip calling jpeg_finish_decompress(). + fDecoderMgr->dinfo()->output_scanline = this->dstInfo().height(); + } + + return rows; +} + +bool SkJpegCodec::onSkipScanlines(int count) { + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFalse("onSkipScanlines"); + } + + return (uint32_t) count == jpeg_skip_scanlines(fDecoderMgr->dinfo(), count); +} + +static bool is_yuv_supported(jpeg_decompress_struct* dinfo) { + // Scaling is not supported in raw data mode. + SkASSERT(dinfo->scale_num == dinfo->scale_denom); + + // I can't imagine that this would ever change, but we do depend on it. + static_assert(8 == DCTSIZE, "DCTSIZE (defined in jpeg library) should always be 8."); + + if (JCS_YCbCr != dinfo->jpeg_color_space) { + return false; + } + + SkASSERT(3 == dinfo->num_components); + SkASSERT(dinfo->comp_info); + + // It is possible to perform a YUV decode for any combination of + // horizontal and vertical sampling that is supported by + // libjpeg/libjpeg-turbo. However, we will start by supporting only the + // common cases (where U and V have samp_factors of one). + // + // The definition of samp_factor is kind of the opposite of what SkCodec + // thinks of as a sampling factor. samp_factor is essentially a + // multiplier, and the larger the samp_factor is, the more samples that + // there will be. Ex: + // U_plane_width = image_width * (U_h_samp_factor / max_h_samp_factor) + // + // Supporting cases where the samp_factors for U or V were larger than + // that of Y would be an extremely difficult change, given that clients + // allocate memory as if the size of the Y plane is always the size of the + // image. However, this case is very, very rare. + if ((1 != dinfo->comp_info[1].h_samp_factor) || + (1 != dinfo->comp_info[1].v_samp_factor) || + (1 != dinfo->comp_info[2].h_samp_factor) || + (1 != dinfo->comp_info[2].v_samp_factor)) + { + return false; + } + + // Support all common cases of Y samp_factors. + // TODO (msarett): As mentioned above, it would be possible to support + // more combinations of samp_factors. The issues are: + // (1) Are there actually any images that are not covered + // by these cases? + // (2) How much complexity would be added to the + // implementation in order to support these rare + // cases? + int hSampY = dinfo->comp_info[0].h_samp_factor; + int vSampY = dinfo->comp_info[0].v_samp_factor; + return (1 == hSampY && 1 == vSampY) || + (2 == hSampY && 1 == vSampY) || + (2 == hSampY && 2 == vSampY) || + (1 == hSampY && 2 == vSampY) || + (4 == hSampY && 1 == vSampY) || + (4 == hSampY && 2 == vSampY); +} + +bool SkJpegCodec::onQueryYUV8(SkYUVASizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const { + jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); + if (!is_yuv_supported(dinfo)) { + return false; + } + + jpeg_component_info * comp_info = dinfo->comp_info; + for (int i = 0; i < 3; ++i) { + sizeInfo->fSizes[i].set(comp_info[i].downsampled_width, comp_info[i].downsampled_height); + sizeInfo->fWidthBytes[i] = comp_info[i].width_in_blocks * DCTSIZE; + } + + // JPEG never has an alpha channel + sizeInfo->fSizes[3].fHeight = sizeInfo->fSizes[3].fWidth = sizeInfo->fWidthBytes[3] = 0; + + sizeInfo->fOrigin = this->getOrigin(); + + if (colorSpace) { + *colorSpace = kJPEG_SkYUVColorSpace; + } + + return true; +} + +SkCodec::Result SkJpegCodec::onGetYUV8Planes(const SkYUVASizeInfo& sizeInfo, + void* planes[SkYUVASizeInfo::kMaxCount]) { + SkYUVASizeInfo defaultInfo; + + // This will check is_yuv_supported(), so we don't need to here. + bool supportsYUV = this->onQueryYUV8(&defaultInfo, nullptr); + if (!supportsYUV || + sizeInfo.fSizes[0] != defaultInfo.fSizes[0] || + sizeInfo.fSizes[1] != defaultInfo.fSizes[1] || + sizeInfo.fSizes[2] != defaultInfo.fSizes[2] || + sizeInfo.fWidthBytes[0] < defaultInfo.fWidthBytes[0] || + sizeInfo.fWidthBytes[1] < defaultInfo.fWidthBytes[1] || + sizeInfo.fWidthBytes[2] < defaultInfo.fWidthBytes[2]) { + return fDecoderMgr->returnFailure("onGetYUV8Planes", kInvalidInput); + } + + // Set the jump location for libjpeg errors + skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); + if (setjmp(jmp)) { + return fDecoderMgr->returnFailure("setjmp", kInvalidInput); + } + + // Get a pointer to the decompress info since we will use it quite frequently + jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo(); + + dinfo->raw_data_out = TRUE; + if (!jpeg_start_decompress(dinfo)) { + return fDecoderMgr->returnFailure("startDecompress", kInvalidInput); + } + + // A previous implementation claims that the return value of is_yuv_supported() + // may change after calling jpeg_start_decompress(). It looks to me like this + // was caused by a bug in the old code, but we'll be safe and check here. + SkASSERT(is_yuv_supported(dinfo)); + + // Currently, we require that the Y plane dimensions match the image dimensions + // and that the U and V planes are the same dimensions. + SkASSERT(sizeInfo.fSizes[1] == sizeInfo.fSizes[2]); + SkASSERT((uint32_t) sizeInfo.fSizes[0].width() == dinfo->output_width && + (uint32_t) sizeInfo.fSizes[0].height() == dinfo->output_height); + + // Build a JSAMPIMAGE to handle output from libjpeg-turbo. A JSAMPIMAGE has + // a 2-D array of pixels for each of the components (Y, U, V) in the image. + // Cheat Sheet: + // JSAMPIMAGE == JSAMPLEARRAY* == JSAMPROW** == JSAMPLE*** + JSAMPARRAY yuv[3]; + + // Set aside enough space for pointers to rows of Y, U, and V. + JSAMPROW rowptrs[2 * DCTSIZE + DCTSIZE + DCTSIZE]; + yuv[0] = &rowptrs[0]; // Y rows (DCTSIZE or 2 * DCTSIZE) + yuv[1] = &rowptrs[2 * DCTSIZE]; // U rows (DCTSIZE) + yuv[2] = &rowptrs[3 * DCTSIZE]; // V rows (DCTSIZE) + + // Initialize rowptrs. + int numYRowsPerBlock = DCTSIZE * dinfo->comp_info[0].v_samp_factor; + for (int i = 0; i < numYRowsPerBlock; i++) { + rowptrs[i] = SkTAddOffset<JSAMPLE>(planes[0], i * sizeInfo.fWidthBytes[0]); + } + for (int i = 0; i < DCTSIZE; i++) { + rowptrs[i + 2 * DCTSIZE] = + SkTAddOffset<JSAMPLE>(planes[1], i * sizeInfo.fWidthBytes[1]); + rowptrs[i + 3 * DCTSIZE] = + SkTAddOffset<JSAMPLE>(planes[2], i * sizeInfo.fWidthBytes[2]); + } + + // After each loop iteration, we will increment pointers to Y, U, and V. + size_t blockIncrementY = numYRowsPerBlock * sizeInfo.fWidthBytes[0]; + size_t blockIncrementU = DCTSIZE * sizeInfo.fWidthBytes[1]; + size_t blockIncrementV = DCTSIZE * sizeInfo.fWidthBytes[2]; + + uint32_t numRowsPerBlock = numYRowsPerBlock; + + // We intentionally round down here, as this first loop will only handle + // full block rows. As a special case at the end, we will handle any + // remaining rows that do not make up a full block. + const int numIters = dinfo->output_height / numRowsPerBlock; + for (int i = 0; i < numIters; i++) { + JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); + if (linesRead < numRowsPerBlock) { + // FIXME: Handle incomplete YUV decodes without signalling an error. + return kInvalidInput; + } + + // Update rowptrs. + for (int i = 0; i < numYRowsPerBlock; i++) { + rowptrs[i] += blockIncrementY; + } + for (int i = 0; i < DCTSIZE; i++) { + rowptrs[i + 2 * DCTSIZE] += blockIncrementU; + rowptrs[i + 3 * DCTSIZE] += blockIncrementV; + } + } + + uint32_t remainingRows = dinfo->output_height - dinfo->output_scanline; + SkASSERT(remainingRows == dinfo->output_height % numRowsPerBlock); + SkASSERT(dinfo->output_scanline == numIters * numRowsPerBlock); + if (remainingRows > 0) { + // libjpeg-turbo needs memory to be padded by the block sizes. We will fulfill + // this requirement using a dummy row buffer. + // FIXME: Should SkCodec have an extra memory buffer that can be shared among + // all of the implementations that use temporary/garbage memory? + SkAutoTMalloc<JSAMPLE> dummyRow(sizeInfo.fWidthBytes[0]); + for (int i = remainingRows; i < numYRowsPerBlock; i++) { + rowptrs[i] = dummyRow.get(); + } + int remainingUVRows = dinfo->comp_info[1].downsampled_height - DCTSIZE * numIters; + for (int i = remainingUVRows; i < DCTSIZE; i++) { + rowptrs[i + 2 * DCTSIZE] = dummyRow.get(); + rowptrs[i + 3 * DCTSIZE] = dummyRow.get(); + } + + JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); + if (linesRead < remainingRows) { + // FIXME: Handle incomplete YUV decodes without signalling an error. + return kInvalidInput; + } + } + + return kSuccess; +} + +// This function is declared in SkJpegInfo.h, used by SkPDF. +bool SkGetJpegInfo(const void* data, size_t len, + SkISize* size, + SkEncodedInfo::Color* colorType, + SkEncodedOrigin* orientation) { + if (!SkJpegCodec::IsJpeg(data, len)) { + return false; + } + + SkMemoryStream stream(data, len); + JpegDecoderMgr decoderMgr(&stream); + // libjpeg errors will be caught and reported here + skjpeg_error_mgr::AutoPushJmpBuf jmp(decoderMgr.errorMgr()); + if (setjmp(jmp)) { + return false; + } + decoderMgr.init(); + jpeg_decompress_struct* dinfo = decoderMgr.dinfo(); + jpeg_save_markers(dinfo, kExifMarker, 0xFFFF); + jpeg_save_markers(dinfo, kICCMarker, 0xFFFF); + if (JPEG_HEADER_OK != jpeg_read_header(dinfo, true)) { + return false; + } + SkEncodedInfo::Color encodedColorType; + if (!decoderMgr.getEncodedColor(&encodedColorType)) { + return false; // Unable to interpret the color channels as colors. + } + if (colorType) { + *colorType = encodedColorType; + } + if (orientation) { + *orientation = get_exif_orientation(dinfo); + } + if (size) { + *size = {SkToS32(dinfo->image_width), SkToS32(dinfo->image_height)}; + } + return true; +} diff --git a/gfx/skia/skia/src/codec/SkJpegCodec.h b/gfx/skia/skia/src/codec/SkJpegCodec.h new file mode 100644 index 0000000000..986d283b3a --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegCodec.h @@ -0,0 +1,144 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkJpegCodec_DEFINED +#define SkJpegCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkStream.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkSwizzler.h" + +class JpegDecoderMgr; + +/* + * + * This class implements the decoding for jpeg images + * + */ +class SkJpegCodec : public SkCodec { +public: + static bool IsJpeg(const void*, size_t); + + /* + * Assumes IsJpeg was called and returned true + * Takes ownership of the stream + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + +protected: + + /* + * Recommend a set of destination dimensions given a requested scale + */ + SkISize onGetScaledDimensions(float desiredScale) const override; + + /* + * Initiates the jpeg decode + */ + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + int*) override; + + bool onQueryYUV8(SkYUVASizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const override; + + Result onGetYUV8Planes(const SkYUVASizeInfo& sizeInfo, + void* planes[SkYUVASizeInfo::kMaxCount]) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kJPEG; + } + + bool onRewind() override; + + bool onDimensionsSupported(const SkISize&) override; + + bool conversionSupported(const SkImageInfo&, bool, bool) override; + +private: + /* + * Allows SkRawCodec to communicate the color profile from the exif data. + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*, + std::unique_ptr<SkEncodedInfo::ICCProfile> defaultColorProfile); + + /* + * Read enough of the stream to initialize the SkJpegCodec. + * Returns a bool representing success or failure. + * + * @param codecOut + * If this returns true, and codecOut was not nullptr, + * codecOut will be set to a new SkJpegCodec. + * + * @param decoderMgrOut + * If this returns true, and codecOut was nullptr, + * decoderMgrOut must be non-nullptr and decoderMgrOut will be set to a new + * JpegDecoderMgr pointer. + * + * @param stream + * Deleted on failure. + * codecOut will take ownership of it in the case where we created a codec. + * Ownership is unchanged when we set decoderMgrOut. + * + * @param defaultColorProfile + * If the jpeg does not have an embedded color profile, the image data should + * be tagged with this color profile. + */ + static Result ReadHeader(SkStream* stream, SkCodec** codecOut, + JpegDecoderMgr** decoderMgrOut, + std::unique_ptr<SkEncodedInfo::ICCProfile> defaultColorProfile); + + /* + * Creates an instance of the decoder + * Called only by NewFromStream + * + * @param info contains properties of the encoded data + * @param stream the encoded image data + * @param decoderMgr holds decompress struct, src manager, and error manager + * takes ownership + */ + SkJpegCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + JpegDecoderMgr* decoderMgr, SkEncodedOrigin origin); + + void initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, + bool needsCMYKToRGB); + void allocateStorage(const SkImageInfo& dstInfo); + int readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, const Options&); + + /* + * Scanline decoding. + */ + SkSampler* getSampler(bool createIfNecessary) override; + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) override; + int onGetScanlines(void* dst, int count, size_t rowBytes) override; + bool onSkipScanlines(int count) override; + + std::unique_ptr<JpegDecoderMgr> fDecoderMgr; + + // We will save the state of the decompress struct after reading the header. + // This allows us to safely call onGetScaledDimensions() at any time. + const int fReadyState; + + + SkAutoTMalloc<uint8_t> fStorage; + uint8_t* fSwizzleSrcRow; + uint32_t* fColorXformSrcRow; + + // libjpeg-turbo provides some subsetting. In the case that libjpeg-turbo + // cannot take the exact the subset that we need, we will use the swizzler + // to further subset the output from libjpeg-turbo. + SkIRect fSwizzlerSubset; + + std::unique_ptr<SkSwizzler> fSwizzler; + + friend class SkRawCodec; + + typedef SkCodec INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp new file mode 100644 index 0000000000..0ada6d8592 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkJpegDecoderMgr.h" + +#include "src/codec/SkJpegUtility.h" + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #include "include/android/SkAndroidFrameworkUtils.h" +#endif + +/* + * Print information, warning, and error messages + */ +static void print_message(const j_common_ptr info, const char caller[]) { + char buffer[JMSG_LENGTH_MAX]; + info->err->format_message(info, buffer); + SkCodecPrintf("libjpeg error %d <%s> from %s\n", info->err->msg_code, buffer, caller); +} + +/* + * Reporting function for error and warning messages. + */ +static void output_message(j_common_ptr info) { + print_message(info, "output_message"); +} + +static void progress_monitor(j_common_ptr info) { + int scan = ((j_decompress_ptr)info)->input_scan_number; + // Progressive images with a very large number of scans can cause the + // decoder to hang. Here we use the progress monitor to abort on + // a very large number of scans. 100 is arbitrary, but much larger + // than the number of scans we might expect in a normal image. + if (scan >= 100) { + skjpeg_err_exit(info); + } +} + +bool JpegDecoderMgr::returnFalse(const char caller[]) { + print_message((j_common_ptr) &fDInfo, caller); + return false; +} + +SkCodec::Result JpegDecoderMgr::returnFailure(const char caller[], SkCodec::Result result) { + print_message((j_common_ptr) &fDInfo, caller); + return result; +} + +bool JpegDecoderMgr::getEncodedColor(SkEncodedInfo::Color* outColor) { + switch (fDInfo.jpeg_color_space) { + case JCS_GRAYSCALE: + *outColor = SkEncodedInfo::kGray_Color; + return true; + case JCS_YCbCr: + *outColor = SkEncodedInfo::kYUV_Color; + return true; + case JCS_RGB: +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkAndroidFrameworkUtils::SafetyNetLog("118372692"); +#endif + *outColor = SkEncodedInfo::kRGB_Color; + return true; + case JCS_YCCK: + *outColor = SkEncodedInfo::kYCCK_Color; + return true; + case JCS_CMYK: + *outColor = SkEncodedInfo::kInvertedCMYK_Color; + return true; + default: + return false; + } +} + +JpegDecoderMgr::JpegDecoderMgr(SkStream* stream) + : fSrcMgr(stream) + , fInit(false) +{ + // Error manager must be set before any calls to libjeg in order to handle failures + fDInfo.err = jpeg_std_error(&fErrorMgr); + fErrorMgr.error_exit = skjpeg_err_exit; +} + +void JpegDecoderMgr::init() { + jpeg_create_decompress(&fDInfo); + fInit = true; + fDInfo.src = &fSrcMgr; + fDInfo.err->output_message = &output_message; + fDInfo.progress = &fProgressMgr; + fProgressMgr.progress_monitor = &progress_monitor; +} + +JpegDecoderMgr::~JpegDecoderMgr() { + if (fInit) { + jpeg_destroy_decompress(&fDInfo); + } +} diff --git a/gfx/skia/skia/src/codec/SkJpegDecoderMgr.h b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.h new file mode 100644 index 0000000000..f992bf5411 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegDecoderMgr.h @@ -0,0 +1,75 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkJpegDecoderMgr_DEFINED +#define SkJpegDecoderMgr_DEFINED + +#include "include/codec/SkCodec.h" +#include "src/codec/SkCodecPriv.h" +#include <stdio.h> +#include "src/codec/SkJpegUtility.h" + +extern "C" { + #include "jpeglib.h" +} + +class JpegDecoderMgr : SkNoncopyable { +public: + + /* + * Print a useful error message and return false + */ + bool returnFalse(const char caller[]); + + /* + * Print a useful error message and return a decode failure + */ + SkCodec::Result returnFailure(const char caller[], SkCodec::Result result); + + /* + * Create the decode manager + * Does not take ownership of stream + */ + JpegDecoderMgr(SkStream* stream); + + /* + * Initialize decompress struct + * Initialize the source manager + */ + void init(); + + /* + * Returns true if it successfully sets outColor to the encoded color, + * and false otherwise. + */ + bool getEncodedColor(SkEncodedInfo::Color* outColor); + + /* + * Free memory used by the decode manager + */ + ~JpegDecoderMgr(); + + /* + * Get the skjpeg_error_mgr in order to set an error return jmp_buf + */ + skjpeg_error_mgr* errorMgr() { return &fErrorMgr; } + + /* + * Get function for the decompress info struct + */ + jpeg_decompress_struct* dinfo() { return &fDInfo; } + +private: + + jpeg_decompress_struct fDInfo; + skjpeg_source_mgr fSrcMgr; + skjpeg_error_mgr fErrorMgr; + jpeg_progress_mgr fProgressMgr; + bool fInit; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkJpegPriv.h b/gfx/skia/skia/src/codec/SkJpegPriv.h new file mode 100644 index 0000000000..2e36397714 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegPriv.h @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkJpegPriv_DEFINED +#define SkJpegPriv_DEFINED + +#include "include/core/SkStream.h" +#include "include/private/SkTArray.h" + +#include <setjmp.h> +// stdio is needed for jpeglib +#include <stdio.h> + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +static constexpr uint32_t kICCMarker = JPEG_APP0 + 2; +static constexpr uint32_t kICCMarkerHeaderSize = 14; +static constexpr uint8_t kICCSig[] = { + 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0', +}; + +/* + * Error handling struct + */ +struct skjpeg_error_mgr : jpeg_error_mgr { + class AutoPushJmpBuf { + public: + AutoPushJmpBuf(skjpeg_error_mgr* mgr) : fMgr(mgr) { + fMgr->fJmpBufStack.push_back(&fJmpBuf); + } + ~AutoPushJmpBuf() { + SkASSERT(fMgr->fJmpBufStack.back() == &fJmpBuf); + fMgr->fJmpBufStack.pop_back(); + } + operator jmp_buf&() { return fJmpBuf; } + + private: + skjpeg_error_mgr* const fMgr; + jmp_buf fJmpBuf; + }; + + SkSTArray<4, jmp_buf*> fJmpBufStack; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkJpegUtility.cpp b/gfx/skia/skia/src/codec/SkJpegUtility.cpp new file mode 100644 index 0000000000..d313892cc3 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegUtility.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkJpegUtility.h" + +#include "src/codec/SkCodecPriv.h" + +/* + * Call longjmp to continue execution on an error + */ +void skjpeg_err_exit(j_common_ptr dinfo) { + // Simply return to Skia client code + // JpegDecoderMgr will take care of freeing memory + skjpeg_error_mgr* error = (skjpeg_error_mgr*) dinfo->err; + (*error->output_message) (dinfo); + if (error->fJmpBufStack.empty()) { + SK_ABORT("JPEG error with no jmp_buf set."); + } + longjmp(*error->fJmpBufStack.back(), 1); +} + +// Functions for buffered sources // + +/* + * Initialize the buffered source manager + */ +static void sk_init_buffered_source(j_decompress_ptr dinfo) { + skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src; + src->next_input_byte = (const JOCTET*) src->fBuffer; + src->bytes_in_buffer = 0; +} + +/* + * Fill the input buffer from the stream + */ +static boolean sk_fill_buffered_input_buffer(j_decompress_ptr dinfo) { + skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src; + size_t bytes = src->fStream->read(src->fBuffer, skjpeg_source_mgr::kBufferSize); + + // libjpeg is still happy with a less than full read, as long as the result is non-zero + if (bytes == 0) { + // Let libjpeg know that the buffer needs to be refilled + src->next_input_byte = nullptr; + src->bytes_in_buffer = 0; + return false; + } + + src->next_input_byte = (const JOCTET*) src->fBuffer; + src->bytes_in_buffer = bytes; + return true; +} + +/* + * Skip a certain number of bytes in the stream + */ +static void sk_skip_buffered_input_data(j_decompress_ptr dinfo, long numBytes) { + skjpeg_source_mgr* src = (skjpeg_source_mgr*) dinfo->src; + size_t bytes = (size_t) numBytes; + + if (bytes > src->bytes_in_buffer) { + size_t bytesToSkip = bytes - src->bytes_in_buffer; + if (bytesToSkip != src->fStream->skip(bytesToSkip)) { + SkCodecPrintf("Failure to skip.\n"); + dinfo->err->error_exit((j_common_ptr) dinfo); + return; + } + + src->next_input_byte = (const JOCTET*) src->fBuffer; + src->bytes_in_buffer = 0; + } else { + src->next_input_byte += numBytes; + src->bytes_in_buffer -= numBytes; + } +} + +/* + * We do not need to do anything to terminate our stream + */ +static void sk_term_source(j_decompress_ptr dinfo) +{ + // The current implementation of SkJpegCodec does not call + // jpeg_finish_decompress(), so this function is never called. + // If we want to modify this function to do something, we also + // need to modify SkJpegCodec to call jpeg_finish_decompress(). +} + +// Functions for memory backed sources // + +/* + * Initialize the mem backed source manager + */ +static void sk_init_mem_source(j_decompress_ptr dinfo) { + /* no work necessary here, all things are done in constructor */ +} + +static void sk_skip_mem_input_data (j_decompress_ptr cinfo, long num_bytes) { + jpeg_source_mgr* src = cinfo->src; + size_t bytes = static_cast<size_t>(num_bytes); + if(bytes > src->bytes_in_buffer) { + src->next_input_byte = nullptr; + src->bytes_in_buffer = 0; + } else { + src->next_input_byte += bytes; + src->bytes_in_buffer -= bytes; + } +} + +static boolean sk_fill_mem_input_buffer (j_decompress_ptr cinfo) { + /* The whole JPEG data is expected to reside in the supplied memory, + * buffer, so any request for more data beyond the given buffer size + * is treated as an error. + */ + return false; +} + +/* + * Constructor for the source manager that we provide to libjpeg + * We provide skia implementations of all of the stream processing functions required by libjpeg + */ +skjpeg_source_mgr::skjpeg_source_mgr(SkStream* stream) + : fStream(stream) +{ + if (stream->hasLength() && stream->getMemoryBase()) { + init_source = sk_init_mem_source; + fill_input_buffer = sk_fill_mem_input_buffer; + skip_input_data = sk_skip_mem_input_data; + resync_to_restart = jpeg_resync_to_restart; + term_source = sk_term_source; + bytes_in_buffer = static_cast<size_t>(stream->getLength()); + next_input_byte = static_cast<const JOCTET*>(stream->getMemoryBase()); + } else { + init_source = sk_init_buffered_source; + fill_input_buffer = sk_fill_buffered_input_buffer; + skip_input_data = sk_skip_buffered_input_data; + resync_to_restart = jpeg_resync_to_restart; + term_source = sk_term_source; + } +} diff --git a/gfx/skia/skia/src/codec/SkJpegUtility.h b/gfx/skia/skia/src/codec/SkJpegUtility.h new file mode 100644 index 0000000000..350f15484a --- /dev/null +++ b/gfx/skia/skia/src/codec/SkJpegUtility.h @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + + +#ifndef SkJpegUtility_codec_DEFINED +#define SkJpegUtility_codec_DEFINED + +#include "include/core/SkStream.h" +#include "src/codec/SkJpegPriv.h" + +#include <setjmp.h> +// stdio is needed for jpeglib +#include <stdio.h> + +extern "C" { + #include "jpeglib.h" + #include "jerror.h" +} + +/* + * Error handling function + */ +void skjpeg_err_exit(j_common_ptr cinfo); + +/* + * Source handling struct for that allows libjpeg to use our stream object + */ +struct skjpeg_source_mgr : jpeg_source_mgr { + skjpeg_source_mgr(SkStream* stream); + + SkStream* fStream; // unowned + enum { + // TODO (msarett): Experiment with different buffer sizes. + // This size was chosen because it matches SkImageDecoder. + kBufferSize = 1024 + }; + uint8_t fBuffer[kBufferSize]; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkMaskSwizzler.cpp b/gfx/skia/skia/src/codec/SkMaskSwizzler.cpp new file mode 100644 index 0000000000..d3f1f36c51 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMaskSwizzler.cpp @@ -0,0 +1,568 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkMaskSwizzler.h" + +static void swizzle_mask16_to_rgba_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_bgra_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_rgba_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_bgra_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_rgba_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask16_to_bgra_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +// TODO (msarett): We have promoted a two byte per pixel image to 8888, only to +// convert it back to 565. Instead, we should swizzle to 565 directly. +static void swizzle_mask16_to_565( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint16_t* srcPtr = ((uint16_t*) srcRow) + startX; + uint16_t* dstPtr = (uint16_t*) dstRow; + for (int i = 0; i < width; i++) { + uint16_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPack888ToRGB16(red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask24_to_rgba_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_bgra_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_rgba_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_bgra_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_rgba_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_bgra_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask24_to_565( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + srcRow += 3 * startX; + uint16_t* dstPtr = (uint16_t*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcRow[0] | (srcRow[1] << 8) | srcRow[2] << 16; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPack888ToRGB16(red, green, blue); + srcRow += 3 * sampleX; + } +} + +static void swizzle_mask32_to_rgba_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_RGBA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_bgra_opaque( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPackARGB_as_BGRA(0xFF, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_rgba_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_RGBA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_bgra_unpremul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = SkPackARGB_as_BGRA(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_rgba_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_rgba(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_bgra_premul( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + SkPMColor* dstPtr = (SkPMColor*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + uint8_t alpha = masks->getAlpha(p); + dstPtr[i] = premultiply_argb_as_bgra(alpha, red, green, blue); + srcPtr += sampleX; + } +} + +static void swizzle_mask32_to_565( + void* dstRow, const uint8_t* srcRow, int width, SkMasks* masks, + uint32_t startX, uint32_t sampleX) { + // Use the masks to decode to the destination + uint32_t* srcPtr = ((uint32_t*) srcRow) + startX; + uint16_t* dstPtr = (uint16_t*) dstRow; + for (int i = 0; i < width; i++) { + uint32_t p = srcPtr[0]; + uint8_t red = masks->getRed(p); + uint8_t green = masks->getGreen(p); + uint8_t blue = masks->getBlue(p); + dstPtr[i] = SkPack888ToRGB16(red, green, blue); + srcPtr += sampleX; + } +} + +/* + * + * Create a new mask swizzler + * + */ +SkMaskSwizzler* SkMaskSwizzler::CreateMaskSwizzler(const SkImageInfo& dstInfo, + bool srcIsOpaque, SkMasks* masks, uint32_t bitsPerPixel, + const SkCodec::Options& options) { + + // Choose the appropriate row procedure + RowProc proc = nullptr; + switch (bitsPerPixel) { + case 16: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask16_to_rgba_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask16_to_rgba_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask16_to_rgba_premul; + break; + default: + break; + } + } + break; + case kBGRA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask16_to_bgra_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask16_to_bgra_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask16_to_bgra_premul; + break; + default: + break; + } + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_mask16_to_565; + break; + default: + break; + } + break; + case 24: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask24_to_rgba_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask24_to_rgba_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask24_to_rgba_premul; + break; + default: + break; + } + } + break; + case kBGRA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask24_to_bgra_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask24_to_bgra_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask24_to_bgra_premul; + break; + default: + break; + } + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_mask24_to_565; + break; + default: + break; + } + break; + case 32: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask32_to_rgba_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask32_to_rgba_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask32_to_rgba_premul; + break; + default: + break; + } + } + break; + case kBGRA_8888_SkColorType: + if (srcIsOpaque) { + proc = &swizzle_mask32_to_bgra_opaque; + } else { + switch (dstInfo.alphaType()) { + case kUnpremul_SkAlphaType: + proc = &swizzle_mask32_to_bgra_unpremul; + break; + case kPremul_SkAlphaType: + proc = &swizzle_mask32_to_bgra_premul; + break; + default: + break; + } + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_mask32_to_565; + break; + default: + break; + } + break; + default: + SkASSERT(false); + return nullptr; + } + + int srcOffset = 0; + int srcWidth = dstInfo.width(); + if (options.fSubset) { + srcOffset = options.fSubset->left(); + srcWidth = options.fSubset->width(); + } + + return new SkMaskSwizzler(masks, proc, srcOffset, srcWidth); +} + +/* + * + * Constructor for mask swizzler + * + */ +SkMaskSwizzler::SkMaskSwizzler(SkMasks* masks, RowProc proc, int srcOffset, int subsetWidth) + : fMasks(masks) + , fRowProc(proc) + , fSubsetWidth(subsetWidth) + , fDstWidth(subsetWidth) + , fSampleX(1) + , fSrcOffset(srcOffset) + , fX0(srcOffset) +{} + +int SkMaskSwizzler::onSetSampleX(int sampleX) { + // FIXME: Share this function with SkSwizzler? + SkASSERT(sampleX > 0); // Surely there is an upper limit? Should there be + // way to report failure? + fSampleX = sampleX; + fX0 = get_start_coord(sampleX) + fSrcOffset; + fDstWidth = get_scaled_dimension(fSubsetWidth, sampleX); + + // check that fX0 is valid + SkASSERT(fX0 >= 0); + return fDstWidth; +} + +/* + * + * Swizzle the specified row + * + */ +void SkMaskSwizzler::swizzle(void* dst, const uint8_t* SK_RESTRICT src) { + SkASSERT(nullptr != dst && nullptr != src); + fRowProc(dst, src, fDstWidth, fMasks, fX0, fSampleX); +} diff --git a/gfx/skia/skia/src/codec/SkMaskSwizzler.h b/gfx/skia/skia/src/codec/SkMaskSwizzler.h new file mode 100644 index 0000000000..c7c44fd354 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMaskSwizzler.h @@ -0,0 +1,72 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkMaskSwizzler_DEFINED +#define SkMaskSwizzler_DEFINED + +#include "include/core/SkTypes.h" +#include "src/codec/SkMasks.h" +#include "src/codec/SkSampler.h" +#include "src/codec/SkSwizzler.h" + +/* + * + * Used to swizzle images whose pixel components are extracted by bit masks + * Currently only used by bmp + * + */ +class SkMaskSwizzler : public SkSampler { +public: + + /* + * @param masks Unowned pointer to helper class + */ + static SkMaskSwizzler* CreateMaskSwizzler(const SkImageInfo& dstInfo, + bool srcIsOpaque, + SkMasks* masks, + uint32_t bitsPerPixel, + const SkCodec::Options& options); + + /* + * Swizzle a row + */ + void swizzle(void* dst, const uint8_t* SK_RESTRICT src); + + int fillWidth() const override { + return fDstWidth; + } + + /** + * Returns the byte offset at which we write to destination memory, taking + * scaling, subsetting, and partial frames into account. + * A similar function exists on SkSwizzler. + */ + int swizzleWidth() const { return fDstWidth; } + +private: + + /* + * Row procedure used for swizzle + */ + typedef void (*RowProc)(void* dstRow, const uint8_t* srcRow, int width, + SkMasks* masks, uint32_t startX, uint32_t sampleX); + + SkMaskSwizzler(SkMasks* masks, RowProc proc, int subsetWidth, int srcOffset); + + int onSetSampleX(int) override; + + SkMasks* fMasks; // unowned + const RowProc fRowProc; + + // FIXME: Can this class share more with SkSwizzler? These variables are all the same. + const int fSubsetWidth; // Width of the subset of source before any sampling. + int fDstWidth; // Width of dst, which may differ with sampling. + int fSampleX; + int fSrcOffset; + int fX0; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkMasks.cpp b/gfx/skia/skia/src/codec/SkMasks.cpp new file mode 100644 index 0000000000..e10551cfe4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMasks.cpp @@ -0,0 +1,162 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkMasks.h" + +/* + * + * Used to convert 1-7 bit color components into 8-bit color components + * + */ +static constexpr uint8_t n_bit_to_8_bit_lookup_table[] = { + // 1 bit + 0, 255, + // 2 bits + 0, 85, 170, 255, + // 3 bits + 0, 36, 73, 109, 146, 182, 219, 255, + // 4 bits + 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, + // 5 bits + 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, + 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255, + // 6 bits + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, + 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, + 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, + 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, + // 7 bits + 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, + 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, + 112, 114, 116, 118, 120, 122, 124, 126, 129, 131, 133, 135, 137, 139, 141, + 143, 145, 147, 149, 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, + 173, 175, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, + 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 229, 231, + 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 +}; + +/* + * + * Convert an n bit component to an 8-bit component + * + */ +static uint8_t convert_to_8(uint8_t component, uint32_t n) { + if (0 == n) { + return 0; + } else if (8 > n) { + return n_bit_to_8_bit_lookup_table[(1 << n) - 2 + component]; + } else { + SkASSERT(8 == n); + return component; + } +} + +static uint8_t get_comp(uint32_t pixel, uint32_t mask, uint32_t shift, + uint32_t size) { + return convert_to_8((pixel & mask) >> shift, size); +} + +/* + * + * Get a color component + * + */ +uint8_t SkMasks::getRed(uint32_t pixel) const { + return get_comp(pixel, fRed.mask, fRed.shift, fRed.size); +} +uint8_t SkMasks::getGreen(uint32_t pixel) const { + return get_comp(pixel, fGreen.mask, fGreen.shift, fGreen.size); +} +uint8_t SkMasks::getBlue(uint32_t pixel) const { + return get_comp(pixel, fBlue.mask, fBlue.shift, fBlue.size); +} +uint8_t SkMasks::getAlpha(uint32_t pixel) const { + return get_comp(pixel, fAlpha.mask, fAlpha.shift, fAlpha.size); +} + +/* + * + * Process an input mask to obtain the necessary information + * + */ +static const SkMasks::MaskInfo process_mask(uint32_t mask) { + // Determine properties of the mask + uint32_t tempMask = mask; + uint32_t shift = 0; + uint32_t size = 0; + if (tempMask != 0) { + // Count trailing zeros on masks + for (; (tempMask & 1) == 0; tempMask >>= 1) { + shift++; + } + // Count the size of the mask + for (; tempMask & 1; tempMask >>= 1) { + size++; + } + // Verify that the mask is continuous + if (tempMask) { + SkCodecPrintf("Warning: Bit mask is not continuous.\n"); + // Finish processing the mask + for (; tempMask; tempMask >>= 1) { + size++; + } + } + // Truncate masks greater than 8 bits + if (size > 8) { + shift += size - 8; + size = 8; + mask &= 0xFF << shift; + } + } + + return { mask, shift, size }; +} + +/* + * + * Create the masks object + * + */ +SkMasks* SkMasks::CreateMasks(InputMasks masks, int bytesPerPixel) { + SkASSERT(0 < bytesPerPixel && bytesPerPixel <= 4); + + // Trim the input masks to match bytesPerPixel. + if (bytesPerPixel < 4) { + int bitsPerPixel = 8*bytesPerPixel; + masks.red &= (1 << bitsPerPixel) - 1; + masks.green &= (1 << bitsPerPixel) - 1; + masks.blue &= (1 << bitsPerPixel) - 1; + masks.alpha &= (1 << bitsPerPixel) - 1; + } + + // Check that masks do not overlap. + if (((masks.red & masks.green) | + (masks.red & masks.blue ) | + (masks.red & masks.alpha) | + (masks.green & masks.blue ) | + (masks.green & masks.alpha) | + (masks.blue & masks.alpha) ) != 0) { + return nullptr; + } + + return new SkMasks(process_mask(masks.red ), + process_mask(masks.green), + process_mask(masks.blue ), + process_mask(masks.alpha)); +} + + +SkMasks::SkMasks(const MaskInfo& red, const MaskInfo& green, + const MaskInfo& blue, const MaskInfo& alpha) + : fRed(red) + , fGreen(green) + , fBlue(blue) + , fAlpha(alpha) +{} diff --git a/gfx/skia/skia/src/codec/SkMasks.h b/gfx/skia/skia/src/codec/SkMasks.h new file mode 100644 index 0000000000..473d6f8baf --- /dev/null +++ b/gfx/skia/skia/src/codec/SkMasks.h @@ -0,0 +1,86 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkMasks_DEFINED +#define SkMasks_DEFINED + +#include "include/core/SkTypes.h" + +/* + * + * Contains useful mask routines for SkMaskSwizzler + * + */ +class SkMasks { +public: + + /* + * + * Input bit masks format + * + */ + struct InputMasks { + uint32_t red; + uint32_t green; + uint32_t blue; + uint32_t alpha; + }; + + /* + * + * Contains all of the information for a single mask + * + */ + struct MaskInfo { + uint32_t mask; + uint32_t shift; + uint32_t size; + }; + + /* + * + * Create the masks object + * + */ + static SkMasks* CreateMasks(InputMasks masks, int bytesPerPixel); + + /* + * + * Get a color component + * + */ + uint8_t getRed(uint32_t pixel) const; + uint8_t getGreen(uint32_t pixel) const; + uint8_t getBlue(uint32_t pixel) const; + uint8_t getAlpha(uint32_t pixel) const; + + /* + * + * Getter for the alpha mask + * The alpha mask may be used in other decoding modes + * + */ + uint32_t getAlphaMask() const { + return fAlpha.mask; + } + +private: + + /* + * + * Constructor + * + */ + SkMasks(const MaskInfo& red, const MaskInfo& green, const MaskInfo& blue, + const MaskInfo& alpha); + + const MaskInfo fRed; + const MaskInfo fGreen; + const MaskInfo fBlue; + const MaskInfo fAlpha; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp new file mode 100644 index 0000000000..aaee8c2d38 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkTypes.h" +#include "src/codec/SkCodecPriv.h" + +bool SkParseEncodedOrigin(const uint8_t* data, size_t data_length, SkEncodedOrigin* orientation) { + SkASSERT(orientation); + bool littleEndian; + // We need eight bytes to read the endian marker and the offset, below. + if (data_length < 8 || !is_valid_endian_marker(data, &littleEndian)) { + return false; + } + + auto getEndianInt = [](const uint8_t* data, bool littleEndian) -> uint32_t { + if (littleEndian) { + return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | (data[0]); + } + + return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); + }; + + // Get the offset from the start of the marker. + // Though this only reads four bytes, use a larger int in case it overflows. + uint64_t offset = getEndianInt(data + 4, littleEndian); + + // Require that the marker is at least large enough to contain the number of entries. + if (data_length < offset + 2) { + return false; + } + uint32_t numEntries = get_endian_short(data + offset, littleEndian); + + // Tag (2 bytes), Datatype (2 bytes), Number of elements (4 bytes), Data (4 bytes) + const uint32_t kEntrySize = 12; + const auto max = SkTo<uint32_t>((data_length - offset - 2) / kEntrySize); + numEntries = SkTMin(numEntries, max); + + // Advance the data to the start of the entries. + data += offset + 2; + + const uint16_t kOriginTag = 0x112; + const uint16_t kOriginType = 3; + for (uint32_t i = 0; i < numEntries; i++, data += kEntrySize) { + uint16_t tag = get_endian_short(data, littleEndian); + uint16_t type = get_endian_short(data + 2, littleEndian); + uint32_t count = getEndianInt(data + 4, littleEndian); + if (kOriginTag == tag && kOriginType == type && 1 == count) { + uint16_t val = get_endian_short(data + 8, littleEndian); + if (0 < val && val <= kLast_SkEncodedOrigin) { + *orientation = (SkEncodedOrigin) val; + return true; + } + } + } + + return false; +} diff --git a/gfx/skia/skia/src/codec/SkParseEncodedOrigin.h b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.h new file mode 100644 index 0000000000..4891557a19 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkParseEncodedOrigin.h @@ -0,0 +1,19 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkParseEncodedOrigin_DEFINED +#define SkParseEncodedOrigin_DEFINED + +#include "include/codec/SkEncodedOrigin.h" + +/** + * If |data| is an EXIF tag representing an SkEncodedOrigin, return true and set |out| + * appropriately. Otherwise return false. + */ +bool SkParseEncodedOrigin(const uint8_t* data, size_t data_length, SkEncodedOrigin* out); + +#endif // SkParseEncodedOrigin_DEFINED diff --git a/gfx/skia/skia/src/codec/SkPngCodec.cpp b/gfx/skia/skia/src/codec/SkPngCodec.cpp new file mode 100644 index 0000000000..4eaa034e64 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkPngCodec.cpp @@ -0,0 +1,1194 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/core/SkBitmap.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkMath.h" +#include "include/core/SkPoint3.h" +#include "include/core/SkSize.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMacros.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkPngCodec.h" +#include "src/codec/SkPngPriv.h" +#include "src/codec/SkSwizzler.h" +#include "src/core/SkOpts.h" +#include "src/core/SkUtils.h" + +#include "png.h" +#include <algorithm> + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #include "include/android/SkAndroidFrameworkUtils.h" +#endif + +// This warning triggers false postives way too often in here. +#if defined(__GNUC__) && !defined(__clang__) + #pragma GCC diagnostic ignored "-Wclobbered" +#endif + +// FIXME (scroggo): We can use png_jumpbuf directly once Google3 is on 1.6 +#define PNG_JMPBUF(x) png_jmpbuf((png_structp) x) + +/////////////////////////////////////////////////////////////////////////////// +// Callback functions +/////////////////////////////////////////////////////////////////////////////// + +// When setjmp is first called, it returns 0, meaning longjmp was not called. +constexpr int kSetJmpOkay = 0; +// An error internal to libpng. +constexpr int kPngError = 1; +// Passed to longjmp when we have decoded as many lines as we need. +constexpr int kStopDecoding = 2; + +static void sk_error_fn(png_structp png_ptr, png_const_charp msg) { + SkCodecPrintf("------ png error %s\n", msg); + longjmp(PNG_JMPBUF(png_ptr), kPngError); +} + +void sk_warning_fn(png_structp, png_const_charp msg) { + SkCodecPrintf("----- png warning %s\n", msg); +} + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +static int sk_read_user_chunk(png_structp png_ptr, png_unknown_chunkp chunk) { + SkPngChunkReader* chunkReader = (SkPngChunkReader*)png_get_user_chunk_ptr(png_ptr); + // readChunk() returning true means continue decoding + return chunkReader->readChunk((const char*)chunk->name, chunk->data, chunk->size) ? 1 : -1; +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Helpers +/////////////////////////////////////////////////////////////////////////////// + +class AutoCleanPng : public SkNoncopyable { +public: + /* + * This class does not take ownership of stream or reader, but if codecPtr + * is non-NULL, and decodeBounds succeeds, it will have created a new + * SkCodec (pointed to by *codecPtr) which will own/ref them, as well as + * the png_ptr and info_ptr. + */ + AutoCleanPng(png_structp png_ptr, SkStream* stream, SkPngChunkReader* reader, + SkCodec** codecPtr) + : fPng_ptr(png_ptr) + , fInfo_ptr(nullptr) + , fStream(stream) + , fChunkReader(reader) + , fOutCodec(codecPtr) + {} + + ~AutoCleanPng() { + // fInfo_ptr will never be non-nullptr unless fPng_ptr is. + if (fPng_ptr) { + png_infopp info_pp = fInfo_ptr ? &fInfo_ptr : nullptr; + png_destroy_read_struct(&fPng_ptr, info_pp, nullptr); + } + } + + void setInfoPtr(png_infop info_ptr) { + SkASSERT(nullptr == fInfo_ptr); + fInfo_ptr = info_ptr; + } + + /** + * Reads enough of the input stream to decode the bounds. + * @return false if the stream is not a valid PNG (or too short). + * true if it read enough of the stream to determine the bounds. + * In the latter case, the stream may have been read beyond the + * point to determine the bounds, and the png_ptr will have saved + * any extra data. Further, if the codecPtr supplied to the + * constructor was not NULL, it will now point to a new SkCodec, + * which owns (or refs, in the case of the SkPngChunkReader) the + * inputs. If codecPtr was NULL, the png_ptr and info_ptr are + * unowned, and it is up to the caller to destroy them. + */ + bool decodeBounds(); + +private: + png_structp fPng_ptr; + png_infop fInfo_ptr; + SkStream* fStream; + SkPngChunkReader* fChunkReader; + SkCodec** fOutCodec; + + void infoCallback(size_t idatLength); + + void releasePngPtrs() { + fPng_ptr = nullptr; + fInfo_ptr = nullptr; + } +}; +#define AutoCleanPng(...) SK_REQUIRE_LOCAL_VAR(AutoCleanPng) + +static inline bool is_chunk(const png_byte* chunk, const char* tag) { + return memcmp(chunk + 4, tag, 4) == 0; +} + +static inline bool process_data(png_structp png_ptr, png_infop info_ptr, + SkStream* stream, void* buffer, size_t bufferSize, size_t length) { + while (length > 0) { + const size_t bytesToProcess = std::min(bufferSize, length); + const size_t bytesRead = stream->read(buffer, bytesToProcess); + png_process_data(png_ptr, info_ptr, (png_bytep) buffer, bytesRead); + if (bytesRead < bytesToProcess) { + return false; + } + length -= bytesToProcess; + } + return true; +} + +bool AutoCleanPng::decodeBounds() { + if (setjmp(PNG_JMPBUF(fPng_ptr))) { + return false; + } + + png_set_progressive_read_fn(fPng_ptr, nullptr, nullptr, nullptr, nullptr); + + // Arbitrary buffer size, though note that it matches (below) + // SkPngCodec::processData(). FIXME: Can we better suit this to the size of + // the PNG header? + constexpr size_t kBufferSize = 4096; + char buffer[kBufferSize]; + + { + // Parse the signature. + if (fStream->read(buffer, 8) < 8) { + return false; + } + + png_process_data(fPng_ptr, fInfo_ptr, (png_bytep) buffer, 8); + } + + while (true) { + // Parse chunk length and type. + if (fStream->read(buffer, 8) < 8) { + // We have read to the end of the input without decoding bounds. + break; + } + + png_byte* chunk = reinterpret_cast<png_byte*>(buffer); + const size_t length = png_get_uint_32(chunk); + + if (is_chunk(chunk, "IDAT")) { + this->infoCallback(length); + return true; + } + + png_process_data(fPng_ptr, fInfo_ptr, chunk, 8); + // Process the full chunk + CRC. + if (!process_data(fPng_ptr, fInfo_ptr, fStream, buffer, kBufferSize, length + 4)) { + return false; + } + } + + return false; +} + +bool SkPngCodec::processData() { + switch (setjmp(PNG_JMPBUF(fPng_ptr))) { + case kPngError: + // There was an error. Stop processing data. + // FIXME: Do we need to discard png_ptr? + return false; + case kStopDecoding: + // We decoded all the lines we want. + return true; + case kSetJmpOkay: + // Everything is okay. + break; + default: + // No other values should be passed to longjmp. + SkASSERT(false); + } + + // Arbitrary buffer size + constexpr size_t kBufferSize = 4096; + char buffer[kBufferSize]; + + bool iend = false; + while (true) { + size_t length; + if (fDecodedIdat) { + // Parse chunk length and type. + if (this->stream()->read(buffer, 8) < 8) { + break; + } + + png_byte* chunk = reinterpret_cast<png_byte*>(buffer); + png_process_data(fPng_ptr, fInfo_ptr, chunk, 8); + if (is_chunk(chunk, "IEND")) { + iend = true; + } + + length = png_get_uint_32(chunk); + } else { + length = fIdatLength; + png_byte idat[] = {0, 0, 0, 0, 'I', 'D', 'A', 'T'}; + png_save_uint_32(idat, length); + png_process_data(fPng_ptr, fInfo_ptr, idat, 8); + fDecodedIdat = true; + } + + // Process the full chunk + CRC. + if (!process_data(fPng_ptr, fInfo_ptr, this->stream(), buffer, kBufferSize, length + 4) + || iend) { + break; + } + } + + return true; +} + +static constexpr SkColorType kXformSrcColorType = kRGBA_8888_SkColorType; + +static inline bool needs_premul(SkAlphaType dstAT, SkEncodedInfo::Alpha encodedAlpha) { + return kPremul_SkAlphaType == dstAT && SkEncodedInfo::kUnpremul_Alpha == encodedAlpha; +} + +// Note: SkColorTable claims to store SkPMColors, which is not necessarily the case here. +bool SkPngCodec::createColorTable(const SkImageInfo& dstInfo) { + + int numColors; + png_color* palette; + if (!png_get_PLTE(fPng_ptr, fInfo_ptr, &palette, &numColors)) { + return false; + } + + // Contents depend on tableColorType and our choice of if/when to premultiply: + // { kPremul, kUnpremul, kOpaque } x { RGBA, BGRA } + SkPMColor colorTable[256]; + SkColorType tableColorType = this->colorXform() ? kXformSrcColorType : dstInfo.colorType(); + + png_bytep alphas; + int numColorsWithAlpha = 0; + if (png_get_tRNS(fPng_ptr, fInfo_ptr, &alphas, &numColorsWithAlpha, nullptr)) { + bool premultiply = needs_premul(dstInfo.alphaType(), this->getEncodedInfo().alpha()); + + // Choose which function to use to create the color table. If the final destination's + // colortype is unpremultiplied, the color table will store unpremultiplied colors. + PackColorProc proc = choose_pack_color_proc(premultiply, tableColorType); + + for (int i = 0; i < numColorsWithAlpha; i++) { + // We don't have a function in SkOpts that combines a set of alphas with a set + // of RGBs. We could write one, but it's hardly worth it, given that this + // is such a small fraction of the total decode time. + colorTable[i] = proc(alphas[i], palette->red, palette->green, palette->blue); + palette++; + } + } + + if (numColorsWithAlpha < numColors) { + // The optimized code depends on a 3-byte png_color struct with the colors + // in RGB order. These checks make sure it is safe to use. + static_assert(3 == sizeof(png_color), "png_color struct has changed. Opts are broken."); +#ifdef SK_DEBUG + SkASSERT(&palette->red < &palette->green); + SkASSERT(&palette->green < &palette->blue); +#endif + + if (is_rgba(tableColorType)) { + SkOpts::RGB_to_RGB1(colorTable + numColorsWithAlpha, (const uint8_t*)palette, + numColors - numColorsWithAlpha); + } else { + SkOpts::RGB_to_BGR1(colorTable + numColorsWithAlpha, (const uint8_t*)palette, + numColors - numColorsWithAlpha); + } + } + + if (this->colorXform() && !this->xformOnDecode()) { + this->applyColorXform(colorTable, colorTable, numColors); + } + + // Pad the color table with the last color in the table (or black) in the case that + // invalid pixel indices exceed the number of colors in the table. + const int maxColors = 1 << fBitDepth; + if (numColors < maxColors) { + SkPMColor lastColor = numColors > 0 ? colorTable[numColors - 1] : SK_ColorBLACK; + sk_memset32(colorTable + numColors, lastColor, maxColors - numColors); + } + + fColorTable.reset(new SkColorTable(colorTable, maxColors)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// Creation +/////////////////////////////////////////////////////////////////////////////// + +bool SkPngCodec::IsPng(const char* buf, size_t bytesRead) { + return !png_sig_cmp((png_bytep) buf, (png_size_t)0, bytesRead); +} + +#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) + +static float png_fixed_point_to_float(png_fixed_point x) { + // We multiply by the same factor that libpng used to convert + // fixed point -> double. Since we want floats, we choose to + // do the conversion ourselves rather than convert + // fixed point -> double -> float. + return ((float) x) * 0.00001f; +} + +static float png_inverted_fixed_point_to_float(png_fixed_point x) { + // This is necessary because the gAMA chunk actually stores 1/gamma. + return 1.0f / png_fixed_point_to_float(x); +} + +#endif // LIBPNG >= 1.6 + +// If there is no color profile information, it will use sRGB. +std::unique_ptr<SkEncodedInfo::ICCProfile> read_color_profile(png_structp png_ptr, + png_infop info_ptr) { + +#if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) + // First check for an ICC profile + png_bytep profile; + png_uint_32 length; + // The below variables are unused, however, we need to pass them in anyway or + // png_get_iCCP() will return nothing. + // Could knowing the |name| of the profile ever be interesting? Maybe for debugging? + png_charp name; + // The |compression| is uninteresting since: + // (1) libpng has already decompressed the profile for us. + // (2) "deflate" is the only mode of decompression that libpng supports. + int compression; + if (PNG_INFO_iCCP == png_get_iCCP(png_ptr, info_ptr, &name, &compression, &profile, + &length)) { + auto data = SkData::MakeWithCopy(profile, length); + return SkEncodedInfo::ICCProfile::Make(std::move(data)); + } + + // Second, check for sRGB. + // Note that Blink does this first. This code checks ICC first, with the thinking that + // an image has both truly wants the potentially more specific ICC chunk, with sRGB as a + // backup in case the decoder does not support full color management. + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_sRGB)) { + // sRGB chunks also store a rendering intent: Absolute, Relative, + // Perceptual, and Saturation. + // FIXME (scroggo): Extract this information from the sRGB chunk once + // we are able to handle this information in + // skcms_ICCProfile + return nullptr; + } + + // Default to SRGB gamut. + skcms_Matrix3x3 toXYZD50 = skcms_sRGB_profile()->toXYZD50; + // Next, check for chromaticities. + png_fixed_point chrm[8]; + png_fixed_point gamma; + if (png_get_cHRM_fixed(png_ptr, info_ptr, &chrm[0], &chrm[1], &chrm[2], &chrm[3], &chrm[4], + &chrm[5], &chrm[6], &chrm[7])) + { + float rx = png_fixed_point_to_float(chrm[2]); + float ry = png_fixed_point_to_float(chrm[3]); + float gx = png_fixed_point_to_float(chrm[4]); + float gy = png_fixed_point_to_float(chrm[5]); + float bx = png_fixed_point_to_float(chrm[6]); + float by = png_fixed_point_to_float(chrm[7]); + float wx = png_fixed_point_to_float(chrm[0]); + float wy = png_fixed_point_to_float(chrm[1]); + + skcms_Matrix3x3 tmp; + if (skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &tmp)) { + toXYZD50 = tmp; + } else { + // Note that Blink simply returns nullptr in this case. We'll fall + // back to srgb. + } + } + + skcms_TransferFunction fn; + if (PNG_INFO_gAMA == png_get_gAMA_fixed(png_ptr, info_ptr, &gamma)) { + fn.a = 1.0f; + fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f; + fn.g = png_inverted_fixed_point_to_float(gamma); + } else { + // Default to sRGB gamma if the image has color space information, + // but does not specify gamma. + // Note that Blink would again return nullptr in this case. + fn = *skcms_sRGB_TransferFunction(); + } + + skcms_ICCProfile skcmsProfile; + skcms_Init(&skcmsProfile); + skcms_SetTransferFunction(&skcmsProfile, &fn); + skcms_SetXYZD50(&skcmsProfile, &toXYZD50); + + return SkEncodedInfo::ICCProfile::Make(skcmsProfile); +#else // LIBPNG >= 1.6 + return nullptr; +#endif // LIBPNG >= 1.6 +} + +void SkPngCodec::allocateStorage(const SkImageInfo& dstInfo) { + switch (fXformMode) { + case kSwizzleOnly_XformMode: + break; + case kColorOnly_XformMode: + // Intentional fall through. A swizzler hasn't been created yet, but one will + // be created later if we are sampling. We'll go ahead and allocate + // enough memory to swizzle if necessary. + case kSwizzleColor_XformMode: { + const int bitsPerPixel = this->getEncodedInfo().bitsPerPixel(); + + // If we have more than 8-bits (per component) of precision, we will keep that + // extra precision. Otherwise, we will swizzle to RGBA_8888 before transforming. + const size_t bytesPerPixel = (bitsPerPixel > 32) ? bitsPerPixel / 8 : 4; + const size_t colorXformBytes = dstInfo.width() * bytesPerPixel; + fStorage.reset(colorXformBytes); + fColorXformSrcRow = fStorage.get(); + break; + } + } +} + +static skcms_PixelFormat png_select_xform_format(const SkEncodedInfo& info) { + // We use kRGB and kRGBA formats because color PNGs are always RGB or RGBA. + if (16 == info.bitsPerComponent()) { + if (SkEncodedInfo::kRGBA_Color == info.color()) { + return skcms_PixelFormat_RGBA_16161616BE; + } else if (SkEncodedInfo::kRGB_Color == info.color()) { + return skcms_PixelFormat_RGB_161616BE; + } + } else if (SkEncodedInfo::kGray_Color == info.color()) { + return skcms_PixelFormat_G_8; + } + + return skcms_PixelFormat_RGBA_8888; +} + +void SkPngCodec::applyXformRow(void* dst, const void* src) { + switch (fXformMode) { + case kSwizzleOnly_XformMode: + fSwizzler->swizzle(dst, (const uint8_t*) src); + break; + case kColorOnly_XformMode: + this->applyColorXform(dst, src, fXformWidth); + break; + case kSwizzleColor_XformMode: + fSwizzler->swizzle(fColorXformSrcRow, (const uint8_t*) src); + this->applyColorXform(dst, fColorXformSrcRow, fXformWidth); + break; + } +} + +static SkCodec::Result log_and_return_error(bool success) { + if (success) return SkCodec::kIncompleteInput; +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkAndroidFrameworkUtils::SafetyNetLog("117838472"); +#endif + return SkCodec::kErrorInInput; +} + +class SkPngNormalDecoder : public SkPngCodec { +public: + SkPngNormalDecoder(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + SkPngChunkReader* reader, png_structp png_ptr, png_infop info_ptr, int bitDepth) + : INHERITED(std::move(info), std::move(stream), reader, png_ptr, info_ptr, bitDepth) + , fRowsWrittenToOutput(0) + , fDst(nullptr) + , fRowBytes(0) + , fFirstRow(0) + , fLastRow(0) + {} + + static void AllRowsCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { + GetDecoder(png_ptr)->allRowsCallback(row, rowNum); + } + + static void RowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int /*pass*/) { + GetDecoder(png_ptr)->rowCallback(row, rowNum); + } + +private: + int fRowsWrittenToOutput; + void* fDst; + size_t fRowBytes; + + // Variables for partial decode + int fFirstRow; // FIXME: Move to baseclass? + int fLastRow; + int fRowsNeeded; + + typedef SkPngCodec INHERITED; + + static SkPngNormalDecoder* GetDecoder(png_structp png_ptr) { + return static_cast<SkPngNormalDecoder*>(png_get_progressive_ptr(png_ptr)); + } + + Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { + const int height = this->dimensions().height(); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, AllRowsCallback, nullptr); + fDst = dst; + fRowBytes = rowBytes; + + fRowsWrittenToOutput = 0; + fFirstRow = 0; + fLastRow = height - 1; + + const bool success = this->processData(); + if (success && fRowsWrittenToOutput == height) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fRowsWrittenToOutput; + } + + return log_and_return_error(success); + } + + void allRowsCallback(png_bytep row, int rowNum) { + SkASSERT(rowNum == fRowsWrittenToOutput); + fRowsWrittenToOutput++; + this->applyXformRow(fDst, row); + fDst = SkTAddOffset<void>(fDst, fRowBytes); + } + + void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, RowCallback, nullptr); + fFirstRow = firstRow; + fLastRow = lastRow; + fDst = dst; + fRowBytes = rowBytes; + fRowsWrittenToOutput = 0; + fRowsNeeded = fLastRow - fFirstRow + 1; + } + + Result decode(int* rowsDecoded) override { + if (this->swizzler()) { + const int sampleY = this->swizzler()->sampleY(); + fRowsNeeded = get_scaled_dimension(fLastRow - fFirstRow + 1, sampleY); + } + + const bool success = this->processData(); + if (success && fRowsWrittenToOutput == fRowsNeeded) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fRowsWrittenToOutput; + } + + return log_and_return_error(success); + } + + void rowCallback(png_bytep row, int rowNum) { + if (rowNum < fFirstRow) { + // Ignore this row. + return; + } + + SkASSERT(rowNum <= fLastRow); + SkASSERT(fRowsWrittenToOutput < fRowsNeeded); + + // If there is no swizzler, all rows are needed. + if (!this->swizzler() || this->swizzler()->rowNeeded(rowNum - fFirstRow)) { + this->applyXformRow(fDst, row); + fDst = SkTAddOffset<void>(fDst, fRowBytes); + fRowsWrittenToOutput++; + } + + if (fRowsWrittenToOutput == fRowsNeeded) { + // Fake error to stop decoding scanlines. + longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding); + } + } +}; + +class SkPngInterlacedDecoder : public SkPngCodec { +public: + SkPngInterlacedDecoder(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + SkPngChunkReader* reader, png_structp png_ptr, + png_infop info_ptr, int bitDepth, int numberPasses) + : INHERITED(std::move(info), std::move(stream), reader, png_ptr, info_ptr, bitDepth) + , fNumberPasses(numberPasses) + , fFirstRow(0) + , fLastRow(0) + , fLinesDecoded(0) + , fInterlacedComplete(false) + , fPng_rowbytes(0) + {} + + static void InterlacedRowCallback(png_structp png_ptr, png_bytep row, png_uint_32 rowNum, int pass) { + auto decoder = static_cast<SkPngInterlacedDecoder*>(png_get_progressive_ptr(png_ptr)); + decoder->interlacedRowCallback(row, rowNum, pass); + } + +private: + const int fNumberPasses; + int fFirstRow; + int fLastRow; + void* fDst; + size_t fRowBytes; + int fLinesDecoded; + bool fInterlacedComplete; + size_t fPng_rowbytes; + SkAutoTMalloc<png_byte> fInterlaceBuffer; + + typedef SkPngCodec INHERITED; + + // FIXME: Currently sharing interlaced callback for all rows and subset. It's not + // as expensive as the subset version of non-interlaced, but it still does extra + // work. + void interlacedRowCallback(png_bytep row, int rowNum, int pass) { + if (rowNum < fFirstRow || rowNum > fLastRow || fInterlacedComplete) { + // Ignore this row + return; + } + + png_bytep oldRow = fInterlaceBuffer.get() + (rowNum - fFirstRow) * fPng_rowbytes; + png_progressive_combine_row(this->png_ptr(), oldRow, row); + + if (0 == pass) { + // The first pass initializes all rows. + SkASSERT(row); + SkASSERT(fLinesDecoded == rowNum - fFirstRow); + fLinesDecoded++; + } else { + SkASSERT(fLinesDecoded == fLastRow - fFirstRow + 1); + if (fNumberPasses - 1 == pass && rowNum == fLastRow) { + // Last pass, and we have read all of the rows we care about. + fInterlacedComplete = true; + if (fLastRow != this->dimensions().height() - 1 || + (this->swizzler() && this->swizzler()->sampleY() != 1)) { + // Fake error to stop decoding scanlines. Only stop if we're not decoding the + // whole image, in which case processing the rest of the image might be + // expensive. When decoding the whole image, read through the IEND chunk to + // preserve Android behavior of leaving the input stream in the right place. + longjmp(PNG_JMPBUF(this->png_ptr()), kStopDecoding); + } + } + } + } + + Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) override { + const int height = this->dimensions().height(); + this->setUpInterlaceBuffer(height); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, + nullptr); + + fFirstRow = 0; + fLastRow = height - 1; + fLinesDecoded = 0; + + const bool success = this->processData(); + png_bytep srcRow = fInterlaceBuffer.get(); + // FIXME: When resuming, this may rewrite rows that did not change. + for (int rowNum = 0; rowNum < fLinesDecoded; rowNum++) { + this->applyXformRow(dst, srcRow); + dst = SkTAddOffset<void>(dst, rowBytes); + srcRow = SkTAddOffset<png_byte>(srcRow, fPng_rowbytes); + } + if (success && fInterlacedComplete) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = fLinesDecoded; + } + + return log_and_return_error(success); + } + + void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) override { + // FIXME: We could skip rows in the interlace buffer that we won't put in the output. + this->setUpInterlaceBuffer(lastRow - firstRow + 1); + png_set_progressive_read_fn(this->png_ptr(), this, nullptr, InterlacedRowCallback, nullptr); + fFirstRow = firstRow; + fLastRow = lastRow; + fDst = dst; + fRowBytes = rowBytes; + fLinesDecoded = 0; + } + + Result decode(int* rowsDecoded) override { + const bool success = this->processData(); + + // Now apply Xforms on all the rows that were decoded. + if (!fLinesDecoded) { + if (rowsDecoded) { + *rowsDecoded = 0; + } + return log_and_return_error(success); + } + + const int sampleY = this->swizzler() ? this->swizzler()->sampleY() : 1; + const int rowsNeeded = get_scaled_dimension(fLastRow - fFirstRow + 1, sampleY); + + // FIXME: For resuming interlace, we may swizzle a row that hasn't changed. But it + // may be too tricky/expensive to handle that correctly. + + // Offset srcRow by get_start_coord rows. We do not need to account for fFirstRow, + // since the first row in fInterlaceBuffer corresponds to fFirstRow. + int srcRow = get_start_coord(sampleY); + void* dst = fDst; + int rowsWrittenToOutput = 0; + while (rowsWrittenToOutput < rowsNeeded && srcRow < fLinesDecoded) { + png_bytep src = SkTAddOffset<png_byte>(fInterlaceBuffer.get(), fPng_rowbytes * srcRow); + this->applyXformRow(dst, src); + dst = SkTAddOffset<void>(dst, fRowBytes); + + rowsWrittenToOutput++; + srcRow += sampleY; + } + + if (success && fInterlacedComplete) { + return kSuccess; + } + + if (rowsDecoded) { + *rowsDecoded = rowsWrittenToOutput; + } + return log_and_return_error(success); + } + + void setUpInterlaceBuffer(int height) { + fPng_rowbytes = png_get_rowbytes(this->png_ptr(), this->info_ptr()); + fInterlaceBuffer.reset(fPng_rowbytes * height); + fInterlacedComplete = false; + } +}; + +// Reads the header and initializes the output fields, if not NULL. +// +// @param stream Input data. Will be read to get enough information to properly +// setup the codec. +// @param chunkReader SkPngChunkReader, for reading unknown chunks. May be NULL. +// If not NULL, png_ptr will hold an *unowned* pointer to it. The caller is +// expected to continue to own it for the lifetime of the png_ptr. +// @param outCodec Optional output variable. If non-NULL, will be set to a new +// SkPngCodec on success. +// @param png_ptrp Optional output variable. If non-NULL, will be set to a new +// png_structp on success. +// @param info_ptrp Optional output variable. If non-NULL, will be set to a new +// png_infop on success; +// @return if kSuccess, the caller is responsible for calling +// png_destroy_read_struct(png_ptrp, info_ptrp). +// Otherwise, the passed in fields (except stream) are unchanged. +static SkCodec::Result read_header(SkStream* stream, SkPngChunkReader* chunkReader, + SkCodec** outCodec, + png_structp* png_ptrp, png_infop* info_ptrp) { + // The image is known to be a PNG. Decode enough to know the SkImageInfo. + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, + sk_error_fn, sk_warning_fn); + if (!png_ptr) { + return SkCodec::kInternalError; + } + +#ifdef PNG_SET_OPTION_SUPPORTED + // This setting ensures that we display images with incorrect CMF bytes. + // See crbug.com/807324. + png_set_option(png_ptr, PNG_MAXIMUM_INFLATE_WINDOW, PNG_OPTION_ON); +#endif + + AutoCleanPng autoClean(png_ptr, stream, chunkReader, outCodec); + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) { + return SkCodec::kInternalError; + } + + autoClean.setInfoPtr(info_ptr); + + if (setjmp(PNG_JMPBUF(png_ptr))) { + return SkCodec::kInvalidInput; + } + +#ifdef PNG_READ_UNKNOWN_CHUNKS_SUPPORTED + // Hookup our chunkReader so we can see any user-chunks the caller may be interested in. + // This needs to be installed before we read the png header. Android may store ninepatch + // chunks in the header. + if (chunkReader) { + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0); + png_set_read_user_chunk_fn(png_ptr, (png_voidp) chunkReader, sk_read_user_chunk); + } +#endif + + const bool decodedBounds = autoClean.decodeBounds(); + + if (!decodedBounds) { + return SkCodec::kIncompleteInput; + } + + // On success, decodeBounds releases ownership of png_ptr and info_ptr. + if (png_ptrp) { + *png_ptrp = png_ptr; + } + if (info_ptrp) { + *info_ptrp = info_ptr; + } + + // decodeBounds takes care of setting outCodec + if (outCodec) { + SkASSERT(*outCodec); + } + return SkCodec::kSuccess; +} + +void AutoCleanPng::infoCallback(size_t idatLength) { + png_uint_32 origWidth, origHeight; + int bitDepth, encodedColorType; + png_get_IHDR(fPng_ptr, fInfo_ptr, &origWidth, &origHeight, &bitDepth, + &encodedColorType, nullptr, nullptr, nullptr); + + // TODO: Should we support 16-bits of precision for gray images? + if (bitDepth == 16 && (PNG_COLOR_TYPE_GRAY == encodedColorType || + PNG_COLOR_TYPE_GRAY_ALPHA == encodedColorType)) { + bitDepth = 8; + png_set_strip_16(fPng_ptr); + } + + // Now determine the default colorType and alphaType and set the required transforms. + // Often, we depend on SkSwizzler to perform any transforms that we need. However, we + // still depend on libpng for many of the rare and PNG-specific cases. + SkEncodedInfo::Color color; + SkEncodedInfo::Alpha alpha; + switch (encodedColorType) { + case PNG_COLOR_TYPE_PALETTE: + // Extract multiple pixels with bit depths of 1, 2, and 4 from a single + // byte into separate bytes (useful for paletted and grayscale images). + if (bitDepth < 8) { + // TODO: Should we use SkSwizzler here? + bitDepth = 8; + png_set_packing(fPng_ptr); + } + + color = SkEncodedInfo::kPalette_Color; + // Set the alpha depending on if a transparency chunk exists. + alpha = png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS) ? + SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha; + break; + case PNG_COLOR_TYPE_RGB: + if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { + // Convert to RGBA if transparency chunk exists. + png_set_tRNS_to_alpha(fPng_ptr); + color = SkEncodedInfo::kRGBA_Color; + alpha = SkEncodedInfo::kBinary_Alpha; + } else { + color = SkEncodedInfo::kRGB_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + case PNG_COLOR_TYPE_GRAY: + // Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel. + if (bitDepth < 8) { + // TODO: Should we use SkSwizzler here? + bitDepth = 8; + png_set_expand_gray_1_2_4_to_8(fPng_ptr); + } + + if (png_get_valid(fPng_ptr, fInfo_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(fPng_ptr); + color = SkEncodedInfo::kGrayAlpha_Color; + alpha = SkEncodedInfo::kBinary_Alpha; + } else { + color = SkEncodedInfo::kGray_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + color = SkEncodedInfo::kGrayAlpha_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + break; + case PNG_COLOR_TYPE_RGBA: + color = SkEncodedInfo::kRGBA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + break; + default: + // All the color types have been covered above. + SkASSERT(false); + color = SkEncodedInfo::kRGBA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } + + const int numberPasses = png_set_interlace_handling(fPng_ptr); + + if (fOutCodec) { + SkASSERT(nullptr == *fOutCodec); + auto profile = read_color_profile(fPng_ptr, fInfo_ptr); + if (profile) { + switch (profile->profile()->data_color_space) { + case skcms_Signature_CMYK: + profile = nullptr; + break; + case skcms_Signature_Gray: + if (SkEncodedInfo::kGray_Color != color && + SkEncodedInfo::kGrayAlpha_Color != color) + { + profile = nullptr; + } + break; + default: + break; + } + } + + if (encodedColorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_color_8p sigBits; + if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) { + if (8 == sigBits->alpha && kGraySigBit_GrayAlphaIsJustAlpha == sigBits->gray) { + color = SkEncodedInfo::kXAlpha_Color; + } + } + } else if (SkEncodedInfo::kOpaque_Alpha == alpha) { + png_color_8p sigBits; + if (png_get_sBIT(fPng_ptr, fInfo_ptr, &sigBits)) { + if (5 == sigBits->red && 6 == sigBits->green && 5 == sigBits->blue) { + // Recommend a decode to 565 if the sBIT indicates 565. + color = SkEncodedInfo::k565_Color; + } + } + } + + SkEncodedInfo encodedInfo = SkEncodedInfo::Make(origWidth, origHeight, color, alpha, + bitDepth, std::move(profile)); + if (1 == numberPasses) { + *fOutCodec = new SkPngNormalDecoder(std::move(encodedInfo), + std::unique_ptr<SkStream>(fStream), fChunkReader, fPng_ptr, fInfo_ptr, bitDepth); + } else { + *fOutCodec = new SkPngInterlacedDecoder(std::move(encodedInfo), + std::unique_ptr<SkStream>(fStream), fChunkReader, fPng_ptr, fInfo_ptr, bitDepth, + numberPasses); + } + static_cast<SkPngCodec*>(*fOutCodec)->setIdatLength(idatLength); + } + + // Release the pointers, which are now owned by the codec or the caller is expected to + // take ownership. + this->releasePngPtrs(); +} + +SkPngCodec::SkPngCodec(SkEncodedInfo&& encodedInfo, std::unique_ptr<SkStream> stream, + SkPngChunkReader* chunkReader, void* png_ptr, void* info_ptr, int bitDepth) + : INHERITED(std::move(encodedInfo), png_select_xform_format(encodedInfo), std::move(stream)) + , fPngChunkReader(SkSafeRef(chunkReader)) + , fPng_ptr(png_ptr) + , fInfo_ptr(info_ptr) + , fColorXformSrcRow(nullptr) + , fBitDepth(bitDepth) + , fIdatLength(0) + , fDecodedIdat(false) +{} + +SkPngCodec::~SkPngCodec() { + this->destroyReadStruct(); +} + +void SkPngCodec::destroyReadStruct() { + if (fPng_ptr) { + // We will never have a nullptr fInfo_ptr with a non-nullptr fPng_ptr + SkASSERT(fInfo_ptr); + png_destroy_read_struct((png_struct**)&fPng_ptr, (png_info**)&fInfo_ptr, nullptr); + fPng_ptr = nullptr; + fInfo_ptr = nullptr; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Getting the pixels +/////////////////////////////////////////////////////////////////////////////// + +SkCodec::Result SkPngCodec::initializeXforms(const SkImageInfo& dstInfo, const Options& options) { + if (setjmp(PNG_JMPBUF((png_struct*)fPng_ptr))) { + SkCodecPrintf("Failed on png_read_update_info.\n"); + return kInvalidInput; + } + png_read_update_info(fPng_ptr, fInfo_ptr); + + // Reset fSwizzler and this->colorXform(). We can't do this in onRewind() because the + // interlaced scanline decoder may need to rewind. + fSwizzler.reset(nullptr); + + // If skcms directly supports the encoded PNG format, we should skip format + // conversion in the swizzler (or skip swizzling altogether). + bool skipFormatConversion = false; + switch (this->getEncodedInfo().color()) { + case SkEncodedInfo::kRGB_Color: + if (this->getEncodedInfo().bitsPerComponent() != 16) { + break; + } + + // Fall through + case SkEncodedInfo::kRGBA_Color: + case SkEncodedInfo::kGray_Color: + skipFormatConversion = this->colorXform(); + break; + default: + break; + } + if (skipFormatConversion && !options.fSubset) { + fXformMode = kColorOnly_XformMode; + return kSuccess; + } + + if (SkEncodedInfo::kPalette_Color == this->getEncodedInfo().color()) { + if (!this->createColorTable(dstInfo)) { + return kInvalidInput; + } + } + + this->initializeSwizzler(dstInfo, options, skipFormatConversion); + return kSuccess; +} + +void SkPngCodec::initializeXformParams() { + switch (fXformMode) { + case kColorOnly_XformMode: + fXformWidth = this->dstInfo().width(); + break; + case kSwizzleColor_XformMode: + fXformWidth = this->swizzler()->swizzleWidth(); + break; + default: + break; + } +} + +void SkPngCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, + bool skipFormatConversion) { + SkImageInfo swizzlerInfo = dstInfo; + Options swizzlerOptions = options; + fXformMode = kSwizzleOnly_XformMode; + if (this->colorXform() && this->xformOnDecode()) { + if (SkEncodedInfo::kGray_Color == this->getEncodedInfo().color()) { + swizzlerInfo = swizzlerInfo.makeColorType(kGray_8_SkColorType); + } else { + swizzlerInfo = swizzlerInfo.makeColorType(kXformSrcColorType); + } + if (kPremul_SkAlphaType == dstInfo.alphaType()) { + swizzlerInfo = swizzlerInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + + fXformMode = kSwizzleColor_XformMode; + + // Here, we swizzle into temporary memory, which is not zero initialized. + // FIXME (msarett): + // Is this a problem? + swizzlerOptions.fZeroInitialized = kNo_ZeroInitialized; + } + + if (skipFormatConversion) { + // We cannot skip format conversion when there is a color table. + SkASSERT(!fColorTable); + int srcBPP = 0; + switch (this->getEncodedInfo().color()) { + case SkEncodedInfo::kRGB_Color: + SkASSERT(this->getEncodedInfo().bitsPerComponent() == 16); + srcBPP = 6; + break; + case SkEncodedInfo::kRGBA_Color: + srcBPP = this->getEncodedInfo().bitsPerComponent() / 2; + break; + case SkEncodedInfo::kGray_Color: + srcBPP = 1; + break; + default: + SkASSERT(false); + break; + } + fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerInfo, swizzlerOptions); + } else { + const SkPMColor* colors = get_color_ptr(fColorTable.get()); + fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), colors, swizzlerInfo, + swizzlerOptions); + } + SkASSERT(fSwizzler); +} + +SkSampler* SkPngCodec::getSampler(bool createIfNecessary) { + if (fSwizzler || !createIfNecessary) { + return fSwizzler.get(); + } + + this->initializeSwizzler(this->dstInfo(), this->options(), true); + return fSwizzler.get(); +} + +bool SkPngCodec::onRewind() { + // This sets fPng_ptr and fInfo_ptr to nullptr. If read_header + // succeeds, they will be repopulated, and if it fails, they will + // remain nullptr. Any future accesses to fPng_ptr and fInfo_ptr will + // come through this function which will rewind and again attempt + // to reinitialize them. + this->destroyReadStruct(); + + png_structp png_ptr; + png_infop info_ptr; + if (kSuccess != read_header(this->stream(), fPngChunkReader.get(), nullptr, + &png_ptr, &info_ptr)) { + return false; + } + + fPng_ptr = png_ptr; + fInfo_ptr = info_ptr; + fDecodedIdat = false; + return true; +} + +SkCodec::Result SkPngCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t rowBytes, const Options& options, + int* rowsDecoded) { + Result result = this->initializeXforms(dstInfo, options); + if (kSuccess != result) { + return result; + } + + if (options.fSubset) { + return kUnimplemented; + } + + this->allocateStorage(dstInfo); + this->initializeXformParams(); + return this->decodeAllRows(dst, rowBytes, rowsDecoded); +} + +SkCodec::Result SkPngCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* dst, size_t rowBytes, const SkCodec::Options& options) { + Result result = this->initializeXforms(dstInfo, options); + if (kSuccess != result) { + return result; + } + + this->allocateStorage(dstInfo); + + int firstRow, lastRow; + if (options.fSubset) { + firstRow = options.fSubset->top(); + lastRow = options.fSubset->bottom() - 1; + } else { + firstRow = 0; + lastRow = dstInfo.height() - 1; + } + this->setRange(firstRow, lastRow, dst, rowBytes); + return kSuccess; +} + +SkCodec::Result SkPngCodec::onIncrementalDecode(int* rowsDecoded) { + // FIXME: Only necessary on the first call. + this->initializeXformParams(); + + return this->decode(rowsDecoded); +} + +std::unique_ptr<SkCodec> SkPngCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result, SkPngChunkReader* chunkReader) { + SkCodec* outCodec = nullptr; + *result = read_header(stream.get(), chunkReader, &outCodec, nullptr, nullptr); + if (kSuccess == *result) { + // Codec has taken ownership of the stream. + SkASSERT(outCodec); + stream.release(); + } + return std::unique_ptr<SkCodec>(outCodec); +} diff --git a/gfx/skia/skia/src/codec/SkPngCodec.h b/gfx/skia/skia/src/codec/SkPngCodec.h new file mode 100644 index 0000000000..423331647c --- /dev/null +++ b/gfx/skia/skia/src/codec/SkPngCodec.h @@ -0,0 +1,121 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPngCodec_DEFINED +#define SkPngCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkPngChunkReader.h" +#include "include/core/SkRefCnt.h" +#include "src/codec/SkColorTable.h" +#include "src/codec/SkSwizzler.h" + +class SkStream; + +class SkPngCodec : public SkCodec { +public: + static bool IsPng(const char*, size_t); + + // Assume IsPng was called and returned true. + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*, + SkPngChunkReader* = nullptr); + + // FIXME (scroggo): Temporarily needed by AutoCleanPng. + void setIdatLength(size_t len) { fIdatLength = len; } + + ~SkPngCodec() override; + +protected: + // We hold the png_ptr and info_ptr as voidp to avoid having to include png.h + // or forward declare their types here. voidp auto-casts to the real pointer types. + struct voidp { + voidp(void* ptr) : fPtr(ptr) {} + + template <typename T> + operator T*() const { return (T*)fPtr; } + + explicit operator bool() const { return fPtr != nullptr; } + + void* fPtr; + }; + + SkPngCodec(SkEncodedInfo&&, std::unique_ptr<SkStream>, SkPngChunkReader*, + void* png_ptr, void* info_ptr, int bitDepth); + + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) + override; + SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kPNG; } + bool onRewind() override; + + SkSampler* getSampler(bool createIfNecessary) override; + void applyXformRow(void* dst, const void* src); + + voidp png_ptr() { return fPng_ptr; } + voidp info_ptr() { return fInfo_ptr; } + + SkSwizzler* swizzler() { return fSwizzler.get(); } + + // Initialize variables used by applyXformRow. + void initializeXformParams(); + + /** + * Pass available input to libpng to process it. + * + * libpng will call any relevant callbacks installed. This will continue decoding + * until it reaches the end of the file, or until a callback tells libpng to stop. + */ + bool processData(); + + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, void* pixels, size_t rowBytes, + const SkCodec::Options&) override; + Result onIncrementalDecode(int*) override; + + sk_sp<SkPngChunkReader> fPngChunkReader; + voidp fPng_ptr; + voidp fInfo_ptr; + + // These are stored here so they can be used both by normal decoding and scanline decoding. + sk_sp<SkColorTable> fColorTable; // May be unpremul. + std::unique_ptr<SkSwizzler> fSwizzler; + SkAutoTMalloc<uint8_t> fStorage; + void* fColorXformSrcRow; + const int fBitDepth; + +private: + + enum XformMode { + // Requires only a swizzle pass. + kSwizzleOnly_XformMode, + + // Requires only a color xform pass. + kColorOnly_XformMode, + + // Requires a swizzle and a color xform. + kSwizzleColor_XformMode, + }; + + bool createColorTable(const SkImageInfo& dstInfo); + // Helper to set up swizzler, color xforms, and color table. Also calls png_read_update_info. + SkCodec::Result initializeXforms(const SkImageInfo& dstInfo, const Options&); + void initializeSwizzler(const SkImageInfo& dstInfo, const Options&, bool skipFormatConversion); + void allocateStorage(const SkImageInfo& dstInfo); + void destroyReadStruct(); + + virtual Result decodeAllRows(void* dst, size_t rowBytes, int* rowsDecoded) = 0; + virtual void setRange(int firstRow, int lastRow, void* dst, size_t rowBytes) = 0; + virtual Result decode(int* rowsDecoded) = 0; + + XformMode fXformMode; + int fXformWidth; + + size_t fIdatLength; + bool fDecodedIdat; + + typedef SkCodec INHERITED; +}; +#endif // SkPngCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkPngPriv.h b/gfx/skia/skia/src/codec/SkPngPriv.h new file mode 100644 index 0000000000..5760179b60 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkPngPriv.h @@ -0,0 +1,19 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPngPriv_DEFINED +#define SkPngPriv_DEFINED + +#include "include/core/SkTypes.h" + +// We store kAlpha_8 images as GrayAlpha in png. Our private signal is significant bits for gray. +// If that is set to 1, we assume the gray channel can be ignored, and we output just alpha. +// We tried 0 at first, but png doesn't like a 0 sigbit for a channel it expects, hence we chose 1. + +static constexpr int kGraySigBit_GrayAlphaIsJustAlpha = 1; + +#endif diff --git a/gfx/skia/skia/src/codec/SkRawCodec.cpp b/gfx/skia/skia/src/codec/SkRawCodec.cpp new file mode 100644 index 0000000000..f8dff7a862 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkRawCodec.cpp @@ -0,0 +1,797 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkRefCnt.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkColorData.h" +#include "include/private/SkMutex.h" +#include "include/private/SkTArray.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkJpegCodec.h" +#include "src/codec/SkRawCodec.h" +#include "src/core/SkColorSpacePriv.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkStreamPriv.h" +#include "src/core/SkTaskGroup.h" + +#include "dng_area_task.h" +#include "dng_color_space.h" +#include "dng_errors.h" +#include "dng_exceptions.h" +#include "dng_host.h" +#include "dng_info.h" +#include "dng_memory.h" +#include "dng_render.h" +#include "dng_stream.h" + +#include "src/piex.h" + +#include <cmath> // for std::round,floor,ceil +#include <limits> + +namespace { + +// Caluclates the number of tiles of tile_size that fit into the area in vertical and horizontal +// directions. +dng_point num_tiles_in_area(const dng_point &areaSize, + const dng_point_real64 &tileSize) { + // FIXME: Add a ceil_div() helper in SkCodecPriv.h + return dng_point(static_cast<int32>((areaSize.v + tileSize.v - 1) / tileSize.v), + static_cast<int32>((areaSize.h + tileSize.h - 1) / tileSize.h)); +} + +int num_tasks_required(const dng_point& tilesInTask, + const dng_point& tilesInArea) { + return ((tilesInArea.v + tilesInTask.v - 1) / tilesInTask.v) * + ((tilesInArea.h + tilesInTask.h - 1) / tilesInTask.h); +} + +// Calculate the number of tiles to process per task, taking into account the maximum number of +// tasks. It prefers to increase horizontally for better locality of reference. +dng_point num_tiles_per_task(const int maxTasks, + const dng_point &tilesInArea) { + dng_point tilesInTask = {1, 1}; + while (num_tasks_required(tilesInTask, tilesInArea) > maxTasks) { + if (tilesInTask.h < tilesInArea.h) { + ++tilesInTask.h; + } else if (tilesInTask.v < tilesInArea.v) { + ++tilesInTask.v; + } else { + ThrowProgramError("num_tiles_per_task calculation is wrong."); + } + } + return tilesInTask; +} + +std::vector<dng_rect> compute_task_areas(const int maxTasks, const dng_rect& area, + const dng_point& tileSize) { + std::vector<dng_rect> taskAreas; + const dng_point tilesInArea = num_tiles_in_area(area.Size(), tileSize); + const dng_point tilesPerTask = num_tiles_per_task(maxTasks, tilesInArea); + const dng_point taskAreaSize = {tilesPerTask.v * tileSize.v, + tilesPerTask.h * tileSize.h}; + for (int v = 0; v < tilesInArea.v; v += tilesPerTask.v) { + for (int h = 0; h < tilesInArea.h; h += tilesPerTask.h) { + dng_rect taskArea; + taskArea.t = area.t + v * tileSize.v; + taskArea.l = area.l + h * tileSize.h; + taskArea.b = Min_int32(taskArea.t + taskAreaSize.v, area.b); + taskArea.r = Min_int32(taskArea.l + taskAreaSize.h, area.r); + + taskAreas.push_back(taskArea); + } + } + return taskAreas; +} + +class SkDngHost : public dng_host { +public: + explicit SkDngHost(dng_memory_allocator* allocater) : dng_host(allocater) {} + + void PerformAreaTask(dng_area_task& task, const dng_rect& area) override { + SkTaskGroup taskGroup; + + // tileSize is typically 256x256 + const dng_point tileSize(task.FindTileSize(area)); + const std::vector<dng_rect> taskAreas = compute_task_areas(this->PerformAreaTaskThreads(), + area, tileSize); + const int numTasks = static_cast<int>(taskAreas.size()); + + SkMutex mutex; + SkTArray<dng_exception> exceptions; + task.Start(numTasks, tileSize, &Allocator(), Sniffer()); + for (int taskIndex = 0; taskIndex < numTasks; ++taskIndex) { + taskGroup.add([&mutex, &exceptions, &task, this, taskIndex, taskAreas, tileSize] { + try { + task.ProcessOnThread(taskIndex, taskAreas[taskIndex], tileSize, this->Sniffer()); + } catch (dng_exception& exception) { + SkAutoMutexExclusive lock(mutex); + exceptions.push_back(exception); + } catch (...) { + SkAutoMutexExclusive lock(mutex); + exceptions.push_back(dng_exception(dng_error_unknown)); + } + }); + } + + taskGroup.wait(); + task.Finish(numTasks); + + // We only re-throw the first exception. + if (!exceptions.empty()) { + Throw_dng_error(exceptions.front().ErrorCode(), nullptr, nullptr); + } + } + + uint32 PerformAreaTaskThreads() override { +#ifdef SK_BUILD_FOR_ANDROID + // Only use 1 thread. DNGs with the warp effect require a lot of memory, + // and the amount of memory required scales linearly with the number of + // threads. The sample used in CTS requires over 500 MB, so even two + // threads is significantly expensive. There is no good way to tell + // whether the image has the warp effect. + return 1; +#else + return kMaxMPThreads; +#endif + } + +private: + typedef dng_host INHERITED; +}; + +// T must be unsigned type. +template <class T> +bool safe_add_to_size_t(T arg1, T arg2, size_t* result) { + SkASSERT(arg1 >= 0); + SkASSERT(arg2 >= 0); + if (arg1 >= 0 && arg2 <= std::numeric_limits<T>::max() - arg1) { + T sum = arg1 + arg2; + if (sum <= std::numeric_limits<size_t>::max()) { + *result = static_cast<size_t>(sum); + return true; + } + } + return false; +} + +bool is_asset_stream(const SkStream& stream) { + return stream.hasLength() && stream.hasPosition(); +} + +} // namespace + +class SkRawStream { +public: + virtual ~SkRawStream() {} + + /* + * Gets the length of the stream. Depending on the type of stream, this may require reading to + * the end of the stream. + */ + virtual uint64 getLength() = 0; + + virtual bool read(void* data, size_t offset, size_t length) = 0; + + /* + * Creates an SkMemoryStream from the offset with size. + * Note: for performance reason, this function is destructive to the SkRawStream. One should + * abandon current object after the function call. + */ + virtual std::unique_ptr<SkMemoryStream> transferBuffer(size_t offset, size_t size) = 0; +}; + +class SkRawLimitedDynamicMemoryWStream : public SkDynamicMemoryWStream { +public: + ~SkRawLimitedDynamicMemoryWStream() override {} + + bool write(const void* buffer, size_t size) override { + size_t newSize; + if (!safe_add_to_size_t(this->bytesWritten(), size, &newSize) || + newSize > kMaxStreamSize) + { + SkCodecPrintf("Error: Stream size exceeds the limit.\n"); + return false; + } + return this->INHERITED::write(buffer, size); + } + +private: + // Most of valid RAW images will not be larger than 100MB. This limit is helpful to avoid + // streaming too large data chunk. We can always adjust the limit here if we need. + const size_t kMaxStreamSize = 100 * 1024 * 1024; // 100MB + + typedef SkDynamicMemoryWStream INHERITED; +}; + +// Note: the maximum buffer size is 100MB (limited by SkRawLimitedDynamicMemoryWStream). +class SkRawBufferedStream : public SkRawStream { +public: + explicit SkRawBufferedStream(std::unique_ptr<SkStream> stream) + : fStream(std::move(stream)) + , fWholeStreamRead(false) + { + // Only use SkRawBufferedStream when the stream is not an asset stream. + SkASSERT(!is_asset_stream(*fStream)); + } + + ~SkRawBufferedStream() override {} + + uint64 getLength() override { + if (!this->bufferMoreData(kReadToEnd)) { // read whole stream + ThrowReadFile(); + } + return fStreamBuffer.bytesWritten(); + } + + bool read(void* data, size_t offset, size_t length) override { + if (length == 0) { + return true; + } + + size_t sum; + if (!safe_add_to_size_t(offset, length, &sum)) { + return false; + } + + return this->bufferMoreData(sum) && fStreamBuffer.read(data, offset, length); + } + + std::unique_ptr<SkMemoryStream> transferBuffer(size_t offset, size_t size) override { + sk_sp<SkData> data(SkData::MakeUninitialized(size)); + if (offset > fStreamBuffer.bytesWritten()) { + // If the offset is not buffered, read from fStream directly and skip the buffering. + const size_t skipLength = offset - fStreamBuffer.bytesWritten(); + if (fStream->skip(skipLength) != skipLength) { + return nullptr; + } + const size_t bytesRead = fStream->read(data->writable_data(), size); + if (bytesRead < size) { + data = SkData::MakeSubset(data.get(), 0, bytesRead); + } + } else { + const size_t alreadyBuffered = SkTMin(fStreamBuffer.bytesWritten() - offset, size); + if (alreadyBuffered > 0 && + !fStreamBuffer.read(data->writable_data(), offset, alreadyBuffered)) { + return nullptr; + } + + const size_t remaining = size - alreadyBuffered; + if (remaining) { + auto* dst = static_cast<uint8_t*>(data->writable_data()) + alreadyBuffered; + const size_t bytesRead = fStream->read(dst, remaining); + size_t newSize; + if (bytesRead < remaining) { + if (!safe_add_to_size_t(alreadyBuffered, bytesRead, &newSize)) { + return nullptr; + } + data = SkData::MakeSubset(data.get(), 0, newSize); + } + } + } + return SkMemoryStream::Make(data); + } + +private: + // Note: if the newSize == kReadToEnd (0), this function will read to the end of stream. + bool bufferMoreData(size_t newSize) { + if (newSize == kReadToEnd) { + if (fWholeStreamRead) { // already read-to-end. + return true; + } + + // TODO: optimize for the special case when the input is SkMemoryStream. + return SkStreamCopy(&fStreamBuffer, fStream.get()); + } + + if (newSize <= fStreamBuffer.bytesWritten()) { // already buffered to newSize + return true; + } + if (fWholeStreamRead) { // newSize is larger than the whole stream. + return false; + } + + // Try to read at least 8192 bytes to avoid to many small reads. + const size_t kMinSizeToRead = 8192; + const size_t sizeRequested = newSize - fStreamBuffer.bytesWritten(); + const size_t sizeToRead = SkTMax(kMinSizeToRead, sizeRequested); + SkAutoSTMalloc<kMinSizeToRead, uint8> tempBuffer(sizeToRead); + const size_t bytesRead = fStream->read(tempBuffer.get(), sizeToRead); + if (bytesRead < sizeRequested) { + return false; + } + return fStreamBuffer.write(tempBuffer.get(), bytesRead); + } + + std::unique_ptr<SkStream> fStream; + bool fWholeStreamRead; + + // Use a size-limited stream to avoid holding too huge buffer. + SkRawLimitedDynamicMemoryWStream fStreamBuffer; + + const size_t kReadToEnd = 0; +}; + +class SkRawAssetStream : public SkRawStream { +public: + explicit SkRawAssetStream(std::unique_ptr<SkStream> stream) + : fStream(std::move(stream)) + { + // Only use SkRawAssetStream when the stream is an asset stream. + SkASSERT(is_asset_stream(*fStream)); + } + + ~SkRawAssetStream() override {} + + uint64 getLength() override { + return fStream->getLength(); + } + + + bool read(void* data, size_t offset, size_t length) override { + if (length == 0) { + return true; + } + + size_t sum; + if (!safe_add_to_size_t(offset, length, &sum)) { + return false; + } + + return fStream->seek(offset) && (fStream->read(data, length) == length); + } + + std::unique_ptr<SkMemoryStream> transferBuffer(size_t offset, size_t size) override { + if (fStream->getLength() < offset) { + return nullptr; + } + + size_t sum; + if (!safe_add_to_size_t(offset, size, &sum)) { + return nullptr; + } + + // This will allow read less than the requested "size", because the JPEG codec wants to + // handle also a partial JPEG file. + const size_t bytesToRead = SkTMin(sum, fStream->getLength()) - offset; + if (bytesToRead == 0) { + return nullptr; + } + + if (fStream->getMemoryBase()) { // directly copy if getMemoryBase() is available. + sk_sp<SkData> data(SkData::MakeWithCopy( + static_cast<const uint8_t*>(fStream->getMemoryBase()) + offset, bytesToRead)); + fStream.reset(); + return SkMemoryStream::Make(data); + } else { + sk_sp<SkData> data(SkData::MakeUninitialized(bytesToRead)); + if (!fStream->seek(offset)) { + return nullptr; + } + const size_t bytesRead = fStream->read(data->writable_data(), bytesToRead); + if (bytesRead < bytesToRead) { + data = SkData::MakeSubset(data.get(), 0, bytesRead); + } + return SkMemoryStream::Make(data); + } + } +private: + std::unique_ptr<SkStream> fStream; +}; + +class SkPiexStream : public ::piex::StreamInterface { +public: + // Will NOT take the ownership of the stream. + explicit SkPiexStream(SkRawStream* stream) : fStream(stream) {} + + ~SkPiexStream() override {} + + ::piex::Error GetData(const size_t offset, const size_t length, + uint8* data) override { + return fStream->read(static_cast<void*>(data), offset, length) ? + ::piex::Error::kOk : ::piex::Error::kFail; + } + +private: + SkRawStream* fStream; +}; + +class SkDngStream : public dng_stream { +public: + // Will NOT take the ownership of the stream. + SkDngStream(SkRawStream* stream) : fStream(stream) {} + + ~SkDngStream() override {} + + uint64 DoGetLength() override { return fStream->getLength(); } + + void DoRead(void* data, uint32 count, uint64 offset) override { + size_t sum; + if (!safe_add_to_size_t(static_cast<uint64>(count), offset, &sum) || + !fStream->read(data, static_cast<size_t>(offset), static_cast<size_t>(count))) { + ThrowReadFile(); + } + } + +private: + SkRawStream* fStream; +}; + +class SkDngImage { +public: + /* + * Initializes the object with the information from Piex in a first attempt. This way it can + * save time and storage to obtain the DNG dimensions and color filter array (CFA) pattern + * which is essential for the demosaicing of the sensor image. + * Note: this will take the ownership of the stream. + */ + static SkDngImage* NewFromStream(SkRawStream* stream) { + std::unique_ptr<SkDngImage> dngImage(new SkDngImage(stream)); +#if defined(IS_FUZZING_WITH_LIBFUZZER) + // Libfuzzer easily runs out of memory after here. To avoid that + // We just pretend all streams are invalid. Our AFL-fuzzer + // should still exercise this code; it's more resistant to OOM. + return nullptr; +#endif + if (!dngImage->initFromPiex() && !dngImage->readDng()) { + return nullptr; + } + + return dngImage.release(); + } + + /* + * Renders the DNG image to the size. The DNG SDK only allows scaling close to integer factors + * down to 80 pixels on the short edge. The rendered image will be close to the specified size, + * but there is no guarantee that any of the edges will match the requested size. E.g. + * 100% size: 4000 x 3000 + * requested size: 1600 x 1200 + * returned size could be: 2000 x 1500 + */ + dng_image* render(int width, int height) { + if (!fHost || !fInfo || !fNegative || !fDngStream) { + if (!this->readDng()) { + return nullptr; + } + } + + // DNG SDK preserves the aspect ratio, so it only needs to know the longer dimension. + const int preferredSize = SkTMax(width, height); + try { + // render() takes ownership of fHost, fInfo, fNegative and fDngStream when available. + std::unique_ptr<dng_host> host(fHost.release()); + std::unique_ptr<dng_info> info(fInfo.release()); + std::unique_ptr<dng_negative> negative(fNegative.release()); + std::unique_ptr<dng_stream> dngStream(fDngStream.release()); + + host->SetPreferredSize(preferredSize); + host->ValidateSizes(); + + negative->ReadStage1Image(*host, *dngStream, *info); + + if (info->fMaskIndex != -1) { + negative->ReadTransparencyMask(*host, *dngStream, *info); + } + + negative->ValidateRawImageDigest(*host); + if (negative->IsDamaged()) { + return nullptr; + } + + const int32 kMosaicPlane = -1; + negative->BuildStage2Image(*host); + negative->BuildStage3Image(*host, kMosaicPlane); + + dng_render render(*host, *negative); + render.SetFinalSpace(dng_space_sRGB::Get()); + render.SetFinalPixelType(ttByte); + + dng_point stage3_size = negative->Stage3Image()->Size(); + render.SetMaximumSize(SkTMax(stage3_size.h, stage3_size.v)); + + return render.Render(); + } catch (...) { + return nullptr; + } + } + + int width() const { + return fWidth; + } + + int height() const { + return fHeight; + } + + bool isScalable() const { + return fIsScalable; + } + + bool isXtransImage() const { + return fIsXtransImage; + } + + // Quick check if the image contains a valid TIFF header as requested by DNG format. + // Does not affect ownership of stream. + static bool IsTiffHeaderValid(SkRawStream* stream) { + const size_t kHeaderSize = 4; + unsigned char header[kHeaderSize]; + if (!stream->read(header, 0 /* offset */, kHeaderSize)) { + return false; + } + + // Check if the header is valid (endian info and magic number "42"). + bool littleEndian; + if (!is_valid_endian_marker(header, &littleEndian)) { + return false; + } + + return 0x2A == get_endian_short(header + 2, littleEndian); + } + +private: + bool init(int width, int height, const dng_point& cfaPatternSize) { + fWidth = width; + fHeight = height; + + // The DNG SDK scales only during demosaicing, so scaling is only possible when + // a mosaic info is available. + fIsScalable = cfaPatternSize.v != 0 && cfaPatternSize.h != 0; + fIsXtransImage = fIsScalable ? (cfaPatternSize.v == 6 && cfaPatternSize.h == 6) : false; + + return width > 0 && height > 0; + } + + bool initFromPiex() { + // Does not take the ownership of rawStream. + SkPiexStream piexStream(fStream.get()); + ::piex::PreviewImageData imageData; + if (::piex::IsRaw(&piexStream) + && ::piex::GetPreviewImageData(&piexStream, &imageData) == ::piex::Error::kOk) + { + dng_point cfaPatternSize(imageData.cfa_pattern_dim[1], imageData.cfa_pattern_dim[0]); + return this->init(static_cast<int>(imageData.full_width), + static_cast<int>(imageData.full_height), cfaPatternSize); + } + return false; + } + + bool readDng() { + try { + // Due to the limit of DNG SDK, we need to reset host and info. + fHost.reset(new SkDngHost(&fAllocator)); + fInfo.reset(new dng_info); + fDngStream.reset(new SkDngStream(fStream.get())); + + fHost->ValidateSizes(); + fInfo->Parse(*fHost, *fDngStream); + fInfo->PostParse(*fHost); + if (!fInfo->IsValidDNG()) { + return false; + } + + fNegative.reset(fHost->Make_dng_negative()); + fNegative->Parse(*fHost, *fDngStream, *fInfo); + fNegative->PostParse(*fHost, *fDngStream, *fInfo); + fNegative->SynchronizeMetadata(); + + dng_point cfaPatternSize(0, 0); + if (fNegative->GetMosaicInfo() != nullptr) { + cfaPatternSize = fNegative->GetMosaicInfo()->fCFAPatternSize; + } + return this->init(static_cast<int>(fNegative->DefaultCropSizeH().As_real64()), + static_cast<int>(fNegative->DefaultCropSizeV().As_real64()), + cfaPatternSize); + } catch (...) { + return false; + } + } + + SkDngImage(SkRawStream* stream) + : fStream(stream) + {} + + dng_memory_allocator fAllocator; + std::unique_ptr<SkRawStream> fStream; + std::unique_ptr<dng_host> fHost; + std::unique_ptr<dng_info> fInfo; + std::unique_ptr<dng_negative> fNegative; + std::unique_ptr<dng_stream> fDngStream; + + int fWidth; + int fHeight; + bool fIsScalable; + bool fIsXtransImage; +}; + +/* + * Tries to handle the image with PIEX. If PIEX returns kOk and finds the preview image, create a + * SkJpegCodec. If PIEX returns kFail, then the file is invalid, return nullptr. In other cases, + * fallback to create SkRawCodec for DNG images. + */ +std::unique_ptr<SkCodec> SkRawCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + std::unique_ptr<SkRawStream> rawStream; + if (is_asset_stream(*stream)) { + rawStream.reset(new SkRawAssetStream(std::move(stream))); + } else { + rawStream.reset(new SkRawBufferedStream(std::move(stream))); + } + + // Does not take the ownership of rawStream. + SkPiexStream piexStream(rawStream.get()); + ::piex::PreviewImageData imageData; + if (::piex::IsRaw(&piexStream)) { + ::piex::Error error = ::piex::GetPreviewImageData(&piexStream, &imageData); + if (error == ::piex::Error::kFail) { + *result = kInvalidInput; + return nullptr; + } + + std::unique_ptr<SkEncodedInfo::ICCProfile> profile; + if (imageData.color_space == ::piex::PreviewImageData::kAdobeRgb) { + skcms_ICCProfile skcmsProfile; + skcms_Init(&skcmsProfile); + skcms_SetTransferFunction(&skcmsProfile, &SkNamedTransferFn::k2Dot2); + skcms_SetXYZD50(&skcmsProfile, &SkNamedGamut::kAdobeRGB); + profile = SkEncodedInfo::ICCProfile::Make(skcmsProfile); + } + + // Theoretically PIEX can return JPEG compressed image or uncompressed RGB image. We only + // handle the JPEG compressed preview image here. + if (error == ::piex::Error::kOk && imageData.preview.length > 0 && + imageData.preview.format == ::piex::Image::kJpegCompressed) + { + // transferBuffer() is destructive to the rawStream. Abandon the rawStream after this + // function call. + // FIXME: one may avoid the copy of memoryStream and use the buffered rawStream. + auto memoryStream = rawStream->transferBuffer(imageData.preview.offset, + imageData.preview.length); + if (!memoryStream) { + *result = kInvalidInput; + return nullptr; + } + return SkJpegCodec::MakeFromStream(std::move(memoryStream), result, + std::move(profile)); + } + } + + if (!SkDngImage::IsTiffHeaderValid(rawStream.get())) { + *result = kUnimplemented; + return nullptr; + } + + // Takes the ownership of the rawStream. + std::unique_ptr<SkDngImage> dngImage(SkDngImage::NewFromStream(rawStream.release())); + if (!dngImage) { + *result = kInvalidInput; + return nullptr; + } + + *result = kSuccess; + return std::unique_ptr<SkCodec>(new SkRawCodec(dngImage.release())); +} + +SkCodec::Result SkRawCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, + size_t dstRowBytes, const Options& options, + int* rowsDecoded) { + const int width = dstInfo.width(); + const int height = dstInfo.height(); + std::unique_ptr<dng_image> image(fDngImage->render(width, height)); + if (!image) { + return kInvalidInput; + } + + // Because the DNG SDK can not guarantee to render to requested size, we allow a small + // difference. Only the overlapping region will be converted. + const float maxDiffRatio = 1.03f; + const dng_point& imageSize = image->Size(); + if (imageSize.h / (float) width > maxDiffRatio || imageSize.h < width || + imageSize.v / (float) height > maxDiffRatio || imageSize.v < height) { + return SkCodec::kInvalidScale; + } + + void* dstRow = dst; + SkAutoTMalloc<uint8_t> srcRow(width * 3); + + dng_pixel_buffer buffer; + buffer.fData = &srcRow[0]; + buffer.fPlane = 0; + buffer.fPlanes = 3; + buffer.fColStep = buffer.fPlanes; + buffer.fPlaneStep = 1; + buffer.fPixelType = ttByte; + buffer.fPixelSize = sizeof(uint8_t); + buffer.fRowStep = width * 3; + + constexpr auto srcFormat = skcms_PixelFormat_RGB_888; + skcms_PixelFormat dstFormat; + if (!sk_select_xform_format(dstInfo.colorType(), false, &dstFormat)) { + return kInvalidConversion; + } + + const skcms_ICCProfile* const srcProfile = this->getEncodedInfo().profile(); + skcms_ICCProfile dstProfileStorage; + const skcms_ICCProfile* dstProfile = nullptr; + if (auto cs = dstInfo.colorSpace()) { + cs->toProfile(&dstProfileStorage); + dstProfile = &dstProfileStorage; + } + + for (int i = 0; i < height; ++i) { + buffer.fArea = dng_rect(i, 0, i + 1, width); + + try { + image->Get(buffer, dng_image::edge_zero); + } catch (...) { + *rowsDecoded = i; + return kIncompleteInput; + } + + if (!skcms_Transform(&srcRow[0], srcFormat, skcms_AlphaFormat_Unpremul, srcProfile, + dstRow, dstFormat, skcms_AlphaFormat_Unpremul, dstProfile, + dstInfo.width())) { + SkDebugf("failed to transform\n"); + *rowsDecoded = i; + return kInternalError; + } + + dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); + } + return kSuccess; +} + +SkISize SkRawCodec::onGetScaledDimensions(float desiredScale) const { + SkASSERT(desiredScale <= 1.f); + + const SkISize dim = this->dimensions(); + SkASSERT(dim.fWidth != 0 && dim.fHeight != 0); + + if (!fDngImage->isScalable()) { + return dim; + } + + // Limits the minimum size to be 80 on the short edge. + const float shortEdge = static_cast<float>(SkTMin(dim.fWidth, dim.fHeight)); + if (desiredScale < 80.f / shortEdge) { + desiredScale = 80.f / shortEdge; + } + + // For Xtrans images, the integer-factor scaling does not support the half-size scaling case + // (stronger downscalings are fine). In this case, returns the factor "3" scaling instead. + if (fDngImage->isXtransImage() && desiredScale > 1.f / 3.f && desiredScale < 1.f) { + desiredScale = 1.f / 3.f; + } + + // Round to integer-factors. + const float finalScale = std::floor(1.f/ desiredScale); + return SkISize::Make(static_cast<int32_t>(std::floor(dim.fWidth / finalScale)), + static_cast<int32_t>(std::floor(dim.fHeight / finalScale))); +} + +bool SkRawCodec::onDimensionsSupported(const SkISize& dim) { + const SkISize fullDim = this->dimensions(); + const float fullShortEdge = static_cast<float>(SkTMin(fullDim.fWidth, fullDim.fHeight)); + const float shortEdge = static_cast<float>(SkTMin(dim.fWidth, dim.fHeight)); + + SkISize sizeFloor = this->onGetScaledDimensions(1.f / std::floor(fullShortEdge / shortEdge)); + SkISize sizeCeil = this->onGetScaledDimensions(1.f / std::ceil(fullShortEdge / shortEdge)); + return sizeFloor == dim || sizeCeil == dim; +} + +SkRawCodec::~SkRawCodec() {} + +SkRawCodec::SkRawCodec(SkDngImage* dngImage) + : INHERITED(SkEncodedInfo::Make(dngImage->width(), dngImage->height(), + SkEncodedInfo::kRGB_Color, + SkEncodedInfo::kOpaque_Alpha, 8), + skcms_PixelFormat_RGBA_8888, nullptr) + , fDngImage(dngImage) {} diff --git a/gfx/skia/skia/src/codec/SkRawCodec.h b/gfx/skia/skia/src/codec/SkRawCodec.h new file mode 100644 index 0000000000..d1c5131afa --- /dev/null +++ b/gfx/skia/skia/src/codec/SkRawCodec.h @@ -0,0 +1,65 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkRawCodec_DEFINED +#define SkRawCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" + +class SkDngImage; +class SkStream; + +/* + * + * This class implements the decoding for RAW images + * + */ +class SkRawCodec : public SkCodec { +public: + + /* + * Creates a RAW decoder + * Takes ownership of the stream + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + + ~SkRawCodec() override; + +protected: + + Result onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options&, + int*) override; + + SkEncodedImageFormat onGetEncodedFormat() const override { + return SkEncodedImageFormat::kDNG; + } + + SkISize onGetScaledDimensions(float desiredScale) const override; + + bool onDimensionsSupported(const SkISize&) override; + + // SkCodec only applies the colorXform if it's necessary for color space + // conversion. SkRawCodec will always convert, so tell SkCodec not to. + bool usesColorXform() const override { return false; } + +private: + + /* + * Creates an instance of the decoder + * Called only by NewFromStream, takes ownership of dngImage. + */ + SkRawCodec(SkDngImage* dngImage); + + std::unique_ptr<SkDngImage> fDngImage; + + typedef SkCodec INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/codec/SkSampledCodec.cpp b/gfx/skia/skia/src/codec/SkSampledCodec.cpp new file mode 100644 index 0000000000..c0c8cf02c4 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampledCodec.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "include/core/SkMath.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSampledCodec.h" +#include "src/codec/SkSampler.h" +#include "src/core/SkMathPriv.h" + +SkSampledCodec::SkSampledCodec(SkCodec* codec, ExifOrientationBehavior behavior) + : INHERITED(codec, behavior) +{} + +SkISize SkSampledCodec::accountForNativeScaling(int* sampleSizePtr, int* nativeSampleSize) const { + SkISize preSampledSize = this->codec()->dimensions(); + int sampleSize = *sampleSizePtr; + SkASSERT(sampleSize > 1); + + if (nativeSampleSize) { + *nativeSampleSize = 1; + } + + // Only JPEG supports native downsampling. + if (this->codec()->getEncodedFormat() == SkEncodedImageFormat::kJPEG) { + // See if libjpeg supports this scale directly + switch (sampleSize) { + case 2: + case 4: + case 8: + // This class does not need to do any sampling. + *sampleSizePtr = 1; + return this->codec()->getScaledDimensions(get_scale_from_sample_size(sampleSize)); + default: + break; + } + + // Check if sampleSize is a multiple of something libjpeg can support. + int remainder; + const int sampleSizes[] = { 8, 4, 2 }; + for (int supportedSampleSize : sampleSizes) { + int actualSampleSize; + SkTDivMod(sampleSize, supportedSampleSize, &actualSampleSize, &remainder); + if (0 == remainder) { + float scale = get_scale_from_sample_size(supportedSampleSize); + + // this->codec() will scale to this size. + preSampledSize = this->codec()->getScaledDimensions(scale); + + // And then this class will sample it. + *sampleSizePtr = actualSampleSize; + if (nativeSampleSize) { + *nativeSampleSize = supportedSampleSize; + } + break; + } + } + } + + return preSampledSize; +} + +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::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()->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.makeDimensions(scaledSize); + + { + // 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 = 0; + const SkCodec::Result incResult = this->codec()->incrementalDecode(&rowsDecoded); + if (incResult == SkCodec::kSuccess) { + return SkCodec::kSuccess; + } + SkASSERT(incResult == SkCodec::kIncompleteInput || incResult == SkCodec::kErrorInInput); + + // FIXME: Can zero initialized be read from SkCodec::fOptions? + this->codec()->fillIncompleteImage(scaledInfo, pixels, rowBytes, + options.fZeroInitialized, scaledSubsetHeight, rowsDecoded); + return incResult; + } 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::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(). + 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.makeDimensions(nativeSize); + + { + // 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 = 0; + 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::kIncompleteInput + || startResult == SkCodec::kErrorInInput) { + return SkCodec::kInvalidInput; + } 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::kIncompleteInput == result || SkCodec::kErrorInInput == result) { + return SkCodec::kInvalidInput; + } else 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 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, options.fZeroInitialized); + } + return SkCodec::kIncompleteInput; + } + default: + SkASSERT(false); + return SkCodec::kUnimplemented; + } +} diff --git a/gfx/skia/skia/src/codec/SkSampledCodec.h b/gfx/skia/skia/src/codec/SkSampledCodec.h new file mode 100644 index 0000000000..c92f944a62 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampledCodec.h @@ -0,0 +1,59 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkSampledCodec_DEFINED +#define SkSampledCodec_DEFINED + +#include "include/codec/SkAndroidCodec.h" +#include "include/codec/SkCodec.h" + +/** + * This class implements the functionality of SkAndroidCodec. Scaling will + * be provided by sampling if it cannot be provided by fCodec. + */ +class SkSampledCodec : public SkAndroidCodec { +public: + explicit SkSampledCodec(SkCodec*, ExifOrientationBehavior); + + ~SkSampledCodec() override {} + +protected: + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override { return true; } + + SkCodec::Result onGetAndroidPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions& options) override; + +private: + /** + * Find the best way to account for native scaling. + * + * Return a size that fCodec can scale to, and adjust sampleSize to finish scaling. + * + * @param sampleSize As an input, the requested sample size. + * As an output, sampling needed after letting fCodec + * scale to the returned dimensions. + * @param nativeSampleSize Optional output parameter. Will be set to the + * effective sample size done by fCodec. + * @return SkISize The size that fCodec should scale to. + */ + SkISize accountForNativeScaling(int* sampleSize, int* nativeSampleSize = nullptr) const; + + /** + * This fulfills the same contract as onGetAndroidPixels(). + * + * We call this function from onGetAndroidPixels() if we have determined + * that fCodec does not support the requested scale, and we need to + * provide the scale by sampling. + */ + SkCodec::Result sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, + const AndroidOptions& options); + + typedef SkAndroidCodec INHERITED; +}; +#endif // SkSampledCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkSampler.cpp b/gfx/skia/skia/src/codec/SkSampler.cpp new file mode 100644 index 0000000000..3820d71495 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampler.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/codec/SkCodec.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSampler.h" +#include "src/core/SkUtils.h" + +void SkSampler::Fill(const SkImageInfo& info, void* dst, size_t rowBytes, + SkCodec::ZeroInitialized zeroInit) { + SkASSERT(dst != nullptr); + + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + return; + } + + const int width = info.width(); + const int numRows = info.height(); + + // Use the proper memset routine to fill the remaining bytes + switch (info.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + uint32_t* dstRow = (uint32_t*) dst; + for (int row = 0; row < numRows; row++) { + sk_memset32(dstRow, 0, width); + dstRow = SkTAddOffset<uint32_t>(dstRow, rowBytes); + } + break; + } + case kRGB_565_SkColorType: { + uint16_t* dstRow = (uint16_t*) dst; + for (int row = 0; row < numRows; row++) { + sk_memset16(dstRow, 0, width); + dstRow = SkTAddOffset<uint16_t>(dstRow, rowBytes); + } + break; + } + case kGray_8_SkColorType: { + uint8_t* dstRow = (uint8_t*) dst; + for (int row = 0; row < numRows; row++) { + memset(dstRow, 0, width); + dstRow = SkTAddOffset<uint8_t>(dstRow, rowBytes); + } + break; + } + case kRGBA_F16_SkColorType: { + uint64_t* dstRow = (uint64_t*) dst; + for (int row = 0; row < numRows; row++) { + sk_memset64(dstRow, 0, width); + dstRow = SkTAddOffset<uint64_t>(dstRow, rowBytes); + } + break; + } + default: + SkCodecPrintf("Error: Unsupported dst color type for fill(). Doing nothing.\n"); + SkASSERT(false); + break; + } +} diff --git a/gfx/skia/skia/src/codec/SkSampler.h b/gfx/skia/skia/src/codec/SkSampler.h new file mode 100644 index 0000000000..d03b80aa26 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSampler.h @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkSampler_DEFINED +#define SkSampler_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkCodecPriv.h" + +class SkSampler : public SkNoncopyable { +public: + /** + * Update the sampler to sample every sampleX'th pixel. Returns the + * width after sampling. + */ + int setSampleX(int sampleX) { + return this->onSetSampleX(sampleX); + } + + /** + * Update the sampler to sample every sampleY'th row. + */ + void setSampleY(int sampleY) { + fSampleY = sampleY; + } + + /** + * Retrieve the value set for sampleY. + */ + int sampleY() const { + return fSampleY; + } + + /** + * Based on fSampleY, return whether this row belongs in the output. + * + * @param row Row of the image, starting with the first row in the subset. + */ + bool rowNeeded(int row) const { + return (row - get_start_coord(fSampleY)) % fSampleY == 0; + } + + /** + * Fill the remainder of the destination with 0. + * + * 0 has a different meaning depending on the SkColorType. For color types + * with transparency, this means transparent. For k565 and kGray, 0 is + * black. + * + * @param info + * Contains the color type of the rows to fill. + * Contains the pixel width of the destination rows to fill + * Contains the number of rows that we need to fill. + * + * @param dst + * The destination row to fill. + * + * @param rowBytes + * Stride in bytes of the destination. + * + * @param zeroInit + * Indicates whether memory is already zero initialized. + */ + static void Fill(const SkImageInfo& info, void* dst, size_t rowBytes, + SkCodec::ZeroInitialized zeroInit); + + virtual int fillWidth() const = 0; + + SkSampler() + : fSampleY(1) + {} + + virtual ~SkSampler() {} +private: + int fSampleY; + + virtual int onSetSampleX(int) = 0; +}; + +#endif // SkSampler_DEFINED diff --git a/gfx/skia/skia/src/codec/SkScalingCodec.h b/gfx/skia/skia/src/codec/SkScalingCodec.h new file mode 100644 index 0000000000..799ca3852b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkScalingCodec.h @@ -0,0 +1,39 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkScalingCodec_DEFINED +#define SkScalingCodec_DEFINED + +#include "include/codec/SkCodec.h" + +// Helper class for an SkCodec that supports arbitrary downscaling. +class SkScalingCodec : public SkCodec { +protected: + SkScalingCodec(SkEncodedInfo&& info, XformFormat srcFormat, std::unique_ptr<SkStream> stream, + SkEncodedOrigin origin = kTopLeft_SkEncodedOrigin) + : INHERITED(std::move(info), srcFormat, std::move(stream), origin) {} + + SkISize onGetScaledDimensions(float desiredScale) const override { + SkISize dim = this->dimensions(); + // SkCodec treats zero dimensional images as errors, so the minimum size + // that we will recommend is 1x1. + dim.fWidth = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fWidth)); + dim.fHeight = SkTMax(1, SkScalarRoundToInt(desiredScale * dim.fHeight)); + return dim; + } + + bool onDimensionsSupported(const SkISize& requested) override { + SkISize dim = this->dimensions(); + int w = requested.width(); + int h = requested.height(); + return 1 <= w && w <= dim.width() && 1 <= h && h <= dim.height(); + } + +private: + typedef SkCodec INHERITED; +}; + +#endif // SkScalingCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkStreamBuffer.cpp b/gfx/skia/skia/src/codec/SkStreamBuffer.cpp new file mode 100644 index 0000000000..cdac862fdd --- /dev/null +++ b/gfx/skia/skia/src/codec/SkStreamBuffer.cpp @@ -0,0 +1,88 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkStreamBuffer.h" + +SkStreamBuffer::SkStreamBuffer(std::unique_ptr<SkStream> stream) + : fStream(std::move(stream)) + , fPosition(0) + , fBytesBuffered(0) + , fHasLengthAndPosition(fStream->hasLength() && fStream->hasPosition()) + , fTrulyBuffered(0) +{} + +SkStreamBuffer::~SkStreamBuffer() { + fMarkedData.foreach([](size_t, SkData** data) { (*data)->unref(); }); +} + +const char* SkStreamBuffer::get() const { + SkASSERT(fBytesBuffered >= 1); + if (fHasLengthAndPosition && fTrulyBuffered < fBytesBuffered) { + const size_t bytesToBuffer = fBytesBuffered - fTrulyBuffered; + char* dst = SkTAddOffset<char>(const_cast<char*>(fBuffer), fTrulyBuffered); + SkDEBUGCODE(const size_t bytesRead =) + // This stream is rewindable, so it should be safe to call the non-const + // read() + const_cast<SkStream*>(fStream.get())->read(dst, bytesToBuffer); + SkASSERT(bytesRead == bytesToBuffer); + fTrulyBuffered = fBytesBuffered; + } + return fBuffer; +} + +bool SkStreamBuffer::buffer(size_t totalBytesToBuffer) { + // FIXME (scroggo): What should we do if the client tries to read too much? + // Should not be a problem in GIF. + SkASSERT(totalBytesToBuffer <= kMaxSize); + + if (totalBytesToBuffer <= fBytesBuffered) { + return true; + } + + if (fHasLengthAndPosition) { + const size_t remaining = fStream->getLength() - fStream->getPosition() + fTrulyBuffered; + fBytesBuffered = SkTMin(remaining, totalBytesToBuffer); + } else { + const size_t extraBytes = totalBytesToBuffer - fBytesBuffered; + const size_t bytesBuffered = fStream->read(fBuffer + fBytesBuffered, extraBytes); + fBytesBuffered += bytesBuffered; + } + return fBytesBuffered == totalBytesToBuffer; +} + +size_t SkStreamBuffer::markPosition() { + SkASSERT(fBytesBuffered >= 1); + if (!fHasLengthAndPosition) { + sk_sp<SkData> data(SkData::MakeWithCopy(fBuffer, fBytesBuffered)); + SkASSERT(nullptr == fMarkedData.find(fPosition)); + fMarkedData.set(fPosition, data.release()); + } + return fPosition; +} + +sk_sp<SkData> SkStreamBuffer::getDataAtPosition(size_t position, size_t length) { + if (!fHasLengthAndPosition) { + SkData** data = fMarkedData.find(position); + SkASSERT(data); + SkASSERT((*data)->size() == length); + return sk_ref_sp<SkData>(*data); + } + + SkASSERT(length <= fStream->getLength() && + position <= fStream->getLength() - length); + + const size_t oldPosition = fStream->getPosition(); + if (!fStream->seek(position)) { + return nullptr; + } + + sk_sp<SkData> data(SkData::MakeUninitialized(length)); + void* dst = data->writable_data(); + const bool success = fStream->read(dst, length) == length; + fStream->seek(oldPosition); + return success ? data : nullptr; +} diff --git a/gfx/skia/skia/src/codec/SkStreamBuffer.h b/gfx/skia/skia/src/codec/SkStreamBuffer.h new file mode 100644 index 0000000000..465d2f54ee --- /dev/null +++ b/gfx/skia/skia/src/codec/SkStreamBuffer.h @@ -0,0 +1,116 @@ +/* + * Copyright 2016 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStreamBuffer_DEFINED +#define SkStreamBuffer_DEFINED + +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/core/SkTypes.h" +#include "include/private/SkTHash.h" + +/** + * Helper class for reading from a stream that may not have all its data + * available yet. + * + * Used by GIFImageReader, and currently set up for that use case. + * + * Buffers up to 256 * 3 bytes (256 colors, with 3 bytes each) to support GIF. + * FIXME (scroggo): Make this more general purpose? + */ +class SkStreamBuffer : SkNoncopyable { +public: + SkStreamBuffer(std::unique_ptr<SkStream>); + ~SkStreamBuffer(); + + /** + * Return a pointer the buffered data. + * + * The number of bytes buffered is the number passed to buffer() + * after the last call to flush(). + */ + const char* get() const; + + /** + * Buffer from the stream into our buffer. + * + * If this call returns true, get() can be used to access |bytes| bytes + * from the stream. In addition, markPosition() can be called to mark this + * position and enable calling getAtPosition() later to retrieve |bytes| + * bytes. + * + * @param bytes Total number of bytes desired. + * + * @return Whether all bytes were successfully buffered. + */ + bool buffer(size_t bytes); + + /** + * Flush the buffer. + * + * After this call, no bytes are buffered. + */ + void flush() { + if (fHasLengthAndPosition) { + if (fTrulyBuffered < fBytesBuffered) { + fStream->move(fBytesBuffered - fTrulyBuffered); + } + fTrulyBuffered = 0; + } + fPosition += fBytesBuffered; + fBytesBuffered = 0; + } + + /** + * Mark the current position in the stream to return to it later. + * + * This is the position of the start of the buffer. After this call, a + * a client can call getDataAtPosition to retrieve all the bytes currently + * buffered. + * + * @return size_t Position which can be passed to getDataAtPosition later + * to retrieve the data currently buffered. + */ + size_t markPosition(); + + /** + * Retrieve data at position, as previously marked by markPosition(). + * + * @param position Position to retrieve data, as marked by markPosition(). + * @param length Amount of data required at position. + * @return SkData The data at position. + */ + sk_sp<SkData> getDataAtPosition(size_t position, size_t length); + +private: + static constexpr size_t kMaxSize = 256 * 3; + + std::unique_ptr<SkStream> fStream; + size_t fPosition; + char fBuffer[kMaxSize]; + size_t fBytesBuffered; + // If the stream has a length and position, we can make two optimizations: + // - We can skip buffering + // - During parsing, we can store the position and size of data that is + // needed later during decoding. + const bool fHasLengthAndPosition; + // When fHasLengthAndPosition is true, we do not need to actually buffer + // inside buffer(). We'll buffer inside get(). This keeps track of how many + // bytes we've buffered inside get(), for the (non-existent) case of: + // buffer(n) + // get() + // buffer(n + u) + // get() + // The second call to get() needs to only truly buffer the part that was + // not already buffered. + mutable size_t fTrulyBuffered; + // Only used if !fHasLengthAndPosition. In that case, markPosition will + // copy into an SkData, stored here. + SkTHashMap<size_t, SkData*> fMarkedData; +}; +#endif // SkStreamBuffer_DEFINED + diff --git a/gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h b/gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h new file mode 100644 index 0000000000..413ec62800 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h @@ -0,0 +1,76 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkStubHeifDecoderAPI_DEFINED +#define SkStubHeifDecoderAPI_DEFINED + +// This stub implementation of HeifDecoderAPI.h lets us compile SkHeifCodec.cpp +// even when libheif is not available. It, of course, does nothing and fails to decode. + +#include <memory> +#include <stddef.h> +#include <stdint.h> + +enum HeifColorFormat { + kHeifColorFormat_RGB565, + kHeifColorFormat_RGBA_8888, + kHeifColorFormat_BGRA_8888, +}; + +struct HeifStream { + virtual ~HeifStream() {} + + virtual size_t read(void*, size_t) = 0; + virtual bool rewind() = 0; + virtual bool seek(size_t) = 0; + virtual bool hasLength() const = 0; + virtual size_t getLength() const = 0; +}; + +struct HeifFrameInfo { + uint32_t mWidth; + uint32_t mHeight; + int32_t mRotationAngle; // Rotation angle, clockwise, should be multiple of 90 + uint32_t mBytesPerPixel; // Number of bytes for one pixel + int64_t mDurationUs; // Duration of the frame in us + std::vector<uint8_t> mIccData; // ICC data array +}; + +struct HeifDecoder { + bool init(HeifStream* stream, HeifFrameInfo*) { + delete stream; + return false; + } + + bool getSequenceInfo(HeifFrameInfo* frameInfo, size_t *frameCount) { + return false; + } + + bool decode(HeifFrameInfo*) { + return false; + } + + bool decodeSequence(int frameIndex, HeifFrameInfo* frameInfo) { + return false; + } + + bool setOutputColor(HeifColorFormat) { + return false; + } + + bool getScanline(uint8_t*) { + return false; + } + + int skipScanlines(int) { + return 0; + } +}; + +static inline HeifDecoder* createHeifDecoder() { return new HeifDecoder; } + +#endif//SkStubHeifDecoderAPI_DEFINED diff --git a/gfx/skia/skia/src/codec/SkSwizzler.cpp b/gfx/skia/skia/src/codec/SkSwizzler.cpp new file mode 100644 index 0000000000..e29c41428b --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSwizzler.cpp @@ -0,0 +1,1237 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "include/private/SkColorData.h" +#include "include/private/SkHalf.h" +#include "include/private/SkTemplates.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkSwizzler.h" +#include "src/core/SkOpts.h" + +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + #include "include/android/SkAndroidFrameworkUtils.h" +#endif + +static void copy(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + memcpy(dst, src + offset, width * bpp); +} + +static void sample1(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint8_t* dst8 = (uint8_t*) dst; + for (int x = 0; x < width; x++) { + dst8[x] = *src; + src += deltaSrc; + } +} + +static void sample2(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint16_t* dst16 = (uint16_t*) dst; + for (int x = 0; x < width; x++) { + dst16[x] = *((const uint16_t*) src); + src += deltaSrc; + } +} + +static void sample4(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = *((const uint32_t*) src); + src += deltaSrc; + } +} + +static void sample6(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint8_t* dst8 = (uint8_t*) dst; + for (int x = 0; x < width; x++) { + memcpy(dst8, src, 6); + dst8 += 6; + src += deltaSrc; + } +} + +static void sample8(void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + src += offset; + uint64_t* dst64 = (uint64_t*) dst; + for (int x = 0; x < width; x++) { + dst64[x] = *((const uint64_t*) src); + src += deltaSrc; + } +} + +// kBit +// These routines exclusively choose between white and black + +#define GRAYSCALE_BLACK 0 +#define GRAYSCALE_WHITE 0xFF + + +// same as swizzle_bit_to_index and swizzle_bit_to_n32 except for value assigned to dst[x] +static void swizzle_bit_to_grayscale( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + + uint8_t* SK_RESTRICT dst = (uint8_t*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7-bitIndex)) & 1) ? GRAYSCALE_WHITE : GRAYSCALE_BLACK; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7-bitIndex)) & 1) ? GRAYSCALE_WHITE : GRAYSCALE_BLACK; + } +} + +#undef GRAYSCALE_BLACK +#undef GRAYSCALE_WHITE + +// same as swizzle_bit_to_grayscale and swizzle_bit_to_index except for value assigned to dst[x] +static void swizzle_bit_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + SkPMColor* SK_RESTRICT dst = (SkPMColor*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7 - bitIndex)) & 1) ? SK_ColorWHITE : SK_ColorBLACK; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7 - bitIndex)) & 1) ? SK_ColorWHITE : SK_ColorBLACK; + } +} + +#define RGB565_BLACK 0 +#define RGB565_WHITE 0xFFFF + +static void swizzle_bit_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + uint16_t* SK_RESTRICT dst = (uint16_t*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7 - bitIndex)) & 1) ? RGB565_WHITE : RGB565_BLACK; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7 - bitIndex)) & 1) ? RGB565_WHITE : RGB565_BLACK; + } +} + +#undef RGB565_BLACK +#undef RGB565_WHITE + +static void swizzle_bit_to_f16( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor* /*ctable*/) { + constexpr uint64_t kWhite = (((uint64_t) SK_Half1) << 0) | + (((uint64_t) SK_Half1) << 16) | + (((uint64_t) SK_Half1) << 32) | + (((uint64_t) SK_Half1) << 48); + constexpr uint64_t kBlack = (((uint64_t) 0) << 0) | + (((uint64_t) 0) << 16) | + (((uint64_t) 0) << 32) | + (((uint64_t) SK_Half1) << 48); + + uint64_t* SK_RESTRICT dst = (uint64_t*) dstRow; + + // increment src by byte offset and bitIndex by bit offset + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + + dst[0] = ((currByte >> (7 - bitIndex)) & 1) ? kWhite : kBlack; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + dst[x] = ((currByte >> (7 - bitIndex)) & 1) ? kWhite : kBlack; + } +} + +// kIndex1, kIndex2, kIndex4 + +static void swizzle_small_index_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + uint16_t* dst = (uint16_t*) dstRow; + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + const uint8_t mask = (1 << bpp) - 1; + uint8_t index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[0] = SkPixel32ToPixel16(ctable[index]); + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[x] = SkPixel32ToPixel16(ctable[index]); + } +} + +static void swizzle_small_index_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + SkPMColor* dst = (SkPMColor*) dstRow; + src += offset / 8; + int bitIndex = offset % 8; + uint8_t currByte = *src; + const uint8_t mask = (1 << bpp) - 1; + uint8_t index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[0] = ctable[index]; + + for (int x = 1; x < dstWidth; x++) { + int bitOffset = bitIndex + deltaSrc; + bitIndex = bitOffset % 8; + currByte = *(src += bitOffset / 8); + index = (currByte >> (8 - bpp - bitIndex)) & mask; + dst[x] = ctable[index]; + } +} + +// kIndex + +static void swizzle_index_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + SkPMColor c = ctable[*src]; + dst[x] = c; + src += deltaSrc; + } +} + +static void swizzle_index_to_n32_skipZ( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + SkPMColor c = ctable[*src]; + if (c != 0) { + dst[x] = c; + } + src += deltaSrc; + } +} + +static void swizzle_index_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bytesPerPixel, int deltaSrc, int offset, const SkPMColor ctable[]) { + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPixel32ToPixel16(ctable[*src]); + src += deltaSrc; + } +} + +// kGray + +static void swizzle_gray_to_n32( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPackARGB32NoCheck(0xFF, *src, *src, *src); + src += deltaSrc; + } +} + +static void fast_swizzle_gray_to_n32( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + // Note that there is no need to distinguish between RGB and BGR. + // Each color channel will get the same value. + SkOpts::gray_to_RGB1((uint32_t*) dst, src + offset, width); +} + +static void swizzle_gray_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bytesPerPixel, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPack888ToRGB16(src[0], src[0], src[0]); + src += deltaSrc; + } +} + +// kGrayAlpha + +static void swizzle_grayalpha_to_n32_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + src += offset; + SkPMColor* dst32 = (SkPMColor*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = SkPackARGB32NoCheck(src[1], src[0], src[0], src[0]); + src += deltaSrc; + } +} + +static void fast_swizzle_grayalpha_to_n32_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + // Note that there is no need to distinguish between RGB and BGR. + // Each color channel will get the same value. + SkOpts::grayA_to_RGBA((uint32_t*) dst, src + offset, width); +} + +static void swizzle_grayalpha_to_n32_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + src += offset; + SkPMColor* dst32 = (SkPMColor*) dst; + for (int x = 0; x < width; x++) { + uint8_t pmgray = SkMulDiv255Round(src[1], src[0]); + dst32[x] = SkPackARGB32NoCheck(src[1], pmgray, pmgray, pmgray); + src += deltaSrc; + } +} + +static void fast_swizzle_grayalpha_to_n32_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + // Note that there is no need to distinguish between rgb and bgr. + // Each color channel will get the same value. + SkOpts::grayA_to_rgbA((uint32_t*) dst, src + offset, width); +} + +static void swizzle_grayalpha_to_a8(void* dst, const uint8_t* src, int width, int bpp, + int deltaSrc, int offset, const SkPMColor[]) { + src += offset; + uint8_t* dst8 = (uint8_t*)dst; + for (int x = 0; x < width; ++x) { + dst8[x] = src[1]; // src[0] is gray, ignored + src += deltaSrc; + } +} + +// kBGR + +static void swizzle_bgr_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPack888ToRGB16(src[2], src[1], src[0]); + src += deltaSrc; + } +} + +// kRGB + +static void swizzle_rgb_to_rgba( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPackARGB_as_RGBA(0xFF, src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void swizzle_rgb_to_bgra( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPackARGB_as_BGRA(0xFF, src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void fast_swizzle_rgb_to_rgba( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGB_to_RGB1((uint32_t*) dst, src + offset, width); +} + +static void fast_swizzle_rgb_to_bgra( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGB_to_BGR1((uint32_t*) dst, src + offset, width); +} + +static void swizzle_rgb_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bytesPerPixel, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = SkPack888ToRGB16(src[0], src[1], src[2]); + src += deltaSrc; + } +} + +// kRGBA + +static void swizzle_rgba_to_rgba_premul( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = premultiply_argb_as_rgba(src[3], src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void swizzle_rgba_to_bgra_premul( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + dst[x] = premultiply_argb_as_bgra(src[3], src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void fast_swizzle_rgba_to_rgba_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGBA_to_rgbA((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void fast_swizzle_rgba_to_bgra_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, + int offset, const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGBA_to_bgrA((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void swizzle_rgba_to_bgra_unpremul( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint32_t* SK_RESTRICT dst = reinterpret_cast<uint32_t*>(dstRow); + for (int x = 0; x < dstWidth; x++) { + unsigned alpha = src[3]; + dst[x] = SkPackARGB_as_BGRA(alpha, src[0], src[1], src[2]); + src += deltaSrc; + } +} + +static void fast_swizzle_rgba_to_bgra_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::RGBA_to_BGRA((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +// 16-bits per component kRGB and kRGBA + +static void swizzle_rgb16_to_rgba( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return 0xFF000000 | (ptr[4] << 16) | (ptr[2] << 8) | ptr[0]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgb16_to_bgra( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return 0xFF000000 | (ptr[0] << 16) | (ptr[2] << 8) | ptr[4]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgb16_to_565( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to565 = [](const uint8_t* ptr) { + return SkPack888ToRGB16(ptr[0], ptr[2], ptr[4]); + }; + + src += offset; + uint16_t* dst16 = (uint16_t*) dst; + for (int x = 0; x < width; x++) { + dst16[x] = strip16to565(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_rgba_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return (ptr[6] << 24) | (ptr[4] << 16) | (ptr[2] << 8) | ptr[0]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_rgba_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto stripAndPremul16to8 = [](const uint8_t* ptr) { + return premultiply_argb_as_rgba(ptr[6], ptr[0], ptr[2], ptr[4]); + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = stripAndPremul16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_bgra_unpremul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto strip16to8 = [](const uint8_t* ptr) { + return (ptr[6] << 24) | (ptr[0] << 16) | (ptr[2] << 8) | ptr[4]; + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = strip16to8(src); + src += deltaSrc; + } +} + +static void swizzle_rgba16_to_bgra_premul( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + auto stripAndPremul16to8 = [](const uint8_t* ptr) { + return premultiply_argb_as_bgra(ptr[6], ptr[0], ptr[2], ptr[4]); + }; + + src += offset; + uint32_t* dst32 = (uint32_t*) dst; + for (int x = 0; x < width; x++) { + dst32[x] = stripAndPremul16to8(src); + src += deltaSrc; + } +} + +// kCMYK +// +// CMYK is stored as four bytes per pixel. +// +// We will implement a crude conversion from CMYK -> RGB using formulas +// from easyrgb.com. +// +// CMYK -> CMY +// C = C * (1 - K) + K +// M = M * (1 - K) + K +// Y = Y * (1 - K) + K +// +// libjpeg actually gives us inverted CMYK, so we must subtract the +// original terms from 1. +// CMYK -> CMY +// C = (1 - C) * (1 - (1 - K)) + (1 - K) +// M = (1 - M) * (1 - (1 - K)) + (1 - K) +// Y = (1 - Y) * (1 - (1 - K)) + (1 - K) +// +// Simplifying the above expression. +// CMYK -> CMY +// C = 1 - CK +// M = 1 - MK +// Y = 1 - YK +// +// CMY -> RGB +// R = (1 - C) * 255 +// G = (1 - M) * 255 +// B = (1 - Y) * 255 +// +// Therefore the full conversion is below. This can be verified at +// www.rapidtables.com (assuming inverted CMYK). +// CMYK -> RGB +// R = C * K * 255 +// G = M * K * 255 +// B = Y * K * 255 +// +// As a final note, we have treated the CMYK values as if they were on +// a scale from 0-1, when in fact they are 8-bit ints scaling from 0-255. +// We must divide each CMYK component by 255 to obtain the true conversion +// we should perform. +// CMYK -> RGB +// R = C * K / 255 +// G = M * K / 255 +// B = Y * K / 255 +static void swizzle_cmyk_to_rgba( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + const uint8_t r = SkMulDiv255Round(src[0], src[3]); + const uint8_t g = SkMulDiv255Round(src[1], src[3]); + const uint8_t b = SkMulDiv255Round(src[2], src[3]); + + dst[x] = SkPackARGB_as_RGBA(0xFF, r, g, b); + src += deltaSrc; + } +} + +static void swizzle_cmyk_to_bgra( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + SkPMColor* SK_RESTRICT dst = (SkPMColor*)dstRow; + for (int x = 0; x < dstWidth; x++) { + const uint8_t r = SkMulDiv255Round(src[0], src[3]); + const uint8_t g = SkMulDiv255Round(src[1], src[3]); + const uint8_t b = SkMulDiv255Round(src[2], src[3]); + + dst[x] = SkPackARGB_as_BGRA(0xFF, r, g, b); + src += deltaSrc; + } +} + +static void fast_swizzle_cmyk_to_rgba( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::inverted_CMYK_to_RGB1((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void fast_swizzle_cmyk_to_bgra( + void* dst, const uint8_t* src, int width, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]) { + + // This function must not be called if we are sampling. If we are not + // sampling, deltaSrc should equal bpp. + SkASSERT(deltaSrc == bpp); + + SkOpts::inverted_CMYK_to_BGR1((uint32_t*) dst, (const uint32_t*)(src + offset), width); +} + +static void swizzle_cmyk_to_565( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + + src += offset; + uint16_t* SK_RESTRICT dst = (uint16_t*)dstRow; + for (int x = 0; x < dstWidth; x++) { + const uint8_t r = SkMulDiv255Round(src[0], src[3]); + const uint8_t g = SkMulDiv255Round(src[1], src[3]); + const uint8_t b = SkMulDiv255Round(src[2], src[3]); + + dst[x] = SkPack888ToRGB16(r, g, b); + src += deltaSrc; + } +} + +template <SkSwizzler::RowProc proc> +void SkSwizzler::SkipLeadingGrayAlphaZerosThen( + void* dst, const uint8_t* src, int width, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + SkASSERT(!ctable); + + const uint16_t* src16 = (const uint16_t*) (src + offset); + uint32_t* dst32 = (uint32_t*) dst; + + // This may miss opportunities to skip when the output is premultiplied, + // e.g. for a src pixel 0x00FF which is not zero but becomes zero after premultiplication. + while (width > 0 && *src16 == 0x0000) { + width--; + dst32++; + src16 += deltaSrc / 2; + } + proc(dst32, (const uint8_t*)src16, width, bpp, deltaSrc, 0, ctable); +} + +template <SkSwizzler::RowProc proc> +void SkSwizzler::SkipLeading8888ZerosThen( + void* SK_RESTRICT dstRow, const uint8_t* SK_RESTRICT src, int dstWidth, + int bpp, int deltaSrc, int offset, const SkPMColor ctable[]) { + SkASSERT(!ctable); + + auto src32 = (const uint32_t*)(src+offset); + auto dst32 = (uint32_t*)dstRow; + + // This may miss opportunities to skip when the output is premultiplied, + // e.g. for a src pixel 0x00FFFFFF which is not zero but becomes zero after premultiplication. + while (dstWidth > 0 && *src32 == 0x00000000) { + dstWidth--; + dst32++; + src32 += deltaSrc/4; + } + proc(dst32, (const uint8_t*)src32, dstWidth, bpp, deltaSrc, 0, ctable); +} + +std::unique_ptr<SkSwizzler> SkSwizzler::MakeSimple(int srcBPP, const SkImageInfo& dstInfo, + const SkCodec::Options& options) { + RowProc proc = nullptr; + switch (srcBPP) { + case 1: // kGray_8_SkColorType + proc = &sample1; + break; + case 2: // kRGB_565_SkColorType + proc = &sample2; + break; + case 4: // kRGBA_8888_SkColorType + // kBGRA_8888_SkColorType + proc = &sample4; + break; + case 6: // 16 bit PNG no alpha + proc = &sample6; + break; + case 8: // 16 bit PNG with alpha + proc = &sample8; + break; + default: + return nullptr; + } + + return Make(dstInfo, ©, proc, nullptr /*ctable*/, srcBPP, + dstInfo.bytesPerPixel(), options, nullptr /*frame*/); +} + +std::unique_ptr<SkSwizzler> SkSwizzler::Make(const SkEncodedInfo& encodedInfo, + const SkPMColor* ctable, + const SkImageInfo& dstInfo, + const SkCodec::Options& options, + const SkIRect* frame) { + if (SkEncodedInfo::kPalette_Color == encodedInfo.color() && nullptr == ctable) { + return nullptr; + } + + RowProc fastProc = nullptr; + RowProc proc = nullptr; + SkCodec::ZeroInitialized zeroInit = options.fZeroInitialized; + const bool premultiply = (SkEncodedInfo::kOpaque_Alpha != encodedInfo.alpha()) && + (kPremul_SkAlphaType == dstInfo.alphaType()); + + switch (encodedInfo.color()) { + case SkEncodedInfo::kGray_Color: + switch (encodedInfo.bitsPerComponent()) { + case 1: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc = &swizzle_bit_to_n32; + break; + case kRGB_565_SkColorType: + proc = &swizzle_bit_to_565; + break; + case kGray_8_SkColorType: + proc = &swizzle_bit_to_grayscale; + break; + case kRGBA_F16_SkColorType: + proc = &swizzle_bit_to_f16; + break; + default: + return nullptr; + } + break; + case 8: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc = &swizzle_gray_to_n32; + fastProc = &fast_swizzle_gray_to_n32; + break; + case kGray_8_SkColorType: + proc = &sample1; + fastProc = © + break; + case kRGB_565_SkColorType: + proc = &swizzle_gray_to_565; + break; + default: + return nullptr; + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kXAlpha_Color: + case SkEncodedInfo::kGrayAlpha_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeadingGrayAlphaZerosThen + <swizzle_grayalpha_to_n32_premul>; + fastProc = &SkipLeadingGrayAlphaZerosThen + <fast_swizzle_grayalpha_to_n32_premul>; + } else { + proc = &swizzle_grayalpha_to_n32_premul; + fastProc = &fast_swizzle_grayalpha_to_n32_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeadingGrayAlphaZerosThen + <swizzle_grayalpha_to_n32_unpremul>; + fastProc = &SkipLeadingGrayAlphaZerosThen + <fast_swizzle_grayalpha_to_n32_unpremul>; + } else { + proc = &swizzle_grayalpha_to_n32_unpremul; + fastProc = &fast_swizzle_grayalpha_to_n32_unpremul; + } + } + break; + case kAlpha_8_SkColorType: + proc = &swizzle_grayalpha_to_a8; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kPalette_Color: + // We assume that the color table is premultiplied and swizzled + // as desired. + switch (encodedInfo.bitsPerComponent()) { + case 1: + case 2: + case 4: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + proc = &swizzle_small_index_to_n32; + break; + case kRGB_565_SkColorType: + proc = &swizzle_small_index_to_565; + break; + default: + return nullptr; + } + break; + case 8: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &swizzle_index_to_n32_skipZ; + } else { + proc = &swizzle_index_to_n32; + } + break; + case kRGB_565_SkColorType: + proc = &swizzle_index_to_565; + break; + default: + return nullptr; + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::k565_Color: + // Treat 565 exactly like RGB (since it's still encoded as 8 bits per component). + // We just mark as 565 when we have a hint that there are only 5/6/5 "significant" + // bits in each channel. + case SkEncodedInfo::kRGB_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = &swizzle_rgb16_to_rgba; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + proc = &swizzle_rgb_to_rgba; + fastProc = &fast_swizzle_rgb_to_rgba; + break; + case kBGRA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = &swizzle_rgb16_to_bgra; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + proc = &swizzle_rgb_to_bgra; + fastProc = &fast_swizzle_rgb_to_bgra; + break; + case kRGB_565_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = &swizzle_rgb16_to_565; + break; + } + + proc = &swizzle_rgb_to_565; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kRGBA_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = premultiply ? &swizzle_rgba16_to_rgba_premul : + &swizzle_rgba16_to_rgba_unpremul; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<swizzle_rgba_to_rgba_premul>; + fastProc = &SkipLeading8888ZerosThen + <fast_swizzle_rgba_to_rgba_premul>; + } else { + proc = &swizzle_rgba_to_rgba_premul; + fastProc = &fast_swizzle_rgba_to_rgba_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<sample4>; + fastProc = &SkipLeading8888ZerosThen<copy>; + } else { + proc = &sample4; + fastProc = © + } + } + break; + case kBGRA_8888_SkColorType: + if (16 == encodedInfo.bitsPerComponent()) { + proc = premultiply ? &swizzle_rgba16_to_bgra_premul : + &swizzle_rgba16_to_bgra_unpremul; + break; + } + + SkASSERT(8 == encodedInfo.bitsPerComponent()); + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<swizzle_rgba_to_bgra_premul>; + fastProc = &SkipLeading8888ZerosThen + <fast_swizzle_rgba_to_bgra_premul>; + } else { + proc = &swizzle_rgba_to_bgra_premul; + fastProc = &fast_swizzle_rgba_to_bgra_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<swizzle_rgba_to_bgra_unpremul>; + fastProc = &SkipLeading8888ZerosThen + <fast_swizzle_rgba_to_bgra_unpremul>; + } else { + proc = &swizzle_rgba_to_bgra_unpremul; + fastProc = &fast_swizzle_rgba_to_bgra_unpremul; + } + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kBGR_Color: + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + proc = &swizzle_rgb_to_rgba; + fastProc = &fast_swizzle_rgb_to_rgba; + break; + case kRGBA_8888_SkColorType: + proc = &swizzle_rgb_to_bgra; + fastProc = &fast_swizzle_rgb_to_bgra; + break; + case kRGB_565_SkColorType: + proc = &swizzle_bgr_to_565; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kBGRX_Color: + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + proc = &swizzle_rgb_to_rgba; + break; + case kRGBA_8888_SkColorType: + proc = &swizzle_rgb_to_bgra; + break; + case kRGB_565_SkColorType: + proc = &swizzle_bgr_to_565; + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kBGRA_Color: + switch (dstInfo.colorType()) { + case kBGRA_8888_SkColorType: + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<swizzle_rgba_to_rgba_premul>; + fastProc = &SkipLeading8888ZerosThen + <fast_swizzle_rgba_to_rgba_premul>; + } else { + proc = &swizzle_rgba_to_rgba_premul; + fastProc = &fast_swizzle_rgba_to_rgba_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<sample4>; + fastProc = &SkipLeading8888ZerosThen<copy>; + } else { + proc = &sample4; + fastProc = © + } + } + break; + case kRGBA_8888_SkColorType: + if (premultiply) { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<swizzle_rgba_to_bgra_premul>; + fastProc = &SkipLeading8888ZerosThen + <fast_swizzle_rgba_to_bgra_premul>; + } else { + proc = &swizzle_rgba_to_bgra_premul; + fastProc = &fast_swizzle_rgba_to_bgra_premul; + } + } else { + if (SkCodec::kYes_ZeroInitialized == zeroInit) { + proc = &SkipLeading8888ZerosThen<swizzle_rgba_to_bgra_unpremul>; + fastProc = &SkipLeading8888ZerosThen + <fast_swizzle_rgba_to_bgra_unpremul>; + } else { + proc = &swizzle_rgba_to_bgra_unpremul; + fastProc = &fast_swizzle_rgba_to_bgra_unpremul; + } + } + break; + default: + return nullptr; + } + break; + case SkEncodedInfo::kInvertedCMYK_Color: + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + proc = &swizzle_cmyk_to_rgba; + fastProc = &fast_swizzle_cmyk_to_rgba; + break; + case kBGRA_8888_SkColorType: + proc = &swizzle_cmyk_to_bgra; + fastProc = &fast_swizzle_cmyk_to_bgra; + break; + case kRGB_565_SkColorType: + proc = &swizzle_cmyk_to_565; + break; + default: + return nullptr; + } + break; + default: + return nullptr; + } + + // Store bpp in bytes if it is an even multiple, otherwise use bits + uint8_t bitsPerPixel = encodedInfo.bitsPerPixel(); + int srcBPP = SkIsAlign8(bitsPerPixel) ? bitsPerPixel / 8 : bitsPerPixel; + int dstBPP = dstInfo.bytesPerPixel(); + return Make(dstInfo, fastProc, proc, ctable, srcBPP, dstBPP, options, frame); +} + +std::unique_ptr<SkSwizzler> SkSwizzler::Make(const SkImageInfo& dstInfo, + RowProc fastProc, RowProc proc, const SkPMColor* ctable, int srcBPP, + int dstBPP, const SkCodec::Options& options, const SkIRect* frame) { + int srcOffset = 0; + int srcWidth = dstInfo.width(); + int dstOffset = 0; + int dstWidth = srcWidth; + if (options.fSubset) { + // We do not currently support subset decodes for image types that may have + // frames (gif). + SkASSERT(!frame); + srcOffset = options.fSubset->left(); + srcWidth = options.fSubset->width(); + dstWidth = srcWidth; + } else if (frame) { + dstOffset = frame->left(); + srcWidth = frame->width(); + } + + return std::unique_ptr<SkSwizzler>(new SkSwizzler(fastProc, proc, ctable, srcOffset, srcWidth, + dstOffset, dstWidth, srcBPP, dstBPP)); +} + +SkSwizzler::SkSwizzler(RowProc fastProc, RowProc proc, const SkPMColor* ctable, int srcOffset, + int srcWidth, int dstOffset, int dstWidth, int srcBPP, int dstBPP) + : fFastProc(fastProc) + , fSlowProc(proc) + , fActualProc(fFastProc ? fFastProc : fSlowProc) + , fColorTable(ctable) + , fSrcOffset(srcOffset) + , fDstOffset(dstOffset) + , fSrcOffsetUnits(srcOffset * srcBPP) + , fDstOffsetBytes(dstOffset * dstBPP) + , fSrcWidth(srcWidth) + , fDstWidth(dstWidth) + , fSwizzleWidth(srcWidth) + , fAllocatedWidth(dstWidth) + , fSampleX(1) + , fSrcBPP(srcBPP) + , fDstBPP(dstBPP) +{} + +int SkSwizzler::onSetSampleX(int sampleX) { + SkASSERT(sampleX > 0); + + fSampleX = sampleX; + fDstOffsetBytes = (fDstOffset / sampleX) * fDstBPP; + fSwizzleWidth = get_scaled_dimension(fSrcWidth, sampleX); + fAllocatedWidth = get_scaled_dimension(fDstWidth, sampleX); + + int frameSampleX = sampleX; + if (fSrcWidth < fDstWidth) { + // Although SkSampledCodec adjusted sampleX so that it will never be + // larger than the width of the image (or subset, if applicable), it + // doesn't account for the width of a subset frame (i.e. gif). As a + // result, get_start_coord(sampleX) could result in fSrcOffsetUnits + // being wider than fSrcWidth. Compute a sampling rate based on the + // frame width to ensure that fSrcOffsetUnits is sensible. + frameSampleX = fSrcWidth / fSwizzleWidth; + } + fSrcOffsetUnits = (get_start_coord(frameSampleX) + fSrcOffset) * fSrcBPP; + + if (fDstOffsetBytes > 0) { + const size_t dstSwizzleBytes = fSwizzleWidth * fDstBPP; + const size_t dstAllocatedBytes = fAllocatedWidth * fDstBPP; + if (fDstOffsetBytes + dstSwizzleBytes > dstAllocatedBytes) { +#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK + SkAndroidFrameworkUtils::SafetyNetLog("118143775"); +#endif + SkASSERT(dstSwizzleBytes <= dstAllocatedBytes); + fDstOffsetBytes = dstAllocatedBytes - dstSwizzleBytes; + } + } + + // The optimized swizzler functions do not support sampling. Sampled swizzles + // are already fast because they skip pixels. We haven't seen a situation + // where speeding up sampling has a significant impact on total decode time. + if (1 == fSampleX && fFastProc) { + fActualProc = fFastProc; + } else { + fActualProc = fSlowProc; + } + + return fAllocatedWidth; +} + +void SkSwizzler::swizzle(void* dst, const uint8_t* SK_RESTRICT src) { + SkASSERT(nullptr != dst && nullptr != src); + fActualProc(SkTAddOffset<void>(dst, fDstOffsetBytes), src, fSwizzleWidth, fSrcBPP, + fSampleX * fSrcBPP, fSrcOffsetUnits, fColorTable); +} diff --git a/gfx/skia/skia/src/codec/SkSwizzler.h b/gfx/skia/skia/src/codec/SkSwizzler.h new file mode 100644 index 0000000000..c71b93a464 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkSwizzler.h @@ -0,0 +1,222 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkSwizzler_DEFINED +#define SkSwizzler_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColor.h" +#include "include/core/SkImageInfo.h" +#include "src/codec/SkSampler.h" + +class SkSwizzler : public SkSampler { +public: + /** + * Create a new SkSwizzler. + * @param encodedInfo Description of the format of the encoded data. + * @param ctable Unowned pointer to an array of up to 256 colors for an + * index source. + * @param dstInfo Describes the destination. + * @param options Contains partial scanline information and whether the dst is zero- + * initialized. + * @param frame Is non-NULL if the source pixels are part of an image + * frame that is a subset of the full image. + * + * Note that a deeper discussion of partial scanline subsets and image frame + * subsets is below. Currently, we do not support both simultaneously. If + * options->fSubset is non-NULL, frame must be NULL. + * + * @return A new SkSwizzler or nullptr on failure. + */ + static std::unique_ptr<SkSwizzler> Make(const SkEncodedInfo& encodedInfo, + const SkPMColor* ctable, const SkImageInfo& dstInfo, const SkCodec::Options&, + const SkIRect* frame = nullptr); + + /** + * Create a simplified swizzler that does not need to do format conversion. The swizzler + * only needs to sample and/or subset. + * + * @param srcBPP Bytes per pixel of the source. + * @param dstInfo Describes the destination. + * @param options Contains partial scanline information and whether the dst is zero- + * initialized. + * @return A new SkSwizzler or nullptr on failure. + */ + static std::unique_ptr<SkSwizzler> MakeSimple(int srcBPP, const SkImageInfo& dstInfo, + const SkCodec::Options&); + + /** + * Swizzle a line. Generally this will be called height times, once + * for each row of source. + * By allowing the caller to pass in the dst pointer, we give the caller + * flexibility to use the swizzler even when the encoded data does not + * store the rows in order. This also improves usability for scaled and + * subset decodes. + * @param dst Where we write the output. + * @param src The next row of the source data. + */ + void swizzle(void* dst, const uint8_t* SK_RESTRICT src); + + int fillWidth() const override { + return fAllocatedWidth; + } + + /** + * If fSampleX > 1, the swizzler is sampling every fSampleX'th pixel and + * discarding the rest. + * + * This getter is currently used by SkBmpStandardCodec for Bmp-in-Ico decodes. + * Ideally, the subclasses of SkCodec would have no knowledge of sampling, but + * this allows us to apply a transparency mask to pixels after swizzling. + */ + int sampleX() const { return fSampleX; } + + /** + * Returns the actual number of pixels written to destination memory, taking + * scaling, subsetting, and partial frames into account. + */ + int swizzleWidth() const { return fSwizzleWidth; } + + /** + * Returns the byte offset at which we write to destination memory, taking + * scaling, subsetting, and partial frames into account. + */ + size_t swizzleOffsetBytes() const { return fDstOffsetBytes; } + +private: + + /** + * Method for converting raw data to Skia pixels. + * @param dstRow Row in which to write the resulting pixels. + * @param src Row of src data, in format specified by SrcConfig + * @param dstWidth Width in pixels of the destination + * @param bpp if bitsPerPixel % 8 == 0, deltaSrc is bytesPerPixel + * else, deltaSrc is bitsPerPixel + * @param deltaSrc bpp * sampleX + * @param ctable Colors (used for kIndex source). + * @param offset The offset before the first pixel to sample. + Is in bytes or bits based on what deltaSrc is in. + */ + typedef void (*RowProc)(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int dstWidth, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]); + + template <RowProc Proc> + static void SkipLeading8888ZerosThen(void* SK_RESTRICT dstRow, + const uint8_t* SK_RESTRICT src, + int dstWidth, int bpp, int deltaSrc, int offset, + const SkPMColor ctable[]); + + template <RowProc Proc> + static void SkipLeadingGrayAlphaZerosThen(void* dst, const uint8_t* src, int width, int bpp, + int deltaSrc, int offset, const SkPMColor ctable[]); + + // May be NULL. We have not implemented optimized functions for all supported transforms. + const RowProc fFastProc; + // Always non-NULL. Supports sampling. + const RowProc fSlowProc; + // The actual RowProc we are using. This depends on if fFastProc is non-NULL and + // whether or not we are sampling. + RowProc fActualProc; + + const SkPMColor* fColorTable; // Unowned pointer + + // Subset Swizzles + // There are two types of subset swizzles that we support. We do not + // support both at the same time. + // TODO: If we want to support partial scanlines for gifs (which may + // use frame subsets), we will need to support both subsetting + // modes at the same time. + // (1) Partial Scanlines + // The client only wants to write a subset of the source pixels + // to the destination. This subset is specified to CreateSwizzler + // using options->fSubset. We will store subset information in + // the following fields. + // + // fSrcOffset: The starting pixel of the source. + // fSrcOffsetUnits: Derived from fSrcOffset with two key + // differences: + // (1) This takes the size of source pixels into + // account by multiplying by fSrcBPP. This may + // be measured in bits or bytes depending on + // which is natural for the SrcConfig. + // (2) If we are sampling, this will be larger + // than fSrcOffset * fSrcBPP, since sampling + // implies that we will skip some pixels. + // fDstOffset: Will be zero. There is no destination offset + // for this type of subset. + // fDstOffsetBytes: Will be zero. + // fSrcWidth: The width of the desired subset of source + // pixels, before any sampling is performed. + // fDstWidth: Will be equal to fSrcWidth, since this is also + // calculated before any sampling is performed. + // For this type of subset, the destination width + // matches the desired subset of the source. + // fSwizzleWidth: The actual number of pixels that will be + // written by the RowProc. This is a scaled + // version of fSrcWidth/fDstWidth. + // fAllocatedWidth: Will be equal to fSwizzleWidth. For this type + // of subset, the number of pixels written is the + // same as the actual width of the destination. + // (2) Frame Subset + // The client will decode the entire width of the source into a + // subset of destination memory. This subset is specified to + // CreateSwizzler in the "frame" parameter. We store subset + // information in the following fields. + // + // fSrcOffset: Will be zero. The starting pixel of the source. + // fSrcOffsetUnits: Will only be non-zero if we are sampling, + // since sampling implies that we will skip some + // pixels. Note that this is measured in bits + // or bytes depending on which is natural for + // SrcConfig. + // fDstOffset: First pixel to write in destination. + // fDstOffsetBytes: fDstOffset * fDstBPP. + // fSrcWidth: The entire width of the source pixels, before + // any sampling is performed. + // fDstWidth: The entire width of the destination memory, + // before any sampling is performed. + // fSwizzleWidth: The actual number of pixels that will be + // written by the RowProc. This is a scaled + // version of fSrcWidth. + // fAllocatedWidth: The actual number of pixels in destination + // memory. This is a scaled version of + // fDstWidth. + // + // If we are not subsetting, these fields are more straightforward. + // fSrcOffset = fDstOffet = fDstOffsetBytes = 0 + // fSrcOffsetUnits may be non-zero (we will skip the first few pixels when sampling) + // fSrcWidth = fDstWidth = Full original width + // fSwizzleWidth = fAllcoatedWidth = Scaled width (if we are sampling) + const int fSrcOffset; + const int fDstOffset; + int fSrcOffsetUnits; + int fDstOffsetBytes; + const int fSrcWidth; + const int fDstWidth; + int fSwizzleWidth; + int fAllocatedWidth; + + int fSampleX; // Step between X samples + const int fSrcBPP; // Bits/bytes per pixel for the SrcConfig + // if bitsPerPixel % 8 == 0 + // fBPP is bytesPerPixel + // else + // fBPP is bitsPerPixel + const int fDstBPP; // Bytes per pixel for the destination color type + + SkSwizzler(RowProc fastProc, RowProc proc, const SkPMColor* ctable, int srcOffset, + int srcWidth, int dstOffset, int dstWidth, int srcBPP, int dstBPP); + static std::unique_ptr<SkSwizzler> Make(const SkImageInfo& dstInfo, RowProc fastProc, + RowProc proc, const SkPMColor* ctable, int srcBPP, int dstBPP, + const SkCodec::Options& options, const SkIRect* frame); + + int onSetSampleX(int) override; + +}; +#endif // SkSwizzler_DEFINED diff --git a/gfx/skia/skia/src/codec/SkWbmpCodec.cpp b/gfx/skia/skia/src/codec/SkWbmpCodec.cpp new file mode 100644 index 0000000000..8190c50322 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWbmpCodec.cpp @@ -0,0 +1,193 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkWbmpCodec.h" + +#include "include/codec/SkCodec.h" +#include "include/core/SkData.h" +#include "include/core/SkStream.h" +#include "include/private/SkColorData.h" +#include "include/private/SkTo.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkColorTable.h" + +// Each bit represents a pixel, so width is actually a number of bits. +// A row will always be stored in bytes, so we round width up to the +// nearest multiple of 8 to get the number of bits actually in the row. +// We then divide by 8 to convert to bytes. +static inline size_t get_src_row_bytes(int width) { + return SkAlign8(width) >> 3; +} + +static inline bool valid_color_type(const SkImageInfo& dstInfo) { + switch (dstInfo.colorType()) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + case kGray_8_SkColorType: + case kRGB_565_SkColorType: + return true; + case kRGBA_F16_SkColorType: + return dstInfo.colorSpace(); + default: + return false; + } +} + +static bool read_byte(SkStream* stream, uint8_t* data) +{ + return stream->read(data, 1) == 1; +} + +// http://en.wikipedia.org/wiki/Variable-length_quantity +static bool read_mbf(SkStream* stream, uint64_t* value) { + uint64_t n = 0; + uint8_t data; + const uint64_t kLimit = 0xFE00000000000000; + SkASSERT(kLimit == ~((~static_cast<uint64_t>(0)) >> 7)); + do { + if (n & kLimit) { // Will overflow on shift by 7. + return false; + } + if (stream->read(&data, 1) != 1) { + return false; + } + n = (n << 7) | (data & 0x7F); + } while (data & 0x80); + *value = n; + return true; +} + +static bool read_header(SkStream* stream, SkISize* size) { + { + uint8_t data; + if (!read_byte(stream, &data) || data != 0) { // unknown type + return false; + } + if (!read_byte(stream, &data) || (data & 0x9F)) { // skip fixed header + return false; + } + } + + uint64_t width, height; + if (!read_mbf(stream, &width) || width > 0xFFFF || !width) { + return false; + } + if (!read_mbf(stream, &height) || height > 0xFFFF || !height) { + return false; + } + if (size) { + *size = SkISize::Make(SkToS32(width), SkToS32(height)); + } + return true; +} + +bool SkWbmpCodec::onRewind() { + return read_header(this->stream(), nullptr); +} + +bool SkWbmpCodec::readRow(uint8_t* row) { + return this->stream()->read(row, fSrcRowBytes) == fSrcRowBytes; +} + +SkWbmpCodec::SkWbmpCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream) + // Wbmp does not need a colorXform, so choose an arbitrary srcFormat. + : INHERITED(std::move(info), skcms_PixelFormat(), + std::move(stream)) + , fSrcRowBytes(get_src_row_bytes(this->dimensions().width())) + , fSwizzler(nullptr) +{} + +SkEncodedImageFormat SkWbmpCodec::onGetEncodedFormat() const { + return SkEncodedImageFormat::kWBMP; +} + +bool SkWbmpCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, + bool /*needsColorXform*/) { + return valid_color_type(dst) && valid_alpha(dst.alphaType(), srcIsOpaque); +} + +SkCodec::Result SkWbmpCodec::onGetPixels(const SkImageInfo& info, + void* dst, + size_t rowBytes, + const Options& options, + int* rowsDecoded) { + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + // Initialize the swizzler + std::unique_ptr<SkSwizzler> swizzler = SkSwizzler::Make(this->getEncodedInfo(), nullptr, info, + options); + SkASSERT(swizzler); + + // Perform the decode + SkISize size = info.dimensions(); + SkAutoTMalloc<uint8_t> src(fSrcRowBytes); + void* dstRow = dst; + for (int y = 0; y < size.height(); ++y) { + if (!this->readRow(src.get())) { + *rowsDecoded = y; + return kIncompleteInput; + } + swizzler->swizzle(dstRow, src.get()); + dstRow = SkTAddOffset<void>(dstRow, rowBytes); + } + return kSuccess; +} + +bool SkWbmpCodec::IsWbmp(const void* buffer, size_t bytesRead) { + SkMemoryStream stream(buffer, bytesRead, false); + return read_header(&stream, nullptr); +} + +std::unique_ptr<SkCodec> SkWbmpCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + SkISize size; + if (!read_header(stream.get(), &size)) { + // This already succeeded in IsWbmp, so this stream was corrupted in/ + // after rewind. + *result = kCouldNotRewind; + return nullptr; + } + *result = kSuccess; + auto info = SkEncodedInfo::Make(size.width(), size.height(), SkEncodedInfo::kGray_Color, + SkEncodedInfo::kOpaque_Alpha, 1); + return std::unique_ptr<SkCodec>(new SkWbmpCodec(std::move(info), std::move(stream))); +} + +int SkWbmpCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { + void* dstRow = dst; + for (int y = 0; y < count; ++y) { + if (!this->readRow(fSrcBuffer.get())) { + return y; + } + fSwizzler->swizzle(dstRow, fSrcBuffer.get()); + dstRow = SkTAddOffset<void>(dstRow, dstRowBytes); + } + return count; +} + +bool SkWbmpCodec::onSkipScanlines(int count) { + const size_t bytesToSkip = count * fSrcRowBytes; + return this->stream()->skip(bytesToSkip) == bytesToSkip; +} + +SkCodec::Result SkWbmpCodec::onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) { + if (options.fSubset) { + // Subsets are not supported. + return kUnimplemented; + } + + fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), nullptr, dstInfo, options); + SkASSERT(fSwizzler); + + fSrcBuffer.reset(fSrcRowBytes); + + return kSuccess; +} diff --git a/gfx/skia/skia/src/codec/SkWbmpCodec.h b/gfx/skia/skia/src/codec/SkWbmpCodec.h new file mode 100644 index 0000000000..30af5d7a5f --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWbmpCodec.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkCodec_wbmp_DEFINED +#define SkCodec_wbmp_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkColorSpace.h" +#include "src/codec/SkSwizzler.h" + +class SkWbmpCodec final : public SkCodec { +public: + static bool IsWbmp(const void*, size_t); + + /* + * Assumes IsWbmp was called and returned true + * Creates a wbmp codec + * Takes ownership of the stream + */ + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + +protected: + SkEncodedImageFormat onGetEncodedFormat() const override; + Result onGetPixels(const SkImageInfo&, void*, size_t, + const Options&, int*) override; + bool onRewind() override; + bool conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, + bool needsXform) override; + // No need to Xform; all pixels are either black or white. + bool usesColorXform() const override { return false; } +private: + SkSampler* getSampler(bool createIfNecessary) override { + SkASSERT(fSwizzler || !createIfNecessary); + return fSwizzler.get(); + } + + /* + * Read a src row from the encoded stream + */ + bool readRow(uint8_t* row); + + SkWbmpCodec(SkEncodedInfo&&, std::unique_ptr<SkStream>); + + const size_t fSrcRowBytes; + + // Used for scanline decodes: + std::unique_ptr<SkSwizzler> fSwizzler; + SkAutoTMalloc<uint8_t> fSrcBuffer; + + int onGetScanlines(void* dst, int count, size_t dstRowBytes) override; + bool onSkipScanlines(int count) override; + Result onStartScanlineDecode(const SkImageInfo& dstInfo, + const Options& options) override; + + typedef SkCodec INHERITED; +}; + +#endif // SkCodec_wbmp_DEFINED diff --git a/gfx/skia/skia/src/codec/SkWebpCodec.cpp b/gfx/skia/skia/src/codec/SkWebpCodec.cpp new file mode 100644 index 0000000000..a90d452b68 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWebpCodec.cpp @@ -0,0 +1,567 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkWebpCodec.h" + +#include "include/codec/SkCodecAnimation.h" +#include "include/core/SkBitmap.h" +#include "include/core/SkCanvas.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/codec/SkCodecAnimationPriv.h" +#include "src/codec/SkCodecPriv.h" +#include "src/codec/SkParseEncodedOrigin.h" +#include "src/codec/SkSampler.h" +#include "src/core/SkMakeUnique.h" +#include "src/core/SkRasterPipeline.h" +#include "src/core/SkStreamPriv.h" + +// A WebP decoder on top of (subset of) libwebp +// For more information on WebP image format, and libwebp library, see: +// https://code.google.com/speed/webp/ +// http://www.webmproject.org/code/#libwebp-webp-image-library +// https://chromium.googlesource.com/webm/libwebp + +// If moving libwebp out of skia source tree, path for webp headers must be +// updated accordingly. Here, we enforce using local copy in webp sub-directory. +#include "webp/decode.h" +#include "webp/demux.h" +#include "webp/encode.h" + +bool SkWebpCodec::IsWebp(const void* buf, size_t bytesRead) { + // WEBP starts with the following: + // RIFFXXXXWEBPVP + // Where XXXX is unspecified. + const char* bytes = static_cast<const char*>(buf); + return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6); +} + +// Parse headers of RIFF container, and check for valid Webp (VP8) content. +// Returns an SkWebpCodec on success +std::unique_ptr<SkCodec> SkWebpCodec::MakeFromStream(std::unique_ptr<SkStream> stream, + Result* result) { + // Webp demux needs a contiguous data buffer. + sk_sp<SkData> data = nullptr; + if (stream->getMemoryBase()) { + // It is safe to make without copy because we'll hold onto the stream. + data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength()); + } else { + data = SkCopyStreamToData(stream.get()); + + // If we are forced to copy the stream to a data, we can go ahead and delete the stream. + stream.reset(nullptr); + } + + // It's a little strange that the |demux| will outlive |webpData|, though it needs the + // pointer in |webpData| to remain valid. This works because the pointer remains valid + // until the SkData is freed. + WebPData webpData = { data->bytes(), data->size() }; + WebPDemuxState state; + SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> demux(WebPDemuxPartial(&webpData, &state)); + switch (state) { + case WEBP_DEMUX_PARSE_ERROR: + *result = kInvalidInput; + return nullptr; + case WEBP_DEMUX_PARSING_HEADER: + *result = kIncompleteInput; + return nullptr; + case WEBP_DEMUX_PARSED_HEADER: + case WEBP_DEMUX_DONE: + SkASSERT(demux); + break; + } + + const int width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); + const int height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); + + // Sanity check for image size that's about to be decoded. + { + const int64_t size = sk_64_mul(width, height); + // now check that if we are 4-bytes per pixel, we also don't overflow + if (!SkTFitsIn<int32_t>(size) || SkTo<int32_t>(size) > (0x7FFFFFFF >> 2)) { + *result = kInvalidInput; + return nullptr; + } + } + + std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr; + { + WebPChunkIterator chunkIterator; + SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); + if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunkIterator)) { + // FIXME: I think this could be MakeWithoutCopy + auto chunk = SkData::MakeWithCopy(chunkIterator.chunk.bytes, chunkIterator.chunk.size); + profile = SkEncodedInfo::ICCProfile::Make(std::move(chunk)); + } + if (profile && profile->profile()->data_color_space != skcms_Signature_RGB) { + profile = nullptr; + } + } + + SkEncodedOrigin origin = kDefault_SkEncodedOrigin; + { + WebPChunkIterator chunkIterator; + SkAutoTCallVProc<WebPChunkIterator, WebPDemuxReleaseChunkIterator> autoCI(&chunkIterator); + if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunkIterator)) { + SkParseEncodedOrigin(chunkIterator.chunk.bytes, chunkIterator.chunk.size, &origin); + } + } + + // Get the first frame and its "features" to determine the color and alpha types. + WebPIterator frame; + SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); + if (!WebPDemuxGetFrame(demux, 1, &frame)) { + *result = kIncompleteInput; + return nullptr; + } + + WebPBitstreamFeatures features; + switch (WebPGetFeatures(frame.fragment.bytes, frame.fragment.size, &features)) { + case VP8_STATUS_OK: + break; + case VP8_STATUS_SUSPENDED: + case VP8_STATUS_NOT_ENOUGH_DATA: + *result = kIncompleteInput; + return nullptr; + default: + *result = kInvalidInput; + return nullptr; + } + + const bool hasAlpha = SkToBool(frame.has_alpha) + || frame.width != width || frame.height != height; + SkEncodedInfo::Color color; + SkEncodedInfo::Alpha alpha; + switch (features.format) { + case 0: + // This indicates a "mixed" format. We could see this for + // animated webps (multiple fragments). + // We could also guess kYUV here, but I think it makes more + // sense to guess kBGRA which is likely closer to the final + // output. Otherwise, we might end up converting + // BGRA->YUVA->BGRA. + // Fallthrough: + case 2: + // This is the lossless format (BGRA). + if (hasAlpha) { + color = SkEncodedInfo::kBGRA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } else { + color = SkEncodedInfo::kBGRX_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + case 1: + // This is the lossy format (YUV). + if (hasAlpha) { + color = SkEncodedInfo::kYUVA_Color; + alpha = SkEncodedInfo::kUnpremul_Alpha; + } else { + color = SkEncodedInfo::kYUV_Color; + alpha = SkEncodedInfo::kOpaque_Alpha; + } + break; + default: + *result = kInvalidInput; + return nullptr; + } + + + *result = kSuccess; + SkEncodedInfo info = SkEncodedInfo::Make(width, height, color, alpha, 8, std::move(profile)); + return std::unique_ptr<SkCodec>(new SkWebpCodec(std::move(info), std::move(stream), + demux.release(), std::move(data), origin)); +} + +static WEBP_CSP_MODE webp_decode_mode(SkColorType dstCT, bool premultiply) { + switch (dstCT) { + case kBGRA_8888_SkColorType: + return premultiply ? MODE_bgrA : MODE_BGRA; + case kRGBA_8888_SkColorType: + return premultiply ? MODE_rgbA : MODE_RGBA; + case kRGB_565_SkColorType: + return MODE_RGB_565; + default: + return MODE_LAST; + } +} + +SkWebpCodec::Frame* SkWebpCodec::FrameHolder::appendNewFrame(bool hasAlpha) { + const int i = this->size(); + fFrames.emplace_back(i, hasAlpha ? SkEncodedInfo::kUnpremul_Alpha + : SkEncodedInfo::kOpaque_Alpha); + return &fFrames[i]; +} + +bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const { + if (!desiredSubset) { + return false; + } + + if (!this->bounds().contains(*desiredSubset)) { + return false; + } + + // As stated below, libwebp snaps to even left and top. Make sure top and left are even, so we + // decode this exact subset. + // Leave right and bottom unmodified, so we suggest a slightly larger subset than requested. + desiredSubset->fLeft = (desiredSubset->fLeft >> 1) << 1; + desiredSubset->fTop = (desiredSubset->fTop >> 1) << 1; + return true; +} + +int SkWebpCodec::onGetRepetitionCount() { + auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); + if (!(flags & ANIMATION_FLAG)) { + return 0; + } + + const int repCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT); + if (0 == repCount) { + return kRepetitionCountInfinite; + } + + return repCount; +} + +int SkWebpCodec::onGetFrameCount() { + auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS); + if (!(flags & ANIMATION_FLAG)) { + return 1; + } + + const uint32_t oldFrameCount = fFrameHolder.size(); + if (fFailed) { + return oldFrameCount; + } + + const uint32_t frameCount = WebPDemuxGetI(fDemux, WEBP_FF_FRAME_COUNT); + if (oldFrameCount == frameCount) { + // We have already parsed this. + return frameCount; + } + + fFrameHolder.reserve(frameCount); + + for (uint32_t i = oldFrameCount; i < frameCount; i++) { + WebPIterator iter; + SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoIter(&iter); + + if (!WebPDemuxGetFrame(fDemux.get(), i + 1, &iter)) { + fFailed = true; + break; + } + + // libwebp only reports complete frames of an animated image. + SkASSERT(iter.complete); + + Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha); + frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height); + frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? + SkCodecAnimation::DisposalMethod::kRestoreBGColor : + SkCodecAnimation::DisposalMethod::kKeep); + frame->setDuration(iter.duration); + if (WEBP_MUX_BLEND != iter.blend_method) { + frame->setBlend(SkCodecAnimation::Blend::kBG); + } + fFrameHolder.setAlphaAndRequiredFrame(frame); + } + + return fFrameHolder.size(); + +} + +const SkFrame* SkWebpCodec::FrameHolder::onGetFrame(int i) const { + return static_cast<const SkFrame*>(this->frame(i)); +} + +const SkWebpCodec::Frame* SkWebpCodec::FrameHolder::frame(int i) const { + SkASSERT(i >= 0 && i < this->size()); + return &fFrames[i]; +} + +bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const { + if (i >= fFrameHolder.size()) { + return false; + } + + const Frame* frame = fFrameHolder.frame(i); + if (!frame) { + return false; + } + + if (frameInfo) { + frameInfo->fRequiredFrame = frame->getRequiredFrame(); + frameInfo->fDuration = frame->getDuration(); + // libwebp only reports fully received frames for an + // animated image. + frameInfo->fFullyReceived = true; + frameInfo->fAlphaType = frame->hasAlpha() ? kUnpremul_SkAlphaType + : kOpaque_SkAlphaType; + frameInfo->fDisposalMethod = frame->getDisposalMethod(); + } + + return true; +} + +static bool is_8888(SkColorType colorType) { + switch (colorType) { + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: + return true; + default: + return false; + } +} + +// Requires that the src input be unpremultiplied (or opaque). +static void blend_line(SkColorType dstCT, void* dst, + SkColorType srcCT, const void* src, + SkAlphaType dstAt, + bool srcHasAlpha, + int width) { + SkRasterPipeline_MemoryCtx dst_ctx = { (void*)dst, 0 }, + src_ctx = { (void*)src, 0 }; + + SkRasterPipeline_<256> p; + + p.append_load_dst(dstCT, &dst_ctx); + if (kUnpremul_SkAlphaType == dstAt) { + p.append(SkRasterPipeline::premul_dst); + } + + p.append_load(srcCT, &src_ctx); + if (srcHasAlpha) { + p.append(SkRasterPipeline::premul); + } + + p.append(SkRasterPipeline::srcover); + + if (kUnpremul_SkAlphaType == dstAt) { + p.append(SkRasterPipeline::unpremul); + } + p.append_store(dstCT, &dst_ctx); + + p.run(0,0, width,1); +} + +SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, + const Options& options, int* rowsDecodedPtr) { + const int index = options.fFrameIndex; + SkASSERT(0 == index || index < fFrameHolder.size()); + SkASSERT(0 == index || !options.fSubset); + + WebPDecoderConfig config; + if (0 == WebPInitDecoderConfig(&config)) { + // ABI mismatch. + // FIXME: New enum for this? + return kInvalidInput; + } + + // Free any memory associated with the buffer. Must be called last, so we declare it first. + SkAutoTCallVProc<WebPDecBuffer, WebPFreeDecBuffer> autoFree(&(config.output)); + + WebPIterator frame; + SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame); + // If this succeeded in onGetFrameCount(), it should succeed again here. + SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame)); + + const bool independent = index == 0 ? true : + (fFrameHolder.frame(index)->getRequiredFrame() == kNoFrame); + // Get the frameRect. libwebp will have already signaled an error if this is not fully + // contained by the canvas. + auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height); + SkASSERT(this->bounds().contains(frameRect)); + const bool frameIsSubset = frameRect != this->bounds(); + if (independent && frameIsSubset) { + SkSampler::Fill(dstInfo, dst, rowBytes, options.fZeroInitialized); + } + + int dstX = frameRect.x(); + int dstY = frameRect.y(); + int subsetWidth = frameRect.width(); + int subsetHeight = frameRect.height(); + if (options.fSubset) { + SkIRect subset = *options.fSubset; + SkASSERT(this->bounds().contains(subset)); + SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); + SkASSERT(this->getValidSubset(&subset) && subset == *options.fSubset); + + if (!SkIRect::Intersects(subset, frameRect)) { + return kSuccess; + } + + int minXOffset = SkTMin(dstX, subset.x()); + int minYOffset = SkTMin(dstY, subset.y()); + dstX -= minXOffset; + dstY -= minYOffset; + frameRect.offset(-minXOffset, -minYOffset); + subset.offset(-minXOffset, -minYOffset); + + // Just like we require that the requested subset x and y offset are even, libwebp + // guarantees that the frame x and y offset are even (it's actually impossible to specify + // an odd frame offset). So we can still guarantee that the adjusted offsets are even. + SkASSERT(SkIsAlign2(subset.fLeft) && SkIsAlign2(subset.fTop)); + + SkIRect intersection; + SkAssertResult(intersection.intersect(frameRect, subset)); + subsetWidth = intersection.width(); + subsetHeight = intersection.height(); + + config.options.use_cropping = 1; + config.options.crop_left = subset.x(); + config.options.crop_top = subset.y(); + config.options.crop_width = subsetWidth; + config.options.crop_height = subsetHeight; + } + + // Ignore the frame size and offset when determining if scaling is necessary. + int scaledWidth = subsetWidth; + int scaledHeight = subsetHeight; + SkISize srcSize = options.fSubset ? options.fSubset->size() : this->dimensions(); + if (srcSize != dstInfo.dimensions()) { + config.options.use_scaling = 1; + + if (frameIsSubset) { + float scaleX = ((float) dstInfo.width()) / srcSize.width(); + float scaleY = ((float) dstInfo.height()) / srcSize.height(); + + // We need to be conservative here and floor rather than round. + // Otherwise, we may find ourselves decoding off the end of memory. + dstX = scaleX * dstX; + scaledWidth = scaleX * scaledWidth; + dstY = scaleY * dstY; + scaledHeight = scaleY * scaledHeight; + if (0 == scaledWidth || 0 == scaledHeight) { + return kSuccess; + } + } else { + scaledWidth = dstInfo.width(); + scaledHeight = dstInfo.height(); + } + + config.options.scaled_width = scaledWidth; + config.options.scaled_height = scaledHeight; + } + + const bool blendWithPrevFrame = !independent && frame.blend_method == WEBP_MUX_BLEND + && frame.has_alpha; + + SkBitmap webpDst; + auto webpInfo = dstInfo; + if (!frame.has_alpha) { + webpInfo = webpInfo.makeAlphaType(kOpaque_SkAlphaType); + } + if (this->colorXform()) { + // Swizzling between RGBA and BGRA is zero cost in a color transform. So when we have a + // color transform, we should decode to whatever is easiest for libwebp, and then let the + // color transform swizzle if necessary. + // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost). Lossless webp is + // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA. + webpInfo = webpInfo.makeColorType(kBGRA_8888_SkColorType); + + if (webpInfo.alphaType() == kPremul_SkAlphaType) { + webpInfo = webpInfo.makeAlphaType(kUnpremul_SkAlphaType); + } + } + + if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) { + // We will decode the entire image and then perform the color transform. libwebp + // does not provide a row-by-row API. This is a shame particularly when we do not want + // 8888, since we will need to create another image sized buffer. + webpDst.allocPixels(webpInfo); + } else { + // libwebp can decode directly into the output memory. + webpDst.installPixels(webpInfo, dst, rowBytes); + } + + config.output.colorspace = webp_decode_mode(webpInfo.colorType(), + frame.has_alpha && dstInfo.alphaType() == kPremul_SkAlphaType && !this->colorXform()); + config.output.is_external_memory = 1; + + config.output.u.RGBA.rgba = reinterpret_cast<uint8_t*>(webpDst.getAddr(dstX, dstY)); + config.output.u.RGBA.stride = static_cast<int>(webpDst.rowBytes()); + config.output.u.RGBA.size = webpDst.computeByteSize(); + + SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config)); + if (!idec) { + return kInvalidInput; + } + + int rowsDecoded = 0; + SkCodec::Result result; + switch (WebPIUpdate(idec, frame.fragment.bytes, frame.fragment.size)) { + case VP8_STATUS_OK: + rowsDecoded = scaledHeight; + result = kSuccess; + break; + case VP8_STATUS_SUSPENDED: + if (!WebPIDecGetRGB(idec, &rowsDecoded, nullptr, nullptr, nullptr) + || rowsDecoded <= 0) { + return kInvalidInput; + } + *rowsDecodedPtr = rowsDecoded + dstY; + result = kIncompleteInput; + break; + default: + return kInvalidInput; + } + + const size_t dstBpp = dstInfo.bytesPerPixel(); + dst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY); + const size_t srcRowBytes = config.output.u.RGBA.stride; + + const auto dstCT = dstInfo.colorType(); + if (this->colorXform()) { + uint32_t* xformSrc = (uint32_t*) config.output.u.RGBA.rgba; + SkBitmap tmp; + void* xformDst; + + if (blendWithPrevFrame) { + // Xform into temporary bitmap big enough for one row. + tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1)); + xformDst = tmp.getPixels(); + } else { + xformDst = dst; + } + + for (int y = 0; y < rowsDecoded; y++) { + this->applyColorXform(xformDst, xformSrc, scaledWidth); + if (blendWithPrevFrame) { + blend_line(dstCT, dst, dstCT, xformDst, + dstInfo.alphaType(), frame.has_alpha, scaledWidth); + dst = SkTAddOffset<void>(dst, rowBytes); + } else { + xformDst = SkTAddOffset<void>(xformDst, rowBytes); + } + xformSrc = SkTAddOffset<uint32_t>(xformSrc, srcRowBytes); + } + } else if (blendWithPrevFrame) { + const uint8_t* src = config.output.u.RGBA.rgba; + + for (int y = 0; y < rowsDecoded; y++) { + blend_line(dstCT, dst, webpDst.colorType(), src, + dstInfo.alphaType(), frame.has_alpha, scaledWidth); + src = SkTAddOffset<const uint8_t>(src, srcRowBytes); + dst = SkTAddOffset<void>(dst, rowBytes); + } + } + + return result; +} + +SkWebpCodec::SkWebpCodec(SkEncodedInfo&& info, std::unique_ptr<SkStream> stream, + WebPDemuxer* demux, sk_sp<SkData> data, SkEncodedOrigin origin) + : INHERITED(std::move(info), skcms_PixelFormat_BGRA_8888, std::move(stream), + origin) + , fDemux(demux) + , fData(std::move(data)) + , fFailed(false) +{ + const auto& eInfo = this->getEncodedInfo(); + fFrameHolder.setScreenSize(eInfo.width(), eInfo.height()); +} diff --git a/gfx/skia/skia/src/codec/SkWebpCodec.h b/gfx/skia/skia/src/codec/SkWebpCodec.h new file mode 100644 index 0000000000..36ff5357f0 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWebpCodec.h @@ -0,0 +1,104 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWebpCodec_DEFINED +#define SkWebpCodec_DEFINED + +#include "include/codec/SkCodec.h" +#include "include/core/SkEncodedImageFormat.h" +#include "include/core/SkImageInfo.h" +#include "include/core/SkTypes.h" +#include "src/codec/SkFrameHolder.h" +#include "src/codec/SkScalingCodec.h" + +#include <vector> + +class SkStream; +extern "C" { + struct WebPDemuxer; + void WebPDemuxDelete(WebPDemuxer* dmux); +} + +class SkWebpCodec final : public SkScalingCodec { +public: + // Assumes IsWebp was called and returned true. + static std::unique_ptr<SkCodec> MakeFromStream(std::unique_ptr<SkStream>, Result*); + static bool IsWebp(const void*, size_t); +protected: + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) override; + SkEncodedImageFormat onGetEncodedFormat() const override { return SkEncodedImageFormat::kWEBP; } + + bool onGetValidSubset(SkIRect* /* desiredSubset */) const override; + + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + + const SkFrameHolder* getFrameHolder() const override { + return &fFrameHolder; + } + +private: + SkWebpCodec(SkEncodedInfo&&, std::unique_ptr<SkStream>, WebPDemuxer*, sk_sp<SkData>, + SkEncodedOrigin); + + SkAutoTCallVProc<WebPDemuxer, WebPDemuxDelete> fDemux; + + // fDemux has a pointer into this data. + // This should not be freed until the decode is completed. + sk_sp<SkData> fData; + + class Frame : public SkFrame { + public: + Frame(int i, SkEncodedInfo::Alpha alpha) + : INHERITED(i) + , fReportedAlpha(alpha) + {} + + protected: + SkEncodedInfo::Alpha onReportedAlpha() const override { + return fReportedAlpha; + } + + private: + const SkEncodedInfo::Alpha fReportedAlpha; + + typedef SkFrame INHERITED; + }; + + class FrameHolder : public SkFrameHolder { + public: + ~FrameHolder() override {} + void setScreenSize(int w, int h) { + fScreenWidth = w; + fScreenHeight = h; + } + Frame* appendNewFrame(bool hasAlpha); + const Frame* frame(int i) const; + int size() const { + return static_cast<int>(fFrames.size()); + } + void reserve(int size) { + fFrames.reserve(size); + } + + protected: + const SkFrame* onGetFrame(int i) const override; + + private: + std::vector<Frame> fFrames; + }; + + FrameHolder fFrameHolder; + // Set to true if WebPDemuxGetFrame fails. This only means + // that we will cap the frame count to the frames that + // succeed. + bool fFailed; + + typedef SkScalingCodec INHERITED; +}; +#endif // SkWebpCodec_DEFINED diff --git a/gfx/skia/skia/src/codec/SkWuffsCodec.cpp b/gfx/skia/skia/src/codec/SkWuffsCodec.cpp new file mode 100644 index 0000000000..f7ada1f548 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWuffsCodec.cpp @@ -0,0 +1,870 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "src/codec/SkWuffsCodec.h" + +#include "include/core/SkBitmap.h" +#include "include/core/SkMatrix.h" +#include "include/core/SkPaint.h" +#include "include/private/SkMalloc.h" +#include "src/codec/SkFrameHolder.h" +#include "src/codec/SkSampler.h" +#include "src/codec/SkScalingCodec.h" +#include "src/core/SkDraw.h" +#include "src/core/SkRasterClip.h" +#include "src/core/SkUtils.h" + +#include <limits.h> + +// Wuffs ships as a "single file C library" or "header file library" as per +// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt +// +// As we have not #define'd WUFFS_IMPLEMENTATION, the #include here is +// including a header file, even though that file name ends in ".c". +#if defined(WUFFS_IMPLEMENTATION) +#error "SkWuffsCodec should not #define WUFFS_IMPLEMENTATION" +#endif +#include "wuffs-v0.2.c" +#if WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT < 1942 +#error "Wuffs version is too old. Upgrade to the latest version." +#endif + +#define SK_WUFFS_CODEC_BUFFER_SIZE 4096 + +static bool fill_buffer(wuffs_base__io_buffer* b, SkStream* s) { + b->compact(); + size_t num_read = s->read(b->data.ptr + b->meta.wi, b->data.len - b->meta.wi); + b->meta.wi += num_read; + b->meta.closed = s->isAtEnd(); + return num_read > 0; +} + +static bool seek_buffer(wuffs_base__io_buffer* b, SkStream* s, uint64_t pos) { + // Try to re-position the io_buffer's meta.ri read-index first, which is + // cheaper than seeking in the backing SkStream. + if ((pos >= b->meta.pos) && (pos - b->meta.pos <= b->meta.wi)) { + b->meta.ri = pos - b->meta.pos; + return true; + } + // Seek in the backing SkStream. + if ((pos > SIZE_MAX) || (!s->seek(pos))) { + return false; + } + b->meta.wi = 0; + b->meta.ri = 0; + b->meta.pos = pos; + b->meta.closed = false; + return true; +} + +static SkEncodedInfo::Alpha wuffs_blend_to_skia_alpha(wuffs_base__animation_blend w) { + return (w == WUFFS_BASE__ANIMATION_BLEND__OPAQUE) ? SkEncodedInfo::kOpaque_Alpha + : SkEncodedInfo::kUnpremul_Alpha; +} + +static SkCodecAnimation::Blend wuffs_blend_to_skia_blend(wuffs_base__animation_blend w) { + return (w == WUFFS_BASE__ANIMATION_BLEND__SRC) ? SkCodecAnimation::Blend::kBG + : SkCodecAnimation::Blend::kPriorFrame; +} + +static SkCodecAnimation::DisposalMethod wuffs_disposal_to_skia_disposal( + wuffs_base__animation_disposal w) { + switch (w) { + case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND: + return SkCodecAnimation::DisposalMethod::kRestoreBGColor; + case WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS: + return SkCodecAnimation::DisposalMethod::kRestorePrevious; + default: + return SkCodecAnimation::DisposalMethod::kKeep; + } +} + +// -------------------------------- Class definitions + +class SkWuffsCodec; + +class SkWuffsFrame final : public SkFrame { +public: + SkWuffsFrame(wuffs_base__frame_config* fc); + + SkCodec::FrameInfo frameInfo(bool fullyReceived) const; + uint64_t ioPosition() const; + + // SkFrame overrides. + SkEncodedInfo::Alpha onReportedAlpha() const override; + +private: + uint64_t fIOPosition; + SkEncodedInfo::Alpha fReportedAlpha; + + typedef SkFrame INHERITED; +}; + +// SkWuffsFrameHolder is a trivial indirector that forwards its calls onto a +// SkWuffsCodec. It is a separate class as SkWuffsCodec would otherwise +// inherit from both SkCodec and SkFrameHolder, and Skia style discourages +// multiple inheritance (e.g. with its "typedef Foo INHERITED" convention). +class SkWuffsFrameHolder final : public SkFrameHolder { +public: + SkWuffsFrameHolder() : INHERITED() {} + + void init(SkWuffsCodec* codec, int width, int height); + + // SkFrameHolder overrides. + const SkFrame* onGetFrame(int i) const override; + +private: + const SkWuffsCodec* fCodec; + + typedef SkFrameHolder INHERITED; +}; + +class SkWuffsCodec final : public SkScalingCodec { +public: + SkWuffsCodec(SkEncodedInfo&& encodedInfo, + std::unique_ptr<SkStream> stream, + std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> dec, + std::unique_ptr<uint8_t, decltype(&sk_free)> pixbuf_ptr, + std::unique_ptr<uint8_t, decltype(&sk_free)> workbuf_ptr, + size_t workbuf_len, + wuffs_base__image_config imgcfg, + wuffs_base__pixel_buffer pixbuf, + wuffs_base__io_buffer iobuf); + + const SkWuffsFrame* frame(int i) const; + +private: + // SkCodec overrides. + SkEncodedImageFormat onGetEncodedFormat() const override; + Result onGetPixels(const SkImageInfo&, void*, size_t, const Options&, int*) override; + const SkFrameHolder* getFrameHolder() const override; + Result onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* dst, + size_t rowBytes, + const SkCodec::Options& options) override; + Result onIncrementalDecode(int* rowsDecoded) override; + int onGetFrameCount() override; + bool onGetFrameInfo(int, FrameInfo*) const override; + int onGetRepetitionCount() override; + + void readFrames(); + Result seekFrame(int frameIndex); + + Result resetDecoder(); + const char* decodeFrameConfig(); + const char* decodeFrame(); + void updateNumFullyReceivedFrames(); + + SkWuffsFrameHolder fFrameHolder; + std::unique_ptr<SkStream> fStream; + std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> fDecoder; + std::unique_ptr<uint8_t, decltype(&sk_free)> fPixbufPtr; + std::unique_ptr<uint8_t, decltype(&sk_free)> fWorkbufPtr; + size_t fWorkbufLen; + + const uint64_t fFirstFrameIOPosition; + wuffs_base__frame_config fFrameConfig; + wuffs_base__pixel_buffer fPixelBuffer; + wuffs_base__io_buffer fIOBuffer; + + // Incremental decoding state. + uint8_t* fIncrDecDst; + size_t fIncrDecRowBytes; + bool fFirstCallToIncrementalDecode; + + uint64_t fNumFullyReceivedFrames; + std::vector<SkWuffsFrame> fFrames; + bool fFramesComplete; + + // If calling an fDecoder method returns an incomplete status, then + // fDecoder is suspended in a coroutine (i.e. waiting on I/O or halted on a + // non-recoverable error). To keep its internal proof-of-safety invariants + // consistent, there's only two things you can safely do with a suspended + // Wuffs object: resume the coroutine, or reset all state (memset to zero + // and start again). + // + // If fDecoderIsSuspended, and we aren't sure that we're going to resume + // the coroutine, then we will need to call this->resetDecoder before + // calling other fDecoder methods. + bool fDecoderIsSuspended; + + uint8_t fBuffer[SK_WUFFS_CODEC_BUFFER_SIZE]; + + typedef SkScalingCodec INHERITED; +}; + +// -------------------------------- SkWuffsFrame implementation + +SkWuffsFrame::SkWuffsFrame(wuffs_base__frame_config* fc) + : INHERITED(fc->index()), + fIOPosition(fc->io_position()), + fReportedAlpha(wuffs_blend_to_skia_alpha(fc->blend())) { + wuffs_base__rect_ie_u32 r = fc->bounds(); + this->setXYWH(r.min_incl_x, r.min_incl_y, r.width(), r.height()); + this->setDisposalMethod(wuffs_disposal_to_skia_disposal(fc->disposal())); + this->setDuration(fc->duration() / WUFFS_BASE__FLICKS_PER_MILLISECOND); + this->setBlend(wuffs_blend_to_skia_blend(fc->blend())); +} + +SkCodec::FrameInfo SkWuffsFrame::frameInfo(bool fullyReceived) const { + SkCodec::FrameInfo ret; + ret.fRequiredFrame = getRequiredFrame(); + ret.fDuration = getDuration(); + ret.fFullyReceived = fullyReceived; + ret.fAlphaType = hasAlpha() ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType; + ret.fDisposalMethod = getDisposalMethod(); + return ret; +} + +uint64_t SkWuffsFrame::ioPosition() const { + return fIOPosition; +} + +SkEncodedInfo::Alpha SkWuffsFrame::onReportedAlpha() const { + return fReportedAlpha; +} + +// -------------------------------- SkWuffsFrameHolder implementation + +void SkWuffsFrameHolder::init(SkWuffsCodec* codec, int width, int height) { + fCodec = codec; + // Initialize SkFrameHolder's (the superclass) fields. + fScreenWidth = width; + fScreenHeight = height; +} + +const SkFrame* SkWuffsFrameHolder::onGetFrame(int i) const { + return fCodec->frame(i); +}; + +// -------------------------------- SkWuffsCodec implementation + +SkWuffsCodec::SkWuffsCodec(SkEncodedInfo&& encodedInfo, + std::unique_ptr<SkStream> stream, + std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> dec, + std::unique_ptr<uint8_t, decltype(&sk_free)> pixbuf_ptr, + std::unique_ptr<uint8_t, decltype(&sk_free)> workbuf_ptr, + size_t workbuf_len, + wuffs_base__image_config imgcfg, + wuffs_base__pixel_buffer pixbuf, + wuffs_base__io_buffer iobuf) + : INHERITED(std::move(encodedInfo), + skcms_PixelFormat_RGBA_8888, + // Pass a nullptr SkStream to the SkCodec constructor. We + // manage the stream ourselves, as the default SkCodec behavior + // is too trigger-happy on rewinding the stream. + nullptr), + fFrameHolder(), + fStream(std::move(stream)), + fDecoder(std::move(dec)), + fPixbufPtr(std::move(pixbuf_ptr)), + fWorkbufPtr(std::move(workbuf_ptr)), + fWorkbufLen(workbuf_len), + fFirstFrameIOPosition(imgcfg.first_frame_io_position()), + fFrameConfig(wuffs_base__null_frame_config()), + fPixelBuffer(pixbuf), + fIOBuffer(wuffs_base__empty_io_buffer()), + fIncrDecDst(nullptr), + fIncrDecRowBytes(0), + fFirstCallToIncrementalDecode(false), + fNumFullyReceivedFrames(0), + fFramesComplete(false), + fDecoderIsSuspended(false) { + fFrameHolder.init(this, imgcfg.pixcfg.width(), imgcfg.pixcfg.height()); + + // Initialize fIOBuffer's fields, copying any outstanding data from iobuf to + // fIOBuffer, as iobuf's backing array may not be valid for the lifetime of + // this SkWuffsCodec object, but fIOBuffer's backing array (fBuffer) is. + SkASSERT(iobuf.data.len == SK_WUFFS_CODEC_BUFFER_SIZE); + memmove(fBuffer, iobuf.data.ptr, iobuf.meta.wi); + fIOBuffer.data = wuffs_base__make_slice_u8(fBuffer, SK_WUFFS_CODEC_BUFFER_SIZE); + fIOBuffer.meta = iobuf.meta; +} + +const SkWuffsFrame* SkWuffsCodec::frame(int i) const { + if ((0 <= i) && (static_cast<size_t>(i) < fFrames.size())) { + return &fFrames[i]; + } + return nullptr; +} + +SkEncodedImageFormat SkWuffsCodec::onGetEncodedFormat() const { + return SkEncodedImageFormat::kGIF; +} + +SkCodec::Result SkWuffsCodec::onGetPixels(const SkImageInfo& dstInfo, + void* dst, + size_t rowBytes, + const Options& options, + int* rowsDecoded) { + SkCodec::Result result = this->onStartIncrementalDecode(dstInfo, dst, rowBytes, options); + if (result != kSuccess) { + return result; + } + return this->onIncrementalDecode(rowsDecoded); +} + +const SkFrameHolder* SkWuffsCodec::getFrameHolder() const { + return &fFrameHolder; +} + +SkCodec::Result SkWuffsCodec::onStartIncrementalDecode(const SkImageInfo& dstInfo, + void* dst, + size_t rowBytes, + const SkCodec::Options& options) { + if (!dst) { + return SkCodec::kInvalidParameters; + } + if (options.fSubset) { + return SkCodec::kUnimplemented; + } + if (options.fFrameIndex > 0 && SkColorTypeIsAlwaysOpaque(dstInfo.colorType())) { + return SkCodec::kInvalidConversion; + } + SkCodec::Result result = this->seekFrame(options.fFrameIndex); + if (result != SkCodec::kSuccess) { + return result; + } + + const char* status = this->decodeFrameConfig(); + if (status == wuffs_base__suspension__short_read) { + return SkCodec::kIncompleteInput; + } else if (status != nullptr) { + SkCodecPrintf("decodeFrameConfig: %s", status); + return SkCodec::kErrorInInput; + } + + uint32_t src_bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(fPixelBuffer.pixcfg.pixel_format()); + if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) { + return SkCodec::kInternalError; + } + size_t src_bytes_per_pixel = src_bits_per_pixel / 8; + + // Zero-initialize Wuffs' buffer covering the frame rect. + wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds(); + wuffs_base__table_u8 pixels = fPixelBuffer.plane(0); + for (uint32_t y = frame_rect.min_incl_y; y < frame_rect.max_excl_y; y++) { + sk_bzero(pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bytes_per_pixel), + frame_rect.width() * src_bytes_per_pixel); + } + + fIncrDecDst = static_cast<uint8_t*>(dst); + fIncrDecRowBytes = rowBytes; + fFirstCallToIncrementalDecode = true; + return SkCodec::kSuccess; +} + +static SkAlphaType to_alpha_type(bool opaque) { + return opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType; +} + +SkCodec::Result SkWuffsCodec::onIncrementalDecode(int* rowsDecoded) { + if (!fIncrDecDst) { + return SkCodec::kInternalError; + } + + SkCodec::Result result = SkCodec::kSuccess; + const char* status = this->decodeFrame(); + bool independent; + SkAlphaType alphaType; + const int index = options().fFrameIndex; + if (index == 0) { + independent = true; + alphaType = to_alpha_type(getEncodedInfo().opaque()); + } else { + const SkWuffsFrame* f = this->frame(index); + independent = f->getRequiredFrame() == SkCodec::kNoFrame; + alphaType = to_alpha_type(f->reportedAlpha() == SkEncodedInfo::kOpaque_Alpha); + } + if (status != nullptr) { + if (status == wuffs_base__suspension__short_read) { + result = SkCodec::kIncompleteInput; + } else { + SkCodecPrintf("decodeFrame: %s", status); + result = SkCodec::kErrorInInput; + } + + if (!independent) { + // For a dependent frame, we cannot blend the partial result, since + // that will overwrite the contribution from prior frames. + return result; + } + } + + uint32_t src_bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(fPixelBuffer.pixcfg.pixel_format()); + if ((src_bits_per_pixel == 0) || (src_bits_per_pixel % 8 != 0)) { + return SkCodec::kInternalError; + } + size_t src_bytes_per_pixel = src_bits_per_pixel / 8; + + wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds(); + if (fFirstCallToIncrementalDecode) { + if (frame_rect.width() > (SIZE_MAX / src_bytes_per_pixel)) { + return SkCodec::kInternalError; + } + + auto bounds = SkIRect::MakeLTRB(frame_rect.min_incl_x, frame_rect.min_incl_y, + frame_rect.max_excl_x, frame_rect.max_excl_y); + + // If the frame rect does not fill the output, ensure that those pixels are not + // left uninitialized. + if (independent && (bounds != this->bounds() || result != kSuccess)) { + SkSampler::Fill(dstInfo(), fIncrDecDst, fIncrDecRowBytes, + options().fZeroInitialized); + } + fFirstCallToIncrementalDecode = false; + } else { + // Existing clients intend to only show frames beyond the first if they + // are complete (based on FrameInfo::fFullyReceived), since it might + // look jarring to draw a partial frame over an existing frame. If they + // changed their behavior and expected to continue decoding a partial + // frame after the first one, we'll need to update our blending code. + // Otherwise, if the frame were interlaced and not independent, the + // second pass may have an overlapping dirty_rect with the first, + // resulting in blending with the first pass. + SkASSERT(index == 0); + } + + if (rowsDecoded) { + *rowsDecoded = dstInfo().height(); + } + + // If the frame's dirty rect is empty, no need to swizzle. + wuffs_base__rect_ie_u32 dirty_rect = fDecoder->frame_dirty_rect(); + if (!dirty_rect.is_empty()) { + wuffs_base__table_u8 pixels = fPixelBuffer.plane(0); + + // The Wuffs model is that the dst buffer is the image, not the frame. + // The expectation is that you allocate the buffer once, but re-use it + // for the N frames, regardless of each frame's top-left co-ordinate. + // + // To get from the start (in the X-direction) of the image to the start + // of the dirty_rect, we adjust s by (dirty_rect.min_incl_x * src_bytes_per_pixel). + uint8_t* s = pixels.ptr + (dirty_rect.min_incl_y * pixels.stride) + + (dirty_rect.min_incl_x * src_bytes_per_pixel); + + // Currently, this is only used for GIF, which will never have an ICC profile. When it is + // used for other formats that might have one, we will need to transform from profiles that + // do not have corresponding SkColorSpaces. + SkASSERT(!getEncodedInfo().profile()); + + auto srcInfo = getInfo().makeWH(dirty_rect.width(), dirty_rect.height()) + .makeAlphaType(alphaType); + SkBitmap src; + src.installPixels(srcInfo, s, pixels.stride); + SkPaint paint; + if (independent) { + paint.setBlendMode(SkBlendMode::kSrc); + } + + SkDraw draw; + draw.fDst.reset(dstInfo(), fIncrDecDst, fIncrDecRowBytes); + SkMatrix matrix = SkMatrix::MakeRectToRect(SkRect::Make(this->dimensions()), + SkRect::Make(this->dstInfo().dimensions()), + SkMatrix::kFill_ScaleToFit); + draw.fMatrix = &matrix; + SkRasterClip rc(SkIRect::MakeSize(this->dstInfo().dimensions())); + draw.fRC = &rc; + + SkMatrix translate = SkMatrix::MakeTrans(dirty_rect.min_incl_x, dirty_rect.min_incl_y); + draw.drawBitmap(src, translate, nullptr, paint); + } + + if (result == SkCodec::kSuccess) { + fIncrDecDst = nullptr; + fIncrDecRowBytes = 0; + } + return result; +} + +int SkWuffsCodec::onGetFrameCount() { + // It is valid, in terms of the SkCodec API, to call SkCodec::getFrameCount + // while in an incremental decode (after onStartIncrementalDecode returns + // and before onIncrementalDecode returns kSuccess). + // + // We should not advance the SkWuffsCodec' stream while doing so, even + // though other SkCodec implementations can return increasing values from + // onGetFrameCount when given more data. If we tried to do so, the + // subsequent resume of the incremental decode would continue reading from + // a different position in the I/O stream, leading to an incorrect error. + // + // Other SkCodec implementations can move the stream forward during + // onGetFrameCount because they assume that the stream is rewindable / + // seekable. For example, an alternative GIF implementation may choose to + // store, for each frame walked past when merely counting the number of + // frames, the I/O position of each of the frame's GIF data blocks. (A GIF + // frame's compressed data can have multiple data blocks, each at most 255 + // bytes in length). Obviously, this can require O(numberOfFrames) extra + // memory to store these I/O positions. The constant factor is small, but + // it's still O(N), not O(1). + // + // Wuffs and SkWuffsCodec tries to minimize relying on the rewindable / + // seekable assumption. By design, Wuffs per se aims for O(1) memory use + // (after any pixel buffers are allocated) instead of O(N), and its I/O + // type, wuffs_base__io_buffer, is not necessarily rewindable or seekable. + // + // The Wuffs API provides a limited, optional form of seeking, to the start + // of an animation frame's data, but does not provide arbitrary save and + // load of its internal state whilst in the middle of an animation frame. + bool incrementalDecodeIsInProgress = fIncrDecDst != nullptr; + + if (!fFramesComplete && !incrementalDecodeIsInProgress) { + this->readFrames(); + this->updateNumFullyReceivedFrames(); + } + return fFrames.size(); +} + +bool SkWuffsCodec::onGetFrameInfo(int i, SkCodec::FrameInfo* frameInfo) const { + const SkWuffsFrame* f = this->frame(i); + if (!f) { + return false; + } + if (frameInfo) { + *frameInfo = f->frameInfo(static_cast<uint64_t>(i) < this->fNumFullyReceivedFrames); + } + return true; +} + +int SkWuffsCodec::onGetRepetitionCount() { + // Convert from Wuffs's loop count to Skia's repeat count. Wuffs' uint32_t + // number is how many times to play the loop. Skia's int number is how many + // times to play the loop *after the first play*. Wuffs and Skia use 0 and + // kRepetitionCountInfinite respectively to mean loop forever. + uint32_t n = fDecoder->num_animation_loops(); + if (n == 0) { + return SkCodec::kRepetitionCountInfinite; + } + n--; + return n < INT_MAX ? n : INT_MAX; +} + +void SkWuffsCodec::readFrames() { + size_t n = fFrames.size(); + int i = n ? n - 1 : 0; + if (this->seekFrame(i) != SkCodec::kSuccess) { + return; + } + + // Iterate through the frames, converting from Wuffs' + // wuffs_base__frame_config type to Skia's SkWuffsFrame type. + for (; i < INT_MAX; i++) { + const char* status = this->decodeFrameConfig(); + if (status == nullptr) { + // No-op. + } else if (status == wuffs_base__warning__end_of_data) { + break; + } else { + return; + } + + if (static_cast<size_t>(i) < fFrames.size()) { + continue; + } + fFrames.emplace_back(&fFrameConfig); + SkWuffsFrame* f = &fFrames[fFrames.size() - 1]; + fFrameHolder.setAlphaAndRequiredFrame(f); + } + + fFramesComplete = true; +} + +SkCodec::Result SkWuffsCodec::seekFrame(int frameIndex) { + if (fDecoderIsSuspended) { + SkCodec::Result res = this->resetDecoder(); + if (res != SkCodec::kSuccess) { + return res; + } + } + + uint64_t pos = 0; + if (frameIndex < 0) { + return SkCodec::kInternalError; + } else if (frameIndex == 0) { + pos = fFirstFrameIOPosition; + } else if (static_cast<size_t>(frameIndex) < fFrames.size()) { + pos = fFrames[frameIndex].ioPosition(); + } else { + return SkCodec::kInternalError; + } + + if (!seek_buffer(&fIOBuffer, fStream.get(), pos)) { + return SkCodec::kInternalError; + } + const char* status = fDecoder->restart_frame(frameIndex, fIOBuffer.reader_io_position()); + if (status != nullptr) { + return SkCodec::kInternalError; + } + return SkCodec::kSuccess; +} + +// An overview of the Wuffs decoding API: +// +// An animated image (such as GIF) has an image header and then N frames. The +// image header gives e.g. the overall image's width and height. Each frame +// consists of a frame header (e.g. frame rectangle bounds, display duration) +// and a payload (the pixels). +// +// In Wuffs terminology, there is one image config and then N pairs of +// (frame_config, frame). To decode everything (without knowing N in advance) +// sequentially: +// - call wuffs_gif__decoder::decode_image_config +// - while (true) { +// - call wuffs_gif__decoder::decode_frame_config +// - if that returned wuffs_base__warning__end_of_data, break +// - call wuffs_gif__decoder::decode_frame +// - } +// +// The first argument to each decode_foo method is the destination struct to +// store the decoded information. +// +// For random (instead of sequential) access to an image's frames, call +// wuffs_gif__decoder::restart_frame to prepare to decode the i'th frame. +// Essentially, it restores the state to be at the top of the while loop above. +// The wuffs_base__io_buffer's reader position will also need to be set at the +// right point in the source data stream. The position for the i'th frame is +// calculated by the i'th decode_frame_config call. You can only call +// restart_frame after decode_image_config is called, explicitly or implicitly +// (see below), as decoding a single frame might require for-all-frames +// information like the overall image dimensions and the global palette. +// +// All of those decode_xxx calls are optional. For example, if +// decode_image_config is not called, then the first decode_frame_config call +// will implicitly parse and verify the image header, before parsing the first +// frame's header. Similarly, you can call only decode_frame N times, without +// calling decode_image_config or decode_frame_config, if you already know +// metadata like N and each frame's rectangle bounds by some other means (e.g. +// this is a first party, statically known image). +// +// Specifically, starting with an unknown (but re-windable) GIF image, if you +// want to just find N (i.e. count the number of frames), you can loop calling +// only the decode_frame_config method and avoid calling the more expensive +// decode_frame method. In terms of the underlying GIF image format, this will +// skip over the LZW-encoded pixel data, avoiding the costly LZW decompression. +// +// Those decode_xxx methods are also suspendible. They will return early (with +// a status code that is_suspendible and therefore isn't is_complete) if there +// isn't enough source data to complete the operation: an incremental decode. +// Calling decode_xxx again with additional source data will resume the +// previous operation, instead of starting a new operation. Calling decode_yyy +// whilst decode_xxx is suspended will result in an error. +// +// Once an error is encountered, whether from invalid source data or from a +// programming error such as calling decode_yyy while suspended in decode_xxx, +// all subsequent calls will be no-ops that return an error. To reset the +// decoder into something that does productive work, memset the entire struct +// to zero, check the Wuffs version and then, in order to be able to call +// restart_frame, call decode_image_config. The io_buffer and its associated +// stream will also need to be rewound. + +static SkCodec::Result reset_and_decode_image_config(wuffs_gif__decoder* decoder, + wuffs_base__image_config* imgcfg, + wuffs_base__io_buffer* b, + SkStream* s) { + // Calling decoder->initialize will memset it to zero. + const char* status = decoder->initialize(sizeof__wuffs_gif__decoder(), WUFFS_VERSION, 0); + if (status != nullptr) { + SkCodecPrintf("initialize: %s", status); + return SkCodec::kInternalError; + } + while (true) { + status = decoder->decode_image_config(imgcfg, b); + if (status == nullptr) { + break; + } else if (status != wuffs_base__suspension__short_read) { + SkCodecPrintf("decode_image_config: %s", status); + return SkCodec::kErrorInInput; + } else if (!fill_buffer(b, s)) { + return SkCodec::kIncompleteInput; + } + } + + // A GIF image's natural color model is indexed color: 1 byte per pixel, + // indexing a 256-element palette. + // + // For Skia, we override that to decode to 4 bytes per pixel, BGRA or RGBA. + wuffs_base__pixel_format pixfmt = 0; + switch (kN32_SkColorType) { + case kBGRA_8888_SkColorType: + pixfmt = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL; + break; + case kRGBA_8888_SkColorType: + pixfmt = WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL; + break; + default: + return SkCodec::kInternalError; + } + if (imgcfg) { + imgcfg->pixcfg.set(pixfmt, WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, imgcfg->pixcfg.width(), + imgcfg->pixcfg.height()); + } + + return SkCodec::kSuccess; +} + +SkCodec::Result SkWuffsCodec::resetDecoder() { + if (!fStream->rewind()) { + return SkCodec::kInternalError; + } + fIOBuffer.meta = wuffs_base__empty_io_buffer_meta(); + + SkCodec::Result result = + reset_and_decode_image_config(fDecoder.get(), nullptr, &fIOBuffer, fStream.get()); + if (result == SkCodec::kIncompleteInput) { + return SkCodec::kInternalError; + } else if (result != SkCodec::kSuccess) { + return result; + } + + fDecoderIsSuspended = false; + return SkCodec::kSuccess; +} + +const char* SkWuffsCodec::decodeFrameConfig() { + while (true) { + const char* status = fDecoder->decode_frame_config(&fFrameConfig, &fIOBuffer); + if ((status == wuffs_base__suspension__short_read) && + fill_buffer(&fIOBuffer, fStream.get())) { + continue; + } + fDecoderIsSuspended = !wuffs_base__status__is_complete(status); + this->updateNumFullyReceivedFrames(); + return status; + } +} + +const char* SkWuffsCodec::decodeFrame() { + while (true) { + const char* status = + fDecoder->decode_frame(&fPixelBuffer, &fIOBuffer, + wuffs_base__make_slice_u8(fWorkbufPtr.get(), fWorkbufLen), NULL); + if ((status == wuffs_base__suspension__short_read) && + fill_buffer(&fIOBuffer, fStream.get())) { + continue; + } + fDecoderIsSuspended = !wuffs_base__status__is_complete(status); + this->updateNumFullyReceivedFrames(); + return status; + } +} + +void SkWuffsCodec::updateNumFullyReceivedFrames() { + // num_decoded_frames's return value, n, can change over time, both up and + // down, as we seek back and forth in the underlying stream. + // fNumFullyReceivedFrames is the highest n we've seen. + uint64_t n = fDecoder->num_decoded_frames(); + if (fNumFullyReceivedFrames < n) { + fNumFullyReceivedFrames = n; + } +} + +// -------------------------------- SkWuffsCodec.h functions + +bool SkWuffsCodec_IsFormat(const void* buf, size_t bytesRead) { + constexpr const char* gif_ptr = "GIF8"; + constexpr size_t gif_len = 4; + return (bytesRead >= gif_len) && (memcmp(buf, gif_ptr, gif_len) == 0); +} + +std::unique_ptr<SkCodec> SkWuffsCodec_MakeFromStream(std::unique_ptr<SkStream> stream, + SkCodec::Result* result) { + uint8_t buffer[SK_WUFFS_CODEC_BUFFER_SIZE]; + wuffs_base__io_buffer iobuf = + wuffs_base__make_io_buffer(wuffs_base__make_slice_u8(buffer, SK_WUFFS_CODEC_BUFFER_SIZE), + wuffs_base__empty_io_buffer_meta()); + wuffs_base__image_config imgcfg = wuffs_base__null_image_config(); + + // Wuffs is primarily a C library, not a C++ one. Furthermore, outside of + // the wuffs_base__etc types, the sizeof a file format specific type like + // GIF's wuffs_gif__decoder can vary between Wuffs versions. If p is of + // type wuffs_gif__decoder*, then the supported API treats p as a pointer + // to an opaque type: a private implementation detail. The API is always + // "set_foo(p, etc)" and not "p->foo = etc". + // + // See https://en.wikipedia.org/wiki/Opaque_pointer#C + // + // Thus, we don't use C++'s new operator (which requires knowing the sizeof + // the struct at compile time). Instead, we use sk_malloc_canfail, with + // sizeof__wuffs_gif__decoder returning the appropriate value for the + // (statically or dynamically) linked version of the Wuffs library. + // + // As a C (not C++) library, none of the Wuffs types have constructors or + // destructors. + // + // In RAII style, we can still use std::unique_ptr with these pointers, but + // we pair the pointer with sk_free instead of C++'s delete. + void* decoder_raw = sk_malloc_canfail(sizeof__wuffs_gif__decoder()); + if (!decoder_raw) { + *result = SkCodec::kInternalError; + return nullptr; + } + std::unique_ptr<wuffs_gif__decoder, decltype(&sk_free)> decoder( + reinterpret_cast<wuffs_gif__decoder*>(decoder_raw), &sk_free); + + SkCodec::Result reset_result = + reset_and_decode_image_config(decoder.get(), &imgcfg, &iobuf, stream.get()); + if (reset_result != SkCodec::kSuccess) { + *result = reset_result; + return nullptr; + } + + uint32_t width = imgcfg.pixcfg.width(); + uint32_t height = imgcfg.pixcfg.height(); + if ((width == 0) || (width > INT_MAX) || (height == 0) || (height > INT_MAX)) { + *result = SkCodec::kInvalidInput; + return nullptr; + } + + uint64_t workbuf_len = decoder->workbuf_len().max_incl; + void* workbuf_ptr_raw = nullptr; + if (workbuf_len) { + workbuf_ptr_raw = workbuf_len <= SIZE_MAX ? sk_malloc_canfail(workbuf_len) : nullptr; + if (!workbuf_ptr_raw) { + *result = SkCodec::kInternalError; + return nullptr; + } + } + std::unique_ptr<uint8_t, decltype(&sk_free)> workbuf_ptr( + reinterpret_cast<uint8_t*>(workbuf_ptr_raw), &sk_free); + + uint64_t pixbuf_len = imgcfg.pixcfg.pixbuf_len(); + void* pixbuf_ptr_raw = pixbuf_len <= SIZE_MAX ? sk_malloc_canfail(pixbuf_len) : nullptr; + if (!pixbuf_ptr_raw) { + *result = SkCodec::kInternalError; + return nullptr; + } + std::unique_ptr<uint8_t, decltype(&sk_free)> pixbuf_ptr( + reinterpret_cast<uint8_t*>(pixbuf_ptr_raw), &sk_free); + wuffs_base__pixel_buffer pixbuf = wuffs_base__null_pixel_buffer(); + + const char* status = pixbuf.set_from_slice( + &imgcfg.pixcfg, wuffs_base__make_slice_u8(pixbuf_ptr.get(), SkToSizeT(pixbuf_len))); + if (status != nullptr) { + SkCodecPrintf("set_from_slice: %s", status); + *result = SkCodec::kInternalError; + return nullptr; + } + + SkEncodedInfo::Color color = + (imgcfg.pixcfg.pixel_format() == WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL) + ? SkEncodedInfo::kBGRA_Color + : SkEncodedInfo::kRGBA_Color; + + // In Skia's API, the alpha we calculate here and return is only for the + // first frame. + SkEncodedInfo::Alpha alpha = imgcfg.first_frame_is_opaque() ? SkEncodedInfo::kOpaque_Alpha + : SkEncodedInfo::kBinary_Alpha; + + SkEncodedInfo encodedInfo = SkEncodedInfo::Make(width, height, color, alpha, 8); + + *result = SkCodec::kSuccess; + return std::unique_ptr<SkCodec>(new SkWuffsCodec( + std::move(encodedInfo), std::move(stream), std::move(decoder), std::move(pixbuf_ptr), + std::move(workbuf_ptr), workbuf_len, imgcfg, pixbuf, iobuf)); +} diff --git a/gfx/skia/skia/src/codec/SkWuffsCodec.h b/gfx/skia/skia/src/codec/SkWuffsCodec.h new file mode 100644 index 0000000000..373c89c131 --- /dev/null +++ b/gfx/skia/skia/src/codec/SkWuffsCodec.h @@ -0,0 +1,17 @@ +/* + * Copyright 2018 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkWuffsCodec_DEFINED +#define SkWuffsCodec_DEFINED + +#include "include/codec/SkCodec.h" + +// These functions' types match DecoderProc in SkCodec.cpp. +bool SkWuffsCodec_IsFormat(const void*, size_t); +std::unique_ptr<SkCodec> SkWuffsCodec_MakeFromStream(std::unique_ptr<SkStream>, SkCodec::Result*); + +#endif // SkWuffsCodec_DEFINED |