summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/encode
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gfx/skia/skia/src/encode/SkEncoder.cpp29
-rw-r--r--gfx/skia/skia/src/encode/SkICC.cpp762
-rw-r--r--gfx/skia/skia/src/encode/SkICCPriv.h63
-rw-r--r--gfx/skia/skia/src/encode/SkImageEncoder.cpp110
-rw-r--r--gfx/skia/skia/src/encode/SkImageEncoderFns.h213
-rw-r--r--gfx/skia/skia/src/encode/SkImageEncoderPriv.h51
-rw-r--r--gfx/skia/skia/src/encode/SkJPEGWriteUtility.cpp79
-rw-r--r--gfx/skia/skia/src/encode/SkJPEGWriteUtility.h42
-rw-r--r--gfx/skia/skia/src/encode/SkJpegEncoder.cpp419
-rw-r--r--gfx/skia/skia/src/encode/SkJpegGainmapEncoder.cpp413
-rw-r--r--gfx/skia/skia/src/encode/SkPngEncoder.cpp493
-rw-r--r--gfx/skia/skia/src/encode/SkWebpEncoder.cpp249
12 files changed, 2923 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/encode/SkEncoder.cpp b/gfx/skia/skia/src/encode/SkEncoder.cpp
new file mode 100644
index 0000000000..a2d6f05b46
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkEncoder.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "include/encode/SkEncoder.h"
+
+#include "include/private/base/SkAssert.h"
+
+bool SkEncoder::encodeRows(int numRows) {
+ SkASSERT(numRows > 0 && fCurrRow < fSrc.height());
+ if (numRows <= 0 || fCurrRow >= fSrc.height()) {
+ return false;
+ }
+
+ if (fCurrRow + numRows > fSrc.height()) {
+ numRows = fSrc.height() - fCurrRow;
+ }
+
+ if (!this->onEncodeRows(numRows)) {
+ // If we fail, short circuit any future calls.
+ fCurrRow = fSrc.height();
+ return false;
+ }
+
+ return true;
+}
diff --git a/gfx/skia/skia/src/encode/SkICC.cpp b/gfx/skia/skia/src/encode/SkICC.cpp
new file mode 100644
index 0000000000..7163563d61
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkICC.cpp
@@ -0,0 +1,762 @@
+/*
+ * 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/encode/SkICC.h"
+
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkData.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkTypes.h"
+#include "include/private/base/SkFixed.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "modules/skcms/skcms.h"
+#include "src/base/SkAutoMalloc.h"
+#include "src/base/SkEndian.h"
+#include "src/base/SkUtils.h"
+#include "src/core/SkMD5.h"
+#include "src/encode/SkICCPriv.h"
+
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <utility>
+#include <vector>
+
+// The number of input and output channels.
+static constexpr size_t kNumChannels = 3;
+
+// The D50 illuminant.
+constexpr float kD50_x = 0.9642f;
+constexpr float kD50_y = 1.0000f;
+constexpr float kD50_z = 0.8249f;
+
+// This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
+// when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
+// The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
+// SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h.
+static SkFixed float_round_to_fixed(float x) {
+ return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
+}
+
+static uint16_t float_round_to_unorm16(float x) {
+ x = x * 65535.f + 0.5;
+ if (x > 65535) return 65535;
+ if (x < 0) return 0;
+ return static_cast<uint16_t>(x);
+}
+
+struct ICCHeader {
+ // Size of the profile (computed)
+ uint32_t size;
+
+ // Preferred CMM type (ignored)
+ uint32_t cmm_type = 0;
+
+ // Version 4.3 or 4.4 if CICP is included.
+ uint32_t version = SkEndian_SwapBE32(0x04300000);
+
+ // Display device profile
+ uint32_t profile_class = SkEndian_SwapBE32(kDisplay_Profile);
+
+ // RGB input color space;
+ uint32_t data_color_space = SkEndian_SwapBE32(kRGB_ColorSpace);
+
+ // Profile connection space.
+ uint32_t pcs = SkEndian_SwapBE32(kXYZ_PCSSpace);
+
+ // Date and time (ignored)
+ uint16_t creation_date_year = SkEndian_SwapBE16(2016);
+ uint16_t creation_date_month = SkEndian_SwapBE16(1); // 1-12
+ uint16_t creation_date_day = SkEndian_SwapBE16(1); // 1-31
+ uint16_t creation_date_hours = 0; // 0-23
+ uint16_t creation_date_minutes = 0; // 0-59
+ uint16_t creation_date_seconds = 0; // 0-59
+
+ // Profile signature
+ uint32_t signature = SkEndian_SwapBE32(kACSP_Signature);
+
+ // Platform target (ignored)
+ uint32_t platform = 0;
+
+ // Flags: not embedded, can be used independently
+ uint32_t flags = 0x00000000;
+
+ // Device manufacturer (ignored)
+ uint32_t device_manufacturer = 0;
+
+ // Device model (ignored)
+ uint32_t device_model = 0;
+
+ // Device attributes (ignored)
+ uint8_t device_attributes[8] = {0};
+
+ // Relative colorimetric rendering intent
+ uint32_t rendering_intent = SkEndian_SwapBE32(1);
+
+ // D50 standard illuminant (X, Y, Z)
+ uint32_t illuminant_X = SkEndian_SwapBE32(float_round_to_fixed(kD50_x));
+ uint32_t illuminant_Y = SkEndian_SwapBE32(float_round_to_fixed(kD50_y));
+ uint32_t illuminant_Z = SkEndian_SwapBE32(float_round_to_fixed(kD50_z));
+
+ // Profile creator (ignored)
+ uint32_t creator = 0;
+
+ // Profile id checksum (ignored)
+ uint8_t profile_id[16] = {0};
+
+ // Reserved (ignored)
+ uint8_t reserved[28] = {0};
+
+ // Technically not part of header, but required
+ uint32_t tag_count = 0;
+};
+
+static sk_sp<SkData> write_xyz_tag(float x, float y, float z) {
+ uint32_t data[] = {
+ SkEndian_SwapBE32(kXYZ_PCSSpace),
+ 0,
+ SkEndian_SwapBE32(float_round_to_fixed(x)),
+ SkEndian_SwapBE32(float_round_to_fixed(y)),
+ SkEndian_SwapBE32(float_round_to_fixed(z)),
+ };
+ return SkData::MakeWithCopy(data, sizeof(data));
+}
+
+static bool nearly_equal(float x, float y) {
+ // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a
+ // tolerance of 0.001f, which doesn't seem to be enough to distinguish
+ // between similar transfer functions, for example: gamma2.2 and sRGB.
+ //
+ // If the tolerance is 0.0f, then this we can't distinguish between two
+ // different encodings of what is clearly the same colorspace. Some
+ // experimentation with example files lead to this number:
+ static constexpr float kTolerance = 1.0f / (1 << 11);
+ return ::fabsf(x - y) <= kTolerance;
+}
+
+static bool nearly_equal(const skcms_TransferFunction& u,
+ const skcms_TransferFunction& v) {
+ return nearly_equal(u.g, v.g)
+ && nearly_equal(u.a, v.a)
+ && nearly_equal(u.b, v.b)
+ && nearly_equal(u.c, v.c)
+ && nearly_equal(u.d, v.d)
+ && nearly_equal(u.e, v.e)
+ && nearly_equal(u.f, v.f);
+}
+
+static bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) {
+ for (int r = 0; r < 3; r++) {
+ for (int c = 0; c < 3; c++) {
+ if (!nearly_equal(u.vals[r][c], v.vals[r][c])) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static constexpr uint32_t kCICPPrimariesSRGB = 1;
+static constexpr uint32_t kCICPPrimariesP3 = 12;
+static constexpr uint32_t kCICPPrimariesRec2020 = 9;
+
+static uint32_t get_cicp_primaries(const skcms_Matrix3x3& toXYZD50) {
+ if (nearly_equal(toXYZD50, SkNamedGamut::kSRGB)) {
+ return kCICPPrimariesSRGB;
+ } else if (nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3)) {
+ return kCICPPrimariesP3;
+ } else if (nearly_equal(toXYZD50, SkNamedGamut::kRec2020)) {
+ return kCICPPrimariesRec2020;
+ }
+ return 0;
+}
+
+static constexpr uint32_t kCICPTrfnSRGB = 1;
+static constexpr uint32_t kCICPTrfn2Dot2 = 4;
+static constexpr uint32_t kCICPTrfnLinear = 8;
+static constexpr uint32_t kCICPTrfnPQ = 16;
+static constexpr uint32_t kCICPTrfnHLG = 18;
+
+static uint32_t get_cicp_trfn(const skcms_TransferFunction& fn) {
+ switch (skcms_TransferFunction_getType(&fn)) {
+ case skcms_TFType_Invalid:
+ return 0;
+ case skcms_TFType_sRGBish:
+ if (nearly_equal(fn, SkNamedTransferFn::kSRGB)) {
+ return kCICPTrfnSRGB;
+ } else if (nearly_equal(fn, SkNamedTransferFn::k2Dot2)) {
+ return kCICPTrfn2Dot2;
+ } else if (nearly_equal(fn, SkNamedTransferFn::kLinear)) {
+ return kCICPTrfnLinear;
+ }
+ break;
+ case skcms_TFType_PQish:
+ // All PQ transfer functions are mapped to the single PQ value,
+ // ignoring their SDR white level.
+ return kCICPTrfnPQ;
+ case skcms_TFType_HLGish:
+ // All HLG transfer functions are mapped to the single HLG value.
+ return kCICPTrfnHLG;
+ case skcms_TFType_HLGinvish:
+ return 0;
+ }
+ return 0;
+}
+
+static std::string get_desc_string(const skcms_TransferFunction& fn,
+ const skcms_Matrix3x3& toXYZD50) {
+ const uint32_t cicp_trfn = get_cicp_trfn(fn);
+ const uint32_t cicp_primaries = get_cicp_primaries(toXYZD50);
+
+ // Use a unique string for sRGB.
+ if (cicp_trfn == kCICPPrimariesSRGB && cicp_primaries == kCICPTrfnSRGB) {
+ return "sRGB";
+ }
+
+ // If available, use the named CICP primaries and transfer function.
+ if (cicp_primaries && cicp_trfn) {
+ std::string result;
+ switch (cicp_primaries) {
+ case kCICPPrimariesSRGB:
+ result += "sRGB";
+ break;
+ case kCICPPrimariesP3:
+ result += "Display P3";
+ break;
+ case kCICPPrimariesRec2020:
+ result += "Rec2020";
+ break;
+ default:
+ result += "Unknown";
+ break;
+ }
+ result += " Gamut with ";
+ switch (cicp_trfn) {
+ case kCICPTrfnSRGB:
+ result += "sRGB";
+ break;
+ case kCICPTrfnLinear:
+ result += "Linear";
+ break;
+ case kCICPTrfn2Dot2:
+ result += "2.2";
+ break;
+ case kCICPTrfnPQ:
+ result += "PQ";
+ break;
+ case kCICPTrfnHLG:
+ result += "HLG";
+ break;
+ default:
+ result += "Unknown";
+ break;
+ }
+ result += " Transfer";
+ return result;
+ }
+
+ // Fall back to a prefix plus md5 hash.
+ SkMD5 md5;
+ md5.write(&toXYZD50, sizeof(toXYZD50));
+ md5.write(&fn, sizeof(fn));
+ SkMD5::Digest digest = md5.finish();
+ std::string md5_hexstring(2 * sizeof(SkMD5::Digest), ' ');
+ for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
+ uint8_t byte = digest.data[i];
+ md5_hexstring[2 * i + 0] = SkHexadecimalDigits::gUpper[byte >> 4];
+ md5_hexstring[2 * i + 1] = SkHexadecimalDigits::gUpper[byte & 0xF];
+ }
+ return "Google/Skia/" + md5_hexstring;
+}
+
+static sk_sp<SkData> write_text_tag(const char* text) {
+ uint32_t text_length = strlen(text);
+ uint32_t header[] = {
+ SkEndian_SwapBE32(kTAG_TextType), // Type signature
+ 0, // Reserved
+ SkEndian_SwapBE32(1), // Number of records
+ SkEndian_SwapBE32(12), // Record size (must be 12)
+ SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
+ SkEndian_SwapBE32(2 * text_length), // Length of string in bytes
+ SkEndian_SwapBE32(28), // Offset of string
+ };
+ SkDynamicMemoryWStream s;
+ s.write(header, sizeof(header));
+ for (size_t i = 0; i < text_length; i++) {
+ // Convert ASCII to big-endian UTF-16.
+ s.write8(0);
+ s.write8(text[i]);
+ }
+ s.padToAlign4();
+ return s.detachAsData();
+}
+
+// Write a CICP tag.
+static sk_sp<SkData> write_cicp_tag(const skcms_CICP& cicp) {
+ SkDynamicMemoryWStream s;
+ s.write32(SkEndian_SwapBE32(kTAG_cicp)); // Type signature
+ s.write32(0); // Reserved
+ s.write8(cicp.color_primaries); // Color primaries
+ s.write8(cicp.transfer_characteristics); // Transfer characteristics
+ s.write8(cicp.matrix_coefficients); // RGB matrix
+ s.write8(cicp.video_full_range_flag); // Full range
+ return s.detachAsData();
+}
+
+// Perform a matrix-vector multiplication. Overwrite the input vector with the result.
+static void skcms_Matrix3x3_apply(const skcms_Matrix3x3* m, float* x) {
+ float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2];
+ float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2];
+ float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2];
+ x[0] = y0;
+ x[1] = y1;
+ x[2] = y2;
+}
+
+void SkICCFloatXYZD50ToGrid16Lab(const float* xyz_float, uint8_t* grid16_lab) {
+ float v[3] = {
+ xyz_float[0] / kD50_x,
+ xyz_float[1] / kD50_y,
+ xyz_float[2] / kD50_z,
+ };
+ for (size_t i = 0; i < 3; ++i) {
+ v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f);
+ }
+ const float L = v[1] * 116.0f - 16.0f;
+ const float a = (v[0] - v[1]) * 500.0f;
+ const float b = (v[1] - v[2]) * 200.0f;
+ const float Lab_unorm[3] = {
+ L * (1 / 100.f),
+ (a + 128.0f) * (1 / 255.0f),
+ (b + 128.0f) * (1 / 255.0f),
+ };
+ // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the
+ // table, but the spec appears to indicate that the value should be 0xFF00.
+ // https://crbug.com/skia/13807
+ for (size_t i = 0; i < 3; ++i) {
+ reinterpret_cast<uint16_t*>(grid16_lab)[i] =
+ SkEndian_SwapBE16(float_round_to_unorm16(Lab_unorm[i]));
+ }
+}
+
+void SkICCFloatToTable16(const float f, uint8_t* table_16) {
+ *reinterpret_cast<uint16_t*>(table_16) = SkEndian_SwapBE16(float_round_to_unorm16(f));
+}
+
+// Compute the tone mapping gain for luminance value L. The gain should be
+// applied after the transfer function is applied.
+float compute_tone_map_gain(const skcms_TransferFunction& fn, float L) {
+ if (L <= 0.f) {
+ return 1.f;
+ }
+ if (skcms_TransferFunction_isPQish(&fn)) {
+ // The PQ transfer function will map to the range [0, 1]. Linearly scale
+ // it up to the range [0, 10,000/203]. We will then tone map that back
+ // down to [0, 1].
+ constexpr float kInputMaxLuminance = 10000 / 203.f;
+ constexpr float kOutputMaxLuminance = 1.0;
+ L *= kInputMaxLuminance;
+
+ // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
+ constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
+ constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
+ return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
+ }
+ if (skcms_TransferFunction_isHLGish(&fn)) {
+ // Let Lw be the brightness of the display in nits.
+ constexpr float Lw = 203.f;
+ const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
+ return std::pow(L, gamma - 1.f);
+ }
+ return 1.f;
+}
+
+// Write a lookup table based curve, potentially including tone mapping.
+static sk_sp<SkData> write_trc_tag(const skcms_Curve& trc) {
+ SkDynamicMemoryWStream s;
+ if (trc.table_entries) {
+ s.write32(SkEndian_SwapBE32(kTAG_CurveType)); // Type
+ s.write32(0); // Reserved
+ s.write32(SkEndian_SwapBE32(trc.table_entries)); // Value count
+ for (uint32_t i = 0; i < trc.table_entries; ++i) {
+ uint16_t value = reinterpret_cast<const uint16_t*>(trc.table_16)[i];
+ s.write16(value);
+ }
+ } else {
+ s.write32(SkEndian_SwapBE32(kTAG_ParaCurveType)); // Type
+ s.write32(0); // Reserved
+ const auto& fn = trc.parametric;
+ SkASSERT(skcms_TransferFunction_isSRGBish(&fn));
+ if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f && fn.d == 0.f && fn.e == 0.f &&
+ fn.f == 0.f) {
+ s.write32(SkEndian_SwapBE16(kExponential_ParaCurveType));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.g)));
+ } else {
+ s.write32(SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.g)));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.a)));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.b)));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.c)));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.d)));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.e)));
+ s.write32(SkEndian_SwapBE32(float_round_to_fixed(fn.f)));
+ }
+ }
+ s.padToAlign4();
+ return s.detachAsData();
+}
+
+void compute_lut_entry(const skcms_Matrix3x3& src_to_XYZD50, float rgb[3]) {
+ // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50.
+ skcms_Matrix3x3 src_to_rec2020;
+ const skcms_Matrix3x3 rec2020_to_XYZD50 = SkNamedGamut::kRec2020;
+ {
+ skcms_Matrix3x3 XYZD50_to_rec2020;
+ skcms_Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020);
+ src_to_rec2020 = skcms_Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50);
+ }
+
+ // Convert the source signal to linear.
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ rgb[i] = skcms_TransferFunction_eval(&SkNamedTransferFn::kPQ, rgb[i]);
+ }
+
+ // Convert source gamut to Rec2020.
+ skcms_Matrix3x3_apply(&src_to_rec2020, rgb);
+
+ // Compute the luminance of the signal.
+ constexpr float kLr = 0.2627f;
+ constexpr float kLg = 0.6780f;
+ constexpr float kLb = 0.0593f;
+ float L = rgb[0] * kLr + rgb[1] * kLg + rgb[2] * kLb;
+
+ // Compute the tone map gain based on the luminance.
+ float tone_map_gain = compute_tone_map_gain(SkNamedTransferFn::kPQ, L);
+
+ // Apply the tone map gain.
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ rgb[i] *= tone_map_gain;
+ }
+
+ // Convert from Rec2020-linear to XYZD50.
+ skcms_Matrix3x3_apply(&rec2020_to_XYZD50, rgb);
+}
+
+sk_sp<SkData> write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
+ SkDynamicMemoryWStream s;
+ for (size_t i = 0; i < 16; ++i) {
+ s.write8(i < kNumChannels ? grid_points[i] : 0); // Grid size
+ }
+ s.write8(2); // Grid byte width (always 16-bit)
+ s.write8(0); // Reserved
+ s.write8(0); // Reserved
+ s.write8(0); // Reserved
+
+ uint32_t value_count = kNumChannels;
+ for (uint32_t i = 0; i < kNumChannels; ++i) {
+ value_count *= grid_points[i];
+ }
+ for (uint32_t i = 0; i < value_count; ++i) {
+ uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
+ s.write16(value);
+ }
+ s.padToAlign4();
+ return s.detachAsData();
+}
+
+// Write an A2B or B2A tag.
+sk_sp<SkData> write_mAB_or_mBA_tag(uint32_t type,
+ const skcms_Curve* b_curves,
+ const skcms_Curve* a_curves,
+ const uint8_t* grid_points,
+ const uint8_t* grid_16,
+ const skcms_Curve* m_curves,
+ const skcms_Matrix3x4* matrix) {
+ const size_t b_curves_offset = 32;
+ sk_sp<SkData> b_curves_data[kNumChannels];
+ size_t clut_offset = 0;
+ sk_sp<SkData> clut;
+ size_t a_curves_offset = 0;
+ sk_sp<SkData> a_curves_data[kNumChannels];
+
+ // The "B" curve is required.
+ SkASSERT(b_curves);
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ b_curves_data[i] = write_trc_tag(b_curves[i]);
+ SkASSERT(b_curves_data[i]);
+ }
+
+ // The "A" curve and CLUT are optional.
+ if (a_curves) {
+ SkASSERT(grid_points);
+ SkASSERT(grid_16);
+
+ clut_offset = b_curves_offset;
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ clut_offset += b_curves_data[i]->size();
+ }
+ clut = write_clut(grid_points, grid_16);
+ SkASSERT(clut);
+
+ a_curves_offset = clut_offset + clut->size();
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ a_curves_data[i] = write_trc_tag(a_curves[i]);
+ SkASSERT(a_curves_data[i]);
+ }
+ }
+
+ // The "M" curves and matrix are not supported yet.
+ SkASSERT(!m_curves);
+ SkASSERT(!matrix);
+
+ SkDynamicMemoryWStream s;
+ s.write32(SkEndian_SwapBE32(type)); // Type signature
+ s.write32(0); // Reserved
+ s.write8(kNumChannels); // Input channels
+ s.write8(kNumChannels); // Output channels
+ s.write16(0); // Reserved
+ s.write32(SkEndian_SwapBE32(b_curves_offset)); // B curve offset
+ s.write32(SkEndian_SwapBE32(0)); // Matrix offset (ignored)
+ s.write32(SkEndian_SwapBE32(0)); // M curve offset (ignored)
+ s.write32(SkEndian_SwapBE32(clut_offset)); // CLUT offset
+ s.write32(SkEndian_SwapBE32(a_curves_offset)); // A curve offset
+ SkASSERT(s.bytesWritten() == b_curves_offset);
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ s.write(b_curves_data[i]->data(), b_curves_data[i]->size());
+ }
+ if (a_curves) {
+ SkASSERT(s.bytesWritten() == clut_offset);
+ s.write(clut->data(), clut->size());
+ SkASSERT(s.bytesWritten() == a_curves_offset);
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ s.write(a_curves_data[i]->data(), a_curves_data[i]->size());
+ }
+ }
+ return s.detachAsData();
+}
+
+sk_sp<SkData> SkWriteICCProfile(const skcms_ICCProfile* profile, const char* desc) {
+ ICCHeader header;
+
+ std::vector<std::pair<uint32_t, sk_sp<SkData>>> tags;
+
+ // Compute profile description tag
+ tags.emplace_back(kTAG_desc, write_text_tag(desc));
+
+ // Compute primaries.
+ if (profile->has_toXYZD50) {
+ const auto& m = profile->toXYZD50;
+ tags.emplace_back(kTAG_rXYZ, write_xyz_tag(m.vals[0][0], m.vals[1][0], m.vals[2][0]));
+ tags.emplace_back(kTAG_gXYZ, write_xyz_tag(m.vals[0][1], m.vals[1][1], m.vals[2][1]));
+ tags.emplace_back(kTAG_bXYZ, write_xyz_tag(m.vals[0][2], m.vals[1][2], m.vals[2][2]));
+ }
+
+ // Compute white point tag (must be D50)
+ tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
+
+ // Compute transfer curves.
+ if (profile->has_trc) {
+ tags.emplace_back(kTAG_rTRC, write_trc_tag(profile->trc[0]));
+
+ // Use empty data to indicate that the entry should use the previous tag's
+ // data.
+ if (!memcmp(&profile->trc[1], &profile->trc[0], sizeof(profile->trc[0]))) {
+ tags.emplace_back(kTAG_gTRC, SkData::MakeEmpty());
+ } else {
+ tags.emplace_back(kTAG_gTRC, write_trc_tag(profile->trc[1]));
+ }
+
+ if (!memcmp(&profile->trc[2], &profile->trc[1], sizeof(profile->trc[1]))) {
+ tags.emplace_back(kTAG_bTRC, SkData::MakeEmpty());
+ } else {
+ tags.emplace_back(kTAG_bTRC, write_trc_tag(profile->trc[2]));
+ }
+ }
+
+ // Compute CICP.
+ if (profile->has_CICP) {
+ // The CICP tag is present in ICC 4.4, so update the header's version.
+ header.version = SkEndian_SwapBE32(0x04400000);
+ tags.emplace_back(kTAG_cicp, write_cicp_tag(profile->CICP));
+ }
+
+ // Compute A2B0.
+ if (profile->has_A2B) {
+ const auto& a2b = profile->A2B;
+ SkASSERT(a2b.output_channels == kNumChannels);
+ auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
+ a2b.output_curves,
+ a2b.input_channels ? a2b.input_curves : nullptr,
+ a2b.input_channels ? a2b.grid_points : nullptr,
+ a2b.input_channels ? a2b.grid_16 : nullptr,
+ a2b.matrix_channels ? a2b.matrix_curves : nullptr,
+ a2b.matrix_channels ? &a2b.matrix : nullptr);
+ tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
+ }
+
+ // Compute B2A0.
+ if (profile->has_B2A) {
+ const auto& b2a = profile->B2A;
+ SkASSERT(b2a.input_channels == kNumChannels);
+ auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
+ b2a.input_curves,
+ b2a.output_channels ? b2a.input_curves : nullptr,
+ b2a.output_channels ? b2a.grid_points : nullptr,
+ b2a.output_channels ? b2a.grid_16 : nullptr,
+ b2a.matrix_channels ? b2a.matrix_curves : nullptr,
+ b2a.matrix_channels ? &b2a.matrix : nullptr);
+ tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
+ }
+
+ // Compute copyright tag
+ tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2016"));
+
+ // Compute the size of the profile.
+ size_t tag_data_size = 0;
+ for (const auto& tag : tags) {
+ tag_data_size += tag.second->size();
+ }
+ size_t tag_table_size = kICCTagTableEntrySize * tags.size();
+ size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
+
+ // Write the header.
+ header.data_color_space = SkEndian_SwapBE32(profile->data_color_space);
+ header.pcs = SkEndian_SwapBE32(profile->pcs);
+ header.size = SkEndian_SwapBE32(profile_size);
+ header.tag_count = SkEndian_SwapBE32(tags.size());
+
+ SkAutoMalloc profile_data(profile_size);
+ uint8_t* ptr = (uint8_t*)profile_data.get();
+ memcpy(ptr, &header, sizeof(header));
+ ptr += sizeof(header);
+
+ // Write the tag table. Track the offset and size of the previous tag to
+ // compute each tag's offset. An empty SkData indicates that the previous
+ // tag is to be reused.
+ size_t last_tag_offset = sizeof(header) + tag_table_size;
+ size_t last_tag_size = 0;
+ for (const auto& tag : tags) {
+ if (!tag.second->isEmpty()) {
+ last_tag_offset = last_tag_offset + last_tag_size;
+ last_tag_size = tag.second->size();
+ }
+ uint32_t tag_table_entry[3] = {
+ SkEndian_SwapBE32(tag.first),
+ SkEndian_SwapBE32(last_tag_offset),
+ SkEndian_SwapBE32(last_tag_size),
+ };
+ memcpy(ptr, tag_table_entry, sizeof(tag_table_entry));
+ ptr += sizeof(tag_table_entry);
+ }
+
+ // Write the tags.
+ for (const auto& tag : tags) {
+ if (tag.second->isEmpty()) continue;
+ memcpy(ptr, tag.second->data(), tag.second->size());
+ ptr += tag.second->size();
+ }
+
+ SkASSERT(profile_size == static_cast<size_t>(ptr - (uint8_t*)profile_data.get()));
+ return SkData::MakeFromMalloc(profile_data.release(), profile_size);
+}
+
+sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn, const skcms_Matrix3x3& toXYZD50) {
+ skcms_ICCProfile profile;
+ memset(&profile, 0, sizeof(profile));
+ std::vector<uint8_t> trc_table;
+ std::vector<uint8_t> a2b_grid;
+
+ profile.data_color_space = skcms_Signature_RGB;
+ profile.pcs = skcms_Signature_XYZ;
+
+ // Populate toXYZD50.
+ {
+ profile.has_toXYZD50 = true;
+ profile.toXYZD50 = toXYZD50;
+ }
+
+ // Populate TRC (except for PQ).
+ if (!skcms_TransferFunction_isPQish(&fn)) {
+ profile.has_trc = true;
+ if (skcms_TransferFunction_isSRGBish(&fn)) {
+ profile.trc[0].table_entries = 0;
+ profile.trc[0].parametric = fn;
+ } else if (skcms_TransferFunction_isHLGish(&fn)) {
+ skcms_TransferFunction scaled_hlg = SkNamedTransferFn::kHLG;
+ scaled_hlg.f = 1 / 12.f - 1.f;
+ constexpr uint32_t kTrcTableSize = 65;
+ trc_table.resize(kTrcTableSize * 2);
+ for (uint32_t i = 0; i < kTrcTableSize; ++i) {
+ float x = i / (kTrcTableSize - 1.f);
+ float y = skcms_TransferFunction_eval(&scaled_hlg, x);
+ y *= compute_tone_map_gain(scaled_hlg, y);
+ SkICCFloatToTable16(y, &trc_table[2 * i]);
+ }
+
+ profile.trc[0].table_entries = kTrcTableSize;
+ profile.trc[0].table_16 = reinterpret_cast<uint8_t*>(trc_table.data());
+ }
+ memcpy(&profile.trc[1], &profile.trc[0], sizeof(profile.trc[0]));
+ memcpy(&profile.trc[2], &profile.trc[0], sizeof(profile.trc[0]));
+ }
+
+ // Populate A2B (PQ only).
+ if (skcms_TransferFunction_isPQish(&fn)) {
+ profile.pcs = skcms_Signature_Lab;
+
+ constexpr uint32_t kGridSize = 17;
+ profile.has_A2B = true;
+ profile.A2B.input_channels = kNumChannels;
+ profile.A2B.output_channels = kNumChannels;
+ for (size_t i = 0; i < 3; ++i) {
+ profile.A2B.input_curves[i].parametric = SkNamedTransferFn::kLinear;
+ profile.A2B.output_curves[i].parametric = SkNamedTransferFn::kLinear;
+ profile.A2B.grid_points[i] = kGridSize;
+ }
+
+ a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2);
+ size_t a2b_grid_index = 0;
+ for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
+ for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
+ for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
+ float rgb[3] = {
+ r_index / (kGridSize - 1.f),
+ g_index / (kGridSize - 1.f),
+ b_index / (kGridSize - 1.f),
+ };
+ compute_lut_entry(toXYZD50, rgb);
+ SkICCFloatXYZD50ToGrid16Lab(rgb, &a2b_grid[a2b_grid_index]);
+ a2b_grid_index += 6;
+ }
+ }
+ }
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ profile.A2B.grid_points[i] = kGridSize;
+ }
+ profile.A2B.grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
+
+ profile.has_B2A = true;
+ profile.B2A.input_channels = kNumChannels;
+ for (size_t i = 0; i < 3; ++i) {
+ profile.B2A.input_curves[i].parametric = SkNamedTransferFn::kLinear;
+ }
+ }
+
+ // Populate CICP.
+ if (skcms_TransferFunction_isHLGish(&fn) || skcms_TransferFunction_isPQish(&fn)) {
+ profile.has_CICP = true;
+ profile.CICP.color_primaries = get_cicp_primaries(toXYZD50);
+ profile.CICP.transfer_characteristics = get_cicp_trfn(fn);
+ profile.CICP.matrix_coefficients = 0;
+ profile.CICP.video_full_range_flag = 1;
+ SkASSERT(profile.CICP.color_primaries);
+ SkASSERT(profile.CICP.transfer_characteristics);
+ }
+
+ std::string description = get_desc_string(fn, toXYZD50);
+ return SkWriteICCProfile(&profile, description.c_str());
+}
diff --git a/gfx/skia/skia/src/encode/SkICCPriv.h b/gfx/skia/skia/src/encode/SkICCPriv.h
new file mode 100644
index 0000000000..18757705ea
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkICCPriv.h
@@ -0,0 +1,63 @@
+/*
+ * 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 SkICCPriv_DEFINED
+#define SkICCPriv_DEFINED
+
+#include "include/core/SkTypes.h" // SkSetFourByteTag
+
+#include <cstddef>
+#include <cstdint>
+
+// This is equal to the header size according to the ICC specification (128)
+// plus the size of the tag count (4). We include the tag count since we
+// always require it to be present anyway.
+static constexpr size_t kICCHeaderSize = 132;
+
+// Contains a signature (4), offset (4), and size (4).
+static constexpr size_t kICCTagTableEntrySize = 12;
+
+static constexpr uint32_t kRGB_ColorSpace = SkSetFourByteTag('R', 'G', 'B', ' ');
+static constexpr uint32_t kCMYK_ColorSpace = SkSetFourByteTag('C', 'M', 'Y', 'K');
+static constexpr uint32_t kGray_ColorSpace = SkSetFourByteTag('G', 'R', 'A', 'Y');
+static constexpr uint32_t kDisplay_Profile = SkSetFourByteTag('m', 'n', 't', 'r');
+static constexpr uint32_t kInput_Profile = SkSetFourByteTag('s', 'c', 'n', 'r');
+static constexpr uint32_t kOutput_Profile = SkSetFourByteTag('p', 'r', 't', 'r');
+static constexpr uint32_t kColorSpace_Profile = SkSetFourByteTag('s', 'p', 'a', 'c');
+static constexpr uint32_t kXYZ_PCSSpace = SkSetFourByteTag('X', 'Y', 'Z', ' ');
+static constexpr uint32_t kLAB_PCSSpace = SkSetFourByteTag('L', 'a', 'b', ' ');
+static constexpr uint32_t kACSP_Signature = SkSetFourByteTag('a', 'c', 's', 'p');
+
+static constexpr uint32_t kTAG_rXYZ = SkSetFourByteTag('r', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_gXYZ = SkSetFourByteTag('g', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_bXYZ = SkSetFourByteTag('b', 'X', 'Y', 'Z');
+static constexpr uint32_t kTAG_rTRC = SkSetFourByteTag('r', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_gTRC = SkSetFourByteTag('g', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_bTRC = SkSetFourByteTag('b', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_kTRC = SkSetFourByteTag('k', 'T', 'R', 'C');
+static constexpr uint32_t kTAG_A2B0 = SkSetFourByteTag('A', '2', 'B', '0');
+static constexpr uint32_t kTAG_B2A0 = SkSetFourByteTag('B', '2', 'A', '0');
+static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
+static constexpr uint32_t kTAG_cicp = SkSetFourByteTag('c', 'i', 'c', 'p');
+static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
+static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
+
+static constexpr uint32_t kTAG_CurveType = SkSetFourByteTag('c', 'u', 'r', 'v');
+static constexpr uint32_t kTAG_ParaCurveType = SkSetFourByteTag('p', 'a', 'r', 'a');
+static constexpr uint32_t kTAG_TextType = SkSetFourByteTag('m', 'l', 'u', 'c');
+static constexpr uint32_t kTAG_mABType = SkSetFourByteTag('m', 'A', 'B', ' ');
+static constexpr uint32_t kTAG_mBAType = SkSetFourByteTag('m', 'B', 'A', ' ');
+
+enum ParaCurveType {
+ kExponential_ParaCurveType = 0,
+ kGAB_ParaCurveType = 1,
+ kGABC_ParaCurveType = 2,
+ kGABDE_ParaCurveType = 3,
+ kGABCDEF_ParaCurveType = 4,
+};
+
+#endif // SkICCPriv_DEFINED
diff --git a/gfx/skia/skia/src/encode/SkImageEncoder.cpp b/gfx/skia/skia/src/encode/SkImageEncoder.cpp
new file mode 100644
index 0000000000..72c4cf8b28
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkImageEncoder.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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/core/SkImageEncoder.h"
+
+#include "include/codec/SkEncodedImageFormat.h"
+#include "include/core/SkBitmap.h"
+#include "include/core/SkData.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkTypes.h"
+#include "include/encode/SkJpegEncoder.h"
+#include "include/encode/SkPngEncoder.h"
+#include "include/encode/SkWebpEncoder.h"
+
+#if SK_ENABLE_NDK_IMAGES || SK_USE_CG_ENCODER || SK_USE_WIC_ENCODER
+#include "src/encode/SkImageEncoderPriv.h"
+#endif
+
+#if !defined(SK_ENCODE_JPEG)|| !defined(SK_ENCODE_PNG) || !defined(SK_ENCODE_WEBP)
+#include <memory>
+
+class SkEncoder;
+#endif
+
+#if !defined(SK_ENCODE_JPEG)
+bool SkJpegEncoder::Encode(SkWStream*, const SkPixmap&, const Options&) { return false; }
+std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream*, const SkPixmap&, const Options&) {
+ return nullptr;
+}
+#endif
+
+#if !defined(SK_ENCODE_PNG)
+bool SkPngEncoder::Encode(SkWStream*, const SkPixmap&, const Options&) { return false; }
+std::unique_ptr<SkEncoder> SkPngEncoder::Make(SkWStream*, const SkPixmap&, const Options&) {
+ return nullptr;
+}
+#endif
+
+#if !defined(SK_ENCODE_WEBP)
+bool SkWebpEncoder::Encode(SkWStream*, const SkPixmap&, const Options&) { return false; }
+#endif
+
+bool SkEncodeImage(SkWStream* dst, const SkBitmap& src, SkEncodedImageFormat f, int q) {
+ SkPixmap pixmap;
+ return src.peekPixels(&pixmap) && SkEncodeImage(dst, pixmap, f, q);
+}
+
+bool SkEncodeImage(SkWStream* dst, const SkPixmap& src,
+ SkEncodedImageFormat format, int quality) {
+ #ifdef SK_USE_CG_ENCODER
+ (void)quality;
+ return SkEncodeImageWithCG(dst, src, format);
+ #elif SK_USE_WIC_ENCODER
+ return SkEncodeImageWithWIC(dst, src, format, quality);
+ #elif SK_ENABLE_NDK_IMAGES
+ return SkEncodeImageWithNDK(dst, src, format, quality);
+ #else
+ switch(format) {
+ case SkEncodedImageFormat::kJPEG: {
+ SkJpegEncoder::Options opts;
+ opts.fQuality = quality;
+ return SkJpegEncoder::Encode(dst, src, opts);
+ }
+ case SkEncodedImageFormat::kPNG: {
+ SkPngEncoder::Options opts;
+ return SkPngEncoder::Encode(dst, src, opts);
+ }
+ case SkEncodedImageFormat::kWEBP: {
+ SkWebpEncoder::Options opts;
+ if (quality == 100) {
+ opts.fCompression = SkWebpEncoder::Compression::kLossless;
+ // Note: SkEncodeImage treats 0 quality as the lowest quality
+ // (greatest compression) and 100 as the highest quality (least
+ // compression). For kLossy, this matches libwebp's
+ // interpretation, so it is passed directly to libwebp. But
+ // with kLossless, libwebp always creates the highest quality
+ // image. In this case, fQuality is reinterpreted as how much
+ // effort (time) to put into making a smaller file. This API
+ // does not provide a way to specify this value (though it can
+ // be specified by using SkWebpEncoder::Encode) so we have to
+ // pick one arbitrarily. This value matches that chosen by
+ // blink::ImageEncoder::ComputeWebpOptions as well
+ // WebPConfigInit.
+ opts.fQuality = 75;
+ } else {
+ opts.fCompression = SkWebpEncoder::Compression::kLossy;
+ opts.fQuality = quality;
+ }
+ return SkWebpEncoder::Encode(dst, src, opts);
+ }
+ default:
+ return false;
+ }
+ #endif
+}
+
+sk_sp<SkData> SkEncodePixmap(const SkPixmap& src, SkEncodedImageFormat format, int quality) {
+ SkDynamicMemoryWStream stream;
+ return SkEncodeImage(&stream, src, format, quality) ? stream.detachAsData() : nullptr;
+}
+
+sk_sp<SkData> SkEncodeBitmap(const SkBitmap& src, SkEncodedImageFormat format, int quality) {
+ SkPixmap pixmap;
+ return src.peekPixels(&pixmap) ? SkEncodePixmap(pixmap, format, quality) : nullptr;
+}
diff --git a/gfx/skia/skia/src/encode/SkImageEncoderFns.h b/gfx/skia/skia/src/encode/SkImageEncoderFns.h
new file mode 100644
index 0000000000..c567716280
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkImageEncoderFns.h
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2012 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 SkImageEncoderFns_DEFINED
+#define SkImageEncoderFns_DEFINED
+
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkData.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkTypes.h"
+#include "include/encode/SkICC.h"
+#include "modules/skcms/skcms.h"
+
+#include <cstring>
+
+typedef void (*transform_scanline_proc)(char* dst, const char* src, int width, int bpp);
+
+static inline void transform_scanline_memcpy(char* dst, const char* src, int width, int bpp) {
+ memcpy(dst, src, width * bpp);
+}
+
+static inline void transform_scanline_A8_to_GrayAlpha(char* dst, const char* src, int width, int) {
+ for (int i = 0; i < width; i++) {
+ *dst++ = 0;
+ *dst++ = *src++;
+ }
+}
+
+
+static void skcms(char* dst, const char* src, int n,
+ skcms_PixelFormat srcFmt, skcms_AlphaFormat srcAlpha,
+ skcms_PixelFormat dstFmt, skcms_AlphaFormat dstAlpha) {
+ SkAssertResult(skcms_Transform(src, srcFmt, srcAlpha, nullptr,
+ dst, dstFmt, dstAlpha, nullptr, n));
+}
+
+static inline void transform_scanline_gray(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_G_8, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_565(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGR_565, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_RGBX(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_888 , skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_BGRX(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_888 , skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_444(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_ABGR_4444, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_888 , skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_rgbA(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_bgrA(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_to_premul_legacy(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_PremulAsEncoded);
+}
+
+static inline void transform_scanline_BGRA(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGRA_8888, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_4444(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_ABGR_4444, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_101010x(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_1010102, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_1010102(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_1010102, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_1010102_premul(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_1010102, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_bgr_101010x(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGRA_1010102, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGB_161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_bgra_1010102(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGRA_1010102, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_bgra_1010102_premul(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_BGRA_1010102, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_F16(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_F16_premul(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_F16_to_8888(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_F16_premul_to_8888(char* dst,
+ const char* src,
+ int width,
+ int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_F16_to_premul_8888(char* dst,
+ const char* src,
+ int width,
+ int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_hhhh, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_8888, skcms_AlphaFormat_PremulAsEncoded);
+}
+
+static inline void transform_scanline_F32(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline void transform_scanline_F32_premul(char* dst, const char* src, int width, int) {
+ skcms(dst, src, width,
+ skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_PremulAsEncoded,
+ skcms_PixelFormat_RGBA_16161616BE, skcms_AlphaFormat_Unpremul);
+}
+
+static inline sk_sp<SkData> icc_from_color_space(const SkColorSpace* cs,
+ const skcms_ICCProfile* profile,
+ const char* profile_description) {
+ // TODO(ccameron): Remove this check.
+ if (!cs) {
+ return nullptr;
+ }
+
+ if (profile) {
+ return SkWriteICCProfile(profile, profile_description);
+ }
+
+ skcms_Matrix3x3 toXYZD50;
+ if (cs->toXYZD50(&toXYZD50)) {
+ skcms_TransferFunction fn;
+ cs->transferFn(&fn);
+ return SkWriteICCProfile(fn, toXYZD50);
+ }
+ return nullptr;
+}
+
+static inline sk_sp<SkData> icc_from_color_space(const SkImageInfo& info,
+ const skcms_ICCProfile* profile,
+ const char* profile_description) {
+ return icc_from_color_space(info.colorSpace(), profile, profile_description);
+}
+
+#endif // SkImageEncoderFns_DEFINED
diff --git a/gfx/skia/skia/src/encode/SkImageEncoderPriv.h b/gfx/skia/skia/src/encode/SkImageEncoderPriv.h
new file mode 100644
index 0000000000..9fedae51f6
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkImageEncoderPriv.h
@@ -0,0 +1,51 @@
+/*
+ * 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 SkImageEncoderPriv_DEFINED
+#define SkImageEncoderPriv_DEFINED
+
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkPixmap.h"
+#include "src/core/SkImageInfoPriv.h"
+
+#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) || \
+ defined(SK_BUILD_FOR_WIN) || defined(SK_ENABLE_NDK_IMAGES)
+#include "include/codec/SkEncodedImageFormat.h"
+class SkWStream;
+#endif
+
+static inline bool SkPixmapIsValid(const SkPixmap& src) {
+ if (!SkImageInfoIsValid(src.info())) {
+ return false;
+ }
+
+ if (!src.addr() || src.rowBytes() < src.info().minRowBytes()) {
+ return false;
+ }
+
+ return true;
+}
+
+#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
+ bool SkEncodeImageWithCG(SkWStream*, const SkPixmap&, SkEncodedImageFormat);
+#else
+ #define SkEncodeImageWithCG(...) false
+#endif
+
+#ifdef SK_BUILD_FOR_WIN
+ bool SkEncodeImageWithWIC(SkWStream*, const SkPixmap&, SkEncodedImageFormat, int quality);
+#else
+ #define SkEncodeImageWithWIC(...) false
+#endif
+
+#ifdef SK_ENABLE_NDK_IMAGES
+ bool SkEncodeImageWithNDK(SkWStream*, const SkPixmap&, SkEncodedImageFormat, int quality);
+#else
+ #define SkEncodeImageWithNDK(...) false
+#endif
+
+#endif // SkImageEncoderPriv_DEFINED
diff --git a/gfx/skia/skia/src/encode/SkJPEGWriteUtility.cpp b/gfx/skia/skia/src/encode/SkJPEGWriteUtility.cpp
new file mode 100644
index 0000000000..024242d545
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkJPEGWriteUtility.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2010 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 "src/encode/SkJPEGWriteUtility.h"
+
+#include "include/core/SkStream.h"
+#include "include/private/base/SkTArray.h"
+#include "src/codec/SkJpegPriv.h"
+
+#include <csetjmp>
+#include <cstddef>
+
+extern "C" {
+ #include "jerror.h"
+ #include "jmorecfg.h"
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void sk_init_destination(j_compress_ptr cinfo) {
+ skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest;
+
+ dest->next_output_byte = dest->fBuffer;
+ dest->free_in_buffer = skjpeg_destination_mgr::kBufferSize;
+}
+
+static boolean sk_empty_output_buffer(j_compress_ptr cinfo) {
+ skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest;
+
+// if (!dest->fStream->write(dest->fBuffer, skjpeg_destination_mgr::kBufferSize - dest->free_in_buffer))
+ if (!dest->fStream->write(dest->fBuffer,
+ skjpeg_destination_mgr::kBufferSize)) {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ return FALSE;
+ }
+
+ dest->next_output_byte = dest->fBuffer;
+ dest->free_in_buffer = skjpeg_destination_mgr::kBufferSize;
+ return TRUE;
+}
+
+static void sk_term_destination (j_compress_ptr cinfo) {
+ skjpeg_destination_mgr* dest = (skjpeg_destination_mgr*)cinfo->dest;
+
+ size_t size = skjpeg_destination_mgr::kBufferSize - dest->free_in_buffer;
+ if (size > 0) {
+ if (!dest->fStream->write(dest->fBuffer, size)) {
+ ERREXIT(cinfo, JERR_FILE_WRITE);
+ return;
+ }
+ }
+
+ dest->fStream->flush();
+}
+
+skjpeg_destination_mgr::skjpeg_destination_mgr(SkWStream* stream) : fStream(stream) {
+ this->init_destination = sk_init_destination;
+ this->empty_output_buffer = sk_empty_output_buffer;
+ this->term_destination = sk_term_destination;
+}
+
+void skjpeg_error_exit(j_common_ptr cinfo) {
+ skjpeg_error_mgr* error = (skjpeg_error_mgr*)cinfo->err;
+
+ (*error->output_message) (cinfo);
+
+ /* Let the memory manager delete any temp files before we die */
+ jpeg_destroy(cinfo);
+
+ if (error->fJmpBufStack.empty()) {
+ SK_ABORT("JPEG error with no jmp_buf set.");
+ }
+ longjmp(*error->fJmpBufStack.back(), -1);
+}
diff --git a/gfx/skia/skia/src/encode/SkJPEGWriteUtility.h b/gfx/skia/skia/src/encode/SkJPEGWriteUtility.h
new file mode 100644
index 0000000000..c534bbf6c1
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkJPEGWriteUtility.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2010 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 SkJpegUtility_DEFINED
+#define SkJpegUtility_DEFINED
+
+#include "include/core/SkTypes.h"
+
+#include <cstdint>
+
+extern "C" {
+ // We need to include stdio.h before jpeg because jpeg does not include it, but uses FILE
+ // See https://github.com/libjpeg-turbo/libjpeg-turbo/issues/17
+ #include <stdio.h> // IWYU pragma: keep
+ #include "jpeglib.h"
+}
+
+class SkWStream;
+
+void skjpeg_error_exit(j_common_ptr cinfo);
+
+/////////////////////////////////////////////////////////////////////////////
+/* Our destination struct for directing decompressed pixels to our stream
+ * object.
+ */
+struct SK_SPI skjpeg_destination_mgr : jpeg_destination_mgr {
+ skjpeg_destination_mgr(SkWStream* stream);
+
+ SkWStream* const fStream;
+
+ enum {
+ kBufferSize = 1024
+ };
+ uint8_t fBuffer[kBufferSize];
+};
+
+#endif
diff --git a/gfx/skia/skia/src/encode/SkJpegEncoder.cpp b/gfx/skia/skia/src/encode/SkJpegEncoder.cpp
new file mode 100644
index 0000000000..d764a52ebc
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkJpegEncoder.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright 2007 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/core/SkTypes.h"
+
+#ifdef SK_ENCODE_JPEG
+
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkData.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkYUVAInfo.h"
+#include "include/core/SkYUVAPixmaps.h"
+#include "include/encode/SkEncoder.h"
+#include "include/encode/SkJpegEncoder.h"
+#include "include/private/base/SkNoncopyable.h"
+#include "include/private/base/SkTemplates.h"
+#include "src/base/SkMSAN.h"
+#include "src/codec/SkJpegConstants.h"
+#include "src/codec/SkJpegPriv.h"
+#include "src/encode/SkImageEncoderFns.h"
+#include "src/encode/SkImageEncoderPriv.h"
+#include "src/encode/SkJPEGWriteUtility.h"
+
+#include <csetjmp>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <utility>
+
+class SkColorSpace;
+
+extern "C" {
+ #include "jpeglib.h"
+ #include "jmorecfg.h"
+}
+
+class SkJpegEncoderMgr final : SkNoncopyable {
+public:
+ /*
+ * Create the decode manager
+ * Does not take ownership of stream.
+ */
+ static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) {
+ return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream));
+ }
+
+ bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options);
+ bool setParams(const SkYUVAPixmapInfo& srcInfo, const SkJpegEncoder::Options& options);
+
+ jpeg_compress_struct* cinfo() { return &fCInfo; }
+
+ skjpeg_error_mgr* errorMgr() { return &fErrMgr; }
+
+ transform_scanline_proc proc() const { return fProc; }
+
+ ~SkJpegEncoderMgr() {
+ jpeg_destroy_compress(&fCInfo);
+ }
+
+private:
+ SkJpegEncoderMgr(SkWStream* stream) : fDstMgr(stream), fProc(nullptr) {
+ fCInfo.err = jpeg_std_error(&fErrMgr);
+ fErrMgr.error_exit = skjpeg_error_exit;
+ jpeg_create_compress(&fCInfo);
+ fCInfo.dest = &fDstMgr;
+ }
+
+ jpeg_compress_struct fCInfo;
+ skjpeg_error_mgr fErrMgr;
+ skjpeg_destination_mgr fDstMgr;
+ transform_scanline_proc fProc;
+};
+
+bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options)
+{
+ auto chooseProc8888 = [&]() {
+ if (kUnpremul_SkAlphaType == srcInfo.alphaType() &&
+ options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) {
+ return transform_scanline_to_premul_legacy;
+ }
+ return (transform_scanline_proc) nullptr;
+ };
+
+ J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA;
+ int numComponents = 0;
+ switch (srcInfo.colorType()) {
+ case kRGBA_8888_SkColorType:
+ fProc = chooseProc8888();
+ jpegColorType = JCS_EXT_RGBA;
+ numComponents = 4;
+ break;
+ case kBGRA_8888_SkColorType:
+ fProc = chooseProc8888();
+ jpegColorType = JCS_EXT_BGRA;
+ numComponents = 4;
+ break;
+ case kRGB_565_SkColorType:
+ fProc = transform_scanline_565;
+ jpegColorType = JCS_RGB;
+ numComponents = 3;
+ break;
+ case kARGB_4444_SkColorType:
+ if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) {
+ return false;
+ }
+
+ fProc = transform_scanline_444;
+ jpegColorType = JCS_RGB;
+ numComponents = 3;
+ break;
+ case kGray_8_SkColorType:
+ case kAlpha_8_SkColorType:
+ case kR8_unorm_SkColorType:
+ jpegColorType = JCS_GRAYSCALE;
+ numComponents = 1;
+ break;
+ case kRGBA_F16_SkColorType:
+ if (kUnpremul_SkAlphaType == srcInfo.alphaType() &&
+ options.fAlphaOption == SkJpegEncoder::AlphaOption::kBlendOnBlack) {
+ fProc = transform_scanline_F16_to_premul_8888;
+ } else {
+ fProc = transform_scanline_F16_to_8888;
+ }
+ jpegColorType = JCS_EXT_RGBA;
+ numComponents = 4;
+ break;
+ default:
+ return false;
+ }
+
+ fCInfo.image_width = srcInfo.width();
+ fCInfo.image_height = srcInfo.height();
+ fCInfo.in_color_space = jpegColorType;
+ fCInfo.input_components = numComponents;
+ jpeg_set_defaults(&fCInfo);
+
+ if (numComponents != 1) {
+ switch (options.fDownsample) {
+ case SkJpegEncoder::Downsample::k420:
+ SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor);
+ SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
+ break;
+ case SkJpegEncoder::Downsample::k422:
+ fCInfo.comp_info[0].h_samp_factor = 2;
+ fCInfo.comp_info[0].v_samp_factor = 1;
+ SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
+ break;
+ case SkJpegEncoder::Downsample::k444:
+ fCInfo.comp_info[0].h_samp_factor = 1;
+ fCInfo.comp_info[0].v_samp_factor = 1;
+ SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
+ SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
+ break;
+ }
+ }
+
+ // Tells libjpeg-turbo to compute optimal Huffman coding tables
+ // for the image. This improves compression at the cost of
+ // slower encode performance.
+ fCInfo.optimize_coding = TRUE;
+ return true;
+}
+
+// Convert a row of an SkYUVAPixmaps to a row of Y,U,V triples.
+// TODO(ccameron): This is horribly inefficient.
+static void yuva_copy_row(const SkYUVAPixmaps* src, int row, uint8_t* dst) {
+ int width = src->plane(0).width();
+ switch (src->yuvaInfo().planeConfig()) {
+ case SkYUVAInfo::PlaneConfig::kY_U_V: {
+ auto [ssWidthU, ssHeightU] = src->yuvaInfo().planeSubsamplingFactors(1);
+ auto [ssWidthV, ssHeightV] = src->yuvaInfo().planeSubsamplingFactors(2);
+ const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src->plane(0).addr(0, row));
+ const uint8_t* srcU =
+ reinterpret_cast<const uint8_t*>(src->plane(1).addr(0, row / ssHeightU));
+ const uint8_t* srcV =
+ reinterpret_cast<const uint8_t*>(src->plane(2).addr(0, row / ssHeightV));
+ for (int col = 0; col < width; ++col) {
+ dst[3 * col + 0] = srcY[col];
+ dst[3 * col + 1] = srcU[col / ssWidthU];
+ dst[3 * col + 2] = srcV[col / ssWidthV];
+ }
+ break;
+ }
+ case SkYUVAInfo::PlaneConfig::kY_UV: {
+ auto [ssWidthUV, ssHeightUV] = src->yuvaInfo().planeSubsamplingFactors(1);
+ const uint8_t* srcY = reinterpret_cast<const uint8_t*>(src->plane(0).addr(0, row));
+ const uint8_t* srcUV =
+ reinterpret_cast<const uint8_t*>(src->plane(1).addr(0, row / ssHeightUV));
+ for (int col = 0; col < width; ++col) {
+ dst[3 * col + 0] = srcY[col];
+ dst[3 * col + 1] = srcUV[2 * (col / ssWidthUV) + 0];
+ dst[3 * col + 2] = srcUV[2 * (col / ssWidthUV) + 1];
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+bool SkJpegEncoderMgr::setParams(const SkYUVAPixmapInfo& srcInfo,
+ const SkJpegEncoder::Options& options) {
+ fCInfo.image_width = srcInfo.yuvaInfo().width();
+ fCInfo.image_height = srcInfo.yuvaInfo().height();
+ fCInfo.in_color_space = JCS_YCbCr;
+ fCInfo.input_components = 3;
+ jpeg_set_defaults(&fCInfo);
+
+ // Support no color space conversion.
+ if (srcInfo.yuvColorSpace() != kJPEG_Full_SkYUVColorSpace) {
+ return false;
+ }
+
+ // Support only 8-bit data.
+ switch (srcInfo.dataType()) {
+ case SkYUVAPixmapInfo::DataType::kUnorm8:
+ break;
+ default:
+ return false;
+ }
+
+ // Support only Y,U,V and Y,UV configurations (they are the only ones supported by
+ // yuva_copy_row).
+ switch (srcInfo.yuvaInfo().planeConfig()) {
+ case SkYUVAInfo::PlaneConfig::kY_U_V:
+ case SkYUVAInfo::PlaneConfig::kY_UV:
+ break;
+ default:
+ return false;
+ }
+
+ // Specify to the encoder to use the same subsampling as the input image. The U and V planes
+ // always have a sampling factor of 1.
+ auto [ssHoriz, ssVert] = SkYUVAInfo::SubsamplingFactors(srcInfo.yuvaInfo().subsampling());
+ fCInfo.comp_info[0].h_samp_factor = ssHoriz;
+ fCInfo.comp_info[0].v_samp_factor = ssVert;
+
+ fCInfo.optimize_coding = TRUE;
+ return true;
+}
+
+std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst,
+ const SkPixmap& src,
+ const Options& options) {
+ return Make(dst, &src, nullptr, nullptr, options);
+}
+
+std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst,
+ const SkYUVAPixmaps& src,
+ const SkColorSpace* srcColorSpace,
+ const Options& options) {
+ return Make(dst, nullptr, &src, srcColorSpace, options);
+}
+
+std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst,
+ const SkPixmap* src,
+ const SkYUVAPixmaps* srcYUVA,
+ const SkColorSpace* srcYUVAColorSpace,
+ const Options& options) {
+ // Exactly one of |src| or |srcYUVA| should be specified.
+ if (srcYUVA) {
+ SkASSERT(!src);
+ if (!srcYUVA->isValid()) {
+ return nullptr;
+ }
+ } else {
+ SkASSERT(src);
+ if (!src || !SkPixmapIsValid(*src)) {
+ return nullptr;
+ }
+ }
+
+ std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
+
+ skjpeg_error_mgr::AutoPushJmpBuf jmp(encoderMgr->errorMgr());
+ if (setjmp(jmp)) {
+ return nullptr;
+ }
+
+ if (srcYUVA) {
+ if (!encoderMgr->setParams(srcYUVA->pixmapsInfo(), options)) {
+ return nullptr;
+ }
+ } else {
+ if (!encoderMgr->setParams(src->info(), options)) {
+ return nullptr;
+ }
+ }
+
+ jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE);
+ jpeg_start_compress(encoderMgr->cinfo(), TRUE);
+
+ // Write XMP metadata. This will only write the standard XMP segment.
+ // TODO(ccameron): Split this into a standard and extended XMP segment if needed.
+ if (options.xmpMetadata) {
+ SkDynamicMemoryWStream s;
+ s.write(kXMPStandardSig, sizeof(kXMPStandardSig));
+ s.write(options.xmpMetadata->data(), options.xmpMetadata->size());
+ auto data = s.detachAsData();
+ jpeg_write_marker(encoderMgr->cinfo(), kXMPMarker, data->bytes(), data->size());
+ }
+
+ // Write the ICC profile.
+ // TODO(ccameron): This limits ICC profile size to a single segment's parameters (less than
+ // 64k). Split larger profiles into more segments.
+ sk_sp<SkData> icc = icc_from_color_space(srcYUVA ? srcYUVAColorSpace : src->colorSpace(),
+ options.fICCProfile,
+ options.fICCProfileDescription);
+ if (icc) {
+ // Create a contiguous block of memory with the icc signature followed by the profile.
+ sk_sp<SkData> markerData =
+ SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size());
+ uint8_t* ptr = (uint8_t*) markerData->writable_data();
+ memcpy(ptr, kICCSig, sizeof(kICCSig));
+ ptr += sizeof(kICCSig);
+ *ptr++ = 1; // This is the first marker.
+ *ptr++ = 1; // Out of one total markers.
+ memcpy(ptr, icc->data(), icc->size());
+
+ jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(), markerData->size());
+ }
+
+ if (srcYUVA) {
+ return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), srcYUVA));
+ }
+ return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), *src));
+}
+
+SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkPixmap& src)
+ : INHERITED(src,
+ encoderMgr->proc() ? encoderMgr->cinfo()->input_components * src.width() : 0)
+ , fEncoderMgr(std::move(encoderMgr)) {}
+
+SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkYUVAPixmaps* src)
+ : INHERITED(src->plane(0), encoderMgr->cinfo()->input_components * src->yuvaInfo().width())
+ , fEncoderMgr(std::move(encoderMgr))
+ , fSrcYUVA(src) {}
+
+SkJpegEncoder::~SkJpegEncoder() {}
+
+bool SkJpegEncoder::onEncodeRows(int numRows) {
+ skjpeg_error_mgr::AutoPushJmpBuf jmp(fEncoderMgr->errorMgr());
+ if (setjmp(jmp)) {
+ return false;
+ }
+
+ if (fSrcYUVA) {
+ // TODO(ccameron): Consider using jpeg_write_raw_data, to avoid having to re-pack the data.
+ for (int i = 0; i < numRows; i++) {
+ yuva_copy_row(fSrcYUVA, fCurrRow + i, fStorage.get());
+ JSAMPLE* jpegSrcRow = fStorage.get();
+ jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
+ }
+ } else {
+ const size_t srcBytes = SkColorTypeBytesPerPixel(fSrc.colorType()) * fSrc.width();
+ const size_t jpegSrcBytes = fEncoderMgr->cinfo()->input_components * fSrc.width();
+ const void* srcRow = fSrc.addr(0, fCurrRow);
+ for (int i = 0; i < numRows; i++) {
+ JSAMPLE* jpegSrcRow = (JSAMPLE*)srcRow;
+ if (fEncoderMgr->proc()) {
+ sk_msan_assert_initialized(srcRow, SkTAddOffset<const void>(srcRow, srcBytes));
+ fEncoderMgr->proc()((char*)fStorage.get(),
+ (const char*)srcRow,
+ fSrc.width(),
+ fEncoderMgr->cinfo()->input_components);
+ jpegSrcRow = fStorage.get();
+ sk_msan_assert_initialized(jpegSrcRow,
+ SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes));
+ } else {
+ // Same as above, but this repetition allows determining whether a
+ // proc was used when msan asserts.
+ sk_msan_assert_initialized(jpegSrcRow,
+ SkTAddOffset<const void>(jpegSrcRow, jpegSrcBytes));
+ }
+
+ jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
+ srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
+ }
+ }
+
+ fCurrRow += numRows;
+ if (fCurrRow == fSrc.height()) {
+ jpeg_finish_compress(fEncoderMgr->cinfo());
+ }
+
+ return true;
+}
+
+bool SkJpegEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
+ auto encoder = SkJpegEncoder::Make(dst, src, options);
+ return encoder.get() && encoder->encodeRows(src.height());
+}
+
+bool SkJpegEncoder::Encode(SkWStream* dst,
+ const SkYUVAPixmaps& src,
+ const SkColorSpace* srcColorSpace,
+ const Options& options) {
+ auto encoder = SkJpegEncoder::Make(dst, src, srcColorSpace, options);
+ return encoder.get() && encoder->encodeRows(src.yuvaInfo().height());
+}
+
+#endif
diff --git a/gfx/skia/skia/src/encode/SkJpegGainmapEncoder.cpp b/gfx/skia/skia/src/encode/SkJpegGainmapEncoder.cpp
new file mode 100644
index 0000000000..80709def8c
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkJpegGainmapEncoder.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2023 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/SkJpegGainmapEncoder.h"
+
+#ifdef SK_ENCODE_JPEG
+
+#include "include/core/SkBitmap.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkStream.h"
+#include "include/encode/SkJpegEncoder.h"
+#include "include/private/SkGainmapInfo.h"
+#include "src/codec/SkCodecPriv.h"
+#include "src/codec/SkJpegConstants.h"
+#include "src/codec/SkJpegMultiPicture.h"
+#include "src/codec/SkJpegPriv.h"
+#include "src/codec/SkJpegSegmentScan.h"
+
+#include <vector>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// XMP helpers
+
+void xmp_write_prefix(SkDynamicMemoryWStream& s, const std::string& ns, const std::string& attrib) {
+ s.writeText(ns.c_str());
+ s.writeText(":");
+ s.writeText(attrib.c_str());
+ s.writeText("=\"");
+}
+
+void xmp_write_suffix(SkDynamicMemoryWStream& s, bool newLine) {
+ s.writeText("\"");
+ if (newLine) {
+ s.writeText("\n");
+ }
+}
+
+void xmp_write_per_channel_attr(SkDynamicMemoryWStream& s,
+ const std::string& ns,
+ const std::string& attrib,
+ SkScalar r,
+ SkScalar g,
+ SkScalar b,
+ bool newLine = true) {
+ xmp_write_prefix(s, ns, attrib);
+ if (r == g && r == b) {
+ s.writeScalarAsText(r);
+ } else {
+ s.writeScalarAsText(r);
+ s.writeText(",");
+ s.writeScalarAsText(g);
+ s.writeText(",");
+ s.writeScalarAsText(b);
+ }
+ xmp_write_suffix(s, newLine);
+}
+
+void xmp_write_scalar_attr(SkDynamicMemoryWStream& s,
+ const std::string& ns,
+ const std::string& attrib,
+ SkScalar value,
+ bool newLine = true) {
+ xmp_write_prefix(s, ns, attrib);
+ s.writeScalarAsText(value);
+ xmp_write_suffix(s, newLine);
+}
+
+void xmp_write_decimal_attr(SkDynamicMemoryWStream& s,
+ const std::string& ns,
+ const std::string& attrib,
+ int32_t value,
+ bool newLine = true) {
+ xmp_write_prefix(s, ns, attrib);
+ s.writeDecAsText(value);
+ xmp_write_suffix(s, newLine);
+}
+
+void xmp_write_string_attr(SkDynamicMemoryWStream& s,
+ const std::string& ns,
+ const std::string& attrib,
+ const std::string& value,
+ bool newLine = true) {
+ xmp_write_prefix(s, ns, attrib);
+ s.writeText(value.c_str());
+ xmp_write_suffix(s, newLine);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// JpegR encoding
+
+bool SkJpegGainmapEncoder::EncodeJpegR(SkWStream* dst,
+ const SkPixmap& base,
+ const SkJpegEncoder::Options& baseOptions,
+ const SkPixmap& gainmap,
+ const SkJpegEncoder::Options& gainmapOptions,
+ const SkGainmapInfo& gainmapInfo) {
+ return EncodeHDRGM(dst, base, baseOptions, gainmap, gainmapOptions, gainmapInfo);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// HDRGM encoding
+
+// Generate the XMP metadata for an HDRGM file.
+sk_sp<SkData> get_hdrgm_xmp_data(const SkGainmapInfo& gainmapInfo) {
+ const float kLog2 = sk_float_log(2.f);
+ SkDynamicMemoryWStream s;
+ s.writeText(
+ "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"XMP Core 5.5.0\">\n"
+ " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
+ " <rdf:Description rdf:about=\"\"\n"
+ " xmlns:hdrgm=\"http://ns.adobe.com/hdr-gain-map/1.0/\"\n");
+ const std::string hdrgmPrefix = " hdrgm";
+ xmp_write_string_attr(s, hdrgmPrefix, "Version", "1.0");
+ xmp_write_per_channel_attr(s,
+ hdrgmPrefix,
+ "GainMapMin",
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fR) / kLog2,
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fG) / kLog2,
+ sk_float_log(gainmapInfo.fGainmapRatioMin.fB) / kLog2);
+ xmp_write_per_channel_attr(s,
+ hdrgmPrefix,
+ "GainMapMax",
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fR) / kLog2,
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fG) / kLog2,
+ sk_float_log(gainmapInfo.fGainmapRatioMax.fB) / kLog2);
+ xmp_write_per_channel_attr(s,
+ hdrgmPrefix,
+ "Gamma",
+ gainmapInfo.fGainmapGamma.fR,
+ gainmapInfo.fGainmapGamma.fG,
+ gainmapInfo.fGainmapGamma.fB);
+ xmp_write_per_channel_attr(s,
+ hdrgmPrefix,
+ "OffsetSDR",
+ gainmapInfo.fEpsilonSdr.fR,
+ gainmapInfo.fEpsilonSdr.fG,
+ gainmapInfo.fEpsilonSdr.fB);
+ xmp_write_per_channel_attr(s,
+ hdrgmPrefix,
+ "OffsetHDR",
+ gainmapInfo.fEpsilonHdr.fR,
+ gainmapInfo.fEpsilonHdr.fG,
+ gainmapInfo.fEpsilonHdr.fB);
+ xmp_write_scalar_attr(
+ s, hdrgmPrefix, "HDRCapacityMin", sk_float_log(gainmapInfo.fDisplayRatioSdr) / kLog2);
+ xmp_write_scalar_attr(
+ s, hdrgmPrefix, "HDRCapacityMax", sk_float_log(gainmapInfo.fDisplayRatioHdr) / kLog2);
+ switch (gainmapInfo.fBaseImageType) {
+ case SkGainmapInfo::BaseImageType::kSDR:
+ xmp_write_string_attr(s, hdrgmPrefix, "BaseRendition", "SDR", /*newLine=*/false);
+ break;
+ case SkGainmapInfo::BaseImageType::kHDR:
+ xmp_write_string_attr(s, hdrgmPrefix, "BaseRendition", "HDR", /*newLine=*/false);
+ break;
+ }
+ s.writeText(
+ "/>\n"
+ " </rdf:RDF>\n"
+ "</x:xmpmeta>");
+ return s.detachAsData();
+}
+
+// Generate the GContainer metadata for an image with a JPEG gainmap.
+static sk_sp<SkData> get_gcontainer_xmp_data(size_t gainmapItemLength) {
+ SkDynamicMemoryWStream s;
+ s.writeText(
+ "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 5.1.2\">\n"
+ " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"
+ " <rdf:Description\n"
+ " xmlns:Container=\"http://ns.google.com/photos/1.0/container/\"\n"
+ " xmlns:Item=\"http://ns.google.com/photos/1.0/container/item/\">\n"
+ " <Container:Directory>\n"
+ " <rdf:Seq>\n"
+ " <rdf:li>\n"
+ " <Container:Item\n"
+ " Item:Semantic=\"Primary\"\n"
+ " Item:Mime=\"image/jpeg\"/>\n"
+ " </rdf:li>\n"
+ " <rdf:li>\n"
+ " <Container:Item\n"
+ " Item:Semantic=\"RecoveryMap\"\n"
+ " Item:Mime=\"image/jpeg\"\n"
+ " ");
+ xmp_write_decimal_attr(s, "Item", "Length", gainmapItemLength, /*newLine=*/false);
+ s.writeText(
+ "/>\n"
+ " </rdf:li>\n"
+ " </rdf:Seq>\n"
+ " </Container:Directory>\n"
+ " </rdf:Description>\n"
+ " </rdf:RDF>\n"
+ "</x:xmpmeta>\n");
+ return s.detachAsData();
+}
+
+// Split an SkData into segments.
+std::vector<sk_sp<SkData>> get_hdrgm_image_segments(sk_sp<SkData> image,
+ size_t segmentMaxDataSize) {
+ // Compute the total size of the header to a gainmap image segment (not including the 2 bytes
+ // for the segment size, which the encoder is responsible for writing).
+ constexpr size_t kGainmapHeaderSize = sizeof(kGainmapSig) + 2 * kGainmapMarkerIndexSize;
+
+ // Compute the payload size for each segment.
+ const size_t kGainmapPayloadSize = segmentMaxDataSize - kGainmapHeaderSize;
+
+ // Compute the number of segments we'll need.
+ const size_t segmentCount = (image->size() + kGainmapPayloadSize - 1) / kGainmapPayloadSize;
+ std::vector<sk_sp<SkData>> result;
+ result.reserve(segmentCount);
+
+ // Move |imageData| through |image| until it hits |imageDataEnd|.
+ const uint8_t* imageData = image->bytes();
+ const uint8_t* imageDataEnd = image->bytes() + image->size();
+ while (imageData < imageDataEnd) {
+ SkDynamicMemoryWStream segmentStream;
+
+ // Write the signature.
+ segmentStream.write(kGainmapSig, sizeof(kGainmapSig));
+
+ // Write the segment index as big-endian.
+ size_t segmentIndex = result.size() + 1;
+ uint8_t segmentIndexBytes[2] = {
+ static_cast<uint8_t>(segmentIndex / 256u),
+ static_cast<uint8_t>(segmentIndex % 256u),
+ };
+ segmentStream.write(segmentIndexBytes, sizeof(segmentIndexBytes));
+
+ // Write the segment count as big-endian.
+ uint8_t segmentCountBytes[2] = {
+ static_cast<uint8_t>(segmentCount / 256u),
+ static_cast<uint8_t>(segmentCount % 256u),
+ };
+ segmentStream.write(segmentCountBytes, sizeof(segmentCountBytes));
+
+ // Verify that our header size math is correct.
+ SkASSERT(segmentStream.bytesWritten() == kGainmapHeaderSize);
+
+ // Write the rest of the segment.
+ size_t bytesToWrite =
+ std::min(imageDataEnd - imageData, static_cast<intptr_t>(kGainmapPayloadSize));
+ segmentStream.write(imageData, bytesToWrite);
+ imageData += bytesToWrite;
+
+ // Verify that our data size math is correct.
+ if (segmentIndex == segmentCount) {
+ SkASSERT(segmentStream.bytesWritten() <= segmentMaxDataSize);
+ } else {
+ SkASSERT(segmentStream.bytesWritten() == segmentMaxDataSize);
+ }
+ result.push_back(segmentStream.detachAsData());
+ }
+
+ // Verify that our segment count math was correct.
+ SkASSERT(imageData == imageDataEnd);
+ SkASSERT(result.size() == segmentCount);
+ return result;
+}
+
+static sk_sp<SkData> encode_to_data(const SkPixmap& pm,
+ const SkJpegEncoder::Options& options,
+ SkData* xmpMetadata) {
+ SkJpegEncoder::Options optionsWithXmp = options;
+ optionsWithXmp.xmpMetadata = xmpMetadata;
+ SkDynamicMemoryWStream encodeStream;
+ auto encoder = SkJpegEncoder::Make(&encodeStream, pm, optionsWithXmp);
+ if (!encoder || !encoder->encodeRows(pm.height())) {
+ return nullptr;
+ }
+ return encodeStream.detachAsData();
+}
+
+static sk_sp<SkData> get_mpf_segment(const SkJpegMultiPictureParameters& mpParams) {
+ SkDynamicMemoryWStream s;
+ auto segmentParameters = mpParams.serialize();
+ const size_t mpParameterLength = kJpegSegmentParameterLengthSize + segmentParameters->size();
+ s.write8(0xFF);
+ s.write8(kMpfMarker);
+ s.write8(mpParameterLength / 256);
+ s.write8(mpParameterLength % 256);
+ s.write(segmentParameters->data(), segmentParameters->size());
+ return s.detachAsData();
+}
+
+bool SkJpegGainmapEncoder::EncodeHDRGM(SkWStream* dst,
+ const SkPixmap& base,
+ const SkJpegEncoder::Options& baseOptions,
+ const SkPixmap& gainmap,
+ const SkJpegEncoder::Options& gainmapOptions,
+ const SkGainmapInfo& gainmapInfo) {
+ // Encode the gainmap image with the HDRGM XMP metadata.
+ sk_sp<SkData> gainmapData;
+ {
+ // We will include the HDRGM XMP metadata in the gainmap image.
+ auto hdrgmXmp = get_hdrgm_xmp_data(gainmapInfo);
+ gainmapData = encode_to_data(gainmap, gainmapOptions, hdrgmXmp.get());
+ if (!gainmapData) {
+ SkCodecPrintf("Failed to encode gainmap image.\n");
+ return false;
+ }
+ }
+
+ // Encode the base image with the Container XMP metadata.
+ sk_sp<SkData> baseData;
+ {
+ auto containerXmp = get_gcontainer_xmp_data(static_cast<int32_t>(gainmapData->size()));
+ baseData = encode_to_data(base, baseOptions, containerXmp.get());
+ if (!baseData) {
+ SkCodecPrintf("Failed to encode base image.\n");
+ return false;
+ }
+ }
+
+ // Combine them into an MPF.
+ const SkData* images[] = {
+ baseData.get(),
+ gainmapData.get(),
+ };
+ return MakeMPF(dst, images, 2);
+}
+
+bool SkJpegGainmapEncoder::MakeMPF(SkWStream* dst, const SkData** images, size_t imageCount) {
+ if (imageCount < 1) {
+ return true;
+ }
+
+ // Create a scan of the primary image.
+ SkJpegSegmentScanner primaryScan;
+ primaryScan.onBytes(images[0]->data(), images[0]->size());
+ if (!primaryScan.isDone()) {
+ SkCodecPrintf("Failed to scan encoded primary image header.\n");
+ return false;
+ }
+
+ // Copy the primary image up to its StartOfScan, then insert the MPF segment, then copy the rest
+ // of the primary image, and all other images.
+ size_t bytesRead = 0;
+ size_t bytesWritten = 0;
+ for (const auto& segment : primaryScan.getSegments()) {
+ // Write all ECD before this segment.
+ {
+ size_t ecdBytesToWrite = segment.offset - bytesRead;
+ if (!dst->write(images[0]->bytes() + bytesRead, ecdBytesToWrite)) {
+ SkCodecPrintf("Failed to write entropy coded data.\n");
+ return false;
+ }
+ bytesWritten += ecdBytesToWrite;
+ bytesRead = segment.offset;
+ }
+
+ // If this isn't a StartOfScan, write just the segment.
+ if (segment.marker != kJpegMarkerStartOfScan) {
+ const size_t bytesToWrite = kJpegMarkerCodeSize + segment.parameterLength;
+ if (!dst->write(images[0]->bytes() + bytesRead, bytesToWrite)) {
+ SkCodecPrintf("Failed to copy segment.\n");
+ return false;
+ }
+ bytesWritten += bytesToWrite;
+ bytesRead += bytesToWrite;
+ continue;
+ }
+
+ // We're now at the StartOfScan.
+ const size_t bytesRemaining = images[0]->size() - bytesRead;
+
+ // Compute the MPF offsets for the images.
+ SkJpegMultiPictureParameters mpParams;
+ {
+ mpParams.images.resize(imageCount);
+ const size_t mpSegmentSize = kJpegMarkerCodeSize + kJpegSegmentParameterLengthSize +
+ mpParams.serialize()->size();
+ mpParams.images[0].size =
+ static_cast<uint32_t>(bytesWritten + mpSegmentSize + bytesRemaining);
+ uint32_t offset =
+ static_cast<uint32_t>(bytesRemaining + mpSegmentSize - kJpegMarkerCodeSize -
+ kJpegSegmentParameterLengthSize - sizeof(kMpfSig));
+ for (size_t i = 0; i < imageCount; ++i) {
+ mpParams.images[i].dataOffset = offset;
+ mpParams.images[i].size = static_cast<uint32_t>(images[i]->size());
+ offset += mpParams.images[i].size;
+ }
+ }
+
+ // Write the MPF segment.
+ auto mpfSegment = get_mpf_segment(mpParams);
+ if (!dst->write(mpfSegment->data(), mpfSegment->size())) {
+ SkCodecPrintf("Failed to write MPF segment.\n");
+ return false;
+ }
+
+ // Write the rest of the primary file.
+ if (!dst->write(images[0]->bytes() + bytesRead, bytesRemaining)) {
+ SkCodecPrintf("Failed to write remainder of primary image.\n");
+ return false;
+ }
+ bytesRead += bytesRemaining;
+ SkASSERT(bytesRead == images[0]->size());
+ break;
+ }
+
+ // Write the remaining files.
+ for (size_t i = 1; i < imageCount; ++i) {
+ if (!dst->write(images[i]->data(), images[i]->size())) {
+ SkCodecPrintf("Failed to write auxiliary image.\n");
+ }
+ }
+ return true;
+}
+
+#endif // SK_ENCODE_JPEG
diff --git a/gfx/skia/skia/src/encode/SkPngEncoder.cpp b/gfx/skia/skia/src/encode/SkPngEncoder.cpp
new file mode 100644
index 0000000000..55ca9f5239
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkPngEncoder.cpp
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2006 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/core/SkTypes.h"
+
+#ifdef SK_ENCODE_PNG
+
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkData.h"
+#include "include/core/SkDataTable.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkString.h"
+#include "include/encode/SkEncoder.h"
+#include "include/encode/SkPngEncoder.h"
+#include "include/private/base/SkDebug.h"
+#include "include/private/base/SkNoncopyable.h"
+#include "include/private/base/SkTemplates.h"
+#include "modules/skcms/skcms.h"
+#include "src/base/SkMSAN.h"
+#include "src/codec/SkPngPriv.h"
+#include "src/encode/SkImageEncoderFns.h"
+#include "src/encode/SkImageEncoderPriv.h"
+
+#include <algorithm>
+#include <csetjmp>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <png.h>
+#include <pngconf.h>
+
+static_assert(PNG_FILTER_NONE == (int)SkPngEncoder::FilterFlag::kNone, "Skia libpng filter err.");
+static_assert(PNG_FILTER_SUB == (int)SkPngEncoder::FilterFlag::kSub, "Skia libpng filter err.");
+static_assert(PNG_FILTER_UP == (int)SkPngEncoder::FilterFlag::kUp, "Skia libpng filter err.");
+static_assert(PNG_FILTER_AVG == (int)SkPngEncoder::FilterFlag::kAvg, "Skia libpng filter err.");
+static_assert(PNG_FILTER_PAETH == (int)SkPngEncoder::FilterFlag::kPaeth, "Skia libpng filter err.");
+static_assert(PNG_ALL_FILTERS == (int)SkPngEncoder::FilterFlag::kAll, "Skia libpng filter err.");
+
+static constexpr bool kSuppressPngEncodeWarnings = true;
+
+static void sk_error_fn(png_structp png_ptr, png_const_charp msg) {
+ if (!kSuppressPngEncodeWarnings) {
+ SkDebugf("libpng encode error: %s\n", msg);
+ }
+
+ longjmp(png_jmpbuf(png_ptr), 1);
+}
+
+static void sk_write_fn(png_structp png_ptr, png_bytep data, png_size_t len) {
+ SkWStream* stream = (SkWStream*)png_get_io_ptr(png_ptr);
+ if (!stream->write(data, len)) {
+ png_error(png_ptr, "sk_write_fn cannot write to stream");
+ }
+}
+
+class SkPngEncoderMgr final : SkNoncopyable {
+public:
+
+ /*
+ * Create the decode manager
+ * Does not take ownership of stream
+ */
+ static std::unique_ptr<SkPngEncoderMgr> Make(SkWStream* stream);
+
+ bool setHeader(const SkImageInfo& srcInfo, const SkPngEncoder::Options& options);
+ bool setColorSpace(const SkImageInfo& info, const SkPngEncoder::Options& options);
+ bool writeInfo(const SkImageInfo& srcInfo);
+ void chooseProc(const SkImageInfo& srcInfo);
+
+ png_structp pngPtr() { return fPngPtr; }
+ png_infop infoPtr() { return fInfoPtr; }
+ int pngBytesPerPixel() const { return fPngBytesPerPixel; }
+ transform_scanline_proc proc() const { return fProc; }
+
+ ~SkPngEncoderMgr() {
+ png_destroy_write_struct(&fPngPtr, &fInfoPtr);
+ }
+
+private:
+
+ SkPngEncoderMgr(png_structp pngPtr, png_infop infoPtr)
+ : fPngPtr(pngPtr)
+ , fInfoPtr(infoPtr)
+ {}
+
+ png_structp fPngPtr;
+ png_infop fInfoPtr;
+ int fPngBytesPerPixel;
+ transform_scanline_proc fProc;
+};
+
+std::unique_ptr<SkPngEncoderMgr> SkPngEncoderMgr::Make(SkWStream* stream) {
+ png_structp pngPtr =
+ png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, sk_error_fn, nullptr);
+ if (!pngPtr) {
+ return nullptr;
+ }
+
+ png_infop infoPtr = png_create_info_struct(pngPtr);
+ if (!infoPtr) {
+ png_destroy_write_struct(&pngPtr, nullptr);
+ return nullptr;
+ }
+
+ png_set_write_fn(pngPtr, (void*)stream, sk_write_fn, nullptr);
+ return std::unique_ptr<SkPngEncoderMgr>(new SkPngEncoderMgr(pngPtr, infoPtr));
+}
+
+bool SkPngEncoderMgr::setHeader(const SkImageInfo& srcInfo, const SkPngEncoder::Options& options) {
+ if (setjmp(png_jmpbuf(fPngPtr))) {
+ return false;
+ }
+
+ int pngColorType;
+ png_color_8 sigBit;
+ int bitDepth = 8;
+ switch (srcInfo.colorType()) {
+ case kRGBA_F16Norm_SkColorType:
+ case kRGBA_F16_SkColorType:
+ case kRGBA_F32_SkColorType:
+ sigBit.red = 16;
+ sigBit.green = 16;
+ sigBit.blue = 16;
+ sigBit.alpha = 16;
+ bitDepth = 16;
+ pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ fPngBytesPerPixel = 8;
+ break;
+ case kGray_8_SkColorType:
+ sigBit.gray = 8;
+ pngColorType = PNG_COLOR_TYPE_GRAY;
+ fPngBytesPerPixel = 1;
+ SkASSERT(srcInfo.isOpaque());
+ break;
+ case kRGBA_8888_SkColorType:
+ case kBGRA_8888_SkColorType:
+ sigBit.red = 8;
+ sigBit.green = 8;
+ sigBit.blue = 8;
+ sigBit.alpha = 8;
+ pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ fPngBytesPerPixel = srcInfo.isOpaque() ? 3 : 4;
+ break;
+ case kRGB_888x_SkColorType:
+ sigBit.red = 8;
+ sigBit.green = 8;
+ sigBit.blue = 8;
+ pngColorType = PNG_COLOR_TYPE_RGB;
+ fPngBytesPerPixel = 3;
+ SkASSERT(srcInfo.isOpaque());
+ break;
+ case kARGB_4444_SkColorType:
+ if (kUnpremul_SkAlphaType == srcInfo.alphaType()) {
+ return false;
+ }
+
+ sigBit.red = 4;
+ sigBit.green = 4;
+ sigBit.blue = 4;
+ sigBit.alpha = 4;
+ pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ fPngBytesPerPixel = srcInfo.isOpaque() ? 3 : 4;
+ break;
+ case kRGB_565_SkColorType:
+ sigBit.red = 5;
+ sigBit.green = 6;
+ sigBit.blue = 5;
+ pngColorType = PNG_COLOR_TYPE_RGB;
+ fPngBytesPerPixel = 3;
+ SkASSERT(srcInfo.isOpaque());
+ break;
+ case kAlpha_8_SkColorType: // store as gray+alpha, but ignore gray
+ sigBit.gray = kGraySigBit_GrayAlphaIsJustAlpha;
+ sigBit.alpha = 8;
+ pngColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
+ fPngBytesPerPixel = 2;
+ break;
+ case kRGBA_1010102_SkColorType:
+ bitDepth = 16;
+ sigBit.red = 10;
+ sigBit.green = 10;
+ sigBit.blue = 10;
+ sigBit.alpha = 2;
+ pngColorType = srcInfo.isOpaque() ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;
+ fPngBytesPerPixel = 8;
+ break;
+ case kRGB_101010x_SkColorType:
+ bitDepth = 16;
+ sigBit.red = 10;
+ sigBit.green = 10;
+ sigBit.blue = 10;
+ pngColorType = PNG_COLOR_TYPE_RGB;
+ fPngBytesPerPixel = 6;
+ break;
+ default:
+ return false;
+ }
+
+ png_set_IHDR(fPngPtr, fInfoPtr, srcInfo.width(), srcInfo.height(),
+ bitDepth, pngColorType,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+ png_set_sBIT(fPngPtr, fInfoPtr, &sigBit);
+
+ int filters = (int)options.fFilterFlags & (int)SkPngEncoder::FilterFlag::kAll;
+ SkASSERT(filters == (int)options.fFilterFlags);
+ png_set_filter(fPngPtr, PNG_FILTER_TYPE_BASE, filters);
+
+ int zlibLevel = std::min(std::max(0, options.fZLibLevel), 9);
+ SkASSERT(zlibLevel == options.fZLibLevel);
+ png_set_compression_level(fPngPtr, zlibLevel);
+
+ // Set comments in tEXt chunk
+ const sk_sp<SkDataTable>& comments = options.fComments;
+ if (comments != nullptr) {
+ std::vector<png_text> png_texts(comments->count());
+ std::vector<SkString> clippedKeys;
+ for (int i = 0; i < comments->count() / 2; ++i) {
+ const char* keyword;
+ const char* originalKeyword = comments->atStr(2 * i);
+ const char* text = comments->atStr(2 * i + 1);
+ if (strlen(originalKeyword) <= PNG_KEYWORD_MAX_LENGTH) {
+ keyword = originalKeyword;
+ } else {
+ SkDEBUGFAILF("PNG tEXt keyword should be no longer than %d.",
+ PNG_KEYWORD_MAX_LENGTH);
+ clippedKeys.emplace_back(originalKeyword, PNG_KEYWORD_MAX_LENGTH);
+ keyword = clippedKeys.back().c_str();
+ }
+ // It seems safe to convert png_const_charp to png_charp for key/text,
+ // and we don't have to provide text_length and other fields as we're providing
+ // 0-terminated c_str with PNG_TEXT_COMPRESSION_NONE (no compression, no itxt).
+ png_texts[i].compression = PNG_TEXT_COMPRESSION_NONE;
+ png_texts[i].key = (png_charp)keyword;
+ png_texts[i].text = (png_charp)text;
+ }
+ png_set_text(fPngPtr, fInfoPtr, png_texts.data(), png_texts.size());
+ }
+
+ return true;
+}
+
+static transform_scanline_proc choose_proc(const SkImageInfo& info) {
+ switch (info.colorType()) {
+ case kUnknown_SkColorType:
+ break;
+
+ // TODO: I don't think this can just use kRGBA's procs.
+ // kPremul is especially tricky here, since it's presumably TF⁻¹(rgb * a),
+ // so to get at unpremul rgb we'd need to undo the transfer function first.
+ case kSRGBA_8888_SkColorType: return nullptr;
+
+ case kRGBA_8888_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ return transform_scanline_RGBX;
+ case kUnpremul_SkAlphaType:
+ return transform_scanline_memcpy;
+ case kPremul_SkAlphaType:
+ return transform_scanline_rgbA;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kBGRA_8888_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ return transform_scanline_BGRX;
+ case kUnpremul_SkAlphaType:
+ return transform_scanline_BGRA;
+ case kPremul_SkAlphaType:
+ return transform_scanline_bgrA;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kRGB_565_SkColorType:
+ return transform_scanline_565;
+ case kRGB_888x_SkColorType:
+ return transform_scanline_RGBX;
+ case kARGB_4444_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ return transform_scanline_444;
+ case kPremul_SkAlphaType:
+ return transform_scanline_4444;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kGray_8_SkColorType:
+ return transform_scanline_memcpy;
+
+ case kRGBA_F16Norm_SkColorType:
+ case kRGBA_F16_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ case kUnpremul_SkAlphaType:
+ return transform_scanline_F16;
+ case kPremul_SkAlphaType:
+ return transform_scanline_F16_premul;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kRGBA_F32_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ case kUnpremul_SkAlphaType:
+ return transform_scanline_F32;
+ case kPremul_SkAlphaType:
+ return transform_scanline_F32_premul;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kRGBA_1010102_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ case kUnpremul_SkAlphaType:
+ return transform_scanline_1010102;
+ case kPremul_SkAlphaType:
+ return transform_scanline_1010102_premul;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kBGRA_1010102_SkColorType:
+ switch (info.alphaType()) {
+ case kOpaque_SkAlphaType:
+ case kUnpremul_SkAlphaType:
+ return transform_scanline_bgra_1010102;
+ case kPremul_SkAlphaType:
+ return transform_scanline_bgra_1010102_premul;
+ default:
+ SkASSERT(false);
+ return nullptr;
+ }
+ case kRGB_101010x_SkColorType: return transform_scanline_101010x;
+ case kBGR_101010x_SkColorType: return transform_scanline_bgr_101010x;
+ case kBGR_101010x_XR_SkColorType: SkASSERT(false); return nullptr;
+
+ case kAlpha_8_SkColorType:
+ return transform_scanline_A8_to_GrayAlpha;
+ case kR8G8_unorm_SkColorType:
+ case kR16G16_unorm_SkColorType:
+ case kR16G16_float_SkColorType:
+ case kA16_unorm_SkColorType:
+ case kA16_float_SkColorType:
+ case kR16G16B16A16_unorm_SkColorType:
+ case kR8_unorm_SkColorType:
+ return nullptr;
+ }
+ SkASSERT(false);
+ return nullptr;
+}
+
+static void set_icc(png_structp png_ptr,
+ png_infop info_ptr,
+ const SkImageInfo& info,
+ const skcms_ICCProfile* profile,
+ const char* profile_description) {
+ sk_sp<SkData> icc = icc_from_color_space(info, profile, profile_description);
+ if (!icc) {
+ return;
+ }
+
+#if PNG_LIBPNG_VER_MAJOR > 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 5)
+ const char* name = "Skia";
+ png_const_bytep iccPtr = icc->bytes();
+#else
+ SkString str("Skia");
+ char* name = str.data();
+ png_charp iccPtr = (png_charp) icc->writable_data();
+#endif
+ png_set_iCCP(png_ptr, info_ptr, name, 0, iccPtr, icc->size());
+}
+
+bool SkPngEncoderMgr::setColorSpace(const SkImageInfo& info, const SkPngEncoder::Options& options) {
+ if (setjmp(png_jmpbuf(fPngPtr))) {
+ return false;
+ }
+
+ if (info.colorSpace() && info.colorSpace()->isSRGB()) {
+ png_set_sRGB(fPngPtr, fInfoPtr, PNG_sRGB_INTENT_PERCEPTUAL);
+ } else {
+ set_icc(fPngPtr, fInfoPtr, info, options.fICCProfile, options.fICCProfileDescription);
+ }
+
+ return true;
+}
+
+bool SkPngEncoderMgr::writeInfo(const SkImageInfo& srcInfo) {
+ if (setjmp(png_jmpbuf(fPngPtr))) {
+ return false;
+ }
+
+ png_write_info(fPngPtr, fInfoPtr);
+ if (kRGBA_F16_SkColorType == srcInfo.colorType() &&
+ kOpaque_SkAlphaType == srcInfo.alphaType())
+ {
+ // For kOpaque, kRGBA_F16, we will keep the row as RGBA and tell libpng
+ // to skip the alpha channel.
+ png_set_filler(fPngPtr, 0, PNG_FILLER_AFTER);
+ }
+
+ return true;
+}
+
+void SkPngEncoderMgr::chooseProc(const SkImageInfo& srcInfo) {
+ fProc = choose_proc(srcInfo);
+}
+
+std::unique_ptr<SkEncoder> SkPngEncoder::Make(SkWStream* dst, const SkPixmap& src,
+ const Options& options) {
+ if (!SkPixmapIsValid(src)) {
+ return nullptr;
+ }
+
+ std::unique_ptr<SkPngEncoderMgr> encoderMgr = SkPngEncoderMgr::Make(dst);
+ if (!encoderMgr) {
+ return nullptr;
+ }
+
+ if (!encoderMgr->setHeader(src.info(), options)) {
+ return nullptr;
+ }
+
+ if (!encoderMgr->setColorSpace(src.info(), options)) {
+ return nullptr;
+ }
+
+ if (!encoderMgr->writeInfo(src.info())) {
+ return nullptr;
+ }
+
+ encoderMgr->chooseProc(src.info());
+
+ return std::unique_ptr<SkPngEncoder>(new SkPngEncoder(std::move(encoderMgr), src));
+}
+
+SkPngEncoder::SkPngEncoder(std::unique_ptr<SkPngEncoderMgr> encoderMgr, const SkPixmap& src)
+ : INHERITED(src, encoderMgr->pngBytesPerPixel() * src.width())
+ , fEncoderMgr(std::move(encoderMgr))
+{}
+
+SkPngEncoder::~SkPngEncoder() {}
+
+bool SkPngEncoder::onEncodeRows(int numRows) {
+ if (setjmp(png_jmpbuf(fEncoderMgr->pngPtr()))) {
+ return false;
+ }
+
+ const void* srcRow = fSrc.addr(0, fCurrRow);
+ for (int y = 0; y < numRows; y++) {
+ sk_msan_assert_initialized(srcRow,
+ (const uint8_t*)srcRow + (fSrc.width() << fSrc.shiftPerPixel()));
+ fEncoderMgr->proc()((char*)fStorage.get(),
+ (const char*)srcRow,
+ fSrc.width(),
+ SkColorTypeBytesPerPixel(fSrc.colorType()));
+
+ png_bytep rowPtr = (png_bytep) fStorage.get();
+ png_write_rows(fEncoderMgr->pngPtr(), &rowPtr, 1);
+ srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
+ }
+
+ fCurrRow += numRows;
+ if (fCurrRow == fSrc.height()) {
+ png_write_end(fEncoderMgr->pngPtr(), fEncoderMgr->infoPtr());
+ }
+
+ return true;
+}
+
+bool SkPngEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
+ auto encoder = SkPngEncoder::Make(dst, src, options);
+ return encoder.get() && encoder->encodeRows(src.height());
+}
+
+#endif
diff --git a/gfx/skia/skia/src/encode/SkWebpEncoder.cpp b/gfx/skia/skia/src/encode/SkWebpEncoder.cpp
new file mode 100644
index 0000000000..2189b807a4
--- /dev/null
+++ b/gfx/skia/skia/src/encode/SkWebpEncoder.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2010 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/core/SkTypes.h"
+
+#ifdef SK_ENCODE_WEBP
+
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkBitmap.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkData.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkSpan.h"
+#include "include/core/SkStream.h"
+#include "include/encode/SkEncoder.h"
+#include "include/encode/SkWebpEncoder.h"
+#include "include/private/base/SkTemplates.h"
+#include "src/core/SkImageInfoPriv.h"
+#include "src/encode/SkImageEncoderFns.h"
+#include "src/encode/SkImageEncoderPriv.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+
+// A WebP encoder only, on top of (subset of) libwebp
+// For more information on WebP image format, and libwebp library, see:
+// http://code.google.com/speed/webp/
+// http://www.webmproject.org/code/#libwebp_webp_image_decoder_library
+// http://review.webmproject.org/gitweb?p=libwebp.git
+
+extern "C" {
+// 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/encode.h"
+#include "webp/mux.h"
+#include "webp/mux_types.h"
+}
+
+static int stream_writer(const uint8_t* data, size_t data_size,
+ const WebPPicture* const picture) {
+ SkWStream* const stream = (SkWStream*)picture->custom_ptr;
+ return stream->write(data, data_size) ? 1 : 0;
+}
+
+using WebPPictureImportProc = int (*) (WebPPicture* picture, const uint8_t* pixels, int stride);
+
+static bool preprocess_webp_picture(WebPPicture* pic,
+ WebPConfig* webp_config,
+ const SkPixmap& pixmap,
+ const SkWebpEncoder::Options& opts) {
+ if (!SkPixmapIsValid(pixmap)) {
+ return false;
+ }
+
+ if (SkColorTypeIsAlphaOnly(pixmap.colorType())) {
+ // Maintain the existing behavior of not supporting encoding alpha-only images.
+ // TODO: Support encoding alpha only to an image with alpha but no color?
+ return false;
+ }
+
+ if (nullptr == pixmap.addr()) {
+ return false;
+ }
+
+ pic->width = pixmap.width();
+ pic->height = pixmap.height();
+
+ // Set compression, method, and pixel format.
+ // libwebp recommends using BGRA for lossless and YUV for lossy.
+ // The choices of |webp_config.method| currently just match Chrome's defaults. We
+ // could potentially expose this decision to the client.
+ if (SkWebpEncoder::Compression::kLossy == opts.fCompression) {
+ webp_config->lossless = 0;
+#ifndef SK_WEBP_ENCODER_USE_DEFAULT_METHOD
+ webp_config->method = 3;
+#endif
+ pic->use_argb = 0;
+ } else {
+ webp_config->lossless = 1;
+ webp_config->method = 0;
+ pic->use_argb = 1;
+ }
+
+ {
+ const SkColorType ct = pixmap.colorType();
+ const bool premul = pixmap.alphaType() == kPremul_SkAlphaType;
+
+ SkBitmap tmpBm;
+ WebPPictureImportProc importProc = nullptr;
+ const SkPixmap* src = &pixmap;
+ if (ct == kRGB_888x_SkColorType) {
+ importProc = WebPPictureImportRGBX;
+ } else if (!premul && ct == kRGBA_8888_SkColorType) {
+ importProc = WebPPictureImportRGBA;
+ }
+#ifdef WebPPictureImportBGRA
+ else if (!premul && ct == kBGRA_8888_SkColorType) {
+ importProc = WebPPictureImportBGRA;
+ }
+#endif
+ else {
+ importProc = WebPPictureImportRGBA;
+ auto info = pixmap.info()
+ .makeColorType(kRGBA_8888_SkColorType)
+ .makeAlphaType(kUnpremul_SkAlphaType);
+ if (!tmpBm.tryAllocPixels(info) ||
+ !pixmap.readPixels(tmpBm.info(), tmpBm.getPixels(), tmpBm.rowBytes())) {
+ return false;
+ }
+ src = &tmpBm.pixmap();
+ }
+
+ if (!importProc(pic, reinterpret_cast<const uint8_t*>(src->addr()), src->rowBytes())) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SkWebpEncoder::Encode(SkWStream* stream, const SkPixmap& pixmap, const Options& opts) {
+ if (!stream) {
+ return false;
+ }
+
+ WebPConfig webp_config;
+ if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
+ return false;
+ }
+
+ WebPPicture pic;
+ WebPPictureInit(&pic);
+ SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
+
+ if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) {
+ return false;
+ }
+
+ // If there is no need to embed an ICC profile, we write directly to the input stream.
+ // Otherwise, we will first encode to |tmp| and use a mux to add the ICC chunk. libwebp
+ // forces us to have an encoded image before we can add a profile.
+ sk_sp<SkData> icc =
+ icc_from_color_space(pixmap.info(), opts.fICCProfile, opts.fICCProfileDescription);
+ SkDynamicMemoryWStream tmp;
+ pic.custom_ptr = icc ? (void*)&tmp : (void*)stream;
+ pic.writer = stream_writer;
+
+ if (!WebPEncode(&webp_config, &pic)) {
+ return false;
+ }
+
+ if (icc) {
+ sk_sp<SkData> encodedData = tmp.detachAsData();
+ WebPData encoded = { encodedData->bytes(), encodedData->size() };
+ WebPData iccChunk = { icc->bytes(), icc->size() };
+
+ SkAutoTCallVProc<WebPMux, WebPMuxDelete> mux(WebPMuxNew());
+ if (WEBP_MUX_OK != WebPMuxSetImage(mux, &encoded, 0)) {
+ return false;
+ }
+
+ if (WEBP_MUX_OK != WebPMuxSetChunk(mux, "ICCP", &iccChunk, 0)) {
+ return false;
+ }
+
+ WebPData assembled;
+ if (WEBP_MUX_OK != WebPMuxAssemble(mux, &assembled)) {
+ return false;
+ }
+
+ stream->write(assembled.bytes, assembled.size);
+ WebPDataClear(&assembled);
+ }
+
+ return true;
+}
+
+bool SkWebpEncoder::EncodeAnimated(SkWStream* stream,
+ SkSpan<const SkEncoder::Frame> frames,
+ const Options& opts) {
+ if (!stream || !frames.size()) {
+ return false;
+ }
+
+ const int canvasWidth = frames.front().pixmap.width();
+ const int canvasHeight = frames.front().pixmap.height();
+ int timestamp = 0;
+
+ std::unique_ptr<WebPAnimEncoder, void (*)(WebPAnimEncoder*)> enc(
+ WebPAnimEncoderNew(canvasWidth, canvasHeight, nullptr), WebPAnimEncoderDelete);
+ if (!enc) {
+ return false;
+ }
+
+ for (const auto& frame : frames) {
+ const auto& pixmap = frame.pixmap;
+
+ if (pixmap.width() != canvasWidth || pixmap.height() != canvasHeight) {
+ return false;
+ }
+
+ WebPConfig webp_config;
+ if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, opts.fQuality)) {
+ return false;
+ }
+
+ WebPPicture pic;
+ WebPPictureInit(&pic);
+ SkAutoTCallVProc<WebPPicture, WebPPictureFree> autoPic(&pic);
+
+ if (!preprocess_webp_picture(&pic, &webp_config, pixmap, opts)) {
+ return false;
+ }
+
+ if (!WebPEncode(&webp_config, &pic)) {
+ return false;
+ }
+
+ if (!WebPAnimEncoderAdd(enc.get(), &pic, timestamp, &webp_config)) {
+ return false;
+ }
+
+ timestamp += frame.duration;
+ }
+
+ // Add a last fake frame to signal the last duration.
+ if (!WebPAnimEncoderAdd(enc.get(), nullptr, timestamp, nullptr)) {
+ return false;
+ }
+
+ WebPData assembled;
+ SkAutoTCallVProc<WebPData, WebPDataClear> autoWebPData(&assembled);
+ if (!WebPAnimEncoderAssemble(enc.get(), &assembled)) {
+ return false;
+ }
+
+ enc.reset();
+
+ return stream->write(assembled.bytes, assembled.size);
+}
+
+#endif