Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/blending.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/blending.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <cstddef>
11
#include <cstring>
12
#include <vector>
13
14
#include "lib/jxl/alpha.h"
15
#include "lib/jxl/base/status.h"
16
#include "lib/jxl/dec_patch_dictionary.h"
17
#include "lib/jxl/frame_header.h"
18
#include "lib/jxl/image.h"
19
#include "lib/jxl/image_metadata.h"
20
21
namespace jxl {
22
23
34.3k
bool NeedsBlending(const FrameHeader& frame_header) {
24
34.3k
  if (!(frame_header.frame_type == FrameType::kRegularFrame ||
25
17.3k
        frame_header.frame_type == FrameType::kSkipProgressive)) {
26
14.5k
    return false;
27
14.5k
  }
28
19.7k
  const auto& info = frame_header.blending_info;
29
19.7k
  bool replace_all = (info.mode == BlendMode::kReplace);
30
19.7k
  for (const auto& ec_i : frame_header.extra_channel_blending_info) {
31
10.0k
    if (ec_i.mode != BlendMode::kReplace) {
32
3.83k
      replace_all = false;
33
3.83k
    }
34
10.0k
  }
35
  // Replace the full frame: nothing to do.
36
19.7k
  if (!frame_header.custom_size_or_origin && replace_all) {
37
11.3k
    return false;
38
11.3k
  }
39
8.45k
  return true;
40
19.7k
}
41
42
Status PerformBlending(
43
    JxlMemoryManager* memory_manager, const float* const* bg,
44
    const float* const* fg, float* const* out, size_t x0, size_t xsize,
45
    const PatchBlending& color_blending, const PatchBlending* ec_blending,
46
20.1M
    const std::vector<ExtraChannelInfo>& extra_channel_info) {
47
20.1M
  bool has_alpha = false;
48
20.1M
  size_t num_ec = extra_channel_info.size();
49
20.2M
  for (size_t i = 0; i < num_ec; i++) {
50
145k
    if (extra_channel_info[i].type == jxl::ExtraChannel::kAlpha) {
51
105k
      has_alpha = true;
52
105k
      break;
53
105k
    }
54
145k
  }
55
20.1M
  if (JXL_UNLIKELY(xsize == 0)) {
56
    // An empty segment is reachable (e.g. a patch whose right edge lands
57
    // exactly on the segment start).  Blending nothing also avoids creating
58
    // zero-width (unallocated) ImageF planes and null memcpy destinations.
59
30.5k
    return true;
60
30.5k
  }
61
40.3M
  JXL_ASSIGN_OR_RETURN(ImageF tmp,
62
40.3M
                       ImageF::Create(memory_manager, xsize, 3 + num_ec));
63
  // Blend extra channels first so that we use the pre-blending alpha.
64
40.3M
  for (size_t i = 0; i < num_ec; i++) {
65
317k
    switch (ec_blending[i].mode) {
66
17.1k
      case PatchBlendMode::kAdd:
67
1.14M
        for (size_t x = 0; x < xsize; x++) {
68
1.13M
          tmp.Row(3 + i)[x] = bg[3 + i][x + x0] + fg[3 + i][x + x0];
69
1.13M
        }
70
17.1k
        continue;
71
72
69.6k
      case PatchBlendMode::kBlendAbove: {
73
69.6k
        size_t alpha = ec_blending[i].alpha_channel;
74
69.6k
        bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
75
69.6k
        PerformAlphaBlending(bg[3 + i] + x0, bg[3 + alpha] + x0, fg[3 + i] + x0,
76
69.6k
                             fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
77
69.6k
                             is_premultiplied, ec_blending[i].clamp);
78
69.6k
        continue;
79
0
      }
80
81
0
      case PatchBlendMode::kBlendBelow: {
82
0
        size_t alpha = ec_blending[i].alpha_channel;
83
0
        bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
84
0
        PerformAlphaBlending(fg[3 + i] + x0, fg[3 + alpha] + x0, bg[3 + i] + x0,
85
0
                             bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
86
0
                             is_premultiplied, ec_blending[i].clamp);
87
0
        continue;
88
0
      }
89
90
13.3k
      case PatchBlendMode::kAlphaWeightedAddAbove: {
91
13.3k
        size_t alpha = ec_blending[i].alpha_channel;
92
13.3k
        PerformAlphaWeightedAdd(bg[3 + i] + x0, fg[3 + i] + x0,
93
13.3k
                                fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
94
13.3k
                                ec_blending[i].clamp);
95
13.3k
        continue;
96
0
      }
97
98
0
      case PatchBlendMode::kAlphaWeightedAddBelow: {
99
0
        size_t alpha = ec_blending[i].alpha_channel;
100
0
        PerformAlphaWeightedAdd(fg[3 + i] + x0, bg[3 + i] + x0,
101
0
                                bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
102
0
                                ec_blending[i].clamp);
103
0
        continue;
104
0
      }
105
106
60.7k
      case PatchBlendMode::kMul:
107
60.7k
        PerformMulBlending(bg[3 + i] + x0, fg[3 + i] + x0, tmp.Row(3 + i),
108
60.7k
                           xsize, ec_blending[i].clamp);
109
60.7k
        continue;
110
111
155k
      case PatchBlendMode::kReplace:
112
155k
        memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg));
113
155k
        continue;
114
115
232
      case PatchBlendMode::kNone:
116
232
        memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg));
