summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/codec
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/skia/skia/src/codec/SkAndroidCodec.cpp416
-rw-r--r--gfx/skia/skia/src/codec/SkAndroidCodecAdapter.cpp30
-rw-r--r--gfx/skia/skia/src/codec/SkAndroidCodecAdapter.h37
-rw-r--r--gfx/skia/skia/src/codec/SkBmpBaseCodec.cpp16
-rw-r--r--gfx/skia/skia/src/codec/SkBmpBaseCodec.h38
-rw-r--r--gfx/skia/skia/src/codec/SkBmpCodec.cpp650
-rw-r--r--gfx/skia/skia/src/codec/SkBmpCodec.h151
-rw-r--r--gfx/skia/skia/src/codec/SkBmpMaskCodec.cpp105
-rw-r--r--gfx/skia/skia/src/codec/SkBmpMaskCodec.h62
-rw-r--r--gfx/skia/skia/src/codec/SkBmpRLECodec.cpp568
-rw-r--r--gfx/skia/skia/src/codec/SkBmpRLECodec.h120
-rw-r--r--gfx/skia/skia/src/codec/SkBmpStandardCodec.cpp341
-rw-r--r--gfx/skia/skia/src/codec/SkBmpStandardCodec.h92
-rw-r--r--gfx/skia/skia/src/codec/SkCodec.cpp867
-rw-r--r--gfx/skia/skia/src/codec/SkCodecAnimationPriv.h32
-rw-r--r--gfx/skia/skia/src/codec/SkCodecImageGenerator.cpp99
-rw-r--r--gfx/skia/skia/src/codec/SkCodecImageGenerator.h47
-rw-r--r--gfx/skia/skia/src/codec/SkCodecPriv.h251
-rw-r--r--gfx/skia/skia/src/codec/SkColorTable.cpp23
-rw-r--r--gfx/skia/skia/src/codec/SkColorTable.h50
-rw-r--r--gfx/skia/skia/src/codec/SkEncodedInfo.cpp28
-rw-r--r--gfx/skia/skia/src/codec/SkFrameHolder.h201
-rw-r--r--gfx/skia/skia/src/codec/SkGifCodec.cpp533
-rw-r--r--gfx/skia/skia/src/codec/SkGifCodec.h156
-rw-r--r--gfx/skia/skia/src/codec/SkHeifCodec.cpp472
-rw-r--r--gfx/skia/skia/src/codec/SkHeifCodec.h128
-rw-r--r--gfx/skia/skia/src/codec/SkIcoCodec.cpp384
-rw-r--r--gfx/skia/skia/src/codec/SkIcoCodec.h99
-rw-r--r--gfx/skia/skia/src/codec/SkJpegCodec.cpp974
-rw-r--r--gfx/skia/skia/src/codec/SkJpegCodec.h144
-rw-r--r--gfx/skia/skia/src/codec/SkJpegDecoderMgr.cpp100
-rw-r--r--gfx/skia/skia/src/codec/SkJpegDecoderMgr.h75
-rw-r--r--gfx/skia/skia/src/codec/SkJpegPriv.h53
-rw-r--r--gfx/skia/skia/src/codec/SkJpegUtility.cpp142
-rw-r--r--gfx/skia/skia/src/codec/SkJpegUtility.h44
-rw-r--r--gfx/skia/skia/src/codec/SkMaskSwizzler.cpp568
-rw-r--r--gfx/skia/skia/src/codec/SkMaskSwizzler.h72
-rw-r--r--gfx/skia/skia/src/codec/SkMasks.cpp162
-rw-r--r--gfx/skia/skia/src/codec/SkMasks.h86
-rw-r--r--gfx/skia/skia/src/codec/SkParseEncodedOrigin.cpp61
-rw-r--r--gfx/skia/skia/src/codec/SkParseEncodedOrigin.h19
-rw-r--r--gfx/skia/skia/src/codec/SkPngCodec.cpp1194
-rw-r--r--gfx/skia/skia/src/codec/SkPngCodec.h121
-rw-r--r--gfx/skia/skia/src/codec/SkPngPriv.h19
-rw-r--r--gfx/skia/skia/src/codec/SkRawCodec.cpp797
-rw-r--r--gfx/skia/skia/src/codec/SkRawCodec.h65
-rw-r--r--gfx/skia/skia/src/codec/SkSampledCodec.cpp352
-rw-r--r--gfx/skia/skia/src/codec/SkSampledCodec.h59
-rw-r--r--gfx/skia/skia/src/codec/SkSampler.cpp64
-rw-r--r--gfx/skia/skia/src/codec/SkSampler.h84
-rw-r--r--gfx/skia/skia/src/codec/SkScalingCodec.h39
-rw-r--r--gfx/skia/skia/src/codec/SkStreamBuffer.cpp88
-rw-r--r--gfx/skia/skia/src/codec/SkStreamBuffer.h116
-rw-r--r--gfx/skia/skia/src/codec/SkStubHeifDecoderAPI.h76
-rw-r--r--gfx/skia/skia/src/codec/SkSwizzler.cpp1237
-rw-r--r--gfx/skia/skia/src/codec/SkSwizzler.h222
-rw-r--r--gfx/skia/skia/src/codec/SkWbmpCodec.cpp193
-rw-r--r--gfx/skia/skia/src/codec/SkWbmpCodec.h62
-rw-r--r--gfx/skia/skia/src/codec/SkWebpCodec.cpp567
-rw-r--r--gfx/skia/skia/src/codec/SkWebpCodec.h104
-rw-r--r--gfx/skia/skia/src/codec/SkWuffsCodec.cpp870
-rw-r--r--gfx/skia/skia/src/codec/SkWuffsCodec.h17
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(&copySubset) || 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, &quotient, &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 = &subset;
+ }
+
+ // 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, &copy, 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 = &copy;
+ 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 = &copy;
+ }
+ }
+ 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 = &copy;
+ }
+ }
+ 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