| /* |
| * Copyright 2025 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkStream.h" |
| #include "include/private/base/SkFloatingPoint.h" |
| #include "src/codec/SkHdrAgtmPriv.h" |
| #include "src/core/SkStreamPriv.h" |
| |
| namespace { |
| |
| // TODO(https://issuetracker.google.com/issues/40044808): Include operator== in SkColorSpace.h. |
| bool operator==(const SkColorSpacePrimaries& a, const SkColorSpacePrimaries& b) { |
| return memcmp(&a, &b, sizeof(a)) == 0; |
| } |
| |
| // Return x clamped to [a, b]. |
| template<typename T, typename U, typename V> |
| T clamp(T x, U a, V b) { |
| return std::min(std::max(x, static_cast<T>(a)), static_cast<T>(b)); |
| } |
| |
| // Convert f by scaling by `scale`, offsetting by `offset`, and then clamping the result to |
| // [`clamp_min`, `clamp_max`]. |
| uint16_t float_to_uint16(float f, |
| uint16_t clamp_min, uint16_t clamp_max, |
| uint16_t offset, float scale) { |
| int32_t v = static_cast<int32_t>(std::lround(f * scale)) + offset; |
| return clamp(v, clamp_min, clamp_max); |
| } |
| |
| // The inverse of float_to_uint16's conversion. |
| float uint16_to_float(uint16_t v, |
| uint16_t clamp_min, uint16_t clamp_max, |
| uint16_t offset, float scale) { |
| return (static_cast<int32_t>(clamp(v, clamp_min, clamp_max)) - offset) / scale; |
| } |
| |
| // Helper class to read individual fields of a uint8_t bitfield. |
| class BitfieldReader { |
| public: |
| bool readFromStream(SkMemoryStream& s) { |
| return s.readU8(&fBits); |
| } |
| uint8_t readBits(uint8_t bits) { |
| // Read the topmost `bits` bits, then shift them off. |
| uint8_t result = fBits >> (8 - bits); |
| fBits <<= bits; |
| fBitsRead += bits; |
| return result; |
| } |
| private: |
| uint8_t fBits = 0; |
| uint8_t fBitsRead = 0; |
| }; |
| |
| // Helper class to write individual fields of a uint8_t bitfield. |
| class BitfieldWriter { |
| public: |
| void writeBits(uint8_t value, uint8_t bits) { |
| // Ensure the value fits in `bits`. |
| SkASSERT(value <= (1 << bits) - 1); |
| fBits <<= bits; |
| fBits |= value; |
| fBitsWritten += bits; |
| } |
| void padAndWriteToStream(SkDynamicMemoryWStream& s) { |
| // Write the remaining bits as zero, then write the result. |
| SkASSERT(fBitsWritten <= 8); |
| fBits <<= (8 - fBitsWritten); |
| fBitsWritten = 8; |
| s.write8(fBits); |
| } |
| private: |
| uint8_t fBits = 0; |
| uint8_t fBitsWritten = 0; |
| }; |
| |
| // Wrapper to return false if the specified call returns false. Used to keep syntax parsing |
| // shorter. |
| #define RETURN_ON_FALSE(x) \ |
| do { \ |
| if (!(x)) { \ |
| SkDebugf("AGTM parsing failed %s at %d\n", #x, __LINE__); \ |
| return false; \ |
| } \ |
| } while (0) |
| |
| // All syntax elements listed in Annex C.2 (excluding reserved_zero). Parsing and serializing of |
| // an skhdr::Agtm is done in two steps: |
| // * converting from the metadata items to/from syntax elements (from Annex C.3) |
| // * serializing the syntax elements to/from a stream (from Annex C.2) |
| struct AgtmSyntax { |
| // Parse or write the syntax elements according to Annex C.2. |
| bool parse_application_info(SkMemoryStream& s); |
| void write_application_info(SkDynamicMemoryWStream& s); |
| |
| // Parse or write according to Table C.1. |
| bool parse_color_volume_transform(SkMemoryStream& s); |
| void write_color_volume_transform(SkDynamicMemoryWStream& s); |
| |
| // Parse or write according to Table C.3. |
| bool parse_adaptive_tone_map(SkMemoryStream& s); |
| void write_adaptive_tone_map(SkDynamicMemoryWStream& s); |
| |
| // Parse or write according to Table C.4. |
| bool parse_component_mixing(uint8_t a, SkMemoryStream& s); |
| void write_component_mixing(uint8_t a, SkDynamicMemoryWStream& s); |
| |
| // Parse or write according to Table C.5. |
| bool parse_gain_curve(uint8_t a, SkMemoryStream& s); |
| void write_gain_curve(uint8_t a, SkDynamicMemoryWStream& s); |
| |
| // syntax elements of smpte_st_2094_50_application_info() |
| uint8_t application_version; |
| |
| // syntax elements of smpte_st_2094_50_color_volume_transform() |
| uint8_t has_custom_hdr_reference_white_flag:1; |
| uint8_t has_adaptive_tone_map_flag:1; |
| uint16_t hdr_reference_white; |
| |
| // syntax elements of smpte_st_2094_50_adaptive_tone_map() |
| uint16_t baseline_hdr_headroom; |
| uint8_t use_reference_white_tone_mapping_flag:1; |
| uint8_t num_alternate_images:3; |
| uint8_t gain_application_space_chromaticities_flag:2; |
| uint8_t has_common_component_mix_params_flag:1; |
| uint8_t has_common_curve_params_flag:1; |
| uint16_t gain_application_space_chromaticities[8]; |
| uint16_t alternate_hdr_headrooms[4]; |
| |
| // syntax elements of smpte_st_2094_50_component_mixing() |
| uint8_t component_mixing_type[4]; |
| uint8_t has_component_mixing_coefficient_flag[4][6]; |
| uint16_t component_mixing_coefficient[4][6]; |
| |
| // syntax elements of smpte_st_2094_50_gain_curve() |
| uint8_t gain_curve_num_control_points_minus_1[4]; |
| uint8_t gain_curve_use_pchip_slope_flag[4]; |
| uint16_t gain_curve_control_points_x[4][32]; |
| uint16_t gain_curve_control_points_y[4][32]; |
| uint16_t gain_curve_control_points_theta[4][32]; |
| }; |
| |
| bool AgtmSyntax::parse_application_info(SkMemoryStream& s) { |
| RETURN_ON_FALSE(s.readU8(&application_version)); |
| RETURN_ON_FALSE(parse_color_volume_transform(s)); |
| return true; |
| } |
| |
| void AgtmSyntax::write_application_info(SkDynamicMemoryWStream& s) { |
| s.write8(application_version); |
| write_color_volume_transform(s); |
| } |
| |
| // Parse according to Table C.2. |
| bool AgtmSyntax::parse_color_volume_transform(SkMemoryStream& s) { |
| BitfieldReader flags; |
| RETURN_ON_FALSE(flags.readFromStream(s)); |
| has_custom_hdr_reference_white_flag = flags.readBits(1); |
| has_adaptive_tone_map_flag = flags.readBits(1); |
| const auto reserved_zero = flags.readBits(6); |
| RETURN_ON_FALSE(reserved_zero == 0); |
| if (has_custom_hdr_reference_white_flag == 1) { |
| RETURN_ON_FALSE(SkStreamPriv::ReadU16BE(&s, &hdr_reference_white)); |
| } |
| if (has_adaptive_tone_map_flag == 1) { |
| RETURN_ON_FALSE(parse_adaptive_tone_map(s)); |
| } |
| return true; |
| } |
| |
| void AgtmSyntax::write_color_volume_transform(SkDynamicMemoryWStream& s) { |
| BitfieldWriter flags; |
| flags.writeBits(has_custom_hdr_reference_white_flag, 1); |
| flags.writeBits(has_adaptive_tone_map_flag, 1); |
| flags.padAndWriteToStream(s); |
| if (has_custom_hdr_reference_white_flag) { |
| SkStreamPriv::WriteU16BE(&s, hdr_reference_white); |
| } |
| if (has_adaptive_tone_map_flag == 1) { |
| write_adaptive_tone_map(s); |
| } |
| } |
| |
| bool AgtmSyntax::parse_adaptive_tone_map(SkMemoryStream& s) { |
| RETURN_ON_FALSE(SkStreamPriv::ReadU16BE(&s, &baseline_hdr_headroom)); |
| BitfieldReader flags; |
| RETURN_ON_FALSE(flags.readFromStream(s) ); |
| use_reference_white_tone_mapping_flag = flags.readBits(1); |
| if (use_reference_white_tone_mapping_flag == 0) { |
| num_alternate_images = flags.readBits(3); |
| gain_application_space_chromaticities_flag = flags.readBits(2); |
| has_common_component_mix_params_flag = flags.readBits(1); |
| has_common_curve_params_flag = flags.readBits(1); |
| if (gain_application_space_chromaticities_flag == 3) { |
| for (uint8_t r = 0; r < 8; ++r) { |
| RETURN_ON_FALSE( |
| SkStreamPriv::ReadU16BE(&s, &gain_application_space_chromaticities[r])); |
| } |
| } |
| for (uint8_t a = 0; a < std::min(num_alternate_images, |
| skhdr::Agtm::kMaxNumAlternateImages); ++a) { |
| RETURN_ON_FALSE(SkStreamPriv::ReadU16BE(&s, &alternate_hdr_headrooms[a])); |
| RETURN_ON_FALSE(parse_component_mixing(a, s)); |
| RETURN_ON_FALSE(parse_gain_curve(a, s)); |
| } |
| } else { |
| const auto reserved_zero = flags.readBits(7); |
| RETURN_ON_FALSE(reserved_zero == 0); |
| } |
| return true; |
| } |
| |
| void AgtmSyntax::write_adaptive_tone_map(SkDynamicMemoryWStream& s) { |
| SkStreamPriv::WriteU16BE(&s, baseline_hdr_headroom); |
| BitfieldWriter flags; |
| flags.writeBits(use_reference_white_tone_mapping_flag, 1); |
| if (use_reference_white_tone_mapping_flag == 0) { |
| flags.writeBits(num_alternate_images, 3); |
| flags.writeBits(gain_application_space_chromaticities_flag, 2); |
| flags.writeBits(has_common_component_mix_params_flag, 1); |
| flags.writeBits(has_common_curve_params_flag, 1); |
| flags.padAndWriteToStream(s); |
| if (gain_application_space_chromaticities_flag == 3) { |
| for (uint8_t r = 0; r < 8; ++r) { |
| SkStreamPriv::WriteU16BE(&s, gain_application_space_chromaticities[r]); |
| } |
| } |
| for (uint8_t a = 0; a < num_alternate_images; ++a) { |
| SkStreamPriv::WriteU16BE(&s, alternate_hdr_headrooms[a]); |
| write_component_mixing(a, s); |
| write_gain_curve(a, s); |
| } |
| } else { |
| flags.padAndWriteToStream(s); |
| } |
| } |
| |
| bool AgtmSyntax::parse_component_mixing(uint8_t a, SkMemoryStream& s) { |
| if (a == 0 || has_common_component_mix_params_flag == 0) { |
| BitfieldReader flags; |
| RETURN_ON_FALSE(flags.readFromStream(s)); |
| component_mixing_type[a] = flags.readBits(2); |
| if (component_mixing_type[a] != 3) { |
| const auto reserved_zero = flags.readBits(6); |
| RETURN_ON_FALSE(reserved_zero == 0); |
| } else { |
| for (uint8_t k = 0; k < 6; ++k) { |
| has_component_mixing_coefficient_flag[a][k] = flags.readBits(1); |
| } |
| for (uint8_t k = 0; k < 6; ++k) { |
| if (has_component_mixing_coefficient_flag[a][k] == 1) { |
| RETURN_ON_FALSE( |
| SkStreamPriv::ReadU16BE(&s, &component_mixing_coefficient[a][k])); |
| } else { |
| component_mixing_coefficient[a][k] = 0; |
| } |
| } |
| } |
| } else { |
| component_mixing_type[a] = component_mixing_type[0]; |
| if (component_mixing_type[a] == 3) { |
| for (uint8_t k = 0; k < 6; ++k) { |
| component_mixing_coefficient[a][k] = component_mixing_coefficient[0][k]; |
| } |
| } |
| } |
| return true; |
| } |
| |
| void AgtmSyntax::write_component_mixing(uint8_t a, SkDynamicMemoryWStream& s) { |
| if (a == 0 || has_common_component_mix_params_flag == 0) { |
| BitfieldWriter flags; |
| flags.writeBits(component_mixing_type[a], 2); |
| if (component_mixing_type[a] == 3) { |
| for (uint8_t k = 0; k < 6; ++k) { |
| flags.writeBits(has_component_mixing_coefficient_flag[a][k], 1); |
| } |
| flags.padAndWriteToStream(s); |
| for (uint8_t k = 0; k < 6; ++k) { |
| if (has_component_mixing_coefficient_flag[a][k] == 1) { |
| SkStreamPriv::WriteU16BE(&s, component_mixing_coefficient[a][k]); |
| } |
| } |
| } else { |
| flags.padAndWriteToStream(s); |
| } |
| } |
| } |
| |
| bool AgtmSyntax::parse_gain_curve(uint8_t a, SkMemoryStream& s) { |
| if (a == 0 || has_common_curve_params_flag == 0) { |
| BitfieldReader flags; |
| RETURN_ON_FALSE(flags.readFromStream(s)); |
| gain_curve_num_control_points_minus_1[a] = flags.readBits(5); |
| gain_curve_use_pchip_slope_flag[a] = flags.readBits(1); |
| const auto reserved_zero = flags.readBits(2); |
| RETURN_ON_FALSE(reserved_zero == 0); |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| RETURN_ON_FALSE(SkStreamPriv::ReadU16BE(&s, &gain_curve_control_points_x[a][c])); |
| } |
| } else { |
| gain_curve_num_control_points_minus_1[a] = gain_curve_num_control_points_minus_1[0]; |
| gain_curve_use_pchip_slope_flag[a] = gain_curve_use_pchip_slope_flag[0]; |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| gain_curve_control_points_x[a][c] = gain_curve_control_points_x[0][c]; |
| } |
| } |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| RETURN_ON_FALSE(SkStreamPriv::ReadU16BE(&s, &gain_curve_control_points_y[a][c])); |
| } |
| if (gain_curve_use_pchip_slope_flag[a] == 0) { |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| RETURN_ON_FALSE(SkStreamPriv::ReadU16BE(&s, &gain_curve_control_points_theta[a][c])); |
| } |
| } |
| return true; |
| } |
| |
| void AgtmSyntax::write_gain_curve(uint8_t a, SkDynamicMemoryWStream& s) { |
| if (a == 0 || has_common_component_mix_params_flag == 0) { |
| BitfieldWriter flags; |
| flags.writeBits(gain_curve_num_control_points_minus_1[a], 5); |
| flags.writeBits(gain_curve_use_pchip_slope_flag[a], 1); |
| flags.padAndWriteToStream(s); |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| SkStreamPriv::WriteU16BE(&s, gain_curve_control_points_x[a][c]); |
| } |
| } |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| SkStreamPriv::WriteU16BE(&s, gain_curve_control_points_y[a][c]); |
| } |
| if (gain_curve_use_pchip_slope_flag[a] == 0) { |
| for (uint8_t c = 0; c < gain_curve_num_control_points_minus_1[a] + 1u; ++c) { |
| SkStreamPriv::WriteU16BE(&s, gain_curve_control_points_theta[a][c]); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| namespace skhdr { |
| |
| bool Agtm::parse(const SkData* data) { |
| if (data == nullptr) { |
| return false; |
| } |
| SkMemoryStream s(data->data(), data->size()); |
| |
| // Parse the syntax according to clause C.2. |
| AgtmSyntax syntax; |
| memset(&syntax, 0, sizeof(syntax)); |
| if (!syntax.parse_application_info(s)) { |
| return false; |
| } |
| |
| // Apply the semantics to map syntax elements to metadata items according to clause C.3.3. |
| if (syntax.has_custom_hdr_reference_white_flag == 1) { |
| fHdrReferenceWhite = uint16_to_float(syntax.hdr_reference_white, 1u, 50000u, 0u, 5.f); |
| } else { |
| fHdrReferenceWhite = kDefaultHdrReferenceWhite; |
| } |
| if (syntax.has_adaptive_tone_map_flag == 0) { |
| fType = Type::kNone; |
| return true; |
| } |
| |
| // Semantics from clause C.3.4. |
| fBaselineHdrHeadroom = uint16_to_float(syntax.baseline_hdr_headroom, 0u, 60000u, 0u, 10000.f); |
| if (syntax.use_reference_white_tone_mapping_flag == 1) { |
| fType = Type::kReferenceWhite; |
| populateUsingRwtmo(); |
| return true; |
| } |
| |
| fType = Type::kCustom; |
| fNumAlternateImages = clamp(syntax.num_alternate_images, 0u, kMaxNumAlternateImages); |
| for (uint8_t a = 0; a < fNumAlternateImages; ++a) { |
| fAlternateHdrHeadroom[a] = uint16_to_float( |
| syntax.alternate_hdr_headrooms[a], 0u, 60000u, 0u, 10000.f); |
| } |
| |
| // Semantics from clause C.3.5. |
| switch (syntax.gain_application_space_chromaticities_flag) { |
| case 0: |
| fGainApplicationSpacePrimaries = SkNamedPrimaries::kRec709; |
| break; |
| case 1: |
| fGainApplicationSpacePrimaries = SkNamedPrimaries::kSMPTE_EG_432_1; |
| break; |
| case 2: |
| fGainApplicationSpacePrimaries = SkNamedPrimaries::kRec2020; |
| break; |
| case 3: { |
| fGainApplicationSpacePrimaries = { |
| .fRX = uint16_to_float( |
| syntax.gain_application_space_chromaticities[0], 0u, 50000u, 0u, 50000.f), |
| .fRY = uint16_to_float( |
| syntax.gain_application_space_chromaticities[1], 0u, 50000u, 0u, 50000.f), |
| .fGX = uint16_to_float( |
| syntax.gain_application_space_chromaticities[2], 0u, 50000u, 0u, 50000.f), |
| .fGY = uint16_to_float( |
| syntax.gain_application_space_chromaticities[3], 0u, 50000u, 0u, 50000.f), |
| .fBX = uint16_to_float( |
| syntax.gain_application_space_chromaticities[4], 0u, 50000u, 0u, 50000.f), |
| .fBY = uint16_to_float( |
| syntax.gain_application_space_chromaticities[5], 0u, 50000u, 0u, 50000.f), |
| .fWX = uint16_to_float( |
| syntax.gain_application_space_chromaticities[6], 0u, 50000u, 0u, 50000.f), |
| .fWY = uint16_to_float( |
| syntax.gain_application_space_chromaticities[7], 0u, 50000u, 0u, 50000.f), |
| }; |
| break; |
| } |
| default: |
| // This should never be hit because syntax.gain_application_space_chromaticities_flag |
| // is read as 2 bits. |
| SkUNREACHABLE; |
| break; |
| } |
| |
| // Semantics from clause C.3.6. |
| for (uint8_t a = 0; a < fNumAlternateImages; ++a) { |
| auto& mix = fGainFunction[a].fComponentMixing; |
| switch (syntax.component_mixing_type[a]) { |
| case 0: |
| mix = {.fMax = 1.f}; |
| break; |
| case 1: |
| mix = {.fComponent = 1.f}; |
| break; |
| case 2: |
| mix = { |
| .fRed = 1.f / 6.f, |
| .fGreen = 1.f / 6.f, |
| .fBlue = 1.f / 6.f, |
| .fMax = 1.f / 2.f, |
| }; |
| break; |
| case 3: |
| mix.fRed = uint16_to_float( |
| syntax.component_mixing_coefficient[a][0], 0u, 50000u, 0u, 50000.f); |
| mix.fGreen = uint16_to_float( |
| syntax.component_mixing_coefficient[a][1], 0u, 50000u, 0u, 50000.f); |
| mix.fBlue = uint16_to_float( |
| syntax.component_mixing_coefficient[a][2], 0u, 50000u, 0u, 50000.f); |
| mix.fMax = uint16_to_float( |
| syntax.component_mixing_coefficient[a][3], 0u, 50000u, 0u, 50000.f); |
| mix.fMin = uint16_to_float( |
| syntax.component_mixing_coefficient[a][4], 0u, 50000u, 0u, 50000.f); |
| mix.fComponent = uint16_to_float( |
| syntax.component_mixing_coefficient[a][5], 0u, 50000u, 0u, 50000.f); |
| break; |
| } |
| } |
| |
| // Semantics from clause C.3.7. |
| for (uint8_t a = 0; a < fNumAlternateImages; ++a) { |
| auto& cubic = fGainFunction[a].fPiecewiseCubic; |
| cubic.fNumControlPoints = syntax.gain_curve_num_control_points_minus_1[a] + 1u; |
| for (uint8_t c = 0; c < cubic.fNumControlPoints; ++c) { |
| cubic.fX[c] = uint16_to_float( |
| syntax.gain_curve_control_points_x[a][c], 0u, 64000u, 0u, 1000.f); |
| cubic.fY[c] = uint16_to_float( |
| syntax.gain_curve_control_points_y[a][c], 0u, 48000u, 24000u, 4000.f); |
| } |
| if (syntax.gain_curve_use_pchip_slope_flag[a] == 0) { |
| for (uint8_t c = 0; c < cubic.fNumControlPoints; ++c) { |
| const float theta = uint16_to_float( |
| syntax.gain_curve_control_points_theta[a][c], |
| 1u, 35999u, 18000u, 36000.f / SK_FloatPI); |
| cubic.fM[c] = std::tan(theta); |
| } |
| } else { |
| cubic.populateSlopeFromPCHIP(); |
| } |
| } |
| |
| return true; |
| } |
| |
| sk_sp<SkData> Agtm::serialize() const { |
| AgtmSyntax syntax; |
| memset(&syntax, 0, sizeof(syntax)); |
| |
| // Populate `syntax` according to the semantics in clause C.3. |
| syntax.application_version = 0; |
| syntax.has_custom_hdr_reference_white_flag = fHdrReferenceWhite != kDefaultHdrReferenceWhite; |
| if (syntax.has_custom_hdr_reference_white_flag) { |
| syntax.hdr_reference_white = float_to_uint16(fHdrReferenceWhite, 1u, 50000u, 0u, 5.f); |
| } |
| |
| syntax.has_adaptive_tone_map_flag = fType != Type::kNone; |
| |
| // Semantics from clause C.3.4. |
| syntax.baseline_hdr_headroom = float_to_uint16(fBaselineHdrHeadroom, 0u, 60000u, 0u, 10000.f); |
| syntax.use_reference_white_tone_mapping_flag = fType == Type::kReferenceWhite; |
| SkASSERT(fNumAlternateImages <= kMaxNumAlternateImages); |
| syntax.num_alternate_images = fNumAlternateImages; |
| for (uint8_t a = 0; a < fNumAlternateImages; ++a) { |
| syntax.alternate_hdr_headrooms[a] = float_to_uint16( |
| fAlternateHdrHeadroom[a], 0u, 60000u, 0u, 10000.f); |
| } |
| |
| // Semantics from clause C.3.5. |
| if (fGainApplicationSpacePrimaries == SkNamedPrimaries::kRec709) { |
| syntax.gain_application_space_chromaticities_flag = 0; |
| } else if (fGainApplicationSpacePrimaries == SkNamedPrimaries::kSMPTE_EG_432_1) { |
| syntax.gain_application_space_chromaticities_flag = 1; |
| } else if (fGainApplicationSpacePrimaries == SkNamedPrimaries::kRec2020) { |
| syntax.gain_application_space_chromaticities_flag = 2; |
| } else { |
| syntax.gain_application_space_chromaticities_flag = 3; |
| } |
| if (syntax.gain_application_space_chromaticities_flag == 3) { |
| syntax.gain_application_space_chromaticities[0] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fRX, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[1] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fRY, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[2] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fGX, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[3] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fGY, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[4] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fBX, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[5] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fBY, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[6] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fWX, 0u, 50000u, 0u, 50000.f); |
| syntax.gain_application_space_chromaticities[7] = float_to_uint16( |
| fGainApplicationSpacePrimaries.fWY, 0u, 50000u, 0u, 50000.f); |
| } |
| |
| // Semantics from clause C.3.6. |
| syntax.has_common_component_mix_params_flag = 0; |
| for (uint8_t a = 0; a < fNumAlternateImages; ++a) { |
| auto& mix = fGainFunction[a].fComponentMixing; |
| if (mix.fRed == 0.f && mix.fGreen == 0.f && mix.fBlue == 0.f && |
| mix.fMax == 1.f && mix.fMin == 0.f && mix.fComponent == 0.f) { |
| syntax.component_mixing_type[a] = 0; |
| } else if (mix.fRed == 0.f && mix.fGreen == 0.f && mix.fBlue == 0.f && |
| mix.fMax == 0.f && mix.fMin == 0.f && mix.fComponent == 1.f) { |
| syntax.component_mixing_type[a] = 1; |
| } else if (mix.fRed == 1.f/6.f && mix.fGreen == 1.f/6.f && mix.fBlue == 1.f/6.f && |
| mix.fMax == 1.f/2.f && mix.fMin == 0.f && mix.fComponent == 0.f) { |
| syntax.component_mixing_type[a] = 2; |
| } else { |
| syntax.component_mixing_type[a] = 3; |
| syntax.component_mixing_coefficient[a][0] = |
| float_to_uint16(mix.fRed, 0u, 50000u, 0u, 50000.f); |
| syntax.component_mixing_coefficient[a][1] = |
| float_to_uint16(mix.fGreen, 0u, 50000u, 0u, 50000.f); |
| syntax.component_mixing_coefficient[a][2] = |
| float_to_uint16(mix.fBlue, 0u, 50000u, 0u, 50000.f); |
| syntax.component_mixing_coefficient[a][3] = |
| float_to_uint16(mix.fMax, 0u, 50000u, 0u, 50000.f); |
| syntax.component_mixing_coefficient[a][4] = |
| float_to_uint16(mix.fMin, 0u, 50000u, 0u, 50000.f); |
| syntax.component_mixing_coefficient[a][5] = |
| float_to_uint16(mix.fComponent, 0u, 50000u, 0u, 50000.f); |
| for (uint8_t k = 0; k < 6; ++k) { |
| syntax.has_component_mixing_coefficient_flag[a][k] = |
| syntax.component_mixing_coefficient[a][k] != 0; |
| } |
| } |
| } |
| |
| // Semantics from clause C.3.7. |
| syntax.has_common_curve_params_flag = 0; |
| for (uint8_t a = 0; a < fNumAlternateImages; ++a) { |
| auto& cubic = fGainFunction[a].fPiecewiseCubic; |
| SkASSERT(PiecewiseCubicFunction::kMinNumControlPoints <= cubic.fNumControlPoints); |
| SkASSERT(cubic.fNumControlPoints <= PiecewiseCubicFunction::kMaxNumControlPoints); |
| syntax.gain_curve_num_control_points_minus_1[a] = cubic.fNumControlPoints - 1u; |
| syntax.gain_curve_use_pchip_slope_flag[a] = 0; |
| for (uint8_t c = 0; c < cubic.fNumControlPoints; ++c) { |
| syntax.gain_curve_control_points_x[a][c] = |
| float_to_uint16(cubic.fX[c], 0u, 64000u, 0u, 1000.f); |
| } |
| for (uint8_t c = 0; c < cubic.fNumControlPoints; ++c) { |
| syntax.gain_curve_control_points_y[a][c] = |
| float_to_uint16(cubic.fY[c], 0u, 48000u, 24000u, 4000.f); |
| } |
| for (uint8_t c = 0; c < cubic.fNumControlPoints; ++c) { |
| float theta = std::atan(cubic.fM[c]); |
| syntax.gain_curve_control_points_theta[a][c] = |
| float_to_uint16(theta, 1u, 35999u, 18000u, 36000.f / SK_FloatPI); |
| } |
| |
| } |
| |
| // Write the syntax according to clause C.2. |
| SkDynamicMemoryWStream s; |
| syntax.write_application_info(s); |
| return s.detachAsData(); |
| } |
| |
| } // namespace skhdr |
| |