splines_test.cc (15784B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include "lib/jxl/splines.h" 7 8 #include <jxl/cms.h> 9 #include <jxl/memory_manager.h> 10 11 #include <cstddef> 12 #include <cstdint> 13 #include <cstdio> 14 #include <ostream> 15 #include <utility> 16 #include <vector> 17 18 #include "lib/extras/codec.h" 19 #include "lib/jxl/base/common.h" 20 #include "lib/jxl/base/compiler_specific.h" 21 #include "lib/jxl/base/printf_macros.h" 22 #include "lib/jxl/base/rect.h" 23 #include "lib/jxl/base/span.h" 24 #include "lib/jxl/base/status.h" 25 #include "lib/jxl/chroma_from_luma.h" 26 #include "lib/jxl/common.h" 27 #include "lib/jxl/enc_aux_out.h" 28 #include "lib/jxl/enc_bit_writer.h" 29 #include "lib/jxl/enc_splines.h" 30 #include "lib/jxl/image.h" 31 #include "lib/jxl/image_ops.h" 32 #include "lib/jxl/image_test_utils.h" 33 #include "lib/jxl/test_memory_manager.h" 34 #include "lib/jxl/test_utils.h" 35 #include "lib/jxl/testing.h" 36 37 namespace jxl { 38 39 std::ostream& operator<<(std::ostream& os, const Spline::Point& p) { 40 return os << "(" << p.x << ", " << p.y << ")"; 41 } 42 43 std::ostream& operator<<(std::ostream& os, const Spline& spline) { 44 return os << "(spline with " << spline.control_points.size() 45 << " control points)"; 46 } 47 48 namespace { 49 50 using ::jxl::test::ReadTestData; 51 52 constexpr int kQuantizationAdjustment = 0; 53 const ColorCorrelation color_correlation{}; 54 const float kYToX = color_correlation.YtoXRatio(0); 55 const float kYToB = color_correlation.YtoBRatio(0); 56 57 constexpr float kTolerance = 0.003125; 58 59 Status DequantizeSplines(const Splines& splines, 60 std::vector<Spline>& dequantized) { 61 const auto& quantized_splines = splines.QuantizedSplines(); 62 const auto& starting_points = splines.StartingPoints(); 63 JXL_ENSURE(quantized_splines.size() == starting_points.size()); 64 65 uint64_t total = 0; 66 for (size_t i = 0; i < quantized_splines.size(); ++i) { 67 dequantized.emplace_back(); 68 JXL_RETURN_IF_ERROR(quantized_splines[i].Dequantize( 69 starting_points[i], kQuantizationAdjustment, kYToX, kYToB, 2u << 30u, 70 &total, dequantized.back())); 71 } 72 return true; 73 } 74 75 } // namespace 76 77 TEST(SplinesTest, Serialization) { 78 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 79 Spline spline1{ 80 /*control_points=*/{ 81 {109, 54}, {218, 159}, {80, 3}, {110, 274}, {94, 185}, {17, 277}}, 82 /*color_dct=*/ 83 {Dct32{36.3, 39.7, 23.2, 67.5, 4.4, 71.5, 62.3, 32.3, 92.2, 10.1, 10.8, 84 9.2, 6.1, 10.5, 79.1, 7, 24.6, 90.8, 5.5, 84, 43.8, 49, 85 33.5, 78.9, 54.5, 77.9, 62.1, 51.4, 36.4, 14.3, 83.7, 35.4}, 86 Dct32{9.4, 53.4, 9.5, 74.9, 72.7, 26.7, 7.9, 0.9, 84.9, 23.2, 26.5, 87 31.1, 91, 11.7, 74.1, 39.3, 23.7, 82.5, 4.8, 2.7, 61.2, 96.4, 88 13.7, 66.7, 62.9, 82.4, 5.9, 98.7, 21.5, 7.9, 51.7, 63.1}, 89 Dct32{48, 39.3, 6.9, 26.3, 33.3, 6.2, 1.7, 98.9, 59.9, 59.6, 95, 90 61.3, 82.7, 53, 6.1, 30.4, 34.7, 96.9, 93.4, 17, 38.8, 80.8, 91 63, 18.6, 43.6, 32.3, 61, 20.2, 24.3, 28.3, 69.1, 62.4}}, 92 /*sigma_dct=*/{32.7, 21.5, 44.4, 1.8, 45.8, 90.6, 29.3, 59.2, 93 23.7, 85.2, 84.8, 27.2, 42.1, 84.1, 50.6, 17.6, 94 93.7, 4.9, 2.6, 69.8, 94.9, 52, 24.3, 18.8, 95 12.1, 95.7, 28.5, 81.4, 89.9, 31.4, 74.8, 52}}; 96 Spline spline2{ 97 /*control_points=*/{{172, 309}, 98 {196, 277}, 99 {42, 238}, 100 {114, 350}, 101 {307, 290}, 102 {316, 269}, 103 {124, 66}, 104 {233, 267}}, 105 /*color_dct=*/ 106 {Dct32{15, 28.9, 22, 6.6, 41.8, 83, 8.6, 56.8, 68.9, 9.7, 5.4, 107 19.8, 70.8, 90, 52.5, 65.2, 7.8, 23.5, 26.4, 72.2, 64.7, 87.1, 108 1.3, 67.5, 46, 68.4, 65.4, 35.5, 29.1, 13, 41.6, 23.9}, 109 Dct32{47.7, 79.4, 62.7, 29.1, 96.8, 18.5, 17.6, 15.2, 80.5, 56, 96.2, 110 59.9, 26.7, 96.1, 92.3, 42.1, 35.8, 54, 23.2, 55, 76, 35.8, 111 58.4, 88.7, 2.4, 78.1, 95.6, 27.5, 6.6, 78.5, 24.1, 69.8}, 112 Dct32{43.8, 96.5, 0.9, 95.1, 49.1, 71.2, 25.1, 33.6, 75.2, 95, 82.1, 113 19.7, 10.5, 44.9, 50, 93.3, 83.5, 99.5, 64.6, 54, 3.5, 99.7, 114 45.3, 82.1, 22.4, 37.9, 60, 32.2, 12.6, 4.6, 65.5, 96.4}}, 115 /*sigma_dct=*/{72.5, 2.6, 41.7, 2.2, 39.7, 79.1, 69.6, 19.9, 116 92.3, 71.5, 41.9, 62.1, 30, 49.4, 70.3, 45.3, 117 62.5, 47.2, 46.7, 41.2, 90.8, 46.8, 91.2, 55, 118 8.1, 69.6, 25.4, 84.7, 61.7, 27.6, 3.7, 46.9}}; 119 Spline spline3{ 120 /*control_points=*/{{100, 186}, 121 {257, 97}, 122 {170, 49}, 123 {25, 169}, 124 {309, 104}, 125 {232, 237}, 126 {385, 101}, 127 {122, 168}, 128 {26, 300}, 129 {390, 88}}, 130 /*color_dct=*/ 131 {Dct32{16.9, 64.8, 4.2, 10.6, 23.5, 17, 79.3, 5.7, 60.4, 16.6, 94.9, 132 63.7, 87.6, 10.5, 3.8, 61.1, 22.9, 81.9, 80.4, 40.5, 45.9, 25.4, 133 39.8, 30, 50.2, 90.4, 27.9, 93.7, 65.1, 48.2, 22.3, 43.9}, 134 Dct32{24.9, 66, 3.5, 90.2, 97.1, 15.8, 35.6, 0.6, 68, 39.6, 24.4, 135 85.9, 57.7, 77.6, 47.5, 67.9, 4.3, 5.4, 91.2, 58.5, 0.1, 52.2, 136 3.5, 47.8, 63.2, 43.5, 85.8, 35.8, 50.2, 35.9, 19.2, 48.2}, 137 Dct32{82.8, 44.9, 76.4, 39.5, 94.1, 14.3, 89.8, 10, 10.5, 74.5, 56.3, 138 65.8, 7.8, 23.3, 52.8, 99.3, 56.8, 46, 76.7, 13.5, 67, 22.4, 139 29.9, 43.3, 70.3, 26, 74.3, 53.9, 62, 19.1, 49.3, 46.7}}, 140 /*sigma_dct=*/{83.5, 1.7, 25.1, 18.7, 46.5, 75.3, 28, 62.3, 141 50.3, 23.3, 85.6, 96, 45.8, 33.1, 33.4, 52.9, 142 26.3, 58.5, 19.6, 70, 92.6, 22.5, 57, 21.6, 143 76.8, 87.5, 22.9, 66.3, 35.7, 35.6, 56.8, 67.2}}; 144 std::vector<Spline> spline_data{spline1, spline2, spline3}; 145 146 std::vector<QuantizedSpline> quantized_splines; 147 std::vector<Spline::Point> starting_points; 148 for (const Spline& spline : spline_data) { 149 JXL_ASSIGN_OR_QUIT( 150 QuantizedSpline qspline, 151 QuantizedSpline::Create(spline, kQuantizationAdjustment, kYToX, kYToB), 152 "Failed to create QuantizedSpline."); 153 quantized_splines.emplace_back(std::move(qspline)); 154 starting_points.push_back(spline.control_points.front()); 155 } 156 157 Splines splines(kQuantizationAdjustment, std::move(quantized_splines), 158 std::move(starting_points)); 159 std::vector<Spline> quantized_spline_data; 160 ASSERT_TRUE(DequantizeSplines(splines, quantized_spline_data)); 161 EXPECT_EQ(quantized_spline_data.size(), spline_data.size()); 162 for (size_t i = 0; i < quantized_spline_data.size(); ++i) { 163 const Spline& actual = quantized_spline_data[i]; 164 const Spline& expected = spline_data[i]; 165 const auto& actual_points = actual.control_points; 166 const auto& expected_points = expected.control_points; 167 EXPECT_EQ(actual_points.size(), expected_points.size()); 168 for (size_t j = 0; j < actual_points.size(); ++j) { 169 EXPECT_NEAR(actual_points[j].x, expected_points[j].x, kTolerance) 170 << "spline " << i << " point " << j; 171 EXPECT_NEAR(actual_points[j].y, expected_points[j].y, kTolerance) 172 << "spline " << i << " point " << j; 173 } 174 } 175 176 BitWriter writer{memory_manager}; 177 ASSERT_TRUE(EncodeSplines(splines, &writer, LayerType::Splines, 178 HistogramParams(), nullptr)); 179 writer.ZeroPadToByte(); 180 const size_t bits_written = writer.BitsWritten(); 181 182 printf("Wrote %" PRIuS " bits of splines.\n", bits_written); 183 184 BitReader reader(writer.GetSpan()); 185 Splines decoded_splines; 186 ASSERT_TRUE( 187 decoded_splines.Decode(memory_manager, &reader, /*num_pixels=*/1000)); 188 ASSERT_TRUE(reader.JumpToByteBoundary()); 189 EXPECT_EQ(reader.TotalBitsConsumed(), bits_written); 190 ASSERT_TRUE(reader.Close()); 191 192 std::vector<Spline> decoded_spline_data; 193 ASSERT_TRUE(DequantizeSplines(decoded_splines, decoded_spline_data)); 194 195 EXPECT_EQ(decoded_spline_data.size(), quantized_spline_data.size()); 196 for (size_t i = 0; i < decoded_spline_data.size(); ++i) { 197 const Spline& actual = decoded_spline_data[i]; 198 const Spline& expected = quantized_spline_data[i]; 199 const auto& actual_points = actual.control_points; 200 const auto& expected_points = expected.control_points; 201 EXPECT_EQ(actual_points.size(), expected_points.size()); 202 for (size_t j = 0; j < actual_points.size(); ++j) { 203 EXPECT_NEAR(actual_points[j].x, expected_points[j].x, kTolerance) 204 << "spline " << i << " point " << j; 205 EXPECT_NEAR(actual_points[j].y, expected_points[j].y, kTolerance) 206 << "spline " << i << " point " << j; 207 } 208 209 const auto& actual_color_dct = actual.color_dct; 210 const auto& expected_color_dct = expected.color_dct; 211 for (size_t j = 0; j < actual_color_dct.size(); ++j) { 212 EXPECT_ARRAY_NEAR(actual_color_dct[j], expected_color_dct[j], kTolerance); 213 } 214 EXPECT_ARRAY_NEAR(actual.sigma_dct, expected.sigma_dct, kTolerance); 215 } 216 } 217 218 TEST(SplinesTest, TooManySplinesTest) { 219 if (JXL_CRASH_ON_ERROR) { 220 GTEST_SKIP() << "Skipping due to JXL_CRASH_ON_ERROR"; 221 } 222 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 223 // This is more than the limit for 1000 pixels. 224 const size_t kNumSplines = 300; 225 226 std::vector<QuantizedSpline> quantized_splines; 227 std::vector<Spline::Point> starting_points; 228 for (size_t i = 0; i < kNumSplines; i++) { 229 Spline spline{ 230 /*control_points=*/{{1.f + i, 2}, {10.f + i, 25}, {30.f + i, 300}}, 231 /*color_dct=*/ 232 {Dct32{1.f, 0.2f, 0.1f}, Dct32{35.7f, 10.3f}, Dct32{35.7f, 7.8f}}, 233 /*sigma_dct=*/{10.f, 0.f, 0.f, 2.f}}; 234 JXL_ASSIGN_OR_QUIT( 235 QuantizedSpline qspline, 236 QuantizedSpline::Create(spline, kQuantizationAdjustment, kYToX, kYToB), 237 "Failed to create QuantizedSpline."); 238 quantized_splines.emplace_back(std::move(qspline)); 239 starting_points.push_back(spline.control_points.front()); 240 } 241 242 Splines splines(kQuantizationAdjustment, std::move(quantized_splines), 243 std::move(starting_points)); 244 BitWriter writer{memory_manager}; 245 ASSERT_TRUE(EncodeSplines(splines, &writer, LayerType::Splines, 246 HistogramParams(SpeedTier::kFalcon, 1), nullptr)); 247 writer.ZeroPadToByte(); 248 // Re-read splines. 249 BitReader reader(writer.GetSpan()); 250 Splines decoded_splines; 251 EXPECT_FALSE( 252 decoded_splines.Decode(memory_manager, &reader, /*num_pixels=*/1000)); 253 EXPECT_TRUE(reader.Close()); 254 } 255 256 TEST(SplinesTest, DuplicatePoints) { 257 if (JXL_CRASH_ON_ERROR) { 258 GTEST_SKIP() << "Skipping due to JXL_CRASH_ON_ERROR"; 259 } 260 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 261 std::vector<Spline::Point> control_points{ 262 {9, 54}, {118, 159}, {97, 3}, // Repeated. 263 {97, 3}, {10, 40}, {150, 25}, {120, 300}}; 264 Spline spline{ 265 control_points, 266 /*color_dct=*/ 267 {Dct32{1.f, 0.2f, 0.1f}, Dct32{35.7f, 10.3f}, Dct32{35.7f, 7.8f}}, 268 /*sigma_dct=*/{10.f, 0.f, 0.f, 2.f}}; 269 std::vector<Spline> spline_data{spline}; 270 std::vector<QuantizedSpline> quantized_splines; 271 std::vector<Spline::Point> starting_points; 272 for (const Spline& spline : spline_data) { 273 JXL_ASSIGN_OR_QUIT( 274 QuantizedSpline qspline, 275 QuantizedSpline::Create(spline, kQuantizationAdjustment, kYToX, kYToB), 276 "Failed to create QuantizedSpline."); 277 quantized_splines.emplace_back(std::move(qspline)); 278 starting_points.push_back(spline.control_points.front()); 279 } 280 Splines splines(kQuantizationAdjustment, std::move(quantized_splines), 281 std::move(starting_points)); 282 283 JXL_TEST_ASSIGN_OR_DIE(Image3F image, 284 Image3F::Create(memory_manager, 320, 320)); 285 ZeroFillImage(&image); 286 EXPECT_FALSE(splines.InitializeDrawCache(image.xsize(), image.ysize(), 287 color_correlation)); 288 } 289 290 TEST(SplinesTest, Drawing) { 291 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 292 CodecInOut io_expected{memory_manager}; 293 const std::vector<uint8_t> orig = ReadTestData("jxl/splines.pfm"); 294 ASSERT_TRUE(SetFromBytes(Bytes(orig), &io_expected, 295 /*pool=*/nullptr)); 296 297 std::vector<Spline::Point> control_points{{9, 54}, {118, 159}, {97, 3}, 298 {10, 40}, {150, 25}, {120, 300}}; 299 // Use values that survive quant/decorellation roundtrip. 300 const Spline spline{ 301 control_points, 302 /*color_dct=*/ 303 {Dct32{0.4989345073699951171875000f, 0.4997999966144561767578125f}, 304 Dct32{0.4772970676422119140625000f, 0.f, 0.5250000357627868652343750f}, 305 Dct32{-0.0176776945590972900390625f, 0.4900000095367431640625000f, 306 0.5250000357627868652343750f}}, 307 /*sigma_dct=*/ 308 {0.9427147507667541503906250f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 309 0.6665999889373779296875000f}}; 310 std::vector<Spline> spline_data = {spline}; 311 std::vector<QuantizedSpline> quantized_splines; 312 std::vector<Spline::Point> starting_points; 313 for (const Spline& spline : spline_data) { 314 JXL_ASSIGN_OR_QUIT( 315 QuantizedSpline qspline, 316 QuantizedSpline::Create(spline, kQuantizationAdjustment, kYToX, kYToB), 317 "Failed to create QuantizedSpline."); 318 quantized_splines.emplace_back(std::move(qspline)); 319 starting_points.push_back(spline.control_points.front()); 320 } 321 Splines splines(kQuantizationAdjustment, std::move(quantized_splines), 322 std::move(starting_points)); 323 324 JXL_TEST_ASSIGN_OR_DIE(Image3F image, 325 Image3F::Create(memory_manager, 320, 320)); 326 ZeroFillImage(&image); 327 ASSERT_TRUE(splines.InitializeDrawCache(image.xsize(), image.ysize(), 328 color_correlation)); 329 splines.AddTo(&image, Rect(image)); 330 331 CodecInOut io_actual{memory_manager}; 332 JXL_TEST_ASSIGN_OR_DIE(Image3F image2, 333 Image3F::Create(memory_manager, 320, 320)); 334 ASSERT_TRUE(CopyImageTo(image, &image2)); 335 ASSERT_TRUE(io_actual.SetFromImage(std::move(image2), ColorEncoding::SRGB())); 336 ASSERT_TRUE(io_actual.frames[0].TransformTo(io_expected.Main().c_current(), 337 *JxlGetDefaultCms())); 338 339 JXL_TEST_ASSERT_OK(VerifyRelativeError( 340 *io_expected.Main().color(), *io_actual.Main().color(), 1e-2f, 1e-1f, _)); 341 } 342 343 TEST(SplinesTest, ClearedEveryFrame) { 344 JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 345 CodecInOut io_expected{memory_manager}; 346 const std::vector<uint8_t> bytes_expected = 347 ReadTestData("jxl/spline_on_first_frame.png"); 348 ASSERT_TRUE(SetFromBytes(Bytes(bytes_expected), &io_expected, 349 /*pool=*/nullptr)); 350 CodecInOut io_actual{memory_manager}; 351 const std::vector<uint8_t> bytes_actual = 352 ReadTestData("jxl/spline_on_first_frame.jxl"); 353 ASSERT_TRUE(test::DecodeFile({}, Bytes(bytes_actual), &io_actual)); 354 355 ASSERT_TRUE(io_actual.frames[0].TransformTo(ColorEncoding::SRGB(), 356 *JxlGetDefaultCms())); 357 for (size_t c = 0; c < 3; ++c) { 358 for (size_t y = 0; y < io_actual.ysize(); ++y) { 359 float* const JXL_RESTRICT row = io_actual.Main().color()->PlaneRow(c, y); 360 for (size_t x = 0; x < io_actual.xsize(); ++x) { 361 row[x] = Clamp1(row[x], 0.f, 1.f); 362 } 363 } 364 } 365 JXL_TEST_ASSERT_OK(VerifyRelativeError( 366 *io_expected.Main().color(), *io_actual.Main().color(), 1e-2f, 1e-1f, _)); 367 } 368 369 } // namespace jxl