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