blob: a7701738ccecf776d33e65290a550033a1c9b8c6 [file] [log] [blame]
maryla-ucc90b0942023-09-11 14:13:29 +02001// Copyright 2023 Google LLC
2// SPDX-License-Identifier: BSD-2-Clause
3
4#include <math.h>
5
6#include "avif/avif.h"
maryla-uc078aa802023-09-11 17:00:50 +02007#include "avifjpeg.h"
maryla-ucc90b0942023-09-11 14:13:29 +02008#include "aviftest_helpers.h"
9#include "gtest/gtest.h"
10
11namespace libavif {
12namespace {
13
14// Used to pass the data folder path to the GoogleTest suites.
15const char* data_path = nullptr;
16
17//------------------------------------------------------------------------------
18
maryla-uc078aa802023-09-11 17:00:50 +020019void CheckGainMapMetadata(const avifGainMapMetadata& m,
20 std::array<double, 3> gain_map_min,
21 std::array<double, 3> gain_map_max,
22 std::array<double, 3> gain_map_gamma,
23 std::array<double, 3> offset_sdr,
24 std::array<double, 3> offset_hdr,
25 double hdr_capacity_min, double hdr_capacity_max,
26 bool base_rendition_is_hdr) {
27 const double kEpsilon = 1e-8;
28
29 EXPECT_NEAR(static_cast<double>(m.gainMapMinN[0]) / m.gainMapMinD[0],
30 gain_map_min[0], kEpsilon);
31 EXPECT_NEAR(static_cast<double>(m.gainMapMinN[1]) / m.gainMapMinD[1],
32 gain_map_min[1], kEpsilon);
33 EXPECT_NEAR(static_cast<double>(m.gainMapMinN[2]) / m.gainMapMinD[2],
34 gain_map_min[2], kEpsilon);
35
36 EXPECT_NEAR(static_cast<double>(m.gainMapMaxN[0]) / m.gainMapMaxD[0],
37 gain_map_max[0], kEpsilon);
38 EXPECT_NEAR(static_cast<double>(m.gainMapMaxN[1]) / m.gainMapMaxD[1],
39 gain_map_max[1], kEpsilon);
40 EXPECT_NEAR(static_cast<double>(m.gainMapMaxN[2]) / m.gainMapMaxD[2],
41 gain_map_max[2], kEpsilon);
42
43 EXPECT_NEAR(static_cast<double>(m.gainMapGammaN[0]) / m.gainMapGammaD[0],
44 gain_map_gamma[0], kEpsilon);
45 EXPECT_NEAR(static_cast<double>(m.gainMapGammaN[1]) / m.gainMapGammaD[1],
46 gain_map_gamma[1], kEpsilon);
47 EXPECT_NEAR(static_cast<double>(m.gainMapGammaN[2]) / m.gainMapGammaD[2],
48 gain_map_gamma[2], kEpsilon);
49
50 EXPECT_NEAR(static_cast<double>(m.offsetSdrN[0]) / m.offsetSdrD[0],
51 offset_sdr[0], kEpsilon);
52 EXPECT_NEAR(static_cast<double>(m.offsetSdrN[1]) / m.offsetSdrD[1],
53 offset_sdr[1], kEpsilon);
54 EXPECT_NEAR(static_cast<double>(m.offsetSdrN[2]) / m.offsetSdrD[2],
55 offset_sdr[2], kEpsilon);
56
57 EXPECT_NEAR(static_cast<double>(m.offsetHdrN[0]) / m.offsetHdrD[0],
58 offset_hdr[0], kEpsilon);
59 EXPECT_NEAR(static_cast<double>(m.offsetHdrN[1]) / m.offsetHdrD[1],
60 offset_hdr[1], kEpsilon);
61 EXPECT_NEAR(static_cast<double>(m.offsetHdrN[2]) / m.offsetHdrD[2],
62 offset_hdr[2], kEpsilon);
63
64 EXPECT_NEAR(static_cast<double>(m.hdrCapacityMinN) / m.hdrCapacityMinD,
65 hdr_capacity_min, kEpsilon);
66 EXPECT_NEAR(static_cast<double>(m.hdrCapacityMaxN) / m.hdrCapacityMaxD,
67 hdr_capacity_max, kEpsilon);
68 EXPECT_EQ(m.baseRenditionIsHDR, base_rendition_is_hdr);
69}
70
maryla-ucc90b0942023-09-11 14:13:29 +020071TEST(JpegTest, ReadJpegWithGainMap) {
72 for (const char* filename : {"paris_exif_xmp_gainmap_bigendian.jpg",
73 "paris_exif_xmp_gainmap_littleendian.jpg"}) {
74 SCOPED_TRACE(filename);
75
76 const testutil::AvifImagePtr image =
77 testutil::ReadImage(data_path, filename, AVIF_PIXEL_FORMAT_YUV444, 8,
78 AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
79 /*ignore_icc=*/false, /*ignore_exif=*/false,
80 /*ignore_xmp=*/true, /*allow_changing_cicp=*/true,
81 /*ignore_gain_map=*/false);
82 ASSERT_NE(image, nullptr);
83 ASSERT_NE(image->gainMap.image, nullptr);
84 EXPECT_EQ(image->gainMap.image->width, 512u);
85 EXPECT_EQ(image->gainMap.image->height, 384u);
86 // Since ignore_xmp is true, there should be no XMP, even if it had to
87 // be read to parse the gain map.
88 EXPECT_EQ(image->xmp.size, 0u);
89
maryla-uc078aa802023-09-11 17:00:50 +020090 CheckGainMapMetadata(image->gainMap.metadata,
91 /*gain_map_min=*/{1.0, 1.0, 1.0},
92 /*gain_map_max=*/{exp2(3.5), exp2(3.6), exp2(3.7)},
93 /*gain_map_gamma=*/{1.0, 1.0, 1.0},
94 /*offset_sdr=*/{0.0, 0.0, 0.0},
95 /*offset_hdr=*/{0.0, 0.0, 0.0},
96 /*hdr_capacity_min=*/1.0,
97 /*hdr_capacity_max=*/exp2(3.5),
98 /*base_rendition_is_hdr=*/false);
maryla-ucc90b0942023-09-11 14:13:29 +020099 }
100}
101
102TEST(JpegTest, IgnoreGainMap) {
103 const testutil::AvifImagePtr image = testutil::ReadImage(
104 data_path, "paris_exif_xmp_gainmap_littleendian.jpg",
105 AVIF_PIXEL_FORMAT_YUV444, 8, AVIF_CHROMA_DOWNSAMPLING_AUTOMATIC,
106 /*ignore_icc=*/false, /*ignore_exif=*/false,
107 /*ignore_xmp=*/false, /*allow_changing_cicp=*/true,
108 /*ignore_gain_map=*/true);
109 ASSERT_NE(image, nullptr);
110 EXPECT_EQ(image->gainMap.image, nullptr);
111 // Check there is xmp since ignore_xmp is false (just making sure that
112 // ignore_gain_map=true has no impact on this).
113 EXPECT_GT(image->xmp.size, 0u);
114}
115
maryla-uc078aa802023-09-11 17:00:50 +0200116TEST(JpegTest, ParseXMP) {
117 const std::string xmp = R"(
118<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
119<x:xmpmeta xmlns:x="adobe:ns:meta/">
120 <foo:myelement> <!-- 7.3 "Other XMP elements may appear around the rdf:RDF element." -->
121 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
122 <rdf:Description xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
123 hdrgm:Version="1.0"
124 hdrgm:BaseRenditionIsHDR="True"
125 hdrgm:OffsetSDR="0.046983"
126 hdrgm:OffsetHDR="0.046983"
127 hdrgm:HDRCapacityMin="0"
128 hdrgm:HDRCapacityMax="3.9">
129 <hdrgm:GainMapMin>
130 <rdf:Seq>
131 <rdf:li>0.025869</rdf:li>
132 <rdf:li>0.075191</rdf:li>
133 <rdf:li>0.142298</rdf:li>
134 </rdf:Seq>
135 </hdrgm:GainMapMin>
136 <hdrgm:GainMapMax>
137 <rdf:Seq>
138 <rdf:li>3.527605</rdf:li>
139 <rdf:li>2.830234</rdf:li>
140 <!-- should work even with some whitespace -->
141 <rdf:li>
142 1.537243
143 </rdf:li>
144 </rdf:Seq>
145 </hdrgm:GainMapMax>
146 <hdrgm:Gamma>
147 <rdf:Seq>
148 <rdf:li>0.506828</rdf:li>
149 <rdf:li>0.590032</rdf:li>
150 <rdf:li>1.517708</rdf:li>
151 </rdf:Seq>
152 </hdrgm:Gamma>
153 </rdf:Description>
154 </rdf:RDF>
155 </foo:myelement>
156</x:xmpmeta>
157<?xpacket end="w"?>
158 )";
159 avifGainMapMetadata metadata;
160 EXPECT_TRUE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
161 &metadata));
162
163 CheckGainMapMetadata(
164 metadata,
165 /*gain_map_min=*/{exp2(0.025869), exp2(0.075191), exp2(0.142298)},
166 /*gain_map_max=*/{exp2(3.527605), exp2(2.830234), exp2(1.537243)},
167 /*gain_map_gamma=*/{1.0 / 0.506828, 1.0 / 0.590032, 1.0 / 1.517708},
168 /*offset_sdr=*/{0.046983, 0.046983, 0.046983},
169 /*offset_hdr=*/{0.046983, 0.046983, 0.046983},
170 /*hdr_capacity_min=*/1.0,
171 /*hdr_capacity_max=*/exp2(3.9),
172 /*base_rendition_is_hdr=*/true);
173}
174
175TEST(JpegTest, ParseXMPAllDefaultValues) {
176 const std::string xmp = R"(
177<x:xmpmeta xmlns:x="adobe:ns:meta/">
178 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
179 <rdf:Description rdf:about="stuff"
180 xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" hdrgm:Version="1.0">
181 </rdf:Description>
182 </rdf:RDF>
183</x:xmpmeta>
184<?xpacket end="w"?>
185 )";
186 avifGainMapMetadata metadata;
187 EXPECT_TRUE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
188 &metadata));
189
190 CheckGainMapMetadata(metadata,
191 /*gain_map_min=*/{1.0, 1.0, 1.0},
192 /*gain_map_max=*/{exp2(1.0), exp2(1.0), exp2(1.0)},
193 /*gain_map_gamma=*/{1.0, 1.0, 1.0},
194 /*offset_sdr=*/{1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0},
195 /*offset_hdr=*/{1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0},
196 /*hdr_capacity_min=*/1.0,
197 /*hdr_capacity_max=*/exp2(1.0),
198 /*base_rendition_is_hdr=*/false);
199}
200
201TEST(JpegTest, ExtendedXmp) {
202 const std::string xmp = R"(
203<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
204<x:xmpmeta xmlns:x="adobe:ns:meta/">
205 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
206 <rdf:Description rdf:about="stuff"
207 xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" hdrgm:Version="1.0"
208 hdrgm:BaseRenditionIsHDR="True">
209 </rdf:Description>
210 </rdf:RDF>
211</x:xmpmeta>
212<?xpacket end="w"?>
213
214<x:xmpmeta xmlns:x="adobe:ns:meta/">
215 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
216 <!-- Imagine this is smoe exetended xmp that avifenc concatenated to
217 the main XMP. As a result we have invalid XMP but should still be
218 able to parse it. -->
219 <stuff></stuff>
220 </rdf:RDF>
221</x:xmpmeta>
222 )";
223 avifGainMapMetadata metadata;
224 EXPECT_TRUE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
225 &metadata));
226
227 // Note that this test passes because the gain map metadata is in the primary
228 // XMP. If it was in the extended part, we wouldn't detect it (but probably
229 // should).
230 CheckGainMapMetadata(metadata,
231 /*gain_map_min=*/{1.0, 1.0, 1.0},
232 /*gain_map_max=*/{exp2(1.0), exp2(1.0), exp2(1.0)},
233 /*gain_map_gamma=*/{1.0, 1.0, 1.0},
234 /*offset_sdr=*/{1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0},
235 /*offset_hdr=*/{1.0 / 64.0, 1.0 / 64.0, 1.0 / 64.0},
236 /*hdr_capacity_min=*/1.0,
237 /*hdr_capacity_max=*/exp2(1.0),
238 /*base_rendition_is_hdr=*/true);
239}
240
241TEST(JpegTest, InvalidNumberOfValues) {
242 const std::string xmp = R"(
243<x:xmpmeta xmlns:x="adobe:ns:meta/">
244 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
245 <rdf:Description xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
246 hdrgm:Version="1.0"
247 hdrgm:BaseRenditionIsHDR="False"
248 hdrgm:OffsetSDR="0.046983"
249 hdrgm:OffsetHDR="0.046983"
250 hdrgm:HDRCapacityMin="0"
251 hdrgm:HDRCapacityMax="3.9">
252 <hdrgm:GainMapMin>
253 <rdf:Seq><!--invalid! only two values-->
254 <rdf:li>0.023869</rdf:li>
255 <rdf:li>0.075191</rdf:li>
256 </rdf:Seq>
257 </hdrgm:GainMapMin>
258 </rdf:RDF>
259</x:xmpmeta>
260 )";
261 avifGainMapMetadata metadata;
262 EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
263 &metadata));
264}
265
266TEST(JpegTest, WrongVersion) {
267 const std::string xmp = R"(
268<x:xmpmeta xmlns:x="adobe:ns:meta/">
269 <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
270 <rdf:Description rdf:about=""
271 xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" hdrgm:Version="2.0">
272 </rdf:Description>
273 </rdf:RDF>
274</x:xmpmeta>
275 )";
276 avifGainMapMetadata metadata;
277 EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
278 &metadata));
279}
280
281TEST(JpegTest, InvalidXMP) {
282 const std::string xmp = R"(
283<x:xmpmeta xmlns:x="adobe:ns:meta/">
284 <rdf:Description rdf:about=""
285 xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" hdrgm:Version="2.0">
286 </rdf:Description>
287 </rdf:RDF>
288</x:xmpmeta>
289 )";
290 avifGainMapMetadata metadata;
291 EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
292 &metadata));
293}
294
295TEST(JpegTest, EmptyXMP) {
296 const std::string xmp = "";
297 avifGainMapMetadata metadata;
298 EXPECT_FALSE(avifJPEGParseGainMapXMP((const uint8_t*)xmp.data(), xmp.size(),
299 &metadata));
300}
301
maryla-ucc90b0942023-09-11 14:13:29 +0200302//------------------------------------------------------------------------------
303
304} // namespace
305} // namespace libavif
306
307int main(int argc, char** argv) {
308 ::testing::InitGoogleTest(&argc, argv);
309 if (argc != 2) {
310 std::cerr << "There must be exactly one argument containing the path to "
311 "the test data folder"
312 << std::endl;
313 return 1;
314 }
315 libavif::data_path = argv[1];
316 return RUN_ALL_TESTS();
317}