/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 |