192 lines
6.1 KiB
C++
192 lines
6.1 KiB
C++
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
#include "lib/jxl/blending.h"
|
|
|
|
#include <jxl/memory_manager.h>
|
|
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "lib/jxl/alpha.h"
|
|
#include "lib/jxl/base/status.h"
|
|
#include "lib/jxl/dec_patch_dictionary.h"
|
|
#include "lib/jxl/frame_header.h"
|
|
#include "lib/jxl/image.h"
|
|
#include "lib/jxl/image_metadata.h"
|
|
|
|
namespace jxl {
|
|
|
|
bool NeedsBlending(const FrameHeader& frame_header) {
|
|
if (!(frame_header.frame_type == FrameType::kRegularFrame ||
|
|
frame_header.frame_type == FrameType::kSkipProgressive)) {
|
|
return false;
|
|
}
|
|
const auto& info = frame_header.blending_info;
|
|
bool replace_all = (info.mode == BlendMode::kReplace);
|
|
for (const auto& ec_i : frame_header.extra_channel_blending_info) {
|
|
if (ec_i.mode != BlendMode::kReplace) {
|
|
replace_all = false;
|
|
}
|
|
}
|
|
// Replace the full frame: nothing to do.
|
|
if (!frame_header.custom_size_or_origin && replace_all) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Status PerformBlending(
|
|
JxlMemoryManager* memory_manager, 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++) {
|
|
if (extra_channel_info[i].type == jxl::ExtraChannel::kAlpha) {
|
|
has_alpha = true;
|
|
break;
|
|
}
|
|
}
|
|
JXL_ASSIGN_OR_RETURN(ImageF tmp,
|
|
ImageF::Create(memory_manager, xsize, 3 + num_ec));
|
|
// Blend extra channels first so that we use the pre-blending alpha.
|
|
for (size_t i = 0; i < num_ec; i++) {
|
|
switch (ec_blending[i].mode) {
|
|
case PatchBlendMode::kAdd:
|
|
for (size_t x = 0; x < xsize; x++) {
|
|
tmp.Row(3 + i)[x] = bg[3 + i][x + x0] + fg[3 + i][x + x0];
|
|
}
|
|
continue;
|
|
|
|
case PatchBlendMode::kBlendAbove: {
|
|
size_t alpha = ec_blending[i].alpha_channel;
|
|
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
|
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);
|
|
continue;
|
|
}
|
|
|
|
case PatchBlendMode::kBlendBelow: {
|
|
size_t alpha = ec_blending[i].alpha_channel;
|
|
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
|
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);
|
|
continue;
|
|
}
|
|
|
|
case PatchBlendMode::kAlphaWeightedAddAbove: {
|
|
size_t alpha = ec_blending[i].alpha_channel;
|
|
PerformAlphaWeightedAdd(bg[3 + i] + x0, fg[3 + i] + x0,
|
|
fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
|
|
ec_blending[i].clamp);
|
|
continue;
|
|
}
|
|
|
|
case PatchBlendMode::kAlphaWeightedAddBelow: {
|
|
size_t alpha = ec_blending[i].alpha_channel;
|
|
PerformAlphaWeightedAdd(fg[3 + i] + x0, bg[3 + i] + x0,
|
|
bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
|
|
ec_blending[i].clamp);
|
|
continue;
|
|
}
|
|
|
|
case PatchBlendMode::kMul:
|
|
PerformMulBlending(bg[3 + i] + x0, fg[3 + i] + x0, tmp.Row(3 + i),
|
|
xsize, ec_blending[i].clamp);
|
|
continue;
|
|
|
|
case PatchBlendMode::kReplace:
|
|
if (xsize) memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg));
|
|
continue;
|
|
|
|
case PatchBlendMode::kNone:
|
|
if (xsize) memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg));
|
|
continue;
|
|
}
|
|
}
|
|
size_t alpha = color_blending.alpha_channel;
|
|
|
|
const auto add = [&]() {
|
|
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 + x0] + fg[p][x + x0];
|
|
}
|
|
}
|
|
};
|
|
|
|
const auto blend_weighted = [&](const float* const* bottom,
|
|
const float* const* top) {
|
|
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
|
PerformAlphaBlending(
|
|
{bottom[0] + x0, bottom[1] + x0, bottom[2] + x0,
|
|
bottom[3 + alpha] + x0},
|
|
{top[0] + x0, top[1] + x0, top[2] + x0, top[3 + alpha] + x0},
|
|
{tmp.Row(0), tmp.Row(1), tmp.Row(2), tmp.Row(3 + alpha)}, xsize,
|
|
is_premultiplied, color_blending.clamp);
|
|
};
|
|
|
|
const auto add_weighted = [&](const float* const* bottom,
|
|
const float* const* top) {
|
|
for (size_t c = 0; c < 3; c++) {
|
|
PerformAlphaWeightedAdd(bottom[c] + x0, top[c] + x0, top[3 + alpha] + x0,
|
|
tmp.Row(c), xsize, color_blending.clamp);
|
|
}
|
|
};
|
|
|
|
const auto copy = [&](const float* const* src) {
|
|
for (size_t p = 0; p < 3; p++) {
|
|
memcpy(tmp.Row(p), src[p] + x0, xsize * sizeof(**src));
|
|
}
|
|
};
|
|
|
|
switch (color_blending.mode) {
|
|
case PatchBlendMode::kAdd:
|
|
add();
|
|
break;
|
|
|
|
case PatchBlendMode::kAlphaWeightedAddAbove:
|
|
has_alpha ? add_weighted(bg, fg) : add();
|
|
break;
|
|
|
|
case PatchBlendMode::kAlphaWeightedAddBelow:
|
|
has_alpha ? add_weighted(fg, bg) : add();
|
|
break;
|
|
|
|
case PatchBlendMode::kBlendAbove:
|
|
has_alpha ? blend_weighted(bg, fg) : copy(fg);
|
|
break;
|
|
|
|
case PatchBlendMode::kBlendBelow:
|
|
has_alpha ? blend_weighted(fg, bg) : copy(fg);
|
|
break;
|
|
|
|
case PatchBlendMode::kMul:
|
|
for (int p = 0; p < 3; p++) {
|
|
PerformMulBlending(bg[p] + x0, fg[p] + x0, tmp.Row(p), xsize,
|
|
color_blending.clamp);
|
|
}
|
|
break;
|
|
|
|
case PatchBlendMode::kReplace:
|
|
copy(fg);
|
|
break;
|
|
|
|
case PatchBlendMode::kNone:
|
|
copy(bg);
|
|
}
|
|
|
|
for (size_t i = 0; i < 3 + num_ec; i++) {
|
|
if (xsize != 0) memcpy(out[i] + x0, tmp.Row(i), xsize * sizeof(**out));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace jxl
|