Repository: SerenityOS/serenity
Pull request #25813: LibGfx/JPEG2000: Add support for remaining progression orders
Author: login: nico
Created: 2025-03-18T23:15:55Z | Merged: 2025-03-20T01:11:02Z

This adds support for progression orders 2-4 (and moves the progression order 1 implementation to SyncGenerator, like #25797 already did for progression order 0). Thanks to SyncGenerator, this looks very close to the spec now :^)

Progression orders 2-4 are very rare in practice. In my 2000-file test PDF set, there's a single PDF that uses progression order 2 (and 0 for 3 and 4).


Diff:
diff --git a/Tests/LibGfx/TestImageDecoder.cpp b/Tests/LibGfx/TestImageDecoder.cpp
index 1dc218063b7887..f199b110c87899 100644
--- a/Tests/LibGfx/TestImageDecoder.cpp
+++ b/Tests/LibGfx/TestImageDecoder.cpp
@@ -706,6 +706,9 @@ TEST_CASE(test_jpeg2000_decode)
         TEST_INPUT("jpeg2000/kakadu-lossless-rgba-u8-prog1-layers1-res6-mct.jp2"sv),
         TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog0-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
         TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog1-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
+        TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog2-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
+        TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog3-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
+        TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog4-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
         TEST_INPUT("jpeg2000/jasper-rgba-u8-cbstyle-01-bypass.jp2"sv),
         TEST_INPUT("jpeg2000/jasper-rgba-u8-cbstyle-01-bypass-layers.jp2"sv),
         TEST_INPUT("jpeg2000/jasper-rgba-u8-cbstyle-01-bypass-finer-layers.jp2"sv),
@@ -945,9 +948,6 @@ TEST_CASE(test_jpeg2000_decode_unsupported)
         TEST_INPUT("jpeg2000/openjpeg-lossless-RGN.jp2"sv),
         TEST_INPUT("jpeg2000/openjpeg-lossless-bgra-u8.jp2"sv),
         TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog0-tile-part-index-overflow.jp2"sv),
-        TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog2-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
-        TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog3-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
-        TEST_INPUT("jpeg2000/openjpeg-lossless-rgba-u8-prog4-tile4x2-cblk4x16-tp3-layers3-res2.jp2"sv),
 
         // FIXME: See FIXME in JPEG2000ColorSpecificationBox::read_from_stream() for lab.
         // TEST_INPUT("jpeg2000/kakadu-lossless-lab-u8-prog1-layers1-res6.jp2"sv),
@@ -1048,6 +1048,105 @@ TEST_CASE(test_jpeg2000_progression_iterators)
                     }
         EXPECT(!iterator.has_next());
     }
