Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/dec_patch_dictionary.cc
Line
Count
Source
1
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2
//
3
// Use of this source code is governed by a BSD-style
4
// license that can be found in the LICENSE file.
5
6
#include "lib/jxl/dec_patch_dictionary.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <algorithm>
11
#include <cstddef>
12
#include <cstdint>
13
#include <cstdlib>
14
#include <utility>
15
#include <vector>
16
17
#include "lib/jxl/base/printf_macros.h"
18
#include "lib/jxl/base/status.h"
19
#include "lib/jxl/blending.h"
20
#include "lib/jxl/common.h"  // kMaxNumReferenceFrames
21
#include "lib/jxl/dec_ans.h"
22
#include "lib/jxl/dec_bit_reader.h"
23
#include "lib/jxl/image.h"
24
#include "lib/jxl/image_bundle.h"
25
#include "lib/jxl/image_metadata.h"
26
#include "lib/jxl/pack_signed.h"
27
#include "lib/jxl/patch_dictionary_internal.h"
28
29
namespace jxl {
30
31
Status PatchDictionary::Decode(JxlMemoryManager* memory_manager, BitReader* br,
32
                               size_t xsize, size_t ysize,
33
                               size_t num_extra_channels,
34
1.17k
                               bool* uses_extra_channels) {
35
  // The dictionary is reused across frames: blendings_ is indexed by position
36
  // index, so stale entries from a previous frame must not remain.
37
1.17k
  positions_.clear();
38
1.17k
  ref_positions_.clear();
39
1.17k
  blendings_.clear();
40
1.17k
  blendings_stride_ = num_extra_channels + 1;
41
1.17k
  std::vector<uint8_t> context_map;
42
1.17k
  ANSCode code;
43
1.17k
  JXL_RETURN_IF_ERROR(DecodeHistograms(
44
1.17k
      memory_manager, br, kNumPatchDictionaryContexts, &code, &context_map));
45
2.28k
  JXL_ASSIGN_OR_RETURN(ANSSymbolReader decoder,
46
2.28k
                       ANSSymbolReader::Create(&code, br));
47
48
3.33M
  auto read_num = [&](size_t context) -> size_t {
49
3.33M
    size_t r = decoder.ReadHybridUint(context, br, context_map);
50
3.33M
    return r;
51
3.33M
  };
52
53
2.28k
  size_t num_ref_patch = read_num(kNumRefPatchContext);
54
  // Limit max memory usage of patches to about 66 bytes per pixel (assuming 8
55
  // bytes per size_t)
56
2.28k
  const size_t num_pixels = xsize * ysize;
57
2.28k
  const size_t max_ref_patches = 1024 + num_pixels / 4;
58
2.28k
  const size_t max_patches = max_ref_patches * 4;
59
2.28k
  const size_t max_blending_infos = max_patches * 4;
60
2.28k
  if (num_ref_patch > max_ref_patches) {
61
5
    return JXL_FAILURE("Too many patches in dictionary");
62
5
  }
63
64
1.13k
  size_t total_patches = 0;
65
1.13k
  size_t next_size = 1;
66
67
27.9k
  for (size_t id = 0; id < num_ref_patch; id++) {
68
26.8k
    PatchReferencePosition ref_pos;
69
26.8k
    ref_pos.ref = read_num(kReferenceFrameContext);
70
26.8k
    if (ref_pos.ref >= kMaxNumReferenceFrames ||
71
26.8k
        reference_frames_->at(ref_pos.ref).frame->xsize() == 0) {
72
10
      return JXL_FAILURE("Invalid reference frame ID");
73
10
    }
74
26.8k
    if (!reference_frames_->at(ref_pos.ref).ib_is_in_xyb) {
75
2
      return JXL_FAILURE(
76
2
          "Patches cannot use frames saved post color transforms");
77
2
    }
78
26.8k
    const ImageBundle& ib = *reference_frames_->at(ref_pos.ref).frame;
79
26.8k
    ref_pos.x0 = read_num(kPatchReferencePositionContext);
80
26.8k
    ref_pos.y0 = read_num(kPatchReferencePositionContext);
81
26.8k
    ref_pos.xsize = read_num(kPatchSizeContext) + 1;
82
26.8k
    ref_pos.ysize = read_num(kPatchSizeContext) + 1;
83
26.8k
    if ((ref_pos.xsize == 0) || (ref_pos.ysize == 0)) {  // overflow
84
0
      return JXL_FAILURE("Invalid position specified in reference frame");
85
0
    }
86
26.8k
    if (ref_pos.xsize > ib.xsize() || ref_pos.ysize > ib.ysize()) {
87
2
      return JXL_FAILURE("Invalid position specified in reference frame");
88
2
    }
89
26.8k
    if ((ref_pos.x0 > ib.xsize() - ref_pos.xsize) ||
90
26.8k
        (ref_pos.y0 > ib.ysize() - ref_pos.ysize)) {
91
0
      return JXL_FAILURE("Invalid position specified in reference frame");
92
0
    }
93
26.8k
    size_t id_count = read_num(kPatchCountContext);
94
26.8k
    if (id_count > max_patches) {
95
1
      return JXL_FAILURE("Too many patches in dictionary");
96
1
    }
97
26.8k
    id_count++;
98
26.8k
    total_patches += id_count;
99
26.8k
    if (total_patches > max_patches) {
100
4
      return JXL_FAILURE("Too many patches in dictionary");
101
4
    }
102
26.8k
    if (next_size < total_patches) {
103
6.48k
      next_size *= 2;
104
6.48k
      next_size = std::min<size_t>(next_size, max_patches);
105
6.48k
    }
106
26.8k
    if (next_size * blendings_stride_ > max_blending_infos) {
107
0
      return JXL_FAILURE("Too many patches in dictionary");
108
0
    }
109
26.8k
    positions_.reserve(next_size);
110
26.8k
    blendings_.reserve(next_size * blendings_stride_);
111
26.8k
    bool choose_alpha = (num_extra_channels > 1);
112
1.07M
    for (size_t i = 0; i < id_count; i++) {
113
1.04M
      PatchPosition pos;
114
1.04M
      pos.ref_pos_idx = ref_positions_.size();
115
1.04M
      if (i == 0) {
116
26.8k
        pos.x = read_num(kPatchPositionContext);
117
26.8k
        pos.y = read_num(kPatchPositionContext);
118
1.02M
      } else {
119
1.02M
        ptrdiff_t deltax = UnpackSigned(read_num(kPatchOffsetContext));
120
1.02M
        if (deltax < 0 && static_cast<size_t>(-deltax) > positions_.back().x) {
121
4
          return JXL_FAILURE("Invalid patch: negative x coordinate (%" PRIuS
122
4
                             " base x %" PRIdS " delta x)",
123
4
                             positions_.back().x, deltax);
124
4
        }
125
1.02M
        pos.x = positions_.back().x + deltax;
126
1.02M
        ptrdiff_t deltay = UnpackSigned(read_num(kPatchOffsetContext));
127
1.02M
        if (deltay < 0 && static_cast<size_t>(-deltay) > positions_.back().y) {
128
6
          return JXL_FAILURE("Invalid patch: negative y coordinate (%" PRIuS
129
6
                             " base y %" PRIdS " delta y)",
130
6
                             positions_.back().y, deltay);
131
6
        }
132
1.02M
        pos.y = positions_.back().y + deltay;
133
1.02M
      }
134
1.04M
      if ((ref_pos.xsize > xsize) || (ref_pos.ysize > ysize)) {
135
0
        return JXL_FAILURE("Invalid patch position");
136
0
      }
137
1.04M
      if ((pos.x > xsize - ref_pos.xsize) || (pos.y > ysize - ref_pos.ysize)) {
138
4
        return JXL_FAILURE("Invalid patch position");
139
4
      }
140
2.10M
      for (size_t j = 0; j < blendings_stride_; j++) {
141
1.05M
        uint32_t blend_mode = read_num(kPatchBlendModeContext);
142
1.05M
        if (blend_mode >= kNumPatchBlendModes) {
143
7
          return JXL_FAILURE("Invalid patch blend mode: %u", blend_mode);
144
7
        }
145
1.05M
        PatchBlending info;
146
1.05M
        info.mode = static_cast<PatchBlendMode>(blend_mode);
147
1.05M
        if (UsesAlpha(info.mode)) {
148
13.9k
          *uses_extra_channels = true;
149
13.9k
        }
150
1.05M
        if (info.mode != PatchBlendMode::kNone && j > 0) {
151
1.34k
          *uses_extra_channels = true;
152
1.34k
        }
153
1.05M
        if (UsesAlpha(info.mode) && choose_alpha) {
154
1.08k
          info.alpha_channel = read_num(kPatchAlphaChannelContext);
155
1.08k
          if (info.alpha_channel >= num_extra_channels) {
156
6
            return JXL_FAILURE(
157
6
                "Invalid alpha channel for blending: %u out of %u\n",
158
6
                info.alpha_channel, static_cast<uint32_t>(num_extra_channels));
159
6
          }
160
1.05M
        } else {
161
1.05M
          info.alpha_channel = 0;
162
1.05M
        }
163
1.05M
        if (UsesClamp(info.mode)) {
164
19.5k
          info.clamp = static_cast<bool>(read_num(kPatchClampContext));
165
1.03M
        } else {
166
1.03M
          info.clamp = false;
167
1.03M
        }
168
1.05M
        blendings_.push_back(info);
169
1.05M
      }
170
1.04M
      positions_.emplace_back(pos);
171
1.04M
    }
172
26.7k
    ref_positions_.emplace_back(ref_pos);
173
26.7k
  }
174
1.09k
  positions_.shrink_to_fit();
175
176
1.09k
  if (!decoder.CheckANSFinalState()) {
177
0
    return JXL_FAILURE("ANS checksum failure.");
178
0
  }
179
180
1.09k
  ComputePatchTree();
181
1.09k
  return true;
182
1.09k
}
183
184
800
int PatchDictionary::GetReferences() const {
185
800
  int result = 0;
186
11.3k
  for (const auto& ref_pos : ref_positions_) {
187
11.3k
    result |= (1 << static_cast<int>(ref_pos.ref));
188
11.3k
  }
189
800
  return result;
190
800
}
191
192
namespace {
193
struct PatchInterval {
194
  size_t idx;
195
  size_t y0, y1;
196
};
197
}  // namespace
198
199
42.2k
void PatchDictionary::ComputePatchTree() {
200
42.2k
  patch_tree_.clear();
201
42.2k
  num_patches_.clear();
202
42.2k
  sorted_patches_y0_.clear();
203
42.2k
  sorted_patches_y1_.clear();
204
42.2k
  if (positions_.empty()) {
205
40.6k
    return;
206
40.6k
  }
207
  // Create a y-interval for each patch.
208
1.59k
  std::vector<PatchInterval> intervals(positions_.size());
209
2.38M
  for (size_t i = 0; i < positions_.size(); ++i) {
210
2.38M
    const auto& pos = positions_[i];
211
2.38M
    intervals[i].idx = i;
212
2.38M
    intervals[i].y0 = pos.y;
213
2.38M
    intervals[i].y1 = pos.y + ref_positions_[pos.ref_pos_idx].ysize;
214
2.38M
  }
215
272k
  auto sort_by_y0 = [&intervals](size_t start, size_t end) {
216
272k
    std::sort(intervals.data() + start, intervals.data() + end,
217
81.6M
              [](const PatchInterval& i0, const PatchInterval& i1) {
218
81.6M
                return i0.y0 < i1.y0;
219
81.6M
              });
220
272k
  };
221
137k
  auto sort_by_y1 = [&intervals](size_t start, size_t end) {
222
137k
    std::sort(intervals.data() + start, intervals.data() + end,
223
68.7M
              [](const PatchInterval& i0, const PatchInterval& i1) {
224
68.7M
                return i0.y1 < i1.y1;
225
68.7M
              });
226
137k
  };
227
  // Count the number of patches for each row.
228
1.59k
  sort_by_y1(0, intervals.size());
229
1.59k
  num_patches_.resize(intervals.back().y1);
230
2.38M
  for (auto iv : intervals) {
231
28.3M
    for (size_t y = iv.y0; y < iv.y1; ++y) num_patches_[y]++;
232
2.38M
  }
233
1.59k
  PatchTreeNode root;
234
1.59k
  root.start = 0;
235
1.59k
  root.num = intervals.size();
236
1.59k
  patch_tree_.push_back(root);
237
1.59k
  size_t next = 0;
238
137k
  while (next < patch_tree_.size()) {
239
136k
    auto& node = patch_tree_[next];
240
136k
    size_t start = node.start;
241
136k
    size_t end = node.start + node.num;
242
    // Choose the y_center for this node to be the median of interval starts.
243
136k
    sort_by_y0(start, end);
244
136k
    size_t middle_idx = start + node.num / 2;
245
136k
    node.y_center = intervals[middle_idx].y0;
246
    // Divide the intervals in [start, end) into three groups:
247
    //   * those completely to the right of y_center: [right_start, end)
248
    //   * those overlapping y_center: [left_end, right_start)
249
    //   * those completely to the left of y_center: [start, left_end)
250
136k
    size_t right_start = middle_idx;
251
1.01M
    while (right_start < end && intervals[right_start].y0 == node.y_center) {
252
881k
      ++right_start;
253
881k
    }
254
136k
    sort_by_y1(start, right_start);
255
136k
    size_t left_end = right_start;
256
2.51M
    while (left_end > start && intervals[left_end - 1].y1 > node.y_center) {
257
2.38M
      --left_end;
258
2.38M
    }
259
    // Fill in sorted_patches_y0_ and sorted_patches_y1_ for the current node.
260
136k
    node.num = right_start - left_end;
261
136k
    node.start = sorted_patches_y0_.size();
262
136k
    for (ptrdiff_t i = static_cast<ptrdiff_t>(right_start) - 1;
263
2.51M
         i >= static_cast<ptrdiff_t>(left_end); --i) {
264
2.38M
      sorted_patches_y1_.emplace_back(intervals[i].y1, intervals[i].idx);
265
2.38M
    }
266
136k
    sort_by_y0(left_end, right_start);
267
2.51M
    for (size_t i = left_end; i < right_start; ++i) {
268
2.38M
      sorted_patches_y0_.emplace_back(intervals[i].y0, intervals[i].idx);
269
2.38M
    }
270
    // Create the left and right nodes (if not empty).
271
136k
    node.left_child = node.right_child = -1;
272
136k
    if (left_end > start) {
273
65.9k
      PatchTreeNode left;
274
65.9k
      left.start = start;
275
65.9k
      left.num = left_end - left.start;
276
65.9k
      patch_tree_[next].left_child = patch_tree_.size();
277
65.9k
      patch_tree_.push_back(left);
278
65.9k
    }
279
136k
    if (right_start < end) {
280
68.8k
      PatchTreeNode right;
281
68.8k
      right.start = right_start;
282
68.8k
      right.num = end - right.start;
283
68.8k
      patch_tree_[next].right_child = patch_tree_.size();
284
68.8k
      patch_tree_.push_back(right);
285
68.8k
    }
286
136k
    ++next;
287
136k
  }
288
1.59k
}
289
290
2.20M
std::vector<size_t> PatchDictionary::GetPatchesForRow(size_t y) const {
291
2.20M
  std::vector<size_t> result;
292
2.20M
  if (y < num_patches_.size() && num_patches_[y] > 0) {
293
1.33M
    result.reserve(num_patches_[y]);
294
10.6M
    for (ptrdiff_t tree_idx = 0; tree_idx != -1;) {
295
9.34M
      JXL_DASSERT(tree_idx < static_cast<ptrdiff_t>(patch_tree_.size()));
296
9.34M
      const auto& node = patch_tree_[tree_idx];
297
9.34M
      if (y <= node.y_center) {
298
21.0M
        for (size_t i = 0; i < node.num; ++i) {
299
20.3M
          const auto& p = sorted_patches_y0_[node.start + i];
300
20.3M
          if (y < p.first) break;
301
16.2M
          result.push_back(p.second);
302
16.2M
        }
303
4.83M
        tree_idx = y < node.y_center ? node.left_child : -1;
304
4.83M
      } else {
305
19.0M
        for (size_t i = 0; i < node.num; ++i) {
306
18.7M
          const auto& p = sorted_patches_y1_[node.start + i];
307
18.7M
          if (y >= p.first) break;
308
14.5M
          result.push_back(p.second);
309
14.5M
        }
310
4.50M
        tree_idx = node.right_child;
311
4.50M
      }
312
9.34M
    }
313
    // Ensure that he relative order of patches that affect the same pixels is
314
    // preserved. This is important for patches that have a blend mode
315
    // different from kAdd.
316
1.33M
    std::sort(result.begin(), result.end());
317
1.33M
  }
318
2.20M
  return result;
319
2.20M
}
320
321
// Adds patches to a segment of `xsize` pixels, starting at `inout`, assumed
322
// to be located at position (x0, y) in the frame.
323
Status PatchDictionary::AddOneRow(
324
    float* const* inout, size_t y, size_t x0, size_t xsize,
325
1.62M
    const std::vector<ExtraChannelInfo>& extra_channel_info) const {
326
1.62M
  size_t num_ec = extra_channel_info.size();
327
1.62M
  JXL_ENSURE(num_ec + 1 <= blendings_stride_);
328
1.62M
  std::vector<const float*> fg_ptrs(3 + num_ec);
329
27.8M
  for (size_t pos_idx : GetPatchesForRow(y)) {
330
27.8M
    const size_t blending_idx = pos_idx * blendings_stride_;
331
27.8M
    const PatchPosition& pos = positions_[pos_idx];
332
27.8M
    const PatchReferencePosition& ref_pos = ref_positions_[pos.ref_pos_idx];
333
27.8M
    size_t by = pos.y;
334
27.8M
    size_t bx = pos.x;
335
27.8M
    size_t patch_xsize = ref_pos.xsize;
336
27.8M
    JXL_ENSURE(y >= by);
337
27.8M
    JXL_ENSURE(y < by + ref_pos.ysize);
338
27.8M
    size_t iy = y - by;
339
27.8M
    size_t ref = ref_pos.ref;
340
27.8M
    if (bx >= x0 + xsize) continue;
341
24.0M
    if (bx + patch_xsize < x0) continue;
342
19.2M
    size_t patch_x0 = std::max(bx, x0);
343
19.2M
    size_t patch_x1 = std::min(bx + patch_xsize, x0 + xsize);
344
77.0M
    for (size_t c = 0; c < 3; c++) {
345
57.8M
      fg_ptrs[c] = reference_frames_->at(ref).frame->color()->ConstPlaneRow(
346
57.8M
                       c, ref_pos.y0 + iy) +
347
57.8M
                   ref_pos.x0 + x0 - bx;
348
57.8M
    }
349
19.2M
    for (size_t i = 0; i < num_ec; i++) {
350
334
      fg_ptrs[3 + i] =
351
334
          reference_frames_->at(ref).frame->extra_channels()[i].ConstRow(
352
334
              ref_pos.y0 + iy) +
353
334
          ref_pos.x0 + x0 - bx;
354
334
    }
355
19.2M
    JXL_RETURN_IF_ERROR(PerformBlending(
356
19.2M
        memory_manager_, inout, fg_ptrs.data(), inout, patch_x0 - x0,
357
19.2M
        patch_x1 - patch_x0, blendings_[blending_idx],
358
19.2M
        blendings_.data() + blending_idx + 1, extra_channel_info));
359
19.2M
  }
360
1.62M
  return true;
361
1.62M
}
362
}  // namespace jxl