Commit 72e2396a for libheif

commit 72e2396a62c5c4d76efff51bbc927dfef0e87e9f
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri Feb 27 11:19:58 2026 +0100

    add unit tests for cloc, snuc, splz

diff --git a/tests/uncompressed_box.cc b/tests/uncompressed_box.cc
index 32d4488f..6f6d3a75 100644
--- a/tests/uncompressed_box.cc
+++ b/tests/uncompressed_box.cc
@@ -4,6 +4,7 @@
   MIT License

   Copyright (c) 2023 Brad Hards <bradh@frogmouth.net>
+  Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
@@ -644,3 +645,277 @@ TEST_CASE("icef_bad_version") {
     REQUIRE(error.sub_error_code == heif_suberror_Unsupported_data_version);
     REQUIRE(error.message == std::string("icef box data version 1 is not implemented yet"));
 }
+
+
+TEST_CASE("cloc")
+{
+  // Construct and set field
+  auto cloc = std::make_shared<Box_cloc>();
+  cloc->set_chroma_location(2);
+  REQUIRE(cloc->get_chroma_location() == 2);
+
+  // Write
+  StreamWriter writer;
+  Error err = cloc->write(writer);
+  REQUIRE(err.error_code == heif_error_Ok);
+  const std::vector<uint8_t> bytes = writer.get_data();
+
+  // FullBox header (12 bytes) + 1 byte payload = 13 bytes
+  std::vector<uint8_t> expected = {
+    0x00, 0x00, 0x00, 0x0D, 'c', 'l', 'o', 'c',
+    0x00, 0x00, 0x00, 0x00,
+    0x02
+  };
+  REQUIRE(bytes == expected);
+
+  // Parse back
+  auto reader = std::make_shared<StreamReader_memory>(bytes.data(), bytes.size(), false);
+  BitstreamRange range(reader, bytes.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error == Error::Ok);
+  REQUIRE(range.error() == 0);
+
+  REQUIRE(box->get_short_type() == fourcc("cloc"));
+  auto parsed = std::dynamic_pointer_cast<Box_cloc>(box);
+  REQUIRE(parsed != nullptr);
+  REQUIRE(parsed->get_chroma_location() == 2);
+
+  // Dump
+  Indent indent;
+  std::string dump_output = parsed->dump(indent);
+  REQUIRE(dump_output == "Box: cloc -----\nsize: 13   (header size: 12)\nversion: 0\nflags: 0\nchroma_location: 2 (h=0,   v=0)\n");
+}
+
+
+TEST_CASE("cloc_bad_version")
+{
+  std::vector<uint8_t> byteArray = {
+    0x00, 0x00, 0x00, 0x0D, 'c', 'l', 'o', 'c',
+    0x01, 0x00, 0x00, 0x00,
+    0x02
+  };
+
+  auto reader = std::make_shared<StreamReader_memory>(byteArray.data(), byteArray.size(), false);
+  BitstreamRange range(reader, byteArray.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error.error_code == heif_error_Unsupported_feature);
+  REQUIRE(error.sub_error_code == heif_suberror_Unsupported_data_version);
+  REQUIRE(error.message == std::string("cloc box data version 1 is not implemented yet"));
+}
+
+
+TEST_CASE("cloc_out_of_range")
+{
+  std::vector<uint8_t> byteArray = {
+    0x00, 0x00, 0x00, 0x0D, 'c', 'l', 'o', 'c',
+    0x00, 0x00, 0x00, 0x00,
+    0x07
+  };
+
+  auto reader = std::make_shared<StreamReader_memory>(byteArray.data(), byteArray.size(), false);
+  BitstreamRange range(reader, byteArray.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error.error_code == heif_error_Invalid_input);
+  REQUIRE(error.sub_error_code == heif_suberror_Invalid_parameter_value);
+}
+
+
+TEST_CASE("splz")
+{
+  // Construct: 2 component indices, 2x1 pattern, angles 45.0 and 90.0
+  auto splz = std::make_shared<Box_splz>();
+  PolarizationPattern pattern;
+  pattern.component_indices = {0, 1};
+  pattern.pattern_width = 2;
+  pattern.pattern_height = 1;
+  pattern.polarization_angles = {45.0f, 90.0f};
+  splz->set_pattern(pattern);
+
+  // Write
+  StreamWriter writer;
+  Error err = splz->write(writer);
+  REQUIRE(err.error_code == heif_error_Ok);
+  const std::vector<uint8_t> bytes = writer.get_data();
+
+  // FullBox header (12) + 4 (count) + 8 (2×index) + 4 (w+h) + 8 (2×float) = 36
+  std::vector<uint8_t> expected = {
+    0x00, 0x00, 0x00, 0x24, 's', 'p', 'l', 'z',
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x02,  // component_count = 2
+    0x00, 0x00, 0x00, 0x00,  // index[0] = 0
+    0x00, 0x00, 0x00, 0x01,  // index[1] = 1
+    0x00, 0x02,              // pattern_width = 2
+    0x00, 0x01,              // pattern_height = 1
+    0x42, 0x34, 0x00, 0x00,  // 45.0f
+    0x42, 0xB4, 0x00, 0x00   // 90.0f
+  };
+  REQUIRE(bytes == expected);
+
+  // Parse back
+  auto reader = std::make_shared<StreamReader_memory>(bytes.data(), bytes.size(), false);
+  BitstreamRange range(reader, bytes.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error == Error::Ok);
+  REQUIRE(range.error() == 0);
+
+  REQUIRE(box->get_short_type() == fourcc("splz"));
+  auto parsed = std::dynamic_pointer_cast<Box_splz>(box);
+  REQUIRE(parsed != nullptr);
+
+  const auto& p = parsed->get_pattern();
+  REQUIRE(p.component_indices.size() == 2);
+  REQUIRE(p.component_indices[0] == 0);
+  REQUIRE(p.component_indices[1] == 1);
+  REQUIRE(p.pattern_width == 2);
+  REQUIRE(p.pattern_height == 1);
+  REQUIRE(p.polarization_angles.size() == 2);
+  REQUIRE(p.polarization_angles[0] == 45.0f);
+  REQUIRE(p.polarization_angles[1] == 90.0f);
+
+  // Dump
+  Indent indent;
+  std::string dump_output = parsed->dump(indent);
+  REQUIRE(dump_output == "Box: splz -----\n"
+                         "size: 36   (header size: 12)\n"
+                         "version: 0\n"
+                         "flags: 0\n"
+                         "component_count: 2\n"
+                         "  component_index[0]: 0\n"
+                         "  component_index[1]: 1\n"
+                         "pattern_width: 2\n"
+                         "pattern_height: 1\n"
+                         "  [0,0]: 45 degrees\n"
+                         "  [1,0]: 90 degrees\n");
+}
+
+
+TEST_CASE("splz_bad_version")
+{
+  std::vector<uint8_t> byteArray = {
+    0x00, 0x00, 0x00, 0x24, 's', 'p', 'l', 'z',
+    0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x02,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x02,
+    0x00, 0x01,
+    0x42, 0x34, 0x00, 0x00,
+    0x42, 0xB4, 0x00, 0x00
+  };
+
+  auto reader = std::make_shared<StreamReader_memory>(byteArray.data(), byteArray.size(), false);
+  BitstreamRange range(reader, byteArray.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error.error_code == heif_error_Unsupported_feature);
+  REQUIRE(error.sub_error_code == heif_suberror_Unsupported_data_version);
+  REQUIRE(error.message == std::string("splz box data version 1 is not implemented yet"));
+}
+
+
+TEST_CASE("snuc")
+{
+  // Construct: 1 component index, nuc_is_applied=true, 2x1 image, 2 gains + 2 offsets
+  auto snuc = std::make_shared<Box_snuc>();
+  SensorNonUniformityCorrection nuc;
+  nuc.component_indices = {0};
+  nuc.nuc_is_applied = true;
+  nuc.image_width = 2;
+  nuc.image_height = 1;
+  nuc.nuc_gains = {1.0f, 2.0f};
+  nuc.nuc_offsets = {0.0f, 3.0f};
+  snuc->set_nuc(nuc);
+
+  // Write
+  StreamWriter writer;
+  Error err = snuc->write(writer);
+  REQUIRE(err.error_code == heif_error_Ok);
+  const std::vector<uint8_t> bytes = writer.get_data();
+
+  // FullBox header (12) + 4 (count) + 4 (index) + 1 (flags) + 4 (width) + 4 (height)
+  // + 8 (2×gain) + 8 (2×offset) = 45
+  std::vector<uint8_t> expected = {
+    0x00, 0x00, 0x00, 0x2D, 's', 'n', 'u', 'c',
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01,  // component_count = 1
+    0x00, 0x00, 0x00, 0x00,  // index[0] = 0
+    0x80,                    // flags: nuc_is_applied = true
+    0x00, 0x00, 0x00, 0x02,  // image_width = 2
+    0x00, 0x00, 0x00, 0x01,  // image_height = 1
+    0x3F, 0x80, 0x00, 0x00,  // gain[0] = 1.0f
+    0x40, 0x00, 0x00, 0x00,  // gain[1] = 2.0f
+    0x00, 0x00, 0x00, 0x00,  // offset[0] = 0.0f
+    0x40, 0x40, 0x00, 0x00   // offset[1] = 3.0f
+  };
+  REQUIRE(bytes == expected);
+
+  // Parse back
+  auto reader = std::make_shared<StreamReader_memory>(bytes.data(), bytes.size(), false);
+  BitstreamRange range(reader, bytes.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error == Error::Ok);
+  REQUIRE(range.error() == 0);
+
+  REQUIRE(box->get_short_type() == fourcc("snuc"));
+  auto parsed = std::dynamic_pointer_cast<Box_snuc>(box);
+  REQUIRE(parsed != nullptr);
+
+  const auto& n = parsed->get_nuc();
+  REQUIRE(n.component_indices.size() == 1);
+  REQUIRE(n.component_indices[0] == 0);
+  REQUIRE(n.nuc_is_applied == true);
+  REQUIRE(n.image_width == 2);
+  REQUIRE(n.image_height == 1);
+  REQUIRE(n.nuc_gains.size() == 2);
+  REQUIRE(n.nuc_gains[0] == 1.0f);
+  REQUIRE(n.nuc_gains[1] == 2.0f);
+  REQUIRE(n.nuc_offsets.size() == 2);
+  REQUIRE(n.nuc_offsets[0] == 0.0f);
+  REQUIRE(n.nuc_offsets[1] == 3.0f);
+
+  // Dump
+  Indent indent;
+  std::string dump_output = parsed->dump(indent);
+  REQUIRE(dump_output == "Box: snuc -----\n"
+                         "size: 45   (header size: 12)\n"
+                         "version: 0\n"
+                         "flags: 0\n"
+                         "component_count: 1\n"
+                         "  component_index[0]: 0\n"
+                         "nuc_is_applied: 1\n"
+                         "image_width: 2\n"
+                         "image_height: 1\n"
+                         "nuc_gains: 2 values\n"
+                         "nuc_offsets: 2 values\n");
+}
+
+
+TEST_CASE("snuc_bad_version")
+{
+  std::vector<uint8_t> byteArray = {
+    0x00, 0x00, 0x00, 0x2D, 's', 'n', 'u', 'c',
+    0x01, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01,
+    0x00, 0x00, 0x00, 0x00,
+    0x80,
+    0x00, 0x00, 0x00, 0x02,
+    0x00, 0x00, 0x00, 0x01,
+    0x3F, 0x80, 0x00, 0x00,
+    0x40, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x40, 0x40, 0x00, 0x00
+  };
+
+  auto reader = std::make_shared<StreamReader_memory>(byteArray.data(), byteArray.size(), false);
+  BitstreamRange range(reader, byteArray.size());
+  std::shared_ptr<Box> box;
+  Error error = Box::read(range, &box, heif_get_global_security_limits());
+  REQUIRE(error.error_code == heif_error_Unsupported_feature);
+  REQUIRE(error.sub_error_code == heif_suberror_Unsupported_data_version);
+  REQUIRE(error.message == std::string("snuc box data version 1 is not implemented yet"));
+}