Coverage Report

Created: 2026-03-31 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/dec_group_border.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_group_border.h"
7
8
#include <algorithm>
9
#include <atomic>
10
#include <cstddef>
11
#include <cstdint>
12
#include <utility>
13
#include <vector>
14
15
#include "lib/jxl/base/rect.h"
16
#include "lib/jxl/base/status.h"
17
#include "lib/jxl/frame_dimensions.h"
18
19
namespace jxl {
20
21
37.9k
void GroupBorderAssigner::Init(const FrameDimensions& frame_dim) {
22
37.9k
  frame_dim_ = frame_dim;
23
37.9k
  size_t x_size = frame_dim_.xsize_groups;
24
37.9k
  size_t y_size = frame_dim_.ysize_groups;
25
37.9k
  counters_.resize(y_size + 1);
26
  // Initialize counters.
27
82.3k
  for (auto& current_row : counters_) {
28
82.3k
    std::vector<std::atomic<uint32_t>> row((x_size + 1 + 7) / 8);
29
83.0k
    for (auto& v : row) {
30
83.0k
      v = 0;
31
83.0k
    }
32
82.3k
    row.swap(current_row);
33
82.3k
  }
34
  // Counters at image borders don't have anything on the other side, we
35
  // pre-fill their value to have more uniform handling afterwards.
36
328k
  auto set = [this](size_t x, size_t y, uint32_t corners) {
37
328k
    counters_[y][x / 8] |= corners << (4 * (x & 7u));
38
328k
  };
39
119k
  for (size_t x = 0; x < x_size + 1; x++) {
40
82.0k
    set(x, 0, kTopLeft | kTopRight);
41
82.0k
    set(x, y_size, kBottomLeft | kBottomRight);
42
82.0k
  }
43
120k
  for (size_t y = 0; y < y_size + 1; y++) {
44
82.3k
    set(0, y, kTopLeft | kBottomLeft);
45
82.3k
    set(x_size, y, kTopRight | kBottomRight);
46
82.3k
  }
47
37.9k
}
48
49
57.8k
void GroupBorderAssigner::ClearDone(size_t group_id) {
50
231k
  auto clear = [this](size_t x, size_t y, uint32_t corners) {
51
231k
    counters_[y][x / 8].fetch_and(~(corners << (4 * (x & 7u))));
52
231k
  };
53
57.8k
  size_t x = group_id % frame_dim_.xsize_groups;
54
57.8k
  size_t y = group_id / frame_dim_.xsize_groups;
55
57.8k
  clear(x, y, kBottomRight);
56
57.8k
  clear(x + 1, y, kBottomLeft);
57
57.8k
  clear(x, y + 1, kTopRight);
58
57.8k
  clear(x + 1, y + 1, kTopLeft);
59
57.8k
}
60
61
// Looking at each corner between groups, we can guarantee that the four
62
// involved groups will agree between each other regarding the order in which
63
// each of the four groups terminated. Thus, the last of the four groups
64
// gets the responsibility of handling the corner. For borders, every border
65
// is assigned to its top corner (for vertical borders) or to its left corner
66
// (for horizontal borders): the order as seen on those corners will decide who
67
// handles that border.
68
69
void GroupBorderAssigner::GroupDone(size_t group_id, size_t padx, size_t pady,
70
                                    Rect* rects_to_finalize,
71
51.4k
                                    size_t* num_to_finalize) {
72
51.4k
  size_t x = group_id % frame_dim_.xsize_groups;
73
51.4k
  size_t y = group_id / frame_dim_.xsize_groups;
74
51.4k
  Rect block_rect(x * frame_dim_.group_dim / kBlockDim,
75
51.4k
                  y * frame_dim_.group_dim / kBlockDim,
76
51.4k
                  frame_dim_.group_dim / kBlockDim,
77
51.4k
                  frame_dim_.group_dim / kBlockDim, frame_dim_.xsize_blocks,
78
51.4k
                  frame_dim_.ysize_blocks);
79
80
205k
  auto fetch_status = [this](size_t x, size_t y, uint32_t bit) {
81
    // Note that the acq-rel semantics of this fetch are actually needed to
82
    // ensure that the pixel data of the group is already written to memory.
83
205k
    size_t shift = 4 * (x & 7u);
84
205k
    size_t status = counters_[y][x / 8].fetch_or(bit << shift);
85
205k
    status >>= shift;
86
205k
    JXL_DASSERT((bit & status) == 0);
87
205k
    return (bit | status) & 0xF;
88
205k
  };
89
90
51.4k
  size_t top_left_status = fetch_status(x, y, kBottomRight);
91
51.4k
  size_t top_right_status = fetch_status(x + 1, y, kBottomLeft);
92
51.4k
  size_t bottom_right_status = fetch_status(x + 1, y + 1, kTopLeft);
93
51.4k
  size_t bottom_left_status = fetch_status(x, y + 1, kTopRight);
94
95
51.4k
  size_t x1 = block_rect.x0() + block_rect.xsize();
96
51.4k
  size_t y1 = block_rect.y0() + block_rect.ysize();
97
98
51.4k
  bool is_last_group_x = frame_dim_.xsize_groups == x + 1;
99
51.4k
  bool is_last_group_y = frame_dim_.ysize_groups == y + 1;
100
101
  // Start of border of neighbouring group, end of border of this group, start
102
  // of border of this group (on the other side), end of border of next group.
103
51.4k
  size_t xpos[4] = {
104
51.4k
      block_rect.x0() == 0 ? 0 : block_rect.x0() * kBlockDim - padx,
105
51.4k
      block_rect.x0() == 0
106
51.4k
          ? 0
107
51.4k
          : std::min(frame_dim_.xsize, block_rect.x0() * kBlockDim + padx),
108
51.4k
      is_last_group_x ? frame_dim_.xsize : x1 * kBlockDim - padx,
109
51.4k
      std::min(frame_dim_.xsize, x1 * kBlockDim + padx)};
110
51.4k
  size_t ypos[4] = {
111
51.4k
      block_rect.y0() == 0 ? 0 : block_rect.y0() * kBlockDim - pady,
112
51.4k
      block_rect.y0() == 0
113
51.4k
          ? 0
114
51.4k
          : std::min(frame_dim_.ysize, block_rect.y0() * kBlockDim + pady),
115
51.4k
      is_last_group_y ? frame_dim_.ysize : y1 * kBlockDim - pady,
116
51.4k
      std::min(frame_dim_.ysize, y1 * kBlockDim + pady)};
117
118
51.4k
  *num_to_finalize = 0;
119
62.3k
  auto append_rect = [&](size_t x0, size_t x1, size_t y0, size_t y1) {
120
62.3k
    Rect rect(xpos[x0], ypos[y0], xpos[x1] - xpos[x0], ypos[y1] - ypos[y0]);
121
62.3k
    if (rect.xsize() == 0 || rect.ysize() == 0) return;
122
51.4k
    JXL_DASSERT(*num_to_finalize < kMaxToFinalize);
123
51.4k
    rects_to_finalize[(*num_to_finalize)++] = rect;
124
51.4k
  };
125
126
  // Because of how group borders are assigned, it is impossible that we need to
127
  // process the left and right side of some area but not the center area. Thus,
128
  // we compute the first/last part to process in every horizontal strip and
129
  // merge them together. We first collect a mask of what parts should be
130
  // processed.
131
  // We do this horizontally rather than vertically because horizontal borders
132
  // are larger.
133
51.4k
  bool available_parts_mask[3][3] = {};  // [x][y]
134
  // Center
135
51.4k
  available_parts_mask[1][1] = true;
136
  // Corners
137
51.4k
  if (top_left_status == 0xF) available_parts_mask[0][0] = true;
138
51.4k
  if (top_right_status == 0xF) available_parts_mask[2][0] = true;
139
51.4k
  if (bottom_right_status == 0xF) available_parts_mask[2][2] = true;
140
51.4k
  if (bottom_left_status == 0xF) available_parts_mask[0][2] = true;
141
  // Other borders
142
51.4k
  if (top_left_status & kTopRight) available_parts_mask[1][0] = true;
143
51.4k
  if (top_left_status & kBottomLeft) available_parts_mask[0][1] = true;
144
51.4k
  if (top_right_status & kBottomRight) available_parts_mask[2][1] = true;
145
51.4k
  if (bottom_left_status & kBottomRight) available_parts_mask[1][2] = true;
146
147
  // Collect horizontal ranges.
148
51.4k
  constexpr size_t kNoSegment = 3;
149
51.4k
  std::pair<size_t, size_t> horizontal_segments[3] = {{kNoSegment, kNoSegment},
150
51.4k
                                                      {kNoSegment, kNoSegment},
151
51.4k
                                                      {kNoSegment, kNoSegment}};
152
205k
  for (size_t py = 0; py < 3; py++) {
153
617k
    for (size_t px = 0; px < 3; px++) {
154
462k
      if (!available_parts_mask[px][py]) continue;
155
407k
      JXL_DASSERT(horizontal_segments[py].second == kNoSegment ||
156
407k
                  horizontal_segments[py].second == px);
157
407k
      JXL_DASSERT((horizontal_segments[py].first == kNoSegment) ==
158
407k
                  (horizontal_segments[py].second == kNoSegment));
159
407k
      if (horizontal_segments[py].first == kNoSegment) {
160
143k
        horizontal_segments[py].first = px;
161
143k
      }
162
407k
      horizontal_segments[py].second = px + 1;
163
407k
    }
164
154k
  }
165
51.4k
  if (horizontal_segments[0] == horizontal_segments[1] &&
166
51.4k
      horizontal_segments[0] == horizontal_segments[2]) {
167
40.5k
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
168
40.5k
                3);
169
40.5k
  } else if (horizontal_segments[0] == horizontal_segments[1]) {
170
10.8k
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
171
10.8k
                2);
172
10.8k
    append_rect(horizontal_segments[2].first, horizontal_segments[2].second, 2,
173
10.8k
                3);
174
10.8k
  } else if (horizontal_segments[1] == horizontal_segments[2]) {
175
0
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
176
0
                1);
177
0
    append_rect(horizontal_segments[1].first, horizontal_segments[1].second, 1,
178
0
                3);
179
0
  } else {
180
0
    append_rect(horizontal_segments[0].first, horizontal_segments[0].second, 0,
181
0
                1);
182
0
    append_rect(horizontal_segments[1].first, horizontal_segments[1].second, 1,
183
0
                2);
184
0
    append_rect(horizontal_segments[2].first, horizontal_segments[2].second, 2,
185
0
                3);
186
0
  }
187
51.4k
}
188
189
}  // namespace jxl