117
232
        continue;
118
317k
    }
119
317k
  }
120
20.1M
  size_t alpha = color_blending.alpha_channel;
121
122
20.1M
  const auto add = [&]() {
123
78.7M
    for (int p = 0; p < 3; p++) {
124
59.0M
      float* out = tmp.Row(p);
125
1.81G
      for (size_t x = 0; x < xsize; x++) {
126
1.75G
        out[x] = bg[p][x + x0] + fg[p][x + x0];
127
1.75G
      }
128
59.0M
    }
129
19.6M
  };
130
131
20.1M
  const auto blend_weighted = [&](const float* const* bottom,
132
20.1M
                                  const float* const* top) {
133
32.4k
    bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
134
32.4k
    PerformAlphaBlending(
135
32.4k
        {bottom[0] + x0, bottom[1] + x0, bottom[2] + x0,
136
32.4k
         bottom[3 + alpha] + x0},
137
32.4k
        {top[0] + x0, top[1] + x0, top[2] + x0, top[3 + alpha] + x0},
138
32.4k
        {tmp.Row(0), tmp.Row(1), tmp.Row(2), tmp.Row(3 + alpha)}, xsize,
139
32.4k
        is_premultiplied, color_blending.clamp);
140
32.4k
  };
141
142
20.1M
  const auto add_weighted = [&](const float* const* bottom,
143
20.1M
                                const float* const* top) {
144
56.9k
    for (size_t c = 0; c < 3; c++) {
145
42.6k
      PerformAlphaWeightedAdd(bottom[c] + x0, top[c] + x0, top[3 + alpha] + x0,
146
42.6k
                              tmp.Row(c), xsize, color_blending.clamp);
147
42.6k
    }
148
14.2k
  };
149
150
20.1M
  const auto copy = [&](const float* const* src) {
151
    // Reaching here implies xsize > 0: empty segments return before the
152
    // zero-width (unallocated) ImageF would hand memcpy a null destination.
153
1.61M
    for (size_t p = 0; p < 3; p++) {
154
1.21M
      memcpy(tmp.Row(p), src[p] + x0, xsize * sizeof(**src));
155
1.21M
    }
156
404k
  };
157
158
20.1M
  switch (color_blending.mode) {
159
19.6M
    case PatchBlendMode::kAdd:
160
19.6M
      add();
161
19.6M
      break;
162
163
84.2k
    case PatchBlendMode::kAlphaWeightedAddAbove:
164
84.2k
      has_alpha ? add_weighted(bg, fg) : add();
165
84.2k
      break;
166
167
4.47k
    case PatchBlendMode::kAlphaWeightedAddBelow:
168
4.47k
      has_alpha ? add_weighted(fg, bg) : add();
169
4.47k
      break;
170
171
331k
    case PatchBlendMode::kBlendAbove:
172
331k
      has_alpha ? blend_weighted(bg, fg) : copy(fg);
173
331k
      break;
174
175
2.04k
    case PatchBlendMode::kBlendBelow:
176
2.04k
      has_alpha ? blend_weighted(fg, bg) : copy(fg);
177
2.04k
      break;
178
179
13.4k
    case PatchBlendMode::kMul:
180
53.6k
      for (int p = 0; p < 3; p++) {
181
40.2k
        PerformMulBlending(bg[p] + x0, fg[p] + x0, tmp.Row(p), xsize,
182
40.2k
                           color_blending.clamp);
183
40.2k
      }
184
13.4k
      break;
185
186
35.5k
    case PatchBlendMode::kReplace:
187
35.5k
      copy(fg);
188
35.5k
      break;
189
190
67.1k
    case PatchBlendMode::kNone:
191
67.1k
      copy(bg);
192
20.1M
  }
193
194
80.9M
  for (size_t i = 0; i < 3 + num_ec; i++) {
195
60.7M
    memcpy(out[i] + x0, tmp.Row(i), xsize * sizeof(**out));
196
60.7M
  }
197
20.1M
  return true;
198
20.1M
}
199
200
}  // namespace jxl