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/render_pipeline/stage_blending.h"
7
8 #undef HWY_TARGET_INCLUDE
9 #define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_blending.cc"
10 #include <hwy/foreach_target.h>
11 #include <hwy/highway.h>
12
13 #include "lib/jxl/base/printf_macros.h"
14 #include "lib/jxl/blending.h"
15
16 HWY_BEFORE_NAMESPACE();
17 namespace jxl {
18 namespace HWY_NAMESPACE {
19
20 class BlendingStage : public RenderPipelineStage {
21 public:
BlendingStage(const PassesDecoderState * dec_state,const ColorEncoding & frame_color_encoding)22 explicit BlendingStage(const PassesDecoderState* dec_state,
23 const ColorEncoding& frame_color_encoding)
24 : RenderPipelineStage(RenderPipelineStage::Settings()),
25 state_(*dec_state->shared) {
26 image_xsize_ = state_.frame_header.nonserialized_metadata->xsize();
27 image_ysize_ = state_.frame_header.nonserialized_metadata->ysize();
28 extra_channel_info_ =
29 &state_.frame_header.nonserialized_metadata->m.extra_channel_info;
30 info_ = state_.frame_header.blending_info;
31 const std::vector<BlendingInfo>& ec_info =
32 state_.frame_header.extra_channel_blending_info;
33 ImageBundle& bg = *state_.reference_frames[info_.source].frame;
34 bg_ = &bg;
35 if (bg.xsize() == 0 && bg.ysize() == 0) {
36 // there is no background, assume it to be all zeroes
37 ImageBundle empty(&state_.metadata->m);
38 Image3F color(image_xsize_, image_ysize_);
39 ZeroFillImage(&color);
40 empty.SetFromImage(std::move(color), frame_color_encoding);
41 if (!ec_info.empty()) {
42 std::vector<ImageF> ec;
43 for (size_t i = 0; i < ec_info.size(); ++i) {
44 ImageF eci(image_xsize_, image_ysize_);
45 ZeroFillImage(&eci);
46 ec.push_back(std::move(eci));
47 }
48 empty.SetExtraChannels(std::move(ec));
49 }
50 bg = std::move(empty);
51 } else if (state_.reference_frames[info_.source].ib_is_in_xyb) {
52 initialized_ = JXL_FAILURE(
53 "Trying to blend XYB reference frame %i and non-XYB frame",
54 info_.source);
55 return;
56 }
57
58 if (bg.xsize() < image_xsize_ || bg.ysize() < image_ysize_ ||
59 bg.origin.x0 != 0 || bg.origin.y0 != 0) {
60 initialized_ = JXL_FAILURE("Trying to use a %" PRIuS "x%" PRIuS
61 " crop as a background",
62 bg.xsize(), bg.ysize());
63 return;
64 }
65 if (state_.metadata->m.xyb_encoded) {
66 if (!dec_state->output_encoding_info.color_encoding_is_original) {
67 initialized_ = JXL_FAILURE("Blending in unsupported color space");
68 return;
69 }
70 }
71
72 blending_info_.resize(ec_info.size() + 1);
73 auto make_blending = [&](const BlendingInfo& info, PatchBlending* pb) {
74 pb->alpha_channel = info.alpha_channel;
75 pb->clamp = info.clamp;
76 switch (info.mode) {
77 case BlendMode::kReplace: {
78 pb->mode = PatchBlendMode::kReplace;
79 break;
80 }
81 case BlendMode::kAdd: {
82 pb->mode = PatchBlendMode::kAdd;
83 break;
84 }
85 case BlendMode::kMul: {
86 pb->mode = PatchBlendMode::kMul;
87 break;
88 }
89 case BlendMode::kBlend: {
90 pb->mode = PatchBlendMode::kBlendAbove;
91 break;
92 }
93 case BlendMode::kAlphaWeightedAdd: {
94 pb->mode = PatchBlendMode::kAlphaWeightedAddAbove;
95 break;
96 }
97 default: {
98 JXL_ABORT("Invalid blend mode"); // should have failed to decode
99 }
100 }
101 };
102 make_blending(info_, &blending_info_[0]);
103 for (size_t i = 0; i < ec_info.size(); i++) {
104 make_blending(ec_info[i], &blending_info_[1 + i]);
105 }
106 }
107
IsInitialized() const108 Status IsInitialized() const override { return initialized_; }
109
ProcessRow(const RowInfo & input_rows,const RowInfo & output_rows,size_t xextra,size_t xsize,size_t xpos,size_t ypos,float * JXL_RESTRICT temp) const110 void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
111 size_t xextra, size_t xsize, size_t xpos, size_t ypos,
112 float* JXL_RESTRICT temp) const final {
113 PROFILER_ZONE("Blend");
114 JXL_ASSERT(initialized_);
115 const FrameOrigin& frame_origin = state_.frame_header.frame_origin;
116 ssize_t bg_xpos = frame_origin.x0 + static_cast<ssize_t>(xpos);
117 ssize_t bg_ypos = frame_origin.y0 + static_cast<ssize_t>(ypos);
118 int offset = 0;
119 if (bg_xpos + static_cast<ssize_t>(xsize) <= 0 ||
120 frame_origin.x0 >= static_cast<ssize_t>(image_xsize_) || bg_ypos < 0 ||
121 bg_ypos >= static_cast<ssize_t>(image_ysize_)) {
122 return;
123 }
124 if (bg_xpos < 0) {
125 xpos -= bg_xpos;
126 offset -= bg_xpos;
127 xsize += bg_xpos;
128 bg_xpos = 0;
129 }
130 if (bg_xpos + xsize > image_xsize_) {
131 xsize =
132 std::max<ssize_t>(0, static_cast<ssize_t>(image_xsize_) - bg_xpos);
133 }
134 std::vector<const float*> bg_row_ptrs_(input_rows.size());
135 std::vector<float*> fg_row_ptrs_(input_rows.size());
136 for (size_t c = 0; c < input_rows.size(); ++c) {
137 bg_row_ptrs_[c] =
138 (c < 3 ? bg_->color()->ConstPlaneRow(c, bg_ypos)
139 : bg_->extra_channels()[c - 3].ConstRow(bg_ypos)) +
140 bg_xpos;
141 fg_row_ptrs_[c] = GetInputRow(input_rows, c, offset);
142 }
143 PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(),
144 fg_row_ptrs_.data(), 0, xsize, blending_info_[0],
145 blending_info_.data() + 1, *extra_channel_info_);
146 }
147
GetChannelMode(size_t c) const148 RenderPipelineChannelMode GetChannelMode(size_t c) const final {
149 return RenderPipelineChannelMode::kInPlace;
150 }
151
152 private:
153 const PassesSharedState& state_;
154 BlendingInfo info_;
155 ImageBundle* bg_;
156 Status initialized_ = true;
157 size_t image_xsize_;
158 size_t image_ysize_;
159 std::vector<PatchBlending> blending_info_;
160 const std::vector<ExtraChannelInfo>* extra_channel_info_;
161 };
162
GetBlendingStage(const PassesDecoderState * dec_state,const ColorEncoding & frame_color_encoding)163 std::unique_ptr<RenderPipelineStage> GetBlendingStage(
164 const PassesDecoderState* dec_state,
165 const ColorEncoding& frame_color_encoding) {
166 return jxl::make_unique<BlendingStage>(dec_state, frame_color_encoding);
167 }
168
169 // NOLINTNEXTLINE(google-readability-namespace-comments)
170 } // namespace HWY_NAMESPACE
171 } // namespace jxl
172 HWY_AFTER_NAMESPACE();
173
174 #if HWY_ONCE
175 namespace jxl {
176
177 HWY_EXPORT(GetBlendingStage);
178
GetBlendingStage(const PassesDecoderState * dec_state,const ColorEncoding & frame_color_encoding)179 std::unique_ptr<RenderPipelineStage> GetBlendingStage(
180 const PassesDecoderState* dec_state,
181 const ColorEncoding& frame_color_encoding) {
182 return HWY_DYNAMIC_DISPATCH(GetBlendingStage)(dec_state,
183 frame_color_encoding);
184 }
185
186 } // namespace jxl
187 #endif
188