/* * Copyright 2024 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/private/SkGainmapInfo.h" #include "include/core/SkColor.h" #include "include/core/SkData.h" #include "include/core/SkRefCnt.h" #include "include/core/SkStream.h" #include "src/base/SkEndian.h" #include "src/codec/SkCodecPriv.h" #include "src/core/SkStreamPriv.h" #include #include #include namespace { constexpr uint8_t kIsMultiChannelMask = (1u << 7); constexpr uint8_t kUseBaseColourSpaceMask = (1u << 6); } // namespace static void write_rational_be(SkDynamicMemoryWStream& s, float x) { // TODO(b/338342146): Select denominator to get maximum precision and robustness. uint32_t denominator = 0x10000000; if (std::abs(x) > 1.f) { denominator = 0x1000; } int32_t numerator = static_cast(std::llround(static_cast(x) * denominator)); SkWStreamWriteS32BE(&s, numerator); SkWStreamWriteU32BE(&s, denominator); } static void write_positive_rational_be(SkDynamicMemoryWStream& s, float x) { // TODO(b/338342146): Select denominator to get maximum precision and robustness. uint32_t denominator = 0x10000000; if (x > 1.f) { denominator = 0x1000; } uint32_t numerator = static_cast(std::llround(static_cast(x) * denominator)); SkWStreamWriteU32BE(&s, numerator); SkWStreamWriteU32BE(&s, denominator); } static bool read_u16_be(SkStream* s, uint16_t* value) { if (!s->readU16(value)) { return false; } *value = SkEndian_SwapBE16(*value); return true; } static bool read_u32_be(SkStream* s, uint32_t* value) { if (!s->readU32(value)) { return false; } *value = SkEndian_SwapBE32(*value); return true; } static bool read_s32_be(SkStream* s, int32_t* value) { if (!s->readS32(value)) { return false; } *value = SkEndian_SwapBE32(*value); return true; } static bool read_rational_be(SkStream* s, float* value) { int32_t numerator = 0; uint32_t denominator = 0; if (!read_s32_be(s, &numerator)) { return false; } if (!read_u32_be(s, &denominator)) { return false; } *value = static_cast(static_cast(numerator) / static_cast(denominator)); return true; } static bool read_positive_rational_be(SkStream* s, float* value) { uint32_t numerator = 0; uint32_t denominator = 0; if (!read_u32_be(s, &numerator)) { return false; } if (!read_u32_be(s, &denominator)) { return false; } *value = static_cast(static_cast(numerator) / static_cast(denominator)); return true; } static bool read_iso_gainmap_version(SkStream* s) { // Ensure minimum version is 0. uint16_t minimum_version = 0; if (!read_u16_be(s, &minimum_version)) { SkCodecPrintf("Failed to read ISO 21496-1 minimum version.\n"); return false; } if (minimum_version != 0) { SkCodecPrintf("Unsupported ISO 21496-1 minimum version.\n"); return false; } // Ensure writer version is present. No value is invalid. uint16_t writer_version = 0; if (!read_u16_be(s, &writer_version)) { SkCodecPrintf("Failed to read ISO 21496-1 version.\n"); return false; } return true; } static bool read_iso_gainmap_info(SkStream* s, SkGainmapInfo& info) { if (!read_iso_gainmap_version(s)) { SkCodecPrintf("Failed to read ISO 21496-1 version.\n"); return false; } uint8_t flags = 0; if (!s->readU8(&flags)) { SkCodecPrintf("Failed to read ISO 21496-1 flags.\n"); return false; } bool isMultiChannel = (flags & kIsMultiChannelMask) != 0; bool useBaseColourSpace = (flags & kUseBaseColourSpaceMask) != 0; float baseHdrHeadroom = 0.f; if (!read_positive_rational_be(s, &baseHdrHeadroom)) { SkCodecPrintf("Failed to read ISO 21496-1 base HDR headroom.\n"); return false; } float altrHdrHeadroom = 0.f; if (!read_positive_rational_be(s, &altrHdrHeadroom)) { SkCodecPrintf("Failed to read ISO 21496-1 altr HDR headroom.\n"); return false; } float gainMapMin[3] = {0.f}; float gainMapMax[3] = {0.f}; float gamma[3] = {0.f}; float baseOffset[3] = {0.f}; float altrOffset[3] = {0.f}; int channelCount = isMultiChannel ? 3 : 1; for (int i = 0; i < channelCount; ++i) { if (!read_rational_be(s, gainMapMin + i)) { SkCodecPrintf("Failed to read ISO 21496-1 gainmap minimum.\n"); return false; } if (!read_rational_be(s, gainMapMax + i)) { SkCodecPrintf("Failed to read ISO 21496-1 gainmap maximum.\n"); return false; } if (!read_positive_rational_be(s, gamma + i)) { SkCodecPrintf("Failed to read ISO 21496-1 gamma.\n"); return false; } if (!read_rational_be(s, baseOffset + i)) { SkCodecPrintf("Failed to read ISO 21496-1 base offset.\n"); return false; } if (!read_rational_be(s, altrOffset + i)) { SkCodecPrintf("Failed to read ISO 21496-1 altr offset.\n"); return false; } } info = SkGainmapInfo(); if (!useBaseColourSpace) { info.fGainmapMathColorSpace = SkColorSpace::MakeSRGB(); } if (baseHdrHeadroom < altrHdrHeadroom) { info.fBaseImageType = SkGainmapInfo::BaseImageType::kSDR; info.fDisplayRatioSdr = std::exp2(baseHdrHeadroom); info.fDisplayRatioHdr = std::exp2(altrHdrHeadroom); } else { info.fBaseImageType = SkGainmapInfo::BaseImageType::kHDR; info.fDisplayRatioHdr = std::exp2(baseHdrHeadroom); info.fDisplayRatioSdr = std::exp2(altrHdrHeadroom); } for (int i = 0; i < 3; ++i) { int j = i >= channelCount ? 0 : i; info.fGainmapRatioMin[i] = std::exp2(gainMapMin[j]); info.fGainmapRatioMax[i] = std::exp2(gainMapMax[j]); info.fGainmapGamma[i] = 1.f / gamma[j]; switch (info.fBaseImageType) { case SkGainmapInfo::BaseImageType::kSDR: info.fEpsilonSdr[i] = baseOffset[j]; info.fEpsilonHdr[i] = altrOffset[j]; break; case SkGainmapInfo::BaseImageType::kHDR: info.fEpsilonHdr[i] = baseOffset[j]; info.fEpsilonSdr[i] = altrOffset[j]; break; } } return true; } bool SkGainmapInfo::isUltraHDRv1Compatible() const { // UltraHDR v1 supports having the base image be HDR in theory, but it is largely // untested. if (fBaseImageType == BaseImageType::kHDR) { return false; } // UltraHDR v1 doesn't support a non-base gainmap math color space. if (fGainmapMathColorSpace) { return false; } return true; } bool SkGainmapInfo::ParseVersion(const SkData* data) { if (!data) { return false; } auto s = SkMemoryStream::MakeDirect(data->data(), data->size()); return read_iso_gainmap_version(s.get()); } bool SkGainmapInfo::Parse(const SkData* data, SkGainmapInfo& info) { if (!data) { return false; } auto s = SkMemoryStream::MakeDirect(data->data(), data->size()); return read_iso_gainmap_info(s.get(), info); } sk_sp SkGainmapInfo::SerializeVersion() { SkDynamicMemoryWStream s; SkWStreamWriteU16BE(&s, 0); // Minimum reader version SkWStreamWriteU16BE(&s, 0); // Writer version return s.detachAsData(); } static bool is_single_channel(SkColor4f c) { return c.fR == c.fG && c.fG == c.fB; }; sk_sp SkGainmapInfo::serialize() const { SkDynamicMemoryWStream s; // Version. SkWStreamWriteU16BE(&s, 0); // Minimum reader version SkWStreamWriteU16BE(&s, 0); // Writer version // Flags. bool all_single_channel = is_single_channel(fGainmapRatioMin) && is_single_channel(fGainmapRatioMax) && is_single_channel(fGainmapGamma) && is_single_channel(fEpsilonSdr) && is_single_channel(fEpsilonHdr); uint8_t flags = 0; if (!fGainmapMathColorSpace) { flags |= kUseBaseColourSpaceMask; } if (!all_single_channel) { flags |= kIsMultiChannelMask; } s.write8(flags); // Base and altr headroom. switch (fBaseImageType) { case SkGainmapInfo::BaseImageType::kSDR: write_positive_rational_be(s, std::log2(fDisplayRatioSdr)); write_positive_rational_be(s, std::log2(fDisplayRatioHdr)); break; case SkGainmapInfo::BaseImageType::kHDR: write_positive_rational_be(s, std::log2(fDisplayRatioHdr)); write_positive_rational_be(s, std::log2(fDisplayRatioSdr)); break; } // Per-channel information. for (int i = 0; i < (all_single_channel ? 1 : 3); ++i) { write_rational_be(s, std::log2(fGainmapRatioMin[i])); write_rational_be(s, std::log2(fGainmapRatioMax[i])); write_positive_rational_be(s, 1.f / fGainmapGamma[i]); switch (fBaseImageType) { case SkGainmapInfo::BaseImageType::kSDR: write_rational_be(s, fEpsilonSdr[i]); write_rational_be(s, fEpsilonHdr[i]); break; case SkGainmapInfo::BaseImageType::kHDR: write_rational_be(s, fEpsilonHdr[i]); write_rational_be(s, fEpsilonSdr[i]); break; } } return s.detachAsData(); }