+
+    {
+        int const layer_count = 2;
+        int const max_number_of_decomposition_levels = 2;
+        int const component_count = 4;
+        int const precinct_count_number = 5;
+        auto precinct_count = [](int, int) { return precinct_count_number; };
+
+        Gfx::IntRect tile_rect { 0, 0, 5 * 32, 32 };
+        auto XRsiz = [&](size_t) { return 1; };
+        auto YRsiz = [&](size_t) { return 1; };
+
+        auto PPx = [&](int r, int) { return 5 - (max_number_of_decomposition_levels - r); };
+        auto PPy = [&](int r, int) { return 5 - (max_number_of_decomposition_levels - r); };
+        auto N_L = [&](int) { return max_number_of_decomposition_levels; };
+        auto num_precincts_wide = [&](int, int) { return precinct_count_number; };
+        auto ll_rect = [&](int r, int) {
+            return tile_rect / (1 << (max_number_of_decomposition_levels - r));
+        };
+        Gfx::JPEG2000::ResolutionLevelPositionComponentLayerProgressionIterator iterator {
+            layer_count, max_number_of_decomposition_levels, component_count, move(precinct_count),
+            move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile_rect, move(ll_rect)
+        };
+
+        for (int resolution_level = 0; resolution_level <= max_number_of_decomposition_levels; ++resolution_level)
+            for (int precinct = 0; precinct < precinct_count_number; ++precinct)
+                for (int component = 0; component < component_count; ++component)
+                    for (int layer = 0; layer < layer_count; ++layer) {
+                        EXPECT(iterator.has_next());
+                        EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = layer, .resolution_level = resolution_level, .component = component, .precinct = precinct }));
+                    }
+        EXPECT(!iterator.has_next());
+    }
+
+    {
+        int const layer_count = 2;
+        int const max_number_of_decomposition_levels = 2;
+        int const component_count = 4;
+        int const precinct_count_number = 5;
+        auto precinct_count = [](int, int) { return precinct_count_number; };
+
+        Gfx::IntRect tile_rect { 0, 0, 5 * 32, 32 };
+        auto XRsiz = [&](size_t) { return 1; };
+        auto YRsiz = [&](size_t) { return 1; };
+
+        auto PPx = [&](int r, int) { return 5 - (max_number_of_decomposition_levels - r); };
+        auto PPy = [&](int r, int) { return 5 - (max_number_of_decomposition_levels - r); };
+        auto N_L = [&](int) { return max_number_of_decomposition_levels; };
+        auto num_precincts_wide = [&](int, int) { return precinct_count_number; };
+        auto ll_rect = [&](int r, int) {
+            return tile_rect / (1 << (max_number_of_decomposition_levels - r));
+        };
+        Gfx::JPEG2000::PositionComponentResolutionLevelLayerProgressionIterator iterator {
+            layer_count, component_count, move(precinct_count),
+            move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile_rect, move(ll_rect)
+        };
+
+        for (int precinct = 0; precinct < precinct_count_number; ++precinct)
+            for (int component = 0; component < component_count; ++component)
+                for (int resolution_level = 0; resolution_level <= max_number_of_decomposition_levels; ++resolution_level)
+                    for (int layer = 0; layer < layer_count; ++layer) {
+                        EXPECT(iterator.has_next());
+                        EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = layer, .resolution_level = resolution_level, .component = component, .precinct = precinct }));
+                    }
+        EXPECT(!iterator.has_next());
+    }
+
+    {
+        int const layer_count = 2;
+        int const max_number_of_decomposition_levels = 2;
+        int const component_count = 4;
+        int const precinct_count_number = 5;
+        auto precinct_count = [](int, int) { return precinct_count_number; };
+
+        Gfx::IntRect tile_rect { 0, 0, 5 * 32, 32 };
+        auto XRsiz = [&](size_t) { return 1; };
+        auto YRsiz = [&](size_t) { return 1; };
+
+        auto PPx = [&](int r, int) { return 5 - (max_number_of_decomposition_levels - r); };
+        auto PPy = [&](int r, int) { return 5 - (max_number_of_decomposition_levels - r); };
+        auto N_L = [&](int) { return max_number_of_decomposition_levels; };
+        auto num_precincts_wide = [&](int, int) { return precinct_count_number; };
+        auto ll_rect = [&](int r, int) {
+            return tile_rect / (1 << (max_number_of_decomposition_levels - r));
+        };
+        Gfx::JPEG2000::ComponentPositionResolutionLevelLayerProgressionIterator iterator {
+            layer_count, component_count, move(precinct_count),
+            move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile_rect, move(ll_rect)
+        };
+
+        for (int component = 0; component < component_count; ++component)
+            for (int precinct = 0; precinct < precinct_count_number; ++precinct)
+                for (int resolution_level = 0; resolution_level <= max_number_of_decomposition_levels; ++resolution_level)
+                    for (int layer = 0; layer < layer_count; ++layer) {
+                        EXPECT(iterator.has_next());
+                        EXPECT_EQ(iterator.next(), (Gfx::JPEG2000::ProgressionData { .layer = layer, .resolution_level = resolution_level, .component = component, .precinct = precinct }));
+                    }
+        EXPECT(!iterator.has_next());
+    }
 }
 
 TEST_CASE(test_jpeg2000_tag_tree)
diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp
index d8a5db6e9b86a0..b275d1d99279ac 100644
--- a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp
+++ b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000Loader.cpp
@@ -1625,11 +1625,48 @@ static ErrorOr<void> compute_decoding_metadata(JPEG2000LoadingContext& context)
         case CodingStyleDefault::ResolutionLayerComponentPosition:
             return make<JPEG2000::ResolutionLevelLayerComponentPositionProgressionIterator>(number_of_layers, max_number_of_decomposition_levels, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component));
         case CodingStyleDefault::ResolutionPositionComponentLayer:
-            return Error::from_string_literal("JPEG2000Loader: ResolutionPositionComponentLayer progression order not yet supported");
         case CodingStyleDefault::PositionComponentResolutionLayer:
