summaryrefslogtreecommitdiff
path: root/lib/jxl/blending.cc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/jxl/blending.cc')
-rw-r--r--lib/jxl/blending.cc392
1 files changed, 36 insertions, 356 deletions
diff --git a/lib/jxl/blending.cc b/lib/jxl/blending.cc
index 5125ccf..ab37fda 100644
--- a/lib/jxl/blending.cc
+++ b/lib/jxl/blending.cc
@@ -10,67 +10,7 @@
namespace jxl {
-namespace {
-
-// Given two rects A and B, returns a set of rects whose union is A \ B. This
-// may require from 0 to 4 rects, one for each non-empty side of B. `storage`
-// must have room to accommodate that many rects. The order is consistent when
-// called successively with "parallel" rects.
-//
-// +----------------------+
-// | top |
-// +------+-------+-------+
-// | left | inner | right |
-// +------+-------+-------+
-// | bottom |
-// +----------------------+
-Span<const Rect> SubtractRect(const Rect& outer, const Rect& inner,
- Rect* storage) {
- size_t num_rects = 0;
-
- const Rect intersection = inner.Intersection(outer);
- if (intersection.xsize() == 0 && intersection.ysize() == 0) {
- storage[num_rects++] = outer;
- return Span<const Rect>(storage, num_rects);
- }
-
- // Left, same height as inner
- if (outer.x0() < inner.x0()) {
- storage[num_rects++] =
- Rect(outer.x0(), inner.y0(),
- std::min(outer.xsize(), inner.x0() - outer.x0()), inner.ysize());
- }
-
- // Right, same height as inner
- if (outer.x0() + outer.xsize() > inner.x0() + inner.xsize()) {
- storage[num_rects++] =
- Rect(inner.x0() + inner.xsize(), inner.y0(),
- std::min(outer.xsize(), outer.x0() + outer.xsize() -
- (inner.x0() + inner.xsize())),
- inner.ysize());
- }
-
- // Top, full width
- if (outer.y0() < inner.y0()) {
- storage[num_rects++] =
- Rect(outer.x0(), outer.y0(), outer.xsize(),
- std::min(outer.ysize(), inner.y0() - outer.y0()));
- }
-
- // Bottom, full width
- if (outer.y0() + outer.ysize() > inner.y0() + inner.ysize()) {
- storage[num_rects++] =
- Rect(outer.x0(), inner.y0() + inner.ysize(), outer.xsize(),
- std::min(outer.ysize(), outer.y0() + outer.ysize() -
- (inner.y0() + inner.ysize())));
- }
-
- return Span<const Rect>(storage, num_rects);
-}
-
-} // namespace
-
-bool ImageBlender::NeedsBlending(PassesDecoderState* dec_state) {
+bool NeedsBlending(PassesDecoderState* dec_state) {
const PassesSharedState& state = *dec_state->shared;
if (!(state.frame_header.frame_type == FrameType::kRegularFrame ||
state.frame_header.frame_type == FrameType::kSkipProgressive)) {
@@ -90,253 +30,11 @@ bool ImageBlender::NeedsBlending(PassesDecoderState* dec_state) {
return true;
}
-Status ImageBlender::PrepareBlending(
- PassesDecoderState* dec_state, FrameOrigin foreground_origin,
- size_t foreground_xsize, size_t foreground_ysize,
- const std::vector<ExtraChannelInfo>* extra_channel_info,
- const ColorEncoding& frame_color_encoding, const Rect& frame_rect,
- Image3F* output, const Rect& output_rect,
- std::vector<ImageF>* output_extra_channels,
- std::vector<Rect> output_extra_channels_rects) {
- const PassesSharedState& state = *dec_state->shared;
- info_ = state.frame_header.blending_info;
-
- ec_info_ = &state.frame_header.extra_channel_blending_info;
-
- frame_rect_ = frame_rect;
- extra_channel_info_ = extra_channel_info;
- output_ = output;
- output_rect_ = output_rect;
- output_extra_channels_ = output_extra_channels;
- output_extra_channels_rects_ = std::move(output_extra_channels_rects);
-
- size_t image_xsize = state.frame_header.nonserialized_metadata->xsize();
- size_t image_ysize = state.frame_header.nonserialized_metadata->ysize();
-
- // the rect in the canvas that needs to be updated
- cropbox_ = frame_rect;
- // the rect of the foreground that overlaps with the canvas
- overlap_ = cropbox_;
- o_ = foreground_origin;
- o_.x0 -= frame_rect.x0();
- o_.y0 -= frame_rect.y0();
- int x0 = (o_.x0 >= 0 ? o_.x0 : 0);
- int y0 = (o_.y0 >= 0 ? o_.y0 : 0);
- int xsize = foreground_xsize;
- if (o_.x0 < 0) xsize += o_.x0;
- int ysize = foreground_ysize;
- if (o_.y0 < 0) ysize += o_.y0;
- xsize = Clamp1(xsize, 0, (int)cropbox_.xsize() - x0);
- ysize = Clamp1(ysize, 0, (int)cropbox_.ysize() - y0);
- cropbox_ = Rect(x0, y0, xsize, ysize);
- x0 = (o_.x0 < 0 ? -o_.x0 : 0);
- y0 = (o_.y0 < 0 ? -o_.y0 : 0);
- overlap_ = Rect(x0, y0, xsize, ysize);
-
- // Image to write to.
- ImageBundle& bg = *state.reference_frames[info_.source].frame;
- bg_ = &bg;
- if (bg.xsize() == 0 && bg.ysize() == 0) {
- // there is no background, assume it to be all zeroes
- ImageBundle empty(&state.metadata->m);
- Image3F color(image_xsize, image_ysize);
- ZeroFillImage(&color);
- empty.SetFromImage(std::move(color), frame_color_encoding);
- if (!output_extra_channels_->empty()) {
- std::vector<ImageF> ec;
- for (size_t i = 0; i < output_extra_channels_->size(); ++i) {
- ImageF eci(image_xsize, image_ysize);
- ZeroFillImage(&eci);
- ec.push_back(std::move(eci));
- }
- empty.SetExtraChannels(std::move(ec));
- }
- bg = std::move(empty);
- } else if (state.reference_frames[info_.source].ib_is_in_xyb) {
- return JXL_FAILURE(
- "Trying to blend XYB reference frame %i and non-XYB frame",
- info_.source);
- }
-
- if (bg.xsize() < image_xsize || bg.ysize() < image_ysize ||
- bg.origin.x0 != 0 || bg.origin.y0 != 0) {
- return JXL_FAILURE("Trying to use a %zux%zu crop as a background",
- bg.xsize(), bg.ysize());
- }
- if (state.metadata->m.xyb_encoded) {
- if (!dec_state->output_encoding_info.color_encoding_is_original) {
- return JXL_FAILURE("Blending in unsupported color space");
- }
- }
-
- if (!overlap_.IsInside(Rect(0, 0, foreground_xsize, foreground_ysize))) {
- return JXL_FAILURE("Trying to use a %zux%zu crop as a foreground",
- foreground_xsize, foreground_ysize);
- }
-
- if (!cropbox_.IsInside(bg)) {
- return JXL_FAILURE(
- "Trying blend %zux%zu to (%zu,%zu), but background is %zux%zu",
- cropbox_.xsize(), cropbox_.ysize(), cropbox_.x0(), cropbox_.y0(),
- bg.xsize(), bg.ysize());
- }
-
- Rect frame_rects_storage[4], output_rects_storage[4];
- Span<const Rect> frame_rects = SubtractRect(
- frame_rect, cropbox_.Translate(frame_rect.x0(), frame_rect.y0()),
- frame_rects_storage);
- Span<const Rect> output_rects = SubtractRect(
- output_rect, cropbox_.Translate(output_rect.x0(), output_rect.y0()),
- output_rects_storage);
- JXL_ASSERT(frame_rects.size() == output_rects.size());
- for (size_t i = 0; i < frame_rects.size(); ++i) {
- CopyImageTo(frame_rects[i], *bg.color(), output_rects[i], output);
- }
- for (size_t i = 0; i < ec_info_->size(); ++i) {
- const auto& eci = (*ec_info_)[i];
- const auto& src = *state.reference_frames[eci.source].frame;
- output_rects =
- SubtractRect(output_extra_channels_rects_[i],
- cropbox_.Translate(output_extra_channels_rects_[i].x0(),
- output_extra_channels_rects_[i].y0()),
- output_rects_storage);
- if (src.xsize() == 0 && src.ysize() == 0) {
- for (size_t j = 0; j < output_rects.size(); ++j) {
- ZeroFillPlane(&(*output_extra_channels_)[i], output_rects[j]);
- }
- } else {
- if (src.extra_channels()[i].xsize() < image_xsize ||
- src.extra_channels()[i].ysize() < image_ysize || src.origin.x0 != 0 ||
- src.origin.y0 != 0) {
- return JXL_FAILURE(
- "Invalid size %zux%zu or origin %+d%+d for extra channel %zu of "
- "reference frame %zu, expected at least %zux%zu+0+0",
- src.extra_channels()[i].xsize(), src.extra_channels()[i].ysize(),
- static_cast<int>(src.origin.x0), static_cast<int>(src.origin.y0), i,
- static_cast<size_t>(eci.source), image_xsize, image_ysize);
- }
- for (size_t j = 0; j < frame_rects.size(); ++j) {
- CopyImageTo(frame_rects[j], src.extra_channels()[i], output_rects[j],
- &(*output_extra_channels_)[i]);
- }
- }
- }
-
- return true;
-}
-
-ImageBlender::RectBlender ImageBlender::PrepareRect(
- const Rect& rect, const Image3F& foreground,
- const std::vector<ImageF>& extra_channels, const Rect& input_rect) const {
- JXL_DASSERT(rect.xsize() == input_rect.xsize());
- JXL_DASSERT(rect.ysize() == input_rect.ysize());
- JXL_DASSERT(input_rect.IsInside(foreground));
-
- RectBlender blender(false);
- blender.extra_channel_info_ = extra_channel_info_;
-
- blender.current_overlap_ = rect.Intersection(overlap_);
- if (blender.current_overlap_.xsize() == 0 ||
- blender.current_overlap_.ysize() == 0) {
- blender.done_ = true;
- return blender;
- }
-
- blender.current_cropbox_ =
- Rect(o_.x0 + blender.current_overlap_.x0(),
- o_.y0 + blender.current_overlap_.y0(),
- blender.current_overlap_.xsize(), blender.current_overlap_.ysize());
-
- // Turn current_overlap_ from being relative to the full foreground to being
- // relative to the rect or input_rect.
- blender.current_overlap_ =
- Rect(blender.current_overlap_.x0() - rect.x0(),
- blender.current_overlap_.y0() - rect.y0(),
- blender.current_overlap_.xsize(), blender.current_overlap_.ysize());
-
- // And this one is relative to the `foreground` subimage.
- const Rect input_overlap(blender.current_overlap_.x0() + input_rect.x0(),
- blender.current_overlap_.y0() + input_rect.y0(),
- blender.current_overlap_.xsize(),
- blender.current_overlap_.ysize());
-
- blender.blending_info_.resize(extra_channels.size() + 1);
- auto make_blending = [&](const BlendingInfo& info, PatchBlending* pb) {
- pb->alpha_channel = info.alpha_channel;
- pb->clamp = info.clamp;
- switch (info.mode) {
- case BlendMode::kReplace: {
- pb->mode = PatchBlendMode::kReplace;
- break;
- }
- case BlendMode::kAdd: {
- pb->mode = PatchBlendMode::kAdd;
- break;
- }
- case BlendMode::kMul: {
- pb->mode = PatchBlendMode::kMul;
- break;
- }
- case BlendMode::kBlend: {
- pb->mode = PatchBlendMode::kBlendAbove;
- break;
- }
- case BlendMode::kAlphaWeightedAdd: {
- pb->mode = PatchBlendMode::kAlphaWeightedAddAbove;
- break;
- }
- default: {
- JXL_ABORT("Invalid blend mode"); // should have failed to decode
- }
- }
- };
- make_blending(info_, &blender.blending_info_[0]);
- for (size_t i = 0; i < extra_channels.size(); i++) {
- make_blending((*ec_info_)[i], &blender.blending_info_[1 + i]);
- }
-
- Rect cropbox_row = blender.current_cropbox_.Line(0);
- Rect overlap_row = input_overlap.Line(0);
- const auto num_ptrs = 3 + extra_channels.size();
- blender.fg_ptrs_.reserve(num_ptrs);
- blender.fg_strides_.reserve(num_ptrs);
- blender.bg_ptrs_.reserve(num_ptrs);
- blender.bg_strides_.reserve(num_ptrs);
- for (size_t c = 0; c < 3; c++) {
- blender.fg_ptrs_.push_back(overlap_row.ConstPlaneRow(foreground, c, 0));
- blender.fg_strides_.push_back(foreground.PixelsPerRow());
- blender.bg_ptrs_.push_back(
- cropbox_row.Translate(frame_rect_.x0(), frame_rect_.y0())
- .PlaneRow(bg_->color(), c, 0));
- blender.bg_strides_.push_back(bg_->color()->PixelsPerRow());
- blender.out_ptrs_.push_back(
- cropbox_row.Translate(output_rect_.x0(), output_rect_.y0())
- .PlaneRow(output_, c, 0));
- blender.out_strides_.push_back(output_->PixelsPerRow());
- }
- for (size_t c = 0; c < extra_channels.size(); c++) {
- blender.fg_ptrs_.push_back(overlap_row.ConstRow(extra_channels[c], 0));
- blender.fg_strides_.push_back(extra_channels[c].PixelsPerRow());
- blender.bg_ptrs_.push_back(
- cropbox_row.Translate(frame_rect_.x0(), frame_rect_.y0())
- .Row(&bg_->extra_channels()[c], 0));
- blender.bg_strides_.push_back(bg_->extra_channels()[c].PixelsPerRow());
- blender.out_ptrs_.push_back(
- cropbox_row
- .Translate(output_extra_channels_rects_[c].x0(),
- output_extra_channels_rects_[c].y0())
- .Row(&(*output_extra_channels_)[c], 0));
- blender.out_strides_.push_back((*output_extra_channels_)[c].PixelsPerRow());
- }
-
- return blender;
-}
-
-Status PerformBlending(
- const float* const* bg, const float* const* fg, float* const* out,
- size_t xsize, const PatchBlending& color_blending,
- const PatchBlending* ec_blending,
- const std::vector<ExtraChannelInfo>& extra_channel_info) {
+void PerformBlending(const float* const* bg, const float* const* fg,
+ float* const* out, size_t x0, size_t xsize,
+ const PatchBlending& color_blending,
+ const PatchBlending* ec_blending,
+ const std::vector<ExtraChannelInfo>& extra_channel_info) {
bool has_alpha = false;
size_t num_ec = extra_channel_info.size();
for (size_t i = 0; i < num_ec; i++) {
@@ -350,35 +48,37 @@ Status PerformBlending(
for (size_t i = 0; i < num_ec; i++) {
if (ec_blending[i].mode == PatchBlendMode::kAdd) {
for (size_t x = 0; x < xsize; x++) {
- tmp.Row(3 + i)[x] = bg[3 + i][x] + fg[3 + i][x];
+ tmp.Row(3 + i)[x] = bg[3 + i][x + x0] + fg[3 + i][x + x0];
}
} else if (ec_blending[i].mode == PatchBlendMode::kBlendAbove) {
size_t alpha = ec_blending[i].alpha_channel;
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
- PerformAlphaBlending(bg[3 + i], bg[3 + alpha], fg[3 + i], fg[3 + alpha],
- tmp.Row(3 + i), xsize, is_premultiplied,
- ec_blending[i].clamp);
+ PerformAlphaBlending(bg[3 + i] + x0, bg[3 + alpha] + x0, fg[3 + i] + x0,
+ fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
+ is_premultiplied, ec_blending[i].clamp);
} else if (ec_blending[i].mode == PatchBlendMode::kBlendBelow) {
size_t alpha = ec_blending[i].alpha_channel;
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
- PerformAlphaBlending(fg[3 + i], fg[3 + alpha], bg[3 + i], bg[3 + alpha],
- tmp.Row(3 + i), xsize, is_premultiplied,
- ec_blending[i].clamp);
+ PerformAlphaBlending(fg[3 + i] + x0, fg[3 + alpha] + x0, bg[3 + i] + x0,
+ bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
+ is_premultiplied, ec_blending[i].clamp);
} else if (ec_blending[i].mode == PatchBlendMode::kAlphaWeightedAddAbove) {
size_t alpha = ec_blending[i].alpha_channel;
- PerformAlphaWeightedAdd(bg[3 + i], fg[3 + i], fg[3 + alpha],
- tmp.Row(3 + i), xsize, ec_blending[i].clamp);
+ PerformAlphaWeightedAdd(bg[3 + i] + x0, fg[3 + i] + x0,
+ fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
+ ec_blending[i].clamp);
} else if (ec_blending[i].mode == PatchBlendMode::kAlphaWeightedAddBelow) {
size_t alpha = ec_blending[i].alpha_channel;
- PerformAlphaWeightedAdd(fg[3 + i], bg[3 + i], bg[3 + alpha],
- tmp.Row(3 + i), xsize, ec_blending[i].clamp);
+ PerformAlphaWeightedAdd(fg[3 + i] + x0, bg[3 + i] + x0,
+ bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
+ ec_blending[i].clamp);
} else if (ec_blending[i].mode == PatchBlendMode::kMul) {
- PerformMulBlending(bg[3 + i], fg[3 + i], tmp.Row(3 + i), xsize,
+ PerformMulBlending(bg[3 + i] + x0, fg[3 + i] + x0, tmp.Row(3 + i), xsize,
ec_blending[i].clamp);
} else if (ec_blending[i].mode == PatchBlendMode::kReplace) {
- memcpy(tmp.Row(3 + i), fg[3 + i], xsize * sizeof(**fg));
+ memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg));
} else if (ec_blending[i].mode == PatchBlendMode::kNone) {
- memcpy(tmp.Row(3 + i), bg[3 + i], xsize * sizeof(**fg));
+ if (xsize) memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg));
} else {
JXL_ABORT("Unreachable");
}
@@ -393,7 +93,7 @@ Status PerformBlending(
for (int p = 0; p < 3; p++) {
float* out = tmp.Row(p);
for (size_t x = 0; x < xsize; x++) {
- out[x] = bg[p][x] + fg[p][x];
+ out[x] = bg[p][x + x0] + fg[p][x + x0];
}
}
} else if (color_blending.mode == PatchBlendMode::kBlendAbove
@@ -401,8 +101,8 @@ Status PerformBlending(
&& has_alpha) {
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
PerformAlphaBlending(
- {bg[0], bg[1], bg[2], bg[3 + alpha]},
- {fg[0], fg[1], fg[2], fg[3 + alpha]},
+ {bg[0] + x0, bg[1] + x0, bg[2] + x0, bg[3 + alpha] + x0},
+ {fg[0] + x0, fg[1] + x0, fg[2] + x0, fg[3 + alpha] + x0},
{tmp.Row(0), tmp.Row(1), tmp.Row(2), tmp.Row(3 + alpha)}, xsize,
is_premultiplied, color_blending.clamp);
} else if (color_blending.mode == PatchBlendMode::kBlendBelow
@@ -410,63 +110,43 @@ Status PerformBlending(
&& has_alpha) {
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
PerformAlphaBlending(
- {fg[0], fg[1], fg[2], fg[3 + alpha]},
- {bg[0], bg[1], bg[2], bg[3 + alpha]},
+ {fg[0] + x0, fg[1] + x0, fg[2] + x0, fg[3 + alpha] + x0},
+ {bg[0] + x0, bg[1] + x0, bg[2] + x0, bg[3 + alpha] + x0},
{tmp.Row(0), tmp.Row(1), tmp.Row(2), tmp.Row(3 + alpha)}, xsize,
is_premultiplied, color_blending.clamp);
} else if (color_blending.mode == PatchBlendMode::kAlphaWeightedAddAbove) {
JXL_DASSERT(has_alpha);
for (size_t c = 0; c < 3; c++) {
- PerformAlphaWeightedAdd(bg[c], fg[c], fg[3 + alpha], tmp.Row(c), xsize,
- color_blending.clamp);
+ PerformAlphaWeightedAdd(bg[c] + x0, fg[c] + x0, fg[3 + alpha] + x0,
+ tmp.Row(c), xsize, color_blending.clamp);
}
} else if (color_blending.mode == PatchBlendMode::kAlphaWeightedAddBelow) {
JXL_DASSERT(has_alpha);
for (size_t c = 0; c < 3; c++) {
- PerformAlphaWeightedAdd(fg[c], bg[c], bg[3 + alpha], tmp.Row(c), xsize,
- color_blending.clamp);
+ PerformAlphaWeightedAdd(fg[c] + x0, bg[c] + x0, bg[3 + alpha] + x0,
+ tmp.Row(c), xsize, color_blending.clamp);
}
} else if (color_blending.mode == PatchBlendMode::kMul) {
for (int p = 0; p < 3; p++) {
- PerformMulBlending(bg[p], fg[p], tmp.Row(p), xsize, color_blending.clamp);
+ PerformMulBlending(bg[p] + x0, fg[p] + x0, tmp.Row(p), xsize,
+ color_blending.clamp);
}
} else if (color_blending.mode == PatchBlendMode::kReplace ||
color_blending.mode == PatchBlendMode::kBlendAbove ||
color_blending.mode == PatchBlendMode::kBlendBelow) { // kReplace
for (size_t p = 0; p < 3; p++) {
- memcpy(tmp.Row(p), fg[p], xsize * sizeof(**fg));
+ memcpy(tmp.Row(p), fg[p] + x0, xsize * sizeof(**fg));
}
} else if (color_blending.mode == PatchBlendMode::kNone) {
for (size_t p = 0; p < 3; p++) {
- memcpy(tmp.Row(p), bg[p], xsize * sizeof(**fg));
+ memcpy(tmp.Row(p), bg[p] + x0, xsize * sizeof(**fg));
}
} else {
JXL_ABORT("Unreachable");
}
for (size_t i = 0; i < 3 + num_ec; i++) {
- memcpy(out[i], tmp.Row(i), xsize * sizeof(**out));
- }
- return true;
-}
-
-Status ImageBlender::RectBlender::DoBlending(size_t y) {
- if (done_ || y < current_overlap_.y0() ||
- y >= current_overlap_.y0() + current_overlap_.ysize()) {
- return true;
- }
- y -= current_overlap_.y0();
- fg_row_ptrs_.resize(fg_ptrs_.size());
- bg_row_ptrs_.resize(bg_ptrs_.size());
- out_row_ptrs_.resize(out_ptrs_.size());
- for (size_t c = 0; c < fg_row_ptrs_.size(); c++) {
- fg_row_ptrs_[c] = fg_ptrs_[c] + y * fg_strides_[c];
- bg_row_ptrs_[c] = bg_ptrs_[c] + y * bg_strides_[c];
- out_row_ptrs_[c] = out_ptrs_[c] + y * out_strides_[c];
+ if (xsize != 0) memcpy(out[i] + x0, tmp.Row(i), xsize * sizeof(**out));
}
- return PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(),
- out_row_ptrs_.data(), current_overlap_.xsize(),
- blending_info_[0], blending_info_.data() + 1,
- *extra_channel_info_);
}
} // namespace jxl