diff --git a/mixed_rev_irrev_4comp.j2c b/mixed_rev_irrev_4comp.j2c new file mode 100644 index 00000000..dfacd42f Binary files /dev/null and b/mixed_rev_irrev_4comp.j2c differ diff --git a/src/core/codestream/ojph_params_local.h b/src/core/codestream/ojph_params_local.h index fd5e2256..91efb94f 100644 --- a/src/core/codestream/ojph_params_local.h +++ b/src/core/codestream/ojph_params_local.h @@ -627,10 +627,24 @@ namespace ojph { void init(param_cod* top_cod, ui16 comp_idx) { type = top_cod ? COC_MAIN : COD_MAIN; - Lcod = 0; - Scod = 0; + if (top_cod) + { + // a freshly materialized COC inherits the COD's current + // settings, matching the fallback used by the const get_coc() + // when no COC marker exists for a component + Lcod = top_cod->Lcod; + Scod = top_cod->Scod; + SGCod = top_cod->SGCod; + SPcod = top_cod->SPcod; + atk = top_cod->atk; + } + else + { + Lcod = 0; + Scod = 0; + atk = NULL; + } next = NULL; - atk = NULL; this->top_cod = top_cod; this->comp_idx = comp_idx; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 56e6ba35..1b6252b4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,8 +40,21 @@ target_link_libraries( GTest::gtest_main ) +# configure QCD/QCC marker tests (library API tests) +add_executable( + test_mixed_coc + test_mixed_coc.cpp +) + +target_link_libraries( + test_mixed_coc + openjph + GTest::gtest_main +) + include(GoogleTest) gtest_add_tests(TARGET test_executables) +gtest_add_tests(TARGET test_mixed_coc) if (MSVC) add_custom_command(TARGET test_executables POST_BUILD diff --git a/tests/test_mixed_coc.cpp b/tests/test_mixed_coc.cpp new file mode 100644 index 00000000..73bc5a95 --- /dev/null +++ b/tests/test_mixed_coc.cpp @@ -0,0 +1,181 @@ +//***************************************************************************/ +// This software is released under the 2-Clause BSD license, included +// below. +// +// Copyright (c) Pierre-Anthony Lemieux +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +//***************************************************************************/ +// This file is part of the OpenJPH software implementation. +// File: test_mixed_coc.cpp +// Author: Pierre-Anthony Lemieux +// Date: 29 June 2026 +//***************************************************************************/ + +#include +#include + +#include "ojph_mem.h" +#include "ojph_file.h" +#include "ojph_codestream.h" +#include "ojph_params.h" +#include "gtest/gtest.h" + +/////////////////////////////////////////////////////////////////////////////// +// Test encoding a 4-component image where 3 components use the irreversible +// (9/7) wavelet and 1 component uses the reversible (5/3) wavelet. +// This exercises COC and QCC marker generation for mixed coding modes. +// Verifies that encoding succeeds, headers can be read back, and the +// per-component coding settings are preserved. +TEST(TestMixedCOC, FourCompMixedReversibility) { + const ojph::ui32 width = 64; + const ojph::ui32 height = 64; + const ojph::ui32 num_comps = 4; + const ojph::ui32 bit_depth = 8; + const ojph::ui32 num_decomps = 5; + + // samples fed into component 3 during encoding, kept around so they can + // be compared against the decoded output further down + std::vector comp3_orig(width * height); + + // encode + { + ojph::codestream codestream; + + ojph::param_siz siz = codestream.access_siz(); + siz.set_image_extent(ojph::point(width, height)); + siz.set_num_components(num_comps); + for (ojph::ui32 c = 0; c < num_comps; ++c) + siz.set_component(c, ojph::point(1, 1), bit_depth, false); + siz.set_image_offset(ojph::point(0, 0)); + siz.set_tile_size(ojph::size(width, height)); + siz.set_tile_offset(ojph::point(0, 0)); + + ojph::param_cod cod = codestream.access_cod(); + cod.set_num_decomposition(num_decomps); + cod.set_block_dims(64, 64); + cod.set_color_transform(false); + cod.set_reversible(false); + + ojph::param_coc coc3 = cod.get_coc(3); + coc3.set_reversible(true); + + codestream.access_qcd().set_irrev_quant(0.01f); + codestream.set_planar(true); + + ojph::j2c_outfile j2c_file; + j2c_file.open("mixed_rev_irrev_4comp.j2c"); + codestream.write_headers(&j2c_file); + + ojph::ui32 next_comp; + ojph::line_buf* cur_line = codestream.exchange(NULL, next_comp); + + for (ojph::ui32 c = 0; c < num_comps; ++c) + { + for (ojph::ui32 y = 0; y < height; ++y) + { + ASSERT_EQ(next_comp, c); + if (c == 3) + { + // non-constant pattern so a roundtrip mismatch would be caught + for (ojph::ui32 x = 0; x < width; ++x) + { + ojph::si32 val = (ojph::si32)((x + y * width) % 256); + cur_line->i32[x] = val; + comp3_orig[y * width + x] = val; + } + } + else if (cur_line->flags & ojph::line_buf::LFT_INTEGER) + memset(cur_line->i32, 0, + sizeof(ojph::si32) * cur_line->size); + else + memset(cur_line->f32, 0, + sizeof(float) * cur_line->size); + cur_line = codestream.exchange(cur_line, next_comp); + } + } + + codestream.flush(); + codestream.close(); + } + + // read back headers and verify per-component settings + { + ojph::codestream codestream; + ojph::j2c_infile j2c_file; + j2c_file.open("mixed_rev_irrev_4comp.j2c"); + codestream.read_headers(&j2c_file); + + ojph::param_siz siz = codestream.access_siz(); + EXPECT_EQ(siz.get_num_components(), num_comps); + for (ojph::ui32 c = 0; c < num_comps; ++c) + EXPECT_EQ(siz.get_bit_depth(c), bit_depth); + + ojph::param_cod cod = codestream.access_cod(); + EXPECT_FALSE(cod.is_reversible()); + + for (ojph::ui32 c = 0; c < 3; ++c) { + ojph::param_coc coc = cod.get_coc(c); + EXPECT_FALSE(coc.is_reversible()) + << "Component " << c << " should be irreversible"; + } + + ojph::param_coc coc3 = cod.get_coc(3); + EXPECT_TRUE(coc3.is_reversible()) + << "Component 3 should be reversible"; + + // decode all components and verify component 3 (reversible) samples + // are identical to what was encoded + codestream.restrict_input_resolution(0, 0); + codestream.set_planar(true); + codestream.create(); + + for (ojph::ui32 c = 0; c < num_comps; ++c) + { + ojph::ui32 comp_height = siz.get_recon_height(c); + ojph::ui32 comp_width = siz.get_recon_width(c); + for (ojph::ui32 y = 0; y < comp_height; ++y) + { + ojph::ui32 comp_num; + ojph::line_buf* line = codestream.pull(comp_num); + ASSERT_EQ(comp_num, c); + if (c == 3) + { + ASSERT_EQ(comp_width, width); + for (ojph::ui32 x = 0; x < width; ++x) + EXPECT_EQ(line->i32[x], comp3_orig[y * width + x]) + << "Component 3 sample mismatch at (" << x << ", " << y << ")"; + } + } + } + + codestream.close(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}