From 4523cf652f568f1fbb57bf9a10ae3caae785cd9f Mon Sep 17 00:00:00 2001
From: Eugene Kliuchnikov <eustas@google.com>
Date: Wed, 29 Oct 2025 15:18:00 +0100
Subject: [PATCH] Fix low memory rendering pipleline (#4495)

Put together calculations of x0/xsize for ProcessRow

Drive-by: fix formatting
---
 .../low_memory_render_pipeline.cc             | 101 +++++++++++-------
 lib/jxl/render_pipeline/stage_blending.cc     |   8 +-
 2 files changed, 64 insertions(+), 45 deletions(-)

diff -urp libjxl-0.10.3.orig/lib/jxl/render_pipeline/low_memory_render_pipeline.cc libjxl-0.10.3/lib/jxl/render_pipeline/low_memory_render_pipeline.cc
--- libjxl-0.10.3.orig/lib/jxl/render_pipeline/low_memory_render_pipeline.cc	2024-06-27 07:10:08.000000000 -0500
+++ libjxl-0.10.3/lib/jxl/render_pipeline/low_memory_render_pipeline.cc	2026-02-24 03:19:07.065363322 -0600
@@ -581,16 +581,13 @@ Status LowMemoryRenderPipeline::RenderRe
         image_area_rect.CeilShiftRight(channel_shifts_[i][anyc_[i]]);
   }
 
-  ssize_t frame_x0 =
-      first_image_dim_stage_ == stages_.size() ? 0 : frame_origin_.x0;
-  ssize_t frame_y0 =
-      first_image_dim_stage_ == stages_.size() ? 0 : frame_origin_.y0;
-  size_t full_image_xsize = first_image_dim_stage_ == stages_.size()
-                                ? frame_dimensions_.xsize_upsampled
-                                : full_image_xsize_;
-  size_t full_image_ysize = first_image_dim_stage_ == stages_.size()
-                                ? frame_dimensions_.ysize_upsampled
-                                : full_image_ysize_;
+  JXL_DASSERT(0 <= first_trailing_stage_);
+  JXL_DASSERT(first_trailing_stage_ < first_image_dim_stage_);
+  JXL_DASSERT(first_image_dim_stage_ <= stages_.size());
+
+  bool offscreen = (first_image_dim_stage_ == stages_.size());
+  ptrdiff_t frame_x0 = offscreen ? 0 : frame_origin_.x0;
+  ptrdiff_t frame_y0 = offscreen ? 0 : frame_origin_.y0;
 
   // Compute actual x-axis bounds for the current image area in the context of
   // the full image this frame is part of. As the left boundary may be negative,
@@ -605,13 +602,30 @@ Status LowMemoryRenderPipeline::RenderRe
     full_image_x0 = 0;
   }
   ssize_t full_image_x1 = frame_x0 + image_area_rect.x1();
-  full_image_x1 = std::min<ssize_t>(full_image_x1, full_image_xsize);
 
-  // If the current image area is entirely outside of the visible image, there
-  // is no point in proceeding. Note: this uses the assumption that if there is
-  // a stage with observable effects (i.e. a kInput stage), it only appears
-  // after the stage that switches to image dimensions.
-  if (full_image_x1 <= full_image_x0) return true;
+  std::vector<Rect> span(stages_.size());
+  for (size_t i = 0; i < stages_.size(); ++i) {
+    if (i < first_trailing_stage_) {
+      span[i] = Rect(group_rect[i].x0(), 0, group_rect[i].xsize(),
+                     image_rect_[i].ysize());
+    } else if (i < first_image_dim_stage_) {
+      // Before the first_image_dim_stage_, coordinates are relative to the
+      // current frame.
+      size_t x0 = full_image_x0 - frame_x0;
+      size_t x1 = full_image_x1 - frame_x0;
+      size_t x_max = frame_dimensions_.xsize_upsampled;
+      size_t cropped_x1 = std::min<ptrdiff_t>(x1, x_max);
+      span[i] = Rect(x0, 0, std::max<ptrdiff_t>(0, cropped_x1 - x0),
+                     frame_dimensions_.ysize_upsampled);
+    } else {
+      size_t x0 = full_image_x0;
+      size_t x1 = full_image_x1;
+      size_t x_max = full_image_xsize_;
+      size_t cropped_x1 = std::min<ptrdiff_t>(x1, x_max);
+      span[i] = Rect(x0, 0, std::max<ptrdiff_t>(0, cropped_x1 - x0),
+                     full_image_ysize_);
+    }
+  }
 
   // Data structures to hold information about input/output rows and their
   // buffers.
