Reland: Add ISO 21496-1 gainmap parsing
To SkGainmapInfo, add the functions ParseVersion, Parse,
SerializeVersion, and serialize. These generate the ISO 21496-1
binary blobs.
Reverted because of unused function in test. Moved function inside
appropriate ifdef.
Bug: b/338342146
Change-Id: I98436f3757f08668c3129b02f40dd5bbe223a073
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/853216
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Christopher Cameron <ccameron@google.com>
Commit-Queue: Christopher Cameron <ccameron@google.com>
diff --git a/gn/codec.gni b/gn/codec.gni
index b77a6a7..5a3aa9c 100644
--- a/gn/codec.gni
+++ b/gn/codec.gni
@@ -43,6 +43,7 @@
"$_src/codec/SkColorPalette.h",
"$_src/codec/SkExif.cpp",
"$_src/codec/SkFrameHolder.h",
+ "$_src/codec/SkGainmapInfo.cpp",
"$_src/codec/SkImageGenerator_FromEncoded.cpp",
"$_src/codec/SkMaskSwizzler.cpp",
"$_src/codec/SkMaskSwizzler.h",
diff --git a/include/private/SkGainmapInfo.h b/include/private/SkGainmapInfo.h
index a4a7a6c..07512a4 100644
--- a/include/private/SkGainmapInfo.h
+++ b/include/private/SkGainmapInfo.h
@@ -10,6 +10,8 @@
#include "include/core/SkColor.h"
#include "include/core/SkColorSpace.h"
+#include "include/core/SkRefCnt.h"
+class SkData;
/**
* Gainmap rendering parameters. Suppose our display has HDR to SDR ratio of H and we wish to
@@ -92,13 +94,38 @@
*/
sk_sp<SkColorSpace> fGainmapMathColorSpace = nullptr;
+ /**
+ * If |data| contains an ISO 21496-1 version that is supported, return true. Otherwise return
+ * false.
+ */
+ static bool ParseVersion(const SkData* data);
+
+ /**
+ * If |data| constains ISO 21496-1 metadata then parse that metadata then use it to populate
+ * |info| and return true, otherwise return false. If |data| indicates that that the base image
+ * color space primaries should be used for gainmap application then set
+ * |fGainmapMathColorSpace| to nullptr, otherwise set |fGainmapMathColorSpace| to sRGB (the
+ * default, to be overwritten by the image decoder).
+ */
+ static bool Parse(const SkData* data, SkGainmapInfo& info);
+
+ /**
+ * Serialize an ISO 21496-1 version 0 blob containing only the version structure.
+ */
+ static sk_sp<SkData> SerializeVersion();
+
+ /**
+ * Serialize an ISO 21496-1 version 0 blob containing this' gainmap parameters.
+ */
+ sk_sp<SkData> serialize() const;
+
inline bool operator==(const SkGainmapInfo& other) const {
return fGainmapRatioMin == other.fGainmapRatioMin &&
fGainmapRatioMax == other.fGainmapRatioMax && fGainmapGamma == other.fGainmapGamma &&
fEpsilonSdr == other.fEpsilonSdr && fEpsilonHdr == other.fEpsilonHdr &&
fDisplayRatioSdr == other.fDisplayRatioSdr &&
fDisplayRatioHdr == other.fDisplayRatioHdr &&
- fBaseImageType == other.fBaseImageType &&
+ fBaseImageType == other.fBaseImageType && fType == other.fType &&
SkColorSpace::Equals(fGainmapMathColorSpace.get(),
other.fGainmapMathColorSpace.get());
}
diff --git a/public.bzl b/public.bzl
index 885dc1b..7465670 100644
--- a/public.bzl
+++ b/public.bzl
@@ -1827,6 +1827,7 @@
"src/codec/SkEncodedInfo.cpp",
"src/codec/SkExif.cpp",
"src/codec/SkFrameHolder.h",
+ "src/codec/SkGainmapInfo.cpp",
"src/codec/SkImageGenerator_FromEncoded.cpp",
"src/codec/SkJpegCodec.cpp",
"src/codec/SkJpegCodec.h",
diff --git a/src/codec/BUILD.bazel b/src/codec/BUILD.bazel
index 154e48c..13a5eb3 100644
--- a/src/codec/BUILD.bazel
+++ b/src/codec/BUILD.bazel
@@ -27,6 +27,7 @@
"SkColorPalette.h",
"SkExif.cpp",
"SkFrameHolder.h",
+ "SkGainmapInfo.cpp",
"SkImageGenerator_FromEncoded.cpp",
"SkMaskSwizzler.cpp",
"SkMaskSwizzler.h",
@@ -319,6 +320,7 @@
"SkColorPalette.cpp",
"SkEncodedInfo.cpp",
"SkExif.cpp",
+ "SkGainmapInfo.cpp",
"SkImageGenerator_FromEncoded.cpp",
"SkMaskSwizzler.cpp",
"SkParseEncodedOrigin.cpp",
diff --git a/src/codec/SkGainmapInfo.cpp b/src/codec/SkGainmapInfo.cpp
new file mode 100644
index 0000000..d0fd75d
--- /dev/null
+++ b/src/codec/SkGainmapInfo.cpp
@@ -0,0 +1,296 @@
+/*
+ * 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 <cmath>
+#include <cstdint>
+#include <memory>
+
+namespace {
+constexpr uint8_t kIsMultiChannelMask = (1u << 7);
+constexpr uint8_t kUseBaseColourSpaceMask = (1u << 6);
+} // namespace
+
+static void write_u16_be(SkWStream* s, uint16_t value) {
+ value = SkEndian_SwapBE16(value);
+ s->write16(value);
+}
+
+static void write_u32_be(SkWStream* s, uint32_t value) {
+ value = SkEndian_SwapBE32(value);
+ s->write32(value);
+}
+
+static void write_s32_be(SkWStream* s, int32_t value) {
+ value = SkEndian_SwapBE32(value);
+ s->write32(value);
+}
+
+static void write_rational_be(SkWStream* 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<int32_t>(static_cast<double>(x) * denominator + 0.5);
+ write_s32_be(s, numerator);
+ write_u32_be(s, denominator);
+}
+
+static void write_positive_rational_be(SkWStream* 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<uint32_t>(static_cast<double>(x) * denominator + 0.5);
+ write_u32_be(s, numerator);
+ write_u32_be(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<float>(static_cast<double>(numerator) / static_cast<double>(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<float>(static_cast<double>(numerator) / static_cast<double>(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::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<SkData> SkGainmapInfo::SerializeVersion() {
+ SkDynamicMemoryWStream s;
+ write_u16_be(&s, 0); // Minimum reader version
+ write_u16_be(&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<SkData> SkGainmapInfo::serialize() const {
+ SkDynamicMemoryWStream s;
+ // Version.
+ write_u16_be(&s, 0); // Minimum reader version
+ write_u16_be(&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();
+}
diff --git a/tests/JpegGainmapTest.cpp b/tests/JpegGainmapTest.cpp
index 0e85eb0..d7c1902 100644
--- a/tests/JpegGainmapTest.cpp
+++ b/tests/JpegGainmapTest.cpp
@@ -35,6 +35,17 @@
namespace {
+// Return true if the relative difference between x and y is less than epsilon.
+static bool approx_eq(float x, float y, float epsilon) {
+ float numerator = std::abs(x - y);
+ // To avoid being too sensitive around zero, set the minimum denominator to epsilon.
+ float denominator = std::max(std::min(std::abs(x), std::abs(y)), epsilon);
+ if (numerator / denominator > epsilon) {
+ return false;
+ }
+ return true;
+}
+
// A test stream to stress the different SkJpegSourceMgr sub-classes.
class TestStream : public SkStream {
public:
@@ -458,8 +469,6 @@
gainmapBitmap.rowBytes()));
}
-static bool approx_eq(float x, float y, float epsilon) { return std::abs(x - y) < epsilon; }
-
DEF_TEST(AndroidCodec_jpegGainmapDecode, r) {
const struct Rec {
const char* path;
@@ -571,11 +580,28 @@
#if !defined(SK_ENABLE_NDK_IMAGES)
-static bool approx_eq_rgb(const SkColor4f& x, const SkColor4f& y, float epsilon) {
+static bool approx_eq(const SkColor4f& x, const SkColor4f& y, float epsilon) {
return approx_eq(x.fR, y.fR, epsilon) && approx_eq(x.fG, y.fG, epsilon) &&
approx_eq(x.fB, y.fB, epsilon);
}
+template <typename Reporter>
+void expect_approx_eq_info(Reporter& r, const SkGainmapInfo& a, const SkGainmapInfo& b) {
+ float kEpsilon = 1e-4f;
+ REPORTER_ASSERT(r, approx_eq(a.fGainmapRatioMin, b.fGainmapRatioMin, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(a.fGainmapRatioMin, b.fGainmapRatioMin, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(a.fGainmapGamma, b.fGainmapGamma, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(a.fEpsilonSdr, b.fEpsilonSdr, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(a.fEpsilonHdr, b.fEpsilonHdr, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(a.fDisplayRatioSdr, b.fDisplayRatioSdr, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(a.fDisplayRatioHdr, b.fDisplayRatioHdr, kEpsilon));
+ REPORTER_ASSERT(r, a.fType == b.fType);
+ REPORTER_ASSERT(r, a.fBaseImageType == b.fBaseImageType);
+ REPORTER_ASSERT(
+ r,
+ SkColorSpace::Equals(a.fGainmapMathColorSpace.get(), b.fGainmapMathColorSpace.get()));
+}
+
DEF_TEST(AndroidCodec_gainmapInfoEncode, r) {
SkDynamicMemoryWStream encodeStream;
SkGainmapInfo gainmapInfo;
@@ -630,7 +656,7 @@
decode_all(r, std::move(decodeStream), baseBitmap, gainmapBitmap, decodedGainmapInfo);
// Verify they are |gainmapInfo| matches |decodedGainmapInfo|.
- REPORTER_ASSERT(r, gainmapInfo == decodedGainmapInfo);
+ expect_approx_eq_info(r, gainmapInfo, decodedGainmapInfo);
}
}
@@ -728,36 +754,7 @@
decode_all(r, std::move(decodeStream), baseBitmap[1], gainmapBitmap[1], gainmapInfo[1]);
// HDRGM will have the same rendering parameters.
- REPORTER_ASSERT(
- r,
- approx_eq_rgb(
- gainmapInfo[0].fGainmapRatioMin, gainmapInfo[1].fGainmapRatioMin,kEpsilon));
- REPORTER_ASSERT(
- r,
- approx_eq_rgb(
- gainmapInfo[0].fGainmapRatioMax, gainmapInfo[1].fGainmapRatioMax, kEpsilon));
- REPORTER_ASSERT(
- r,
- approx_eq_rgb(
- gainmapInfo[0].fGainmapGamma, gainmapInfo[1].fGainmapGamma, kEpsilon));
- REPORTER_ASSERT(
- r,
- approx_eq(gainmapInfo[0].fEpsilonSdr.fR, gainmapInfo[1].fEpsilonSdr.fR, kEpsilon));
- REPORTER_ASSERT(
- r,
- approx_eq(gainmapInfo[0].fEpsilonHdr.fR, gainmapInfo[1].fEpsilonHdr.fR, kEpsilon));
- REPORTER_ASSERT(
- r,
- approx_eq(
- gainmapInfo[0].fDisplayRatioSdr,
- gainmapInfo[1].fDisplayRatioSdr,
- kEpsilon));
- REPORTER_ASSERT(
- r,
- approx_eq(
- gainmapInfo[0].fDisplayRatioHdr,
- gainmapInfo[1].fDisplayRatioHdr,
- kEpsilon));
+ expect_approx_eq_info(r, gainmapInfo[0], gainmapInfo[1]);
// Render a few pixels and verify that they come out the same. Rendering requires SkSL.
const struct Rec {
@@ -796,8 +793,88 @@
SkColor4f p1 = render_gainmap_pixel(
rec.hdrRatio, baseBitmap[1], gainmapBitmap[1], gainmapInfo[1], rec.x, rec.y);
- REPORTER_ASSERT(r, approx_eq_rgb(p0, p1, kEpsilon));
+ REPORTER_ASSERT(r, approx_eq(p0, p1, kEpsilon));
}
}
}
+
+DEF_TEST(AndroidCodec_gainmapInfoParse, r) {
+ const uint8_t versionData[] = {
+ 0x00, // Minimum version
+ 0x00,
+ 0x00, // Writer version
+ 0x00,
+ };
+ const uint8_t data[] = {
+ 0x00, 0x00, // Minimum version
+ 0x00, 0x00, // Writer version
+ 0xc0, // Flags
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // Base HDR headroom
+ 0x00, 0x01, 0x45, 0x3e, 0x00, 0x00, 0x80, 0x00, // Altr HDR headroom
+ 0xfc, 0x23, 0x05, 0x14, 0x40, 0x00, 0x00, 0x00, // Red: Gainmap min
+ 0x00, 0x01, 0x1f, 0xe1, 0x00, 0x00, 0x80, 0x00, // Red: Gainmap max
+ 0x10, 0x4b, 0x9f, 0x0a, 0x40, 0x00, 0x00, 0x00, // Red: Gamma
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Red: Base offset
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Red: Altr offset
+ 0xfd, 0xdb, 0x68, 0x04, 0x40, 0x00, 0x00, 0x00, // Green: Gainmap min
+ 0x00, 0x01, 0x11, 0x68, 0x00, 0x00, 0x80, 0x00, // Green: Gainmap max
+ 0x10, 0x28, 0xf9, 0x53, 0x40, 0x00, 0x00, 0x00, // Green: Gamma
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Green: Base offset
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Green: Altr offset
+ 0xf7, 0x16, 0x7b, 0x90, 0x40, 0x00, 0x00, 0x00, // Blue: Gainmap min
+ 0x00, 0x01, 0x0f, 0x9a, 0x00, 0x00, 0x80, 0x00, // Blue: Gainmap max
+ 0x12, 0x95, 0xa8, 0x3f, 0x40, 0x00, 0x00, 0x00, // Blue: Gamma
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Blue: Base offset
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // Blue: Altr offset
+ };
+ SkGainmapInfo kExpectedInfo = {{0.959023f, 0.977058f, 0.907989f, 1.f},
+ {4.753710f, 4.395375f, 4.352630f, 1.f},
+ {3.927490f, 3.960382f, 3.443712f, 1.f},
+ {0.015625f, 0.015625f, 0.015625f, 1.f},
+ {0.015625f, 0.015625f, 0.015625f, 1.f},
+ 1.000000f,
+ 5.819739f,
+ SkGainmapInfo::BaseImageType::kSDR,
+ SkGainmapInfo::Type::kDefault,
+ nullptr};
+ SkGainmapInfo kSingleChannelInfo = {{0.1234567e-4f, 0.1234567e-4f, 0.1234567e-4f, 1.f},
+ {-0.1234567e-4f, -0.1234567e-4f, -0.1234567e-4f, 1.f},
+ {0.1234567e+0f, 0.1234567e+0f, 0.1234567e+0f, 1.f},
+ {0.1234567e+4f, 0.1234567e+4f, 0.1234567e+4f, 1.f},
+ {0.1234567e+4f, 0.1234567e+4f, 0.1234567e+4f, 1.f},
+ 1.,
+ 4.f,
+ SkGainmapInfo::BaseImageType::kHDR,
+ SkGainmapInfo::Type::kDefault,
+ SkColorSpace::MakeSRGB()};
+
+ // Verify the version from data.
+ REPORTER_ASSERT(r,
+ SkGainmapInfo::ParseVersion(
+ SkData::MakeWithoutCopy(versionData, sizeof(versionData)).get()));
+
+ // Verify the SkGainmapInfo from data.
+ SkGainmapInfo info;
+ REPORTER_ASSERT(r,
+ SkGainmapInfo::Parse(SkData::MakeWithoutCopy(data, sizeof(data)).get(), info));
+ expect_approx_eq_info(r, info, kExpectedInfo);
+
+ // Verify the parsed version.
+ REPORTER_ASSERT(r, SkGainmapInfo::ParseVersion(SkGainmapInfo::SerializeVersion().get()));
+
+ // Verify the round-trip SkGainmapInfo.
+ auto dataInfo = info.serialize();
+ SkGainmapInfo infoRoundTrip;
+ REPORTER_ASSERT(r, SkGainmapInfo::Parse(dataInfo.get(), infoRoundTrip));
+ expect_approx_eq_info(r, info, infoRoundTrip);
+
+ // Serialize a single-channel SkGainmapInfo. The serialized data should be smaller.
+ auto dataSingleChannelInfo = kSingleChannelInfo.serialize();
+ REPORTER_ASSERT(r, dataSingleChannelInfo->size() < dataInfo->size());
+ SkGainmapInfo singleChannelInfoRoundTrip;
+ REPORTER_ASSERT(r,
+ SkGainmapInfo::Parse(dataSingleChannelInfo.get(), singleChannelInfoRoundTrip));
+ expect_approx_eq_info(r, singleChannelInfoRoundTrip, kSingleChannelInfo);
+}
+
#endif // !defined(SK_ENABLE_NDK_IMAGES)