-            return Error::from_string_literal("JPEG2000Loader: PositionComponentResolutionLayer progression order not yet supported");
-        case CodingStyleDefault::ComponentPositionResolutionLayer:
-            return Error::from_string_literal("JPEG2000Loader: ComponentPositionResolutionLayer progression order not yet supported");
+        case CodingStyleDefault::ComponentPositionResolutionLayer: {
+            auto XRsiz = [&](size_t i) { return context.siz.components[i].horizontal_separation; };
+            auto YRsiz = [&](size_t i) { return context.siz.components[i].vertical_separation; };
+
+            // "To use this progression, XRsiz and YRsiz values must be powers of two for each component."
+            for (size_t component_index = 0; component_index < context.siz.components.size(); ++component_index) {
+                if (!is_power_of_two(XRsiz(component_index)) || !is_power_of_two(YRsiz(component_index)))
+                    return Error::from_string_literal("JPEG2000Loader: ResolutionPositionComponentLayer progression order requires XRsiz and YRsiz to be powers of two");
+            }
+
+            auto PPx = [&](int resolution_level, int component) {
+                return context.coding_style_parameters_for_component(tile, component).precinct_sizes[resolution_level].PPx;
+            };
+            auto PPy = [&](int resolution_level, int component) {
+                return context.coding_style_parameters_for_component(tile, component).precinct_sizes[resolution_level].PPy;
+            };
+            auto N_L = [&](int component) {
+                return context.coding_style_parameters_for_component(tile, component).number_of_decomposition_levels;
+            };
+            auto num_precincts_wide = [&](int resolution_level, int component) {
+                auto const& sub_band = resolution_level == 0 ? tile.components[component].nLL : tile.components[component].decompositions[resolution_level - 1][0];
+                return sub_band.num_precincts_wide;
+            };
+            auto ll_rect = [&](int resolution_level, int component) {
+                // The outer N_L lambda has been move()d away by the time this runs and can't be called here.
+                auto N_L = context.coding_style_parameters_for_component(tile, component).number_of_decomposition_levels;
+                return context.siz.reference_grid_coordinates_for_ll_band(tile.rect, component, resolution_level, N_L);
+            };
+
+            if (tile.cod.value_or(context.cod).progression_order == CodingStyleDefault::ResolutionPositionComponentLayer)
+                return make<JPEG2000::ResolutionLevelPositionComponentLayerProgressionIterator>(
+                    number_of_layers, max_number_of_decomposition_levels, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component),
+                    move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile.rect, move(ll_rect));
+            if (tile.cod.value_or(context.cod).progression_order == CodingStyleDefault::PositionComponentResolutionLayer)
+                return make<JPEG2000::PositionComponentResolutionLevelLayerProgressionIterator>(
+                    number_of_layers, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component),
+                    move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile.rect, move(ll_rect));
+            return make<JPEG2000::ComponentPositionResolutionLevelLayerProgressionIterator>(
+                number_of_layers, context.siz.components.size(), move(number_of_precincts_from_resolution_level_and_component),
+                move(XRsiz), move(YRsiz), move(PPx), move(PPy), move(N_L), move(num_precincts_wide), tile.rect, move(ll_rect));
+        }
         }
         VERIFY_NOT_REACHED();
     };
diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp
index 5921bc4313e746..23a844cecceb31 100644
--- a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp
+++ b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp
@@ -51,23 +51,29 @@ SyncGenerator<ProgressionData> LayerResolutionLevelComponentPositionProgressionI
 }
 
 ResolutionLevelLayerComponentPositionProgressionIterator::ResolutionLevelLayerComponentPositionProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function<int(int resolution_level, int component)> precinct_count)
-    : m_precinct_count(move(precinct_count))
+    : m_layer_count(layer_count)
+    , m_max_number_of_decomposition_levels(max_number_of_decomposition_levels)
+    , m_component_count(component_count)
+    , m_precinct_count(move(precinct_count))
+    , m_generator(generator())
 {
-    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);
+    m_next = m_generator.next();
 }
 
 bool ResolutionLevelLayerComponentPositionProgressionIterator::has_next() const
 {
-    return m_next != ProgressionData { 0, m_end.resolution_level, 0, 0 };
+    return m_next.has_value();
 }
 
 ProgressionData ResolutionLevelLayerComponentPositionProgressionIterator::next()
 {
-    ProgressionData current_data = m_next;
+    auto result = m_next;
+    m_next = m_generator.next();
+    return result.value();
+}
 
+SyncGenerator<ProgressionData> ResolutionLevelLayerComponentPositionProgressionIterator::generator()
+{
     // B.12.1.2 Resolution level-layer-component-position progression
     // "for each r = 0,..., Nmax
     //      for each l = 0,..., L – 1
@@ -77,32 +83,267 @@ ProgressionData ResolutionLevelLayerComponentPositionProgressionIterator::next()
     // 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;
+    for (int r = 0; r <= m_max_number_of_decomposition_levels; ++r)
+        for (int l = 0; l < m_layer_count; ++l)
+            for (int i = 0; i < m_component_count; ++i)
+                for (int k = 0; k < m_precinct_count(r, i); ++k)
+                    co_yield ProgressionData { l, r, i, k };
+}
 
