diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/skia/skia/src/encode/SkEncoder.cpp | 29 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkICC.cpp | 762 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkICCPriv.h | 63 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkImageEncoder.cpp | 110 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkImageEncoderFns.h | 213 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkImageEncoderPriv.h | 51 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkJPEGWriteUtility.cpp | 79 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkJPEGWriteUtility.h | 42 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkJpegEncoder.cpp | 419 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkJpegGainmapEncoder.cpp | 413 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkPngEncoder.cpp | 493 | ||||
-rw-r--r-- | gfx/skia/skia/src/encode/SkWebpEncoder.cpp | 249 |
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 |