| /* | |
| * 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/SkColorFilter.h" | |
| #include "include/core/SkStream.h" | |
| #include "include/core/SkString.h" | |
| #include "include/private/SkHdrMetadata.h" | |
| #include "src/codec/SkHdrAgtmPriv.h" | |
| #include "src/core/SkStreamPriv.h" | |
| namespace skhdr { | |
| SkString ContentLightLevelInformation::toString() const { | |
| return SkStringPrintf("{maxCLL:%f, maxFALL:%f}", fMaxCLL, fMaxFALL); | |
| } | |
| bool ContentLightLevelInformation::parse(const SkData* data) { | |
| if (data->size() != 4) { | |
| return false; | |
| } | |
| SkMemoryStream s(data->data(), data->size()); | |
| uint16_t max_cll = 0; | |
| uint16_t max_fall = 0; | |
| if (!SkStreamPriv::ReadU16BE(&s, &max_cll)) { | |
| return false; | |
| } | |
| if (!SkStreamPriv::ReadU16BE(&s, &max_fall)) { | |
| return false; | |
| } | |
| fMaxCLL = max_cll; | |
| fMaxFALL = max_fall; | |
| return true; | |
| } | |
| sk_sp<SkData> ContentLightLevelInformation::serialize() const { | |
| SkDynamicMemoryWStream s; | |
| SkStreamPriv::WriteU16BE(&s, getUint16MaxCLL()); | |
| SkStreamPriv::WriteU16BE(&s, getUint16MaxFALL()); | |
| return s.detachAsData(); | |
| } | |
| // The PNG CLLI metadata stores luminance as an integer equal to the floating point value, multipled | |
| // by clli_png_luminance_divisor. | |
| static constexpr float clli_png_luminance_divisor = 10000.f; | |
| bool ContentLightLevelInformation::parsePngChunk(const SkData* data) { | |
| if (data->size() != 8) { | |
| return false; | |
| } | |
| SkMemoryStream s(data->data(), data->size()); | |
| uint32_t max_cll_times_10000 = 0; | |
| uint32_t max_fall_times_10000 = 0; | |
| if (!SkStreamPriv::ReadU32BE(&s, &max_cll_times_10000)) { | |
| return false; | |
| } | |
| if (!SkStreamPriv::ReadU32BE(&s, &max_fall_times_10000)) { | |
| return false; | |
| } | |
| fMaxCLL = max_cll_times_10000 / clli_png_luminance_divisor; | |
| fMaxFALL = max_fall_times_10000 / clli_png_luminance_divisor; | |
| return true; | |
| } | |
| sk_sp<SkData> ContentLightLevelInformation::serializePngChunk() const { | |
| SkDynamicMemoryWStream s; | |
| SkStreamPriv::WriteU32BE(&s, std::llroundf(fMaxCLL * clli_png_luminance_divisor)); | |
| SkStreamPriv::WriteU32BE(&s, std::llroundf(fMaxFALL * clli_png_luminance_divisor)); | |
| return s.detachAsData(); | |
| } | |
| bool ContentLightLevelInformation::operator==(const ContentLightLevelInformation& other) const { | |
| return fMaxCLL == other.fMaxCLL && | |
| fMaxFALL == other.fMaxFALL; | |
| } | |
| // The MDCV metadata stores chrominance [luminance] as an integer equal to the floating point value, | |
| // multipled by mdcv_chrominance[luminance]_divisor. | |
| static constexpr float mdcv_chrominance_divisor = 50000.f; | |
| static constexpr float mdcv_luminance_divisor = 10000.f; | |
| SkString MasteringDisplayColorVolume::toString() const { | |
| return SkStringPrintf( | |
| "{red:[%1.8f,%1.8f], green:[%1.8f,%1.8f], blue:[%1.8f,%1.8f], white:[%1.8f,%1.8f], maxLum:%f, minLum:%f}", | |
| fDisplayPrimaries.fRX, fDisplayPrimaries.fRY, | |
| fDisplayPrimaries.fGX, fDisplayPrimaries.fGY, | |
| fDisplayPrimaries.fBX, fDisplayPrimaries.fBY, | |
| fDisplayPrimaries.fWX, fDisplayPrimaries.fWY, | |
| fMaximumDisplayMasteringLuminance, | |
| fMinimumDisplayMasteringLuminance); | |
| } | |
| bool MasteringDisplayColorVolume::parse(const SkData* data) { | |
| if (data->size() != 24) { | |
| return false; | |
| } | |
| SkMemoryStream s(data->data(), data->size()); | |
| uint16_t chromaticities_times_50000[8]; | |
| for (auto& chromaticity_times_50000 : chromaticities_times_50000) { | |
| if (!SkStreamPriv::ReadU16BE(&s, &chromaticity_times_50000)) { | |
| return false; | |
| } | |
| } | |
| uint32_t max_luminance_times_10000 = 0; | |
| uint32_t min_luminance_times_10000 = 0; | |
| if (!SkStreamPriv::ReadU32BE(&s, &max_luminance_times_10000)) { | |
| return false; | |
| } | |
| if (!SkStreamPriv::ReadU32BE(&s, &min_luminance_times_10000)) { | |
| return false; | |
| } | |
| fDisplayPrimaries = SkColorSpacePrimaries({ | |
| chromaticities_times_50000[0] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[1] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[2] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[3] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[4] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[5] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[6] / mdcv_chrominance_divisor, | |
| chromaticities_times_50000[7] / mdcv_chrominance_divisor, | |
| }); | |
| fMaximumDisplayMasteringLuminance = max_luminance_times_10000 / mdcv_luminance_divisor; | |
| fMinimumDisplayMasteringLuminance = min_luminance_times_10000 / mdcv_luminance_divisor; | |
| return true; | |
| } | |
| sk_sp<SkData> MasteringDisplayColorVolume::serialize() const { | |
| SkDynamicMemoryWStream s; | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fRX * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fRY * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fGX * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fGY * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fBX * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fBY * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fWX * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU16BE(&s, std::llroundf(fDisplayPrimaries.fWY * mdcv_chrominance_divisor)); | |
| SkStreamPriv::WriteU32BE( | |
| &s, std::llroundf(fMaximumDisplayMasteringLuminance * mdcv_luminance_divisor)); | |
| SkStreamPriv::WriteU32BE( | |
| &s, std::llroundf(fMinimumDisplayMasteringLuminance * mdcv_luminance_divisor)); | |
| return s.detachAsData(); | |
| } | |
| bool MasteringDisplayColorVolume::operator==(const MasteringDisplayColorVolume& other) const { | |
| return fDisplayPrimaries.fRX == other.fDisplayPrimaries.fRX && | |
| fDisplayPrimaries.fRY == other.fDisplayPrimaries.fRY && | |
| fDisplayPrimaries.fGX == other.fDisplayPrimaries.fGX && | |
| fDisplayPrimaries.fGY == other.fDisplayPrimaries.fGY && | |
| fDisplayPrimaries.fBX == other.fDisplayPrimaries.fBX && | |
| fDisplayPrimaries.fBY == other.fDisplayPrimaries.fBY && | |
| fDisplayPrimaries.fWX == other.fDisplayPrimaries.fWX && | |
| fDisplayPrimaries.fWY == other.fDisplayPrimaries.fWY && | |
| fMaximumDisplayMasteringLuminance == other.fMaximumDisplayMasteringLuminance && | |
| fMinimumDisplayMasteringLuminance == other.fMinimumDisplayMasteringLuminance; | |
| } | |
| Metadata Metadata::MakeEmpty() { | |
| return Metadata(); | |
| } | |
| bool Metadata::getContentLightLevelInformation(ContentLightLevelInformation* clli) const { | |
| if (!fContentLightLevelInformation.has_value()) { | |
| return false; | |
| } | |
| if (clli) { | |
| *clli = fContentLightLevelInformation.value(); | |
| } | |
| return true; | |
| } | |
| bool Metadata::getMasteringDisplayColorVolume(MasteringDisplayColorVolume* mdcv) const { | |
| if (!fMasteringDisplayColorVolume.has_value()) { | |
| return false; | |
| } | |
| if (mdcv) { | |
| *mdcv = fMasteringDisplayColorVolume.value(); | |
| } | |
| return true; | |
| } | |
| bool Metadata::getAdaptiveGlobalToneMap(AdaptiveGlobalToneMap* agtm) const { | |
| if (!fAdaptiveGlobalToneMap.has_value()) { | |
| return false; | |
| } | |
| if (agtm) { | |
| *agtm = fAdaptiveGlobalToneMap.value(); | |
| } | |
| return true; | |
| } | |
| sk_sp<const SkData> Metadata::getSerializedAgtm() const { | |
| return fAdaptiveGlobalToneMap.has_value() ? | |
| fAdaptiveGlobalToneMap->serialize() : nullptr; | |
| } | |
| void Metadata::setMasteringDisplayColorVolume(const MasteringDisplayColorVolume& mdcv) { | |
| fMasteringDisplayColorVolume = mdcv; | |
| } | |
| void Metadata::setContentLightLevelInformation(const ContentLightLevelInformation& clli) { | |
| fContentLightLevelInformation = clli; | |
| } | |
| void Metadata::setAdaptiveGlobalToneMap(const AdaptiveGlobalToneMap& agtm) { | |
| fAdaptiveGlobalToneMap = agtm; | |
| } | |
| void Metadata::setSerializedAgtm(sk_sp<const SkData> agtm_encoded) { | |
| AdaptiveGlobalToneMap agtm; | |
| if (agtm.parse(agtm_encoded.get())) { | |
| fAdaptiveGlobalToneMap = agtm; | |
| } else { | |
| fAdaptiveGlobalToneMap.reset(); | |
| } | |
| } | |
| SkString Metadata::toString() const { | |
| return SkStringPrintf("{clli:%s, mdcv:%s, agtm:%s}", | |
| fContentLightLevelInformation.has_value() ? | |
| fContentLightLevelInformation->toString().c_str() : "None", | |
| fMasteringDisplayColorVolume.has_value() ? | |
| fMasteringDisplayColorVolume->toString().c_str() : "None", | |
| fAdaptiveGlobalToneMap.has_value() ? | |
| fAdaptiveGlobalToneMap->toString().c_str() : "None"); | |
| } | |
| bool Metadata::operator==(const Metadata& other) const { | |
| return fContentLightLevelInformation == other.fContentLightLevelInformation && | |
| fMasteringDisplayColorVolume == other.fMasteringDisplayColorVolume && | |
| fAdaptiveGlobalToneMap == other.fAdaptiveGlobalToneMap; | |
| } | |
| sk_sp<SkColorFilter> Metadata::makeToneMapColorFilter( | |
| float targetedHdrHeadroom, const SkColorSpace* inputColorSpace) const { | |
| AdaptiveGlobalToneMap agtm; | |
| float scaleFactor = 1.f; | |
| if (!AgtmHelpers::PopulateToneMapAgtmParams(*this, inputColorSpace, &agtm, &scaleFactor)) { | |
| return nullptr; | |
| } | |
| auto& hatm = agtm.fHeadroomAdaptiveToneMap; | |
| if (!hatm.has_value()) { | |
| // TODO(https://crbug.com/395659818): Add default tone mapping. | |
| return nullptr; | |
| } | |
| return AgtmHelpers::MakeColorFilter(hatm.value(), targetedHdrHeadroom, scaleFactor); | |
| } | |
| } // namespace skhdr | |