-    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;
+ResolutionLevelPositionComponentLayerProgressionIterator::ResolutionLevelPositionComponentLayerProgressionIterator(
+    int layer_count, int max_number_of_decomposition_levels, int component_count, Function<int(int resolution_level, int component)> precinct_count,
+    Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
+    Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
+    Function<int(int component)> N_L,
+    Function<int(int resolution_level, int component)> num_precincts_wide,
+    Gfx::IntRect tile_rect,
+    Function<IntRect(int resolution_level, int component)> ll_rect)
+    : m_layer_count(layer_count)
+    , m_max_number_of_decomposition_levels(max_number_of_decomposition_levels)
+    , m_component_count(component_count)
+    , m_precinct_count(move(precinct_count))
+    , m_XRsiz(move(XRsiz))
+    , m_YRsiz(move(YRsiz))
+    , m_PPx(move(PPx))
+    , m_PPy(move(PPy))
+    , m_N_L(move(N_L))
+    , m_num_precincts_wide(move(num_precincts_wide))
+    , m_tile_rect(tile_rect)
+    , m_ll_rect(move(ll_rect))
+    , m_generator(generator())
+{
+    m_next = m_generator.next();
+}
+
+bool ResolutionLevelPositionComponentLayerProgressionIterator::has_next() const
+{
+    return m_next.has_value();
+}
+
+ProgressionData ResolutionLevelPositionComponentLayerProgressionIterator::next()
+{
+    auto result = m_next;
+    m_next = m_generator.next();
+    return result.value();
+}
+
+SyncGenerator<ProgressionData> ResolutionLevelPositionComponentLayerProgressionIterator::generator()
+{
+    auto compute_precinct = [&](int x, int y, int r, int i) {
+        // (B-20)
+        auto const trx0 = m_ll_rect(r, i).left();
+        auto const try0 = m_ll_rect(r, i).top();
+        auto const x_offset = floor_div(ceil_div(x, m_XRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPx(r, i)) - floor_div(trx0, 1 << m_PPx(r, i));
+        auto const y_offset = floor_div(ceil_div(y, m_YRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPy(r, i)) - floor_div(try0, 1 << m_PPy(r, i));
+        return x_offset + m_num_precincts_wide(r, i) * y_offset;
+    };
+    // B.12.1.3 Resolution level-position-component-layer progression
+    // "for each r = 0,..., Nmax
+    //      for each y = ty0,..., ty1 – 1,
+    //          for each x = tx0,..., tx1 – 1,
+    //              for each i = 0,..., Csiz – 1
+    //                  if ((y divisible by YRsiz(i) * 2 ** (PPy(r, i) + N_L(i) - r) OR
+    //                      ((y == ty0) AND (try0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPy(r, i) + N_L(i) - r))))
+    //                  if ((x divisible by XRsiz(i) * 2 ** (PPx(r, i) + N_L(i) - r) OR
+    //                      ((x == tx0) AND (trx0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPx(r, i) + N_L(i) - r))))
+    //          for the next precinct, k, if one exists,
+    //              for each l = 0,..., L – 1
+    //                  packet for component i, resolution level r, layer l, and precinct k."
+    // The motivation for this loop is to walk corresponding precincts in different components at the same time,
+    // even if the components have different precinct counts.
+    for (int r = 0; r <= m_max_number_of_decomposition_levels; ++r) {
+        auto const tx0 = m_tile_rect.left();
+        auto const ty0 = m_tile_rect.top();
+        for (int y = ty0; y < m_tile_rect.bottom(); ++y) {
+            for (int x = tx0; x < m_tile_rect.right(); ++x) {
+                for (int i = 0; i < m_component_count; ++i) {
+                    auto const trx0 = m_ll_rect(r, i).left();
+                    auto const try0 = m_ll_rect(r, i).top();
+                    if ((y % (m_YRsiz(i) * (1 << (m_PPy(r, i) + m_N_L(i) - r))) == 0)
+                        || ((y == ty0) && (try0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPy(r, i) + m_N_L(i) - r)) != 0))) {
+                        if ((x % (m_XRsiz(i) * (1 << (m_PPx(r, i) + m_N_L(i) - r))) == 0)
+                            || ((x == tx0) && (trx0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPx(r, i) + m_N_L(i) - r)) != 0))) {
+                            if (int k = compute_precinct(x, y, r, i); k < m_precinct_count(r, i)) {
+                                for (int l = 0; l < m_layer_count; ++l) {
+                                    co_yield ProgressionData { l, r, i, k };
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
+}
 
-    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;
+PositionComponentResolutionLevelLayerProgressionIterator::PositionComponentResolutionLevelLayerProgressionIterator(
+    int layer_count, int component_count, Function<int(int resolution_level, int component)> precinct_count,
+    Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
+    Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
+    Function<int(int component)> N_L,
+    Function<int(int resolution_level, int component)> num_precincts_wide,
+    Gfx::IntRect tile_rect,
+    Function<IntRect(int resolution_level, int component)> ll_rect)
+    : m_layer_count(layer_count)
+    , m_component_count(component_count)
+    , m_precinct_count(move(precinct_count))
+    , m_XRsiz(move(XRsiz))
+    , m_YRsiz(move(YRsiz))
+    , m_PPx(move(PPx))
+    , m_PPy(move(PPy))
+    , m_N_L(move(N_L))
+    , m_num_precincts_wide(move(num_precincts_wide))
+    , m_tile_rect(tile_rect)
+    , m_ll_rect(move(ll_rect))
+    , m_generator(generator())
+{
+    m_next = m_generator.next();
+}
+
+bool PositionComponentResolutionLevelLayerProgressionIterator::has_next() const
+{
+    return m_next.has_value();
+}
+
+ProgressionData PositionComponentResolutionLevelLayerProgressionIterator::next()
+{
+    auto result = m_next;
+    m_next = m_generator.next();
+    return result.value();
+}
+
+SyncGenerator<ProgressionData> PositionComponentResolutionLevelLayerProgressionIterator::generator()
+{
+    auto compute_precinct = [&](int x, int y, int r, int i) {
+        // (B-20)
+        auto const trx0 = m_ll_rect(r, i).left();
+        auto const try0 = m_ll_rect(r, i).top();
+        auto const x_offset = floor_div(ceil_div(x, m_XRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPx(r, i)) - floor_div(trx0, 1 << m_PPx(r, i));
+        auto const y_offset = floor_div(ceil_div(y, m_YRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPy(r, i)) - floor_div(try0, 1 << m_PPy(r, i));
+        return x_offset + m_num_precincts_wide(r, i) * y_offset;
+    };
+    // B.12.1.4 Position-component-resolution level-layer progression
+    // "for each y = ty0,..., ty1 – 1,
+    //      for each x = tx0,..., tx1 – 1,
+    //          for each i = 0,..., Csiz – 1
+    //              for each r = 0,..., NL where NL is the number of decomposition levels for component i,
+    //                  if ((y divisible by YRsiz(i) * 2 ** (PPy(r, i) + N_L(i) - r) OR
+    //                      ((y == ty0) AND (try0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPy(r, i) + N_L(i) - r))))
+    //                      if ((x divisible by XRsiz(i) * 2 ** (PPx(r, i) + N_L(i) - r) OR
+    //                          ((x == tx0) AND (trx0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPx(r, i) + N_L(i) - r))))
+    //                              for the next precinct, k, if one exists, in the sequence shown in Figure B.8
+    //                                  for each l = 0,..., L – 1
+    //                                      packet for component i, resolution level r, layer l, and precinct k."
+    // The motivation for this loop is to walk corresponding precincts in different components and resolution levels at the same time,
+    // even if the components or resolution levels have different precinct counts.
+    auto const tx0 = m_tile_rect.left();
+    auto const ty0 = m_tile_rect.top();
+    for (int y = ty0; y < m_tile_rect.bottom(); ++y) {
+        for (int x = tx0; x < m_tile_rect.right(); ++x) {
+            for (int i = 0; i < m_component_count; ++i) {
+                for (int r = 0; r <= m_N_L(i); ++r) {
+                    auto const trx0 = m_ll_rect(r, i).left();
+                    auto const try0 = m_ll_rect(r, i).top();
+                    if ((y % (m_YRsiz(i) * (1 << (m_PPy(r, i) + m_N_L(i) - r))) == 0)
+                        || ((y == ty0) && (try0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPy(r, i) + m_N_L(i) - r)) != 0))) {
+                        if ((x % (m_XRsiz(i) * (1 << (m_PPx(r, i) + m_N_L(i) - r))) == 0)
+                            || ((x == tx0) && (trx0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPx(r, i) + m_N_L(i) - r)) != 0))) {
+                            if (int k = compute_precinct(x, y, r, i); k < m_precinct_count(r, i)) {
+                                for (int l = 0; l < m_layer_count; ++l) {
+                                    co_yield ProgressionData { l, r, i, k };
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
+}
 
-    m_next.layer = 0;
+ComponentPositionResolutionLevelLayerProgressionIterator::ComponentPositionResolutionLevelLayerProgressionIterator(
+    int layer_count, int component_count, Function<int(int resolution_level, int component)> precinct_count,
+    Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
+    Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
+    Function<int(int component)> N_L,
+    Function<int(int resolution_level, int component)> num_precincts_wide,
+    Gfx::IntRect tile_rect,
+    Function<IntRect(int resolution_level, int component)> ll_rect)
+    : m_layer_count(layer_count)
+    , m_component_count(component_count)
+    , m_precinct_count(move(precinct_count))
+    , m_XRsiz(move(XRsiz))
+    , m_YRsiz(move(YRsiz))
+    , m_PPx(move(PPx))
+    , m_PPy(move(PPy))
+    , m_N_L(move(N_L))
+    , m_num_precincts_wide(move(num_precincts_wide))
+    , m_tile_rect(tile_rect)
+    , m_ll_rect(move(ll_rect))
+    , m_generator(generator())
+{
+    m_next = m_generator.next();
+}
 
-    ++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());
+bool ComponentPositionResolutionLevelLayerProgressionIterator::has_next() const
+{
+    return m_next.has_value();
+}
 
-    return current_data;
+ProgressionData ComponentPositionResolutionLevelLayerProgressionIterator::next()
+{
+    auto result = m_next;
+    m_next = m_generator.next();
+    return result.value();
+}
+
+SyncGenerator<ProgressionData> ComponentPositionResolutionLevelLayerProgressionIterator::generator()
+{
+    auto compute_precinct = [&](int x, int y, int r, int i) {
+        // (B-20)
+        auto const trx0 = m_ll_rect(r, i).left();
+        auto const try0 = m_ll_rect(r, i).top();
+        auto const x_offset = floor_div(ceil_div(x, m_XRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPx(r, i)) - floor_div(trx0, 1 << m_PPx(r, i));
+        auto const y_offset = floor_div(ceil_div(y, m_YRsiz(i) * (1 << (m_N_L(i) - r))), 1 << m_PPy(r, i)) - floor_div(try0, 1 << m_PPy(r, i));
+        return x_offset + m_num_precincts_wide(r, i) * y_offset;
+    };
+    // B.12.1.5 Component-position-resolution level-layer progression
+    // "for each i = 0,..., Csiz – 1
+    //      for each y = ty0,..., ty1 – 1,
+    //          for each x = tx0,..., tx1 – 1,
+    //              for each r = 0,..., NL where NL is the number of decomposition levels for component i,
+    //                  if ((y divisible by YRsiz(i) * 2 ** (PPy(r, i) + N_L(i) - r) OR
+    //                      ((y == ty0) AND (try0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPy(r, i) + N_L(i) - r))))
+    //                      if ((x divisible by XRsiz(i) * 2 ** (PPx(r, i) + N_L(i) - r) OR
+    //                          ((x == tx0) AND (trx0 * 2 ** (N_L(i) - r) NOT divisible by 2 ** (PPx(r, i) + N_L(i) - r))))
+    //                              for the next precinct, k, if one exists, in the sequence shown in Figure B.8
+    //                                  for each l = 0,..., L – 1
+    //                                      packet for component i, resolution level r, layer l, and precinct k."
+    // The motivation for this loop is to walk corresponding precincts in different resolution levels at the same time,
+    // even if the resolution levels have different precinct counts.
+    auto const tx0 = m_tile_rect.left();
+    auto const ty0 = m_tile_rect.top();
+    for (int i = 0; i < m_component_count; ++i) {
+        for (int y = ty0; y < m_tile_rect.bottom(); ++y) {
+            for (int x = tx0; x < m_tile_rect.right(); ++x) {
+                for (int r = 0; r <= m_N_L(i); ++r) {
+                    auto const trx0 = m_ll_rect(r, i).left();
+                    auto const try0 = m_ll_rect(r, i).top();
+                    if ((y % (m_YRsiz(i) * (1 << (m_PPy(r, i) + m_N_L(i) - r))) == 0)
+                        || ((y == ty0) && (try0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPy(r, i) + m_N_L(i) - r)) != 0))) {
+                        if ((x % (m_XRsiz(i) * (1 << (m_PPx(r, i) + m_N_L(i) - r))) == 0)
+                            || ((x == tx0) && (trx0 * (1 << (m_N_L(i) - r)) % (1 << (m_PPx(r, i) + m_N_L(i) - r)) != 0))) {
+                            if (int k = compute_precinct(x, y, r, i); k < m_precinct_count(r, i)) {
+                                for (int l = 0; l < m_layer_count; ++l) {
+                                    co_yield ProgressionData { l, r, i, k };
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 
 }
diff --git a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h
index 794471e85b722d..553f831b79af5f 100644
--- a/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h
+++ b/Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.h
@@ -6,8 +6,10 @@
 
 #pragma once
 
+#include <AK/Format.h>
 #include <AK/Function.h>
 #include <AK/SyncGenerator.h>
+#include <LibGfx/Rect.h>
 
 namespace Gfx::JPEG2000 {
 
@@ -57,9 +59,123 @@ class ResolutionLevelLayerComponentPositionProgressionIterator : public Progress
     virtual ProgressionData next() override;
 
 private:
+    SyncGenerator<ProgressionData> generator();
+
+    Optional<ProgressionData> m_next;
+    int m_layer_count { 0 };
+    int m_max_number_of_decomposition_levels { 0 };
+    int m_component_count { 0 };
+    Function<int(int resolution_level, int component)> m_precinct_count;
+    SyncGenerator<ProgressionData> m_generator;
+};
+
+// B.12.1.3 Resolution level-position-component-layer progression
+class ResolutionLevelPositionComponentLayerProgressionIterator : public ProgressionIterator {
+public:
+    // FIXME: Supporting POC packets will probably require changes to this.
+    ResolutionLevelPositionComponentLayerProgressionIterator(int layer_count, int max_number_of_decomposition_levels, int component_count, Function<int(int resolution_level, int component)> precinct_count,
+        Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
+        Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
+        Function<int(int component)> N_L,
+        Function<int(int resolution_level, int component)> num_precincts_wide,
+        Gfx::IntRect tile_rect,
+        Function<IntRect(int resolution_level, int component)> ll_rect);
+    virtual bool has_next() const override;
+    virtual ProgressionData next() override;
+
+private:
+    SyncGenerator<ProgressionData> generator();
+
+    Optional<ProgressionData> m_next;
+    int m_layer_count { 0 };
+    int m_max_number_of_decomposition_levels { 0 };
+    int m_component_count { 0 };
+    Function<int(int resolution_level, int component)> m_precinct_count;
+    Function<int(int component)> m_XRsiz;
+    Function<int(int component)> m_YRsiz;
+    Function<int(int resolution_level, int component)> m_PPx;
+    Function<int(int resolution_level, int component)> m_PPy;
+    Function<int(int component)> m_N_L;
+    Function<int(int resolution_level, int component)> m_num_precincts_wide;
+    Gfx::IntRect m_tile_rect;
+    Function<IntRect(int resolution_level, int component)> m_ll_rect;
+    SyncGenerator<ProgressionData> m_generator;
+};
+
+// B.12.1.4 Position-component-resolution level-layer progression
+class PositionComponentResolutionLevelLayerProgressionIterator : public ProgressionIterator {
+public:
+    // FIXME: Supporting POC packets will probably require changes to this.
+    PositionComponentResolutionLevelLayerProgressionIterator(int layer_count, int component_count, Function<int(int resolution_level, int component)> precinct_count,
+        Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
+        Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
+        Function<int(int component)> N_L,
+        Function<int(int resolution_level, int component)> num_precincts_wide,
+        Gfx::IntRect tile_rect,
+        Function<IntRect(int resolution_level, int component)> ll_rect);
+    virtual bool has_next() const override;
+    virtual ProgressionData next() override;
+
+private:
+    SyncGenerator<ProgressionData> generator();
+
+    Optional<ProgressionData> m_next;
+    int m_layer_count { 0 };
+    int m_component_count { 0 };
     Function<int(int resolution_level, int component)> m_precinct_count;
-    ProgressionData m_next {};
-    ProgressionData m_end {};
+    Function<int(int component)> m_XRsiz;
+    Function<int(int component)> m_YRsiz;
+    Function<int(int resolution_level, int component)> m_PPx;
+    Function<int(int resolution_level, int component)> m_PPy;
+    Function<int(int component)> m_N_L;
+    Function<int(int resolution_level, int component)> m_num_precincts_wide;
+    Gfx::IntRect m_tile_rect;
+    Function<IntRect(int resolution_level, int component)> m_ll_rect;
+    SyncGenerator<ProgressionData> m_generator;
+};
+
+// B.12.1.5 Component-position-resolution level-layer progression
+class ComponentPositionResolutionLevelLayerProgressionIterator : public ProgressionIterator {
+public:
+    // FIXME: Supporting POC packets will probably require changes to this.
+    ComponentPositionResolutionLevelLayerProgressionIterator(int layer_count, int component_count, Function<int(int resolution_level, int component)> precinct_count,
+        Function<int(int component)> XRsiz, Function<int(int component)> YRsiz,
+        Function<int(int resolution_level, int component)> PPx, Function<int(int resolution_level, int component)> PPy,
+        Function<int(int component)> N_L,
+        Function<int(int resolution_level, int component)> num_precincts_wide,
+        Gfx::IntRect tile_rect,
+        Function<IntRect(int resolution_level, int component)> ll_rect);
+    virtual bool has_next() const override;
+    virtual ProgressionData next() override;
+
+private:
+    SyncGenerator<ProgressionData> generator();
+
+    Optional<ProgressionData> m_next;
+    int m_layer_count { 0 };
+    int m_component_count { 0 };
+    Function<int(int resolution_level, int component)> m_precinct_count;
+    Function<int(int component)> m_XRsiz;
+    Function<int(int component)> m_YRsiz;
+    Function<int(int resolution_level, int component)> m_PPx;
+    Function<int(int resolution_level, int component)> m_PPy;
+    Function<int(int component)> m_N_L;
+    Function<int(int resolution_level, int component)> m_num_precincts_wide;
+    Gfx::IntRect m_tile_rect;
+    Function<IntRect(int resolution_level, int component)> m_ll_rect;
+    SyncGenerator<ProgressionData> m_generator;
+};
+
+}
+
+namespace AK {
+
+template<>
+struct Formatter<Gfx::JPEG2000::ProgressionData> : Formatter<FormatString> {
+    ErrorOr<void> format(FormatBuilder& builder, Gfx::JPEG2000::ProgressionData const& value)
+    {
+        return Formatter<FormatString>::format(builder, "layer={}, resolution_level={}, component={}, precinct={}"sv, value.layer, value.resolution_level, value.component, value.precinct);
+    }
 };
 
 }
diff --git a/Userland/Libraries/LibGfx/Rect.h b/Userland/Libraries/LibGfx/Rect.h
index 820085a7d4bdcc..5e7a8e36dbebc6 100644
--- a/Userland/Libraries/LibGfx/Rect.h
+++ b/Userland/Libraries/LibGfx/Rect.h
@@ -479,6 +479,15 @@ class Rect {
         return *this;
     }
 
+    [[nodiscard]] Rect<T> operator/(T factor) const { return { m_location / factor, m_size / factor }; }
+
+    Rect<T>& operator/=(T factor)
+    {
+        m_location /= factor;
+        m_size /= factor;
+        return *this;
+    }
+
     void intersect(Rect<T> const& other)
     {
         T l = max(left(), other.left());
diff --git a/Userland/Libraries/LibGfx/Size.h b/Userland/Libraries/LibGfx/Size.h
index 69be4f6309c1f2..fdb6879b3c4e69 100644
--- a/Userland/Libraries/LibGfx/Size.h
+++ b/Userland/Libraries/LibGfx/Size.h
@@ -146,6 +146,15 @@ class Size {
         return *this;
     }
 
+    [[nodiscard]] Size<T> operator/(T factor) const { return { m_width / factor, m_height / factor }; }
+
+    Size<T>& operator/=(T factor)
+    {
+        m_width /= factor;
+        m_height /= factor;
+        return *this;
+    }
+
     [[nodiscard]] constexpr T primary_size_for_orientation(Orientation orientation) const
     {
         return orientation == Orientation::Vertical ? height() : width();


Reviews:
user: login: LucasChollet
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T23:39:46Z
user: login: nico
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T23:43:27Z
user: login: LucasChollet
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T23:55:52Z
user: login: nico
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T00:14:51Z
user: login: LucasChollet
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T00:49:13Z
user: login: DanShaders
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T22:42:27Z
user: login: nico
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T22:50:00Z

Review comments:
diff_hunk: @@ -479,6 +479,15 @@ class Rect {
         return *this;
     }
 
+    [[nodiscard]] Rect<T> operator/(T factor) const { return { m_location / factor, m_size / factor }; }
+
+    Rect<T>& operator/=(T factor)
+    {
+        m_location /= factor;
+        m_size /= factor;
+        return *this;
+    }
path: Userland/Libraries/LibGfx/Rect.h
user: login: LucasChollet
body: I'm pretty sure it's the kind of operator, for which I need to look at its definition to know what it does for the location.

And it seems that `scale_by` already does the same thing (same for Size). I understand that you can't use that for your use case, as you can't multiply by 0.5 (or any 1 / (1 << n)), but this is IMO a hint that it shouldn't be an operator. So I guess that we might want `scale_[up,down]_by` variants for int-based `Rect`, or you could either use the `set_size` method like this:
```cpp
auto ll_rect = [=](int r, int) {
    return tile_rect.set_size(tile_rect.size() / (1 << (max_number_of_decomposition_levels - r)));
};
```
created_at: 2025-03-18T23:39:45Z
author_association: MEMBER
diff_hunk: @@ -479,6 +479,15 @@ class Rect {
         return *this;
     }
 
+    [[nodiscard]] Rect<T> operator/(T factor) const { return { m_location / factor, m_size / factor }; }
+
+    Rect<T>& operator/=(T factor)
+    {
+        m_location /= factor;
+        m_size /= factor;
+        return *this;
+    }
path: Userland/Libraries/LibGfx/Rect.h
user: login: nico
body: I want to scale down the location too though.

`operator/=` does the same thing as `operator*=`. Isn't having both self-consistent? Do you have a problem with `*=` too?
created_at: 2025-03-18T23:43:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -479,6 +479,15 @@ class Rect {
         return *this;
     }
 
+    [[nodiscard]] Rect<T> operator/(T factor) const { return { m_location / factor, m_size / factor }; }
+
+    Rect<T>& operator/=(T factor)
+    {
+        m_location /= factor;
+        m_size /= factor;
+        return *this;
+    }
path: Userland/Libraries/LibGfx/Rect.h
user: login: LucasChollet
body: I didn't realize that we already had `operator*=`, and yes I don't like it either. But I guess this was already discussed at the time, so I should probably learn to live with the knowledge of its existence instead.

> I want to scale down the location too though.

Where are you doing that :thinking:, I wasn't able to find where this was used outside of tests, and there it seems that the location of `tile_rect` is initialized and stays to null. (Not important)
created_at: 2025-03-18T23:55:52Z
author_association: MEMBER
diff_hunk: @@ -479,6 +479,15 @@ class Rect {
         return *this;
     }
 
+    [[nodiscard]] Rect<T> operator/(T factor) const { return { m_location / factor, m_size / factor }; }
+
+    Rect<T>& operator/=(T factor)
+    {
+        m_location /= factor;
+        m_size /= factor;
+        return *this;
+    }
path: Userland/Libraries/LibGfx/Rect.h
user: login: nico
body: Yes, it's only used in tests. I suppose the location is 0, 0 there and it's not important that it's getting scaled, but it's supposed to be consistent with `reference_grid_coordinates_for_ll_band()` in JPEG2000Loader.cpp, and that does scale both origin and size (using formulas from the spec).
created_at: 2025-03-19T00:14:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -479,6 +479,15 @@ class Rect {
         return *this;
     }
 
+    [[nodiscard]] Rect<T> operator/(T factor) const { return { m_location / factor, m_size / factor }; }
+
+    Rect<T>& operator/=(T factor)
+    {
+        m_location /= factor;
+        m_size /= factor;
+        return *this;
+    }
path: Userland/Libraries/LibGfx/Rect.h
user: login: LucasChollet
body: Oh ok, that's why. Thanks for clarifying that!
created_at: 2025-03-19T00:49:13Z
author_association: MEMBER
path: Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp
user: login: DanShaders
body: Commit message: so where is GeneratorIterator?
created_at: 2025-03-19T22:42:26Z
author_association: CONTRIBUTOR
path: Userland/Libraries/LibGfx/ImageFormats/JPEG2000ProgressionIterators.cpp
user: login: nico
body: Fixed, thanks for the catch!
created_at: 2025-03-19T22:50:00Z
author_association: CONTRIBUTOR

PR comments:
user: login: nico
created_at: 2025-03-18T23:18:31Z
body: Before:

<img width="824" alt="Screenshot 2025-03-18 at 7 14 26 PM" src="https://github.com/user-attachments/assets/4b38b3d0-1b62-43cb-9917-0f3b1fdae613" />

After:

<img width="824" alt="Screenshot 2025-03-18 at 7 14 46 PM" src="https://github.com/user-attachments/assets/f6e51aa2-d035-4c46-81e0-97855fa084da" />

Hm, that actually looks kinda pixely – maybe there's more to do here. But it's progress, and the included tests do decode correctly.
author_association: CONTRIBUTOR
user: login: nico
created_at: 2025-03-22T01:45:15Z
body: > Hm, that actually looks kinda pixely – maybe there's more to do here.

-> #25817


author_association: CONTRIBUTOR