@@ -702,18 +716,17 @@ Status LowMemoryRenderPipeline::RenderRe
 
       ssize_t image_y = static_cast<ssize_t>(group_rect[i].y0()) + y;
       // Do not produce rows in out-of-bounds areas.
-      if (image_y < 0 ||
-          image_y >= static_cast<ssize_t>(image_rect_[i].ysize())) {
-        continue;
-      }
+      if (image_y < 0) continue;
+      if (image_y >= static_cast<ptrdiff_t>(span[i].y1())) continue;
 
       // Get the input/output rows and potentially apply mirroring to the input.
       prepare_io_rows(y, i);
 
       // Produce output rows.
+      if (span[i].xsize() == 0) continue;
       JXL_RETURN_IF_ERROR(stages_[i]->ProcessRow(
-          input_rows[i], output_rows, xpadding_for_output_[i],
-          group_rect[i].xsize(), group_rect[i].x0(), image_y, thread_id));
+          input_rows[i], output_rows, xpadding_for_output_[i], span[i].xsize(),
+          span[i].x0(), image_y, thread_id));
     }
 
     // Process trailing stages, i.e. the final set of non-kInOut stages; they
@@ -741,21 +754,24 @@ Status LowMemoryRenderPipeline::RenderRe
     // (and may be necessary for correctness, as some stages assume coordinates
     // are within bounds).
     ssize_t full_image_y = frame_y0 + image_area_rect.y0() + y;
-    if (full_image_y < 0 ||
-        full_image_y >= static_cast<ssize_t>(full_image_ysize)) {
-      continue;
-    }
+    if (full_image_y < 0) continue;
 
-    for (size_t i = first_trailing_stage_; i < stages_.size(); i++) {
-      // Before the first_image_dim_stage_, coordinates are relative to the
-      // current frame.
-      size_t x0 =
-          i < first_image_dim_stage_ ? full_image_x0 - frame_x0 : full_image_x0;
-      size_t y =
-          i < first_image_dim_stage_ ? full_image_y - frame_y0 : full_image_y;
+    for (size_t i = first_trailing_stage_; i < first_image_dim_stage_; i++) {
+      if (span[i].xsize() == 0) continue;
+      size_t y0 = image_area_rect.y0() + y;
+      if (y0 >= span[i].y1()) continue;
       JXL_RETURN_IF_ERROR(stages_[i]->ProcessRow(
           input_rows[first_trailing_stage_], output_rows,
-          /*xextra=*/0, full_image_x1 - full_image_x0, x0, y, thread_id));
+          /*xextra=*/0, span[i].xsize(), span[i].x0(), y0, thread_id));
+    }
+
+    for (size_t i = first_image_dim_stage_; i < stages_.size(); i++) {
+      if (span[i].xsize() == 0) continue;
+      if (full_image_y >= static_cast<ptrdiff_t>(span[i].y1())) continue;
+      JXL_RETURN_IF_ERROR(
+          stages_[i]->ProcessRow(input_rows[first_trailing_stage_], output_rows,
+                                 /*xextra=*/0, span[i].xsize(), span[i].x0(),
+                                 full_image_y, thread_id));
     }
   }
   return true;
