From 801d7e2ced1f435631e6299d20eef6d99ec1e620 Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Sun, 5 Jan 2025 15:57:10 -0500 Subject: [PATCH] LibGfx/JPEG2000: Add LRCP and RLCP progression iterators --- .../Userland/Libraries/LibGfx/BUILD.gn | 1 + Tests/LibGfx/TestImageDecoder.cpp | 72 +++++++++++ Userland/Libraries/LibGfx/CMakeLists.txt | 1 + .../JPEG2000ProgressionIterators.cpp | 121 ++++++++++++++++++ .../JPEG2000ProgressionIterators.h | 59 +++++++++ 5 files changed, 254 insertions(+) create mode 100644 Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp create mode 100644 Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h diff --git a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn index 373955b7937..05292aa9f0b 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibGfx/BUILD.gn @@ -80,6 +80,7 @@ shared_library("LibGfx") { "ImageFormats/ImageDecoder.cpp", "ImageFormats/JBIG2Loader.cpp", "ImageFormats/JPEG2000Loader.cpp", + "ImageFormats/JPEG2000ProgressionIterators.cpp", "ImageFormats/JPEGLoader.cpp", "ImageFormats/JPEGWriter.cpp", "ImageFormats/JPEGXLEntropyDecoder.cpp", diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp index 51cad29d8da..202148720f4 100644 --- a/Tests/LibGfx/TestImageDecoder.cpp +++ b/Tests/LibGfx/TestImageDecoder.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -632,6 +633,77 @@ TEST_CASE(test_jpeg2000_gray) EXPECT_EQ(icc_bytes->size(), 912u); } +TEST_CASE(test_jpeg2000_progression_iterators) +{ + { + int const layer_count = 2; + int const max_number_of_decomposition_levels = 2; + int const component_count = 2; + auto precinct_count = [](int, int) { return 1; }; + Gfx::JPEG2000::LayerResolutionLevelComponentPositionProgressionIterator iterator { layer_count, max_number_of_decomposition_levels, component_count, move(precinct_count) }; + + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 0, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 0, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 1, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 1, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 2, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 2, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 0, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 0, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 1, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 1, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 2, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 2, .component = 1, .precinct = 0 })); + EXPECT(!iterator.has_next()); + } + + { + int const layer_count = 2; + int const max_number_of_decomposition_levels = 2; + int const component_count = 2; + auto precinct_count = [](int, int) { return 1; }; + Gfx::JPEG2000::ResolutionLevelLayerComponentPositionProgressionIterator iterator { layer_count, max_number_of_decomposition_levels, component_count, move(precinct_count) }; + + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 0, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 0, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 0, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 0, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 1, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 1, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 1, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 1, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 2, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 0, .resolution_level = 2, .component = 1, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 2, .component = 0, .precinct = 0 })); + EXPECT(iterator.has_next()); + EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = 1, .resolution_level = 2, .component = 1, .precinct = 0 })); + EXPECT(!iterator.has_next()); + } +} + TEST_CASE(test_jpeg2000_tag_tree) { { diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index a1363bf87bc..4dde644c68d 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -54,6 +54,7 @@ set(SOURCES ImageFormats/ISOBMFF/Reader.cpp ImageFormats/JBIG2Loader.cpp ImageFormats/JPEG2000Loader.cpp + ImageFormats/JPEG2000ProgressionIterators.cpp ImageFormats/JPEGLoader.cpp ImageFormats/JPEGXLEntropyDecoder.cpp ImageFormats/JPEGXLICC.cpp diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp new file mode 100644 index 00000000000..c2df522c6b7 --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025, Nico Weber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Gfx::JPEG2000 { + +LayerResolutionLevelComponentPositionProgressionIterator::LayerResolutionLevelComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function precinct_count) + : m_precinct_count(move(precinct_count)) +{ + m_end.layer = layer_count; + m_end.resolution_level = max_number_of_decomposition_levels + 1; + m_end.component = component_count; + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); +} + +bool LayerResolutionLevelComponentPositionProgressionIterator::has_next() const +{ + return m_next != ProgressionData { m_end.layer, 0, 0, 0 }; +} + +ProgressionData LayerResolutionLevelComponentPositionProgressionIterator::next() +{ + ProgressionData current_data = m_next; + + // B.12.1.1 Layer-resolution level-component-position progression + // "for each l = 0,..., L – 1 + // for each r = 0,..., Nmax + // for each i = 0,..., Csiz – 1 + // for each k = 0,..., numprecincts – 1 + // packet for component i, resolution level r, layer l, and precinct k. + // Here, L is the number of layers and Nmax is the maximum number of decomposition levels, N_L, used in any component of the tile." + // FIXME: This always iterates up to Nmax, instead of just N_l of each component. That means several of the iteration results will be invalid and skipped. + // (This is a performance issue, not a correctness issue.) + + ++m_next.precinct; + if (m_next.precinct < m_end.precinct) + return current_data; + + m_next.precinct = 0; + ++m_next.component; + if (m_next.component < m_end.component) { + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); + return current_data; + } + + m_next.component = 0; + ++m_next.resolution_level; + if (m_next.resolution_level < m_end.resolution_level) { + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); + return current_data; + } + + m_next.resolution_level = 0; + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); + + ++m_next.layer; + VERIFY(m_next.layer < m_end.layer || !has_next()); + + return current_data; +} + +ResolutionLevelLayerComponentPositionProgressionIterator::ResolutionLevelLayerComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function precinct_count) + : m_precinct_count(move(precinct_count)) +{ + m_end.layer = layer_count; + m_end.resolution_level = max_number_of_decomposition_levels + 1; + m_end.component = component_count; + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); +} + +bool ResolutionLevelLayerComponentPositionProgressionIterator::has_next() const +{ + return m_next != ProgressionData { 0, m_end.resolution_level, 0, 0 }; +} + +ProgressionData ResolutionLevelLayerComponentPositionProgressionIterator::next() +{ + ProgressionData current_data = m_next; + + // B.12.1.2 Resolution level-layer-component-position progression + // "for each r = 0,..., Nmax + // for each l = 0,..., L – 1 + // for each i = 0,..., Csiz – 1 + // for each k = 0,..., numprecincts – 1 + // packet for component i, resolution level r, layer l, and precinct k." + // FIXME: This always iterates up to Nmax, instead of just N_l of each component. That means several of the iteration results will be invalid and skipped. + // (This is a performance issue, not a correctness issue.) + + ++m_next.precinct; + if (m_next.precinct < m_end.precinct) + return current_data; + + m_next.precinct = 0; + ++m_next.component; + if (m_next.component < m_end.component) { + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); + return current_data; + } + + m_next.component = 0; + ++m_next.layer; + if (m_next.layer < m_end.layer) { + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); + return current_data; + } + + m_next.layer = 0; + + ++m_next.resolution_level; + if (has_next()) + m_end.precinct = m_precinct_count(m_next.resolution_level, m_next.component); + VERIFY(m_next.resolution_level < m_end.resolution_level || !has_next()); + + return current_data; +} + +} diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h new file mode 100644 index 00000000000..8e0b72021da --- /dev/null +++ b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2025, Nico Weber + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Gfx::JPEG2000 { + +// B.12 Progression order +struct ProgressionData { + int layer { 0 }; + int resolution_level { 0 }; + int component { 0 }; + int precinct { 0 }; + + bool operator==(ProgressionData const&) const = default; +}; + +class ProgressionIterator { +public: + virtual ~ProgressionIterator() = default; + + virtual bool has_next() const = 0; + virtual ProgressionData next() = 0; +}; + +// B.12.1.1 Layer-resolution level-component-position progression +class LayerResolutionLevelComponentPositionProgressionIterator : public ProgressionIterator { +public: + // FIXME: Supporting POC packets will probably require changes to this. + LayerResolutionLevelComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function precinct_count); + virtual bool has_next() const override; + virtual ProgressionData next() override; + +private: + Function m_precinct_count; + ProgressionData m_next {}; + ProgressionData m_end {}; +}; + +// B.12.1.2 Resolution level-layer-component-position progression +class ResolutionLevelLayerComponentPositionProgressionIterator : public ProgressionIterator { +public: + // FIXME: Supporting POC packets will probably require changes to this. + ResolutionLevelLayerComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function precinct_count); + virtual bool has_next() const override; + virtual ProgressionData next() override; + +private: + Function m_precinct_count; + ProgressionData m_next {}; + ProgressionData m_end {}; +}; + +}