maryla-uc | c90b094 | 2023-09-11 14:13:29 +0200 | [diff] [blame] | 1 | // Copyright 2023 Google LLC |
| 2 | // SPDX-License-Identifier: BSD-2-Clause |
| 3 | |
| 4 | #include <math.h> |
| 5 | |
| 6 | #include "avif/avif.h" |
maryla-uc | 078aa80 | 2023-09-11 17:00:50 +0200 | [diff] [blame] | 7 | #include "avifjpeg.h" |
maryla-uc | c90b094 | 2023-09-11 14:13:29 +0200 | [diff] [blame] | 8 | #include "aviftest_helpers.h" |
| 9 | #include "gtest/gtest.h" |
| 10 | |
| 11 | namespace libavif { |
| 12 | namespace { |
| 13 | |
| 14 | // Used to pass the data folder path to the GoogleTest suites. |
| 15 | const char* data_path = nullptr; |
| 16 | |
| 17 | //------------------------------------------------------------------------------ |
| 18 | |
maryla-uc | 078aa80 | 2023-09-11 17:00:50 +0200 | [diff] [blame] | 19 | void 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-uc | c90b094 | 2023-09-11 14:13:29 +0200 | [diff] [blame] | 71 | TEST(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-uc | 078aa80 | 2023-09-11 17:00:50 +0200 | [diff] [blame] | 90 | 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-uc | c90b094 | 2023-09-11 14:13:29 +0200 | [diff] [blame] | 99 | } |
| 100 | } |
| 101 | |
| 102 | TEST(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-uc | 078aa80 | 2023-09-11 17:00:50 +0200 | [diff] [blame] | 116 | TEST(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 | |
| 175 | TEST(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 | |
| 201 | TEST(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 | |
| 241 | TEST(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 | |
| 266 | TEST(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 | |
| 281 | TEST(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 | |
| 295 | TEST(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-uc | c90b094 | 2023-09-11 14:13:29 +0200 | [diff] [blame] | 302 | //------------------------------------------------------------------------------ |
| 303 | |
| 304 | } // namespace |
| 305 | } // namespace libavif |
| 306 | |
| 307 | int 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 | } |