Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct BrightContrastApplyOp {
|
||||
float mul;
|
||||
float add;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
|
||||
float4 result;
|
||||
result = input * this->mul + this->add;
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void brightcontrast_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModBrightContrast", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const BrightContrastModifierData *bcmd = reinterpret_cast<BrightContrastModifierData *>(smd);
|
||||
|
||||
BrightContrastApplyOp op;
|
||||
|
||||
/* The algorithm is by Werner D. Streidt
|
||||
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
|
||||
* Extracted from OpenCV `demhist.cpp`. */
|
||||
const float brightness = bcmd->bright / 100.0f;
|
||||
const float contrast = bcmd->contrast;
|
||||
float delta = contrast / 200.0f;
|
||||
|
||||
if (contrast > 0) {
|
||||
op.mul = 1.0f - delta * 2.0f;
|
||||
op.mul = 1.0f / max_ff(op.mul, FLT_EPSILON);
|
||||
op.add = op.mul * (brightness - delta);
|
||||
}
|
||||
else {
|
||||
delta *= -1;
|
||||
op.mul = max_ff(1.0f - delta * 2.0f, 0.0f);
|
||||
op.add = op.mul * brightness + delta;
|
||||
}
|
||||
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void brightcontrast_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
layout.prop(ptr, "bright", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout.prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void brightcontrast_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_BrightContrast, brightcontrast_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_BrightContrast = {
|
||||
/*idname*/ "BrightContrast",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Brightness/Contrast"),
|
||||
/*struct_name*/ "BrightContrastModifierData",
|
||||
/*struct_size*/ sizeof(BrightContrastModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ brightcontrast_apply,
|
||||
/*panel_register*/ brightcontrast_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,396 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* Lift-Gamma-Gain math. NOTE: lift is actually (2-lift). */
|
||||
static float color_balance_lgg(
|
||||
float in, const float lift, const float gain, const float gamma, const float mul)
|
||||
{
|
||||
float x = (((in - 1.0f) * lift) + 1.0f) * gain;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, gamma) * mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Slope-Offset-Power (ASC CDL) math, see https://en.wikipedia.org/wiki/ASC_CDL */
|
||||
static float color_balance_sop(
|
||||
float in, const float slope, const float offset, const float power, float mul)
|
||||
{
|
||||
float x = in * slope + offset;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, power);
|
||||
x *= mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a larger lookup table than 256 possible byte values: due to alpha
|
||||
* pre-multiplication, dark values with low alphas might need more precision.
|
||||
*/
|
||||
static constexpr int CB_TABLE_SIZE = 1024;
|
||||
|
||||
static void make_cb_table_lgg(
|
||||
float lift, float gain, float gamma, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_lgg(x, lift, gain, gamma, mul);
|
||||
}
|
||||
}
|
||||
|
||||
static void make_cb_table_sop(
|
||||
float slope, float offset, float power, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_sop(x, slope, offset, power, mul);
|
||||
}
|
||||
}
|
||||
|
||||
struct ColorBalanceApplyOp {
|
||||
int method;
|
||||
float3 lift, gain, gamma;
|
||||
float3 slope, offset, power;
|
||||
float multiplier;
|
||||
float lut[3][CB_TABLE_SIZE];
|
||||
|
||||
/* Apply on a byte image via a table lookup. */
|
||||
template<typename MaskSampler>
|
||||
void apply(uchar *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
int p0 = int(input.x * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p1 = int(input.y * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p2 = int(input.z * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
result.x = this->lut[0][p0];
|
||||
result.y = this->lut[1][p1];
|
||||
result.z = this->lut[2][p2];
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply on a float image by doing full math. */
|
||||
template<typename MaskSampler>
|
||||
void apply(float *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Lift/Gamma/Gain */
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_lgg(
|
||||
input.x, this->lift.x, this->gain.x, this->gamma.x, this->multiplier);
|
||||
result.y = color_balance_lgg(
|
||||
input.y, this->lift.y, this->gain.y, this->gamma.y, this->multiplier);
|
||||
result.z = color_balance_lgg(
|
||||
input.z, this->lift.z, this->gain.z, this->gamma.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
/* Slope/Offset/Power */
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_sop(
|
||||
input.x, this->slope.x, this->offset.x, this->power.x, this->multiplier);
|
||||
result.y = color_balance_sop(
|
||||
input.y, this->slope.y, this->offset.y, this->power.y, this->multiplier);
|
||||
result.z = color_balance_sop(
|
||||
input.z, this->slope.z, this->offset.z, this->power.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_lgg(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN);
|
||||
|
||||
this->lift = 2.0f - float3(data.lift);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_LIFT) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
/* tweak to give more subtle results
|
||||
* values above 1.0 are scaled */
|
||||
if (this->lift[c] > 1.0f) {
|
||||
this->lift[c] = powf(this->lift[c] - 1.0f, 2.0f) + 1.0f;
|
||||
}
|
||||
this->lift[c] = 2.0f - this->lift[c];
|
||||
}
|
||||
}
|
||||
|
||||
this->gain = float3(data.gain);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_GAIN) {
|
||||
this->gain = math::rcp(math::max(this->gain, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->gamma = float3(data.gamma);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_GAMMA)) {
|
||||
this->gamma = math::rcp(math::max(this->gamma, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init_sop(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER);
|
||||
|
||||
this->slope = float3(data.slope);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_SLOPE) {
|
||||
this->slope = math::rcp(math::max(this->slope, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->offset = float3(data.offset) - 1.0f;
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_OFFSET) {
|
||||
this->offset = -this->offset;
|
||||
}
|
||||
|
||||
this->power = float3(data.power);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_POWER)) {
|
||||
this->power = math::rcp(math::max(this->power, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init(const ColorBalanceModifierData &data, bool byte_image)
|
||||
{
|
||||
this->multiplier = data.color_multiply;
|
||||
this->method = data.color_balance.method;
|
||||
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
init_lgg(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_lgg(
|
||||
this->lift[c], this->gain[c], this->gamma[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
init_sop(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_sop(
|
||||
this->slope[c], this->offset[c], this->power[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void colorBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
ColorBalanceModifierData *cbmd = reinterpret_cast<ColorBalanceModifierData *>(smd);
|
||||
|
||||
cbmd->color_multiply = 1.0f;
|
||||
cbmd->color_balance.method = SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN;
|
||||
|
||||
for (int c = 0; c < 3; c++) {
|
||||
cbmd->color_balance.lift[c] = 1.0f;
|
||||
cbmd->color_balance.gamma[c] = 1.0f;
|
||||
cbmd->color_balance.gain[c] = 1.0f;
|
||||
cbmd->color_balance.slope[c] = 1.0f;
|
||||
cbmd->color_balance.offset[c] = 1.0f;
|
||||
cbmd->color_balance.power[c] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModColorBalance", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const ColorBalanceModifierData *cbmd = reinterpret_cast<const ColorBalanceModifierData *>(smd);
|
||||
|
||||
ColorBalanceApplyOp op;
|
||||
op.init(*cbmd, context.result.image->byte_data() != nullptr);
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
PointerRNA color_balance = RNA_pointer_get(ptr, "color_balance");
|
||||
const int correction_method = RNA_enum_get(&color_balance, "correction_method");
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
layout.prop(ptr, "color_multiply", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout.prop(&color_balance, "correction_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
ui::Layout &flow = layout.grid_flow(true, 0, true, false, false);
|
||||
flow.use_property_split_set(false);
|
||||
if (correction_method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Split into separate scopes to be able to reuse "split" and "col" variable names. */
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Lift"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "lift", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_lift", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "lift", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Gamma"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "gamma", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_gamma", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "gamma", true, false, true, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Gain"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "gain", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_gain", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "gain", true, false, true, true);
|
||||
}
|
||||
}
|
||||
else if (correction_method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Offset"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "offset", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_offset", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "offset", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Power"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "power", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_power", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "power", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
ui::Layout &split = flow.column(false).split(0.35f, false);
|
||||
ui::Layout &col = split.column(true);
|
||||
col.label(IFACE_("Slope"), ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "slope", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(
|
||||
&color_balance, "invert_slope", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
|
||||
template_color_picker(&split, &color_balance, "slope", true, false, false, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_ColorBalance, colorBalance_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_ColorBalance = {
|
||||
/*idname*/ "ColorBalance",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color Balance"),
|
||||
/*struct_name*/ "ColorBalanceModifierData",
|
||||
/*struct_size*/ sizeof(ColorBalanceModifierData),
|
||||
/*init_data*/ colorBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ colorBalance_apply,
|
||||
/*panel_register*/ colorBalance_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,496 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_rotation.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "COM_domain.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_anim_data.hh"
|
||||
#include "BKE_animsys.h"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_idprop.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_runtime.hh"
|
||||
|
||||
#include "DEG_depsgraph_query.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "NOD_composite.hh"
|
||||
#include "NOD_compositor_nodes_caller_ui.hh"
|
||||
#include "NOD_compositor_nodes_srna.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_transform.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "cache/compositor_cache.hh"
|
||||
#include "compositor.hh"
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
void compositor_nodes_update_interface(Scene &sequencer_scene,
|
||||
SequencerCompositorModifierData &cmd)
|
||||
{
|
||||
if (!cmd.modifier.system_properties) {
|
||||
cmd.modifier.system_properties =
|
||||
bke::idprop::create_group("SequencerCompositorModifierProperties").release();
|
||||
}
|
||||
PointerRNA properties_ptr = RNA_pointer_create_discrete(
|
||||
&sequencer_scene.id, RNA_SequencerCompositorModifierProperties, &cmd);
|
||||
RNA_ensure_and_sync_system_properties(properties_ptr, *cmd.modifier.system_properties);
|
||||
|
||||
DEG_id_tag_update(&sequencer_scene.id, ID_RECALC_SEQUENCER_STRIPS);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void set_float_array(PointerRNA *input_props_ptr, compositor::Result &result)
|
||||
{
|
||||
T value;
|
||||
RNA_float_get_array(input_props_ptr, "value", value);
|
||||
result.set_single_value(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void set_int_array(PointerRNA *input_props_ptr, compositor::Result &result)
|
||||
{
|
||||
T value;
|
||||
RNA_int_get_array(input_props_ptr, "value", value);
|
||||
result.set_single_value(value);
|
||||
}
|
||||
|
||||
static void set_single_input_from_rna_value(PointerRNA *input_props_ptr,
|
||||
const eNodeSocketDatatype socket_type,
|
||||
compositor::Result &result,
|
||||
const std::optional<int> dimensions = {})
|
||||
{
|
||||
using namespace nodes;
|
||||
switch (socket_type) {
|
||||
case SOCK_FLOAT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const float value = RNA_float_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_VECTOR: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
switch (dimensions.value_or(3)) {
|
||||
case 2: {
|
||||
set_float_array<float2>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
set_float_array<float3>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
set_float_array<float4>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_RGBA: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
ColorGeometry4f value;
|
||||
RNA_float_get_array(input_props_ptr, "value", value);
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_BOOLEAN: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const bool value = RNA_boolean_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const int value = RNA_int_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_ROTATION: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
float3 value_euler;
|
||||
RNA_float_get_array(input_props_ptr, "value", value_euler);
|
||||
math::Quaternion value_rotation = math::to_quaternion(math::EulerXYZ(value_euler));
|
||||
result.set_single_value(value_rotation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_MENU: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const MenuValue value = MenuValue(RNA_enum_get(input_props_ptr, "value"));
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_STRING: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
const std::string value = RNA_string_get(input_props_ptr, "value");
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_INT_VECTOR: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
switch (dimensions.value_or(2)) {
|
||||
case 2: {
|
||||
set_int_array<int2>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
set_int_array<int3>(input_props_ptr, result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_OBJECT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
Object *value = RNA_pointer_get(input_props_ptr, "value").data_as<Object>();
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_FONT: {
|
||||
const auto type = CompositorNodesInputType(RNA_enum_get(input_props_ptr, "type"));
|
||||
if (type == CompositorNodesInputType::Value) {
|
||||
VFont *value = RNA_pointer_get(input_props_ptr, "value").data_as<VFont>();
|
||||
result.set_single_value(value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SOCK_IMAGE:
|
||||
case SOCK_COLLECTION:
|
||||
case SOCK_TEXTURE:
|
||||
case SOCK_MATERIAL:
|
||||
case SOCK_SCENE:
|
||||
case SOCK_TEXT_ID:
|
||||
case SOCK_MASK:
|
||||
case SOCK_SOUND:
|
||||
case SOCK_GEOMETRY:
|
||||
case SOCK_MATRIX:
|
||||
case SOCK_BUNDLE:
|
||||
case SOCK_CLOSURE:
|
||||
case SOCK_SHADER:
|
||||
case SOCK_CUSTOM:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<int> get_socket_dimension(const bNodeTreeInterfaceSocket *socket,
|
||||
const eNodeSocketDatatype socket_type)
|
||||
{
|
||||
if (socket_type == SOCK_VECTOR) {
|
||||
return static_cast<bNodeSocketValueVector *>(socket->socket_data)->dimensions;
|
||||
}
|
||||
else if (socket_type == SOCK_INT_VECTOR) {
|
||||
return static_cast<bNodeSocketValueIntVector *>(socket->socket_data)->dimensions;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
class CompositorModifierContext : public CompositorContext {
|
||||
private:
|
||||
const ModifierApplyContext &mod_context_;
|
||||
SequencerCompositorModifierData *modifier_data_;
|
||||
|
||||
ImBuf *image_buffer_;
|
||||
compositor::Result mask_;
|
||||
ImBuf *mask_buffer_ = nullptr;
|
||||
int timeline_frame_;
|
||||
bool owns_mask_ = false;
|
||||
PointerRNA properties_ptr_;
|
||||
|
||||
public:
|
||||
CompositorModifierContext(const ModifierApplyContext &mod_context,
|
||||
compositor::StaticCacheManager &cache_manager,
|
||||
SequencerCompositorModifierData *modifier_data)
|
||||
: CompositorContext(cache_manager, mod_context.render_data, mod_context.strip),
|
||||
mod_context_(mod_context),
|
||||
modifier_data_(modifier_data),
|
||||
image_buffer_(mod_context.result.image),
|
||||
mask_(*this, compositor::ResultType::Color, compositor::ResultPrecision::Full),
|
||||
timeline_frame_(mod_context.timeline_frame)
|
||||
{
|
||||
PointerRNA ptr = RNA_pointer_create_discrete(
|
||||
&mod_context.render_data.scene->id, RNA_SequencerCompositorModifierData, modifier_data);
|
||||
properties_ptr_ = RNA_pointer_get(&ptr, "properties");
|
||||
}
|
||||
|
||||
void free_resources()
|
||||
{
|
||||
IMB_freeImBuf(this->mask_buffer_);
|
||||
this->mask_buffer_ = nullptr;
|
||||
|
||||
if (this->owns_mask_) {
|
||||
this->mask_.release();
|
||||
this->owns_mask_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
compositor::Domain get_compositing_domain() const override
|
||||
{
|
||||
return compositor::Domain(int2(image_buffer_->x, image_buffer_->y));
|
||||
}
|
||||
|
||||
void write_viewer(compositor::Result &viewer_result) override
|
||||
{
|
||||
write_viewer_impl(viewer_result, *image_buffer_);
|
||||
}
|
||||
|
||||
void evaluate()
|
||||
{
|
||||
using namespace compositor;
|
||||
const StripModifierData &smd = this->modifier_data_->modifier;
|
||||
const bool is_mask_used = smd.mask_input_type == STRIP_MASK_INPUT_STRIP ?
|
||||
smd.mask_strip != nullptr :
|
||||
smd.mask_id != nullptr;
|
||||
|
||||
const bNodeTree &node_group = *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph,
|
||||
modifier_data_->node_group);
|
||||
const bke::DataBlockComputeContext compute_context(nullptr, this->get_scene().id);
|
||||
NodeGroupOperation node_group_operation(*this,
|
||||
node_group,
|
||||
this->needed_outputs(),
|
||||
node_group.active_viewer_key,
|
||||
bke::NODE_INSTANCE_KEY_BASE,
|
||||
compute_context);
|
||||
set_output_refcount(node_group, node_group_operation);
|
||||
|
||||
node_group.ensure_topology_cache();
|
||||
PointerRNA inputs_ptr = RNA_pointer_get(&properties_ptr_, "inputs");
|
||||
BLI_assert(inputs_ptr.data != nullptr);
|
||||
|
||||
/* Map the inputs to the operation. */
|
||||
Vector<std::unique_ptr<Result>> inputs;
|
||||
const Span<const bNodeTreeInterfaceSocket *> interface_inputs = node_group.interface_inputs();
|
||||
for (const bNodeTreeInterfaceSocket *input_socket : interface_inputs) {
|
||||
bke::bNodeSocketType *typeinfo = input_socket->socket_typeinfo();
|
||||
const eNodeSocketDatatype socket_type = typeinfo ? typeinfo->type : SOCK_CUSTOM;
|
||||
const bool valid_socket_type = typeinfo && node_group.typeinfo->valid_socket_type(
|
||||
node_group.typeinfo, typeinfo);
|
||||
/* Fallback to ResultType::Float for invalid inputs. */
|
||||
const ResultType result_type = valid_socket_type ?
|
||||
compositor::get_node_interface_socket_result_type(
|
||||
*input_socket) :
|
||||
ResultType::Float;
|
||||
Result *input_result = new Result(this->create_result(result_type, ResultPrecision::Full));
|
||||
if (input_socket == interface_inputs[0]) {
|
||||
if (socket_type == SOCK_RGBA) {
|
||||
/* First socket is the image input. */
|
||||
create_result_from_input(*input_result, *image_buffer_);
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
}
|
||||
else if (is_mask_used && input_socket == interface_inputs[1]) {
|
||||
if (socket_type == SOCK_RGBA) {
|
||||
/* Second socket is the mask input. */
|
||||
render_mask_input(this->mod_context_, this->timeline_frame_);
|
||||
if (this->mask_.is_allocated()) {
|
||||
input_result->set_type(this->mask_.type());
|
||||
input_result->set_precision(this->mask_.precision());
|
||||
input_result->share_data(this->mask_);
|
||||
input_result->set_transformation(this->mod_context_.transform_comp_result);
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
}
|
||||
else if (valid_socket_type) {
|
||||
PointerRNA input_props_ptr = RNA_pointer_get(&inputs_ptr, input_socket->identifier);
|
||||
input_result->allocate_single_value();
|
||||
set_single_input_from_rna_value(&input_props_ptr,
|
||||
socket_type,
|
||||
*input_result,
|
||||
get_socket_dimension(input_socket, socket_type));
|
||||
}
|
||||
else {
|
||||
input_result->allocate_invalid();
|
||||
}
|
||||
|
||||
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
|
||||
inputs.append(std::unique_ptr<Result>(input_result));
|
||||
}
|
||||
|
||||
{
|
||||
PRF_scope_with_name("SeqCompositorEvaluate", ProfileCategory::Draw);
|
||||
node_group_operation.evaluate();
|
||||
}
|
||||
this->write_outputs(node_group, node_group_operation, *this->image_buffer_);
|
||||
}
|
||||
|
||||
/* Render mask - similar to #modifier_render_mask_input except for the Mask ID
|
||||
* path we do a more efficient approach than rendering into a full ImBuf. */
|
||||
void render_mask_input(const ModifierApplyContext &context, int timeline_frame)
|
||||
{
|
||||
PRF_scope_with_name("SeqRenderMaskInput", ProfileCategory::Draw);
|
||||
const StripModifierData &smd = this->modifier_data_->modifier;
|
||||
if (smd.mask_input_type == STRIP_MASK_INPUT_STRIP && smd.mask_strip) {
|
||||
this->mask_buffer_ = seq_render_strip(&context.render_data,
|
||||
&context.render_state,
|
||||
smd.mask_strip,
|
||||
timeline_frame)
|
||||
.image;
|
||||
if (this->mask_buffer_ != nullptr) {
|
||||
this->create_result_from_input(this->mask_, *this->mask_buffer_);
|
||||
this->owns_mask_ = true;
|
||||
}
|
||||
}
|
||||
else if (smd.mask_input_type == STRIP_MASK_INPUT_ID && smd.mask_id) {
|
||||
int frame_index = 0;
|
||||
if (smd.mask_time == STRIP_MASK_TIME_RELATIVE) {
|
||||
frame_index = smd.mask_id->sfra + timeline_frame - context.strip.start;
|
||||
}
|
||||
else if (smd.mask_time == STRIP_MASK_TIME_ABSOLUTE) {
|
||||
frame_index = timeline_frame;
|
||||
}
|
||||
|
||||
/* Mask is a grayscale value, similar to alpha, so conceptually it is already a
|
||||
* "linear" quantity. However, masks used to be turned into grayscale images and
|
||||
* interpreted as being in "sequencer working space" (default: sRGB), so keep at least
|
||||
* that behavior working as before -- if sequencer space is sRGB, convert value to
|
||||
* linear for the compositor. */
|
||||
const bool seq_space_is_srgb = IMB_colormanagement_space_name_is_srgb(
|
||||
context.render_data.scene->sequencer_colorspace_settings.name);
|
||||
|
||||
const int width = context.render_data.rectx;
|
||||
const int height = context.render_data.recty;
|
||||
this->mask_.set_type(compositor::ResultType::Float);
|
||||
this->mask_.share_data(
|
||||
this->cache_manager().cached_masks.get(*this,
|
||||
smd.mask_id,
|
||||
compositor::Domain(int2(width, height)),
|
||||
1.0f,
|
||||
true,
|
||||
frame_index,
|
||||
1,
|
||||
0.0f,
|
||||
seq_space_is_srgb));
|
||||
this->owns_mask_ = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void compositor_modifier_init_data(StripModifierData *strip_modifier_data)
|
||||
{
|
||||
SequencerCompositorModifierData *modifier_data =
|
||||
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
|
||||
modifier_data->node_group = nullptr;
|
||||
}
|
||||
|
||||
static void compositor_modifier_apply(ModifierApplyContext &context,
|
||||
StripModifierData *strip_modifier_data)
|
||||
{
|
||||
PRF_scope_with_name("SeqModCompositor", ProfileCategory::Draw);
|
||||
SequencerCompositorModifierData *modifier_data =
|
||||
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
|
||||
if (!modifier_data->node_group) {
|
||||
return;
|
||||
}
|
||||
|
||||
CompositorCache &com_cache = context.render_data.scene->ed->runtime->ensure_compositor_cache();
|
||||
CompositorModifierContext com_mod_context(context, com_cache.get_cache_manager(), modifier_data);
|
||||
|
||||
if (com_mod_context.use_gpu()) {
|
||||
com_mod_context.set_gpu_supported(render_begin_gpu(context.render_data));
|
||||
}
|
||||
|
||||
com_cache.recreate_if_needed(
|
||||
com_mod_context.use_gpu(), com_mod_context.get_precision(), context.render_data.gpu_context);
|
||||
com_mod_context.evaluate();
|
||||
com_mod_context.cache_manager().reset();
|
||||
com_mod_context.free_resources();
|
||||
if (com_mod_context.use_gpu()) {
|
||||
render_end_gpu(context.render_data);
|
||||
}
|
||||
|
||||
context.result.translation += com_mod_context.get_result_translation();
|
||||
}
|
||||
|
||||
static PointerRNA *modifier_panel_get_property_pointers(Panel *panel)
|
||||
{
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
BLI_assert(!RNA_pointer_is_null(ptr));
|
||||
BLI_assert(RNA_struct_is_a(ptr->type, RNA_StripModifier));
|
||||
ui::panel_context_pointer_set(panel, "modifier", ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void compositor_modifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *modifier_ptr = modifier_panel_get_property_pointers(panel);
|
||||
nodes::draw_compositor_nodes_modifier_ui(*C, modifier_ptr, layout);
|
||||
}
|
||||
|
||||
static void compositor_modifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(
|
||||
region_type, eSeqModifierType_Compositor, compositor_modifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Compositor = {
|
||||
/*idname*/ "Compositor",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Compositor"),
|
||||
/*struct_name*/ "SequencerCompositorModifierData",
|
||||
/*struct_size*/ sizeof(SequencerCompositorModifierData),
|
||||
/*init_data*/ compositor_modifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ compositor_modifier_apply,
|
||||
/*panel_register*/ compositor_modifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,147 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_c.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void curves_init_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_set_defaults(&cmd->curve_mapping, 4, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
}
|
||||
|
||||
static void curves_free_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_free_data(&cmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void curves_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
CurvesModifierData *cmd_target = reinterpret_cast<CurvesModifierData *>(target);
|
||||
|
||||
BKE_curvemapping_copy_data(&cmd_target->curve_mapping, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct CurvesApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
|
||||
result.w = input.w;
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void curves_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModCurves", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
|
||||
const float black[3] = {0.0f, 0.0f, 0.0f};
|
||||
const float white[3] = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
BKE_curvemapping_init(&cmd->curve_mapping);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, false);
|
||||
BKE_curvemapping_set_black_white(&cmd->curve_mapping, black, white);
|
||||
|
||||
CurvesApplyOp op;
|
||||
op.curve_mapping = &cmd->curve_mapping;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void curves_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
template_curve_mapping(&layout, ptr, "curve_mapping", 'c', false, false, false, true, false);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void curves_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Curves, curves_panel_draw);
|
||||
}
|
||||
|
||||
static void curves_write(BlendWriter *writer, const StripModifierData *smd)
|
||||
{
|
||||
const CurvesModifierData *cmd = reinterpret_cast<const CurvesModifierData *>(smd);
|
||||
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void curves_read(BlendDataReader *reader, StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Curves = {
|
||||
/*idname*/ "Curves",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Curves"),
|
||||
/*struct_name*/ "CurvesModifierData",
|
||||
/*struct_size*/ sizeof(CurvesModifierData),
|
||||
/*init_data*/ curves_init_data,
|
||||
/*free_data*/ curves_free_data,
|
||||
/*copy_data*/ curves_copy_data,
|
||||
/*apply*/ curves_apply,
|
||||
/*panel_register*/ curves_register,
|
||||
/*blend_write*/ curves_write,
|
||||
/*blend_read*/ curves_read,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,174 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void hue_correct_init_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
int c;
|
||||
|
||||
BKE_curvemapping_set_defaults(&hcmd->curve_mapping, 1, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
hcmd->curve_mapping.preset = CURVE_PRESET_MID8;
|
||||
|
||||
for (c = 0; c < 3; c++) {
|
||||
CurveMap *cuma = &hcmd->curve_mapping.cm[c];
|
||||
BKE_curvemap_reset(
|
||||
cuma, &hcmd->curve_mapping.clipr, hcmd->curve_mapping.preset, CurveMapSlopeType::Positive);
|
||||
}
|
||||
/* use wrapping for all hue correct modifiers */
|
||||
hcmd->curve_mapping.flag |= CUMA_USE_WRAPPING;
|
||||
/* default to showing Saturation */
|
||||
hcmd->curve_mapping.cur = 1;
|
||||
}
|
||||
|
||||
static void hue_correct_free_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_free_data(&hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void hue_correct_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
HueCorrectModifierData *hcmd_target = reinterpret_cast<HueCorrectModifierData *>(target);
|
||||
|
||||
BKE_curvemapping_copy_data(&hcmd_target->curve_mapping, &hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct HueCorrectApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
|
||||
float3 hsv;
|
||||
rgb_to_hsv(input.x, input.y, input.z, &hsv.x, &hsv.y, &hsv.z);
|
||||
|
||||
/* adjust hue, scaling returned default 0.5 up to 1 */
|
||||
float f;
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 0, hsv.x);
|
||||
hsv.x += f - 0.5f;
|
||||
|
||||
/* adjust saturation, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 1, hsv.x);
|
||||
hsv.y *= (f * 2.0f);
|
||||
|
||||
/* adjust value, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 2, hsv.x);
|
||||
hsv.z *= (f * 2.0f);
|
||||
|
||||
hsv.x = hsv.x - floorf(hsv.x); /* mod 1.0 */
|
||||
hsv.y = math::clamp(hsv.y, 0.0f, 1.0f);
|
||||
|
||||
/* convert back to rgb */
|
||||
hsv_to_rgb(hsv.x, hsv.y, hsv.z, &result.x, &result.y, &result.z);
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void hue_correct_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModHueCorrect", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
HueCorrectModifierData *hcmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
|
||||
BKE_curvemapping_init(&hcmd->curve_mapping);
|
||||
|
||||
HueCorrectApplyOp op;
|
||||
op.curve_mapping = &hcmd->curve_mapping;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void hue_correct_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
template_curve_mapping(&layout, ptr, "curve_mapping", 'h', false, false, false, false, false);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void hue_correct_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_HueCorrect, hue_correct_panel_draw);
|
||||
}
|
||||
|
||||
static void hue_correct_write(BlendWriter *writer, const StripModifierData *smd)
|
||||
{
|
||||
const HueCorrectModifierData *hmd = reinterpret_cast<const HueCorrectModifierData *>(smd);
|
||||
BKE_curvemapping_blend_write(writer, &hmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void hue_correct_read(BlendDataReader *reader, StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hmd = reinterpret_cast<HueCorrectModifierData *>(smd);
|
||||
BKE_curvemapping_blend_read(reader, &hmd->curve_mapping);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_HueCorrect = {
|
||||
/*idname*/ "HueCorrect",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Hue Correct"),
|
||||
/*struct_name*/ "HueCorrectModifierData",
|
||||
/*struct_size*/ sizeof(HueCorrectModifierData),
|
||||
/*init_data*/ hue_correct_init_data,
|
||||
/*free_data*/ hue_correct_free_data,
|
||||
/*copy_data*/ hue_correct_copy_data,
|
||||
/*apply*/ hue_correct_apply,
|
||||
/*panel_register*/ hue_correct_register,
|
||||
/*blend_write*/ hue_correct_write,
|
||||
/*blend_read*/ hue_correct_read,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,104 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_matrix.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct MaskApplyOp {
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float m = mask.load_mask_min();
|
||||
|
||||
if constexpr (std::is_same_v<ImageT, uchar>) {
|
||||
/* Byte buffer is straight, so only affect on alpha itself, this is
|
||||
* the only way to alpha-over byte strip after applying mask modifier. */
|
||||
image[3] = uchar(image[3] * m);
|
||||
}
|
||||
else if constexpr (std::is_same_v<ImageT, float>) {
|
||||
/* Float buffers are pre-multiplied, so need to pre-multiply color as well to make it
|
||||
* easy to alpha-over masked strip. */
|
||||
float4 pix(image);
|
||||
pix *= m;
|
||||
*reinterpret_cast<float4 *>(image) = pix;
|
||||
}
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void maskmodifier_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModMask", ProfileCategory::Draw);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
if (mask != nullptr && (mask->byte_data() != nullptr || mask->float_data() != nullptr)) {
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
|
||||
MaskApplyOp op;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
|
||||
/* Image has gained transparency. */
|
||||
context.result.image->color_mode = ImColorMode::RGBA;
|
||||
}
|
||||
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void maskmodifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
draw_mask_input_type_settings(C, layout, ptr);
|
||||
}
|
||||
|
||||
static void maskmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Mask, maskmodifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Mask = {
|
||||
/*idname*/ "Mask",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Mask"),
|
||||
/*struct_name*/ "SequencerMaskModifierData",
|
||||
/*struct_size*/ sizeof(SequencerMaskModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ maskmodifier_apply,
|
||||
/*panel_register*/ maskmodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
StripModifierTypeInfo seqModifierType_None = {
|
||||
/*idname*/ "None",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "None"),
|
||||
/*struct_name*/ "StripModifierData",
|
||||
/*struct_size*/ sizeof(StripModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ nullptr,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void echomodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
EchoModifierData *emd = reinterpret_cast<EchoModifierData *>(smd);
|
||||
|
||||
emd->delay = 1.0f;
|
||||
emd->feedback = 0.5f;
|
||||
emd->mix = 0.5f;
|
||||
}
|
||||
|
||||
static void echomodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
|
||||
col.prop(ptr, "delay", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "feedback", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "mix", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void echomodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Echo, echomodifier_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Echo = {
|
||||
/*idname*/ "Echo",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Echo"),
|
||||
/*struct_name*/ "EchoModifierData",
|
||||
/*struct_size*/ sizeof(EchoModifierData),
|
||||
/*init_data*/ echomodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ echomodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,96 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLO_read_write.hh"
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_sound.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void sound_equalizermodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &flow = layout.grid_flow(true, 0, true, false, false);
|
||||
RNA_BEGIN (ptr, sound_eq, "graphics") {
|
||||
PointerRNA curve_mapping = RNA_pointer_get(&sound_eq, "curve_mapping");
|
||||
const float clip_min_x = RNA_float_get(&curve_mapping, "clip_min_x");
|
||||
const float clip_max_x = RNA_float_get(&curve_mapping, "clip_max_x");
|
||||
|
||||
ui::Layout &col = flow.column(false);
|
||||
ui::Layout &split = col.split(0.4f, false);
|
||||
split.label(fmt::format("{:.2f}", clip_min_x), ICON_NONE);
|
||||
split.label("Hz", ICON_NONE);
|
||||
split.alignment_set(ui::LayoutAlign::Right);
|
||||
split.label(fmt::format("{:.2f}", clip_max_x), ICON_NONE);
|
||||
template_curve_mapping(&col, &sound_eq, "curve_mapping", 0, false, true, true, false, false);
|
||||
ui::Layout &row = col.row(false);
|
||||
row.alignment_set(ui::LayoutAlign::Center);
|
||||
row.label("dB", ICON_NONE);
|
||||
}
|
||||
RNA_END;
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(
|
||||
region_type, eSeqModifierType_SoundEqualizer, sound_equalizermodifier_draw);
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_write(BlendWriter *writer, const StripModifierData *smd)
|
||||
{
|
||||
const SoundEqualizerModifierData *semd = reinterpret_cast<const SoundEqualizerModifierData *>(
|
||||
smd);
|
||||
for (EQCurveMappingData &eqcmd : semd->graphics) {
|
||||
writer->write_struct_by_name("EQCurveMappingData", &eqcmd);
|
||||
BKE_curvemapping_blend_write(writer, &eqcmd.curve_mapping);
|
||||
}
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_read(BlendDataReader *reader, StripModifierData *smd)
|
||||
{
|
||||
SoundEqualizerModifierData *semd = reinterpret_cast<SoundEqualizerModifierData *>(smd);
|
||||
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
|
||||
for (EQCurveMappingData &eqcmd : semd->graphics) {
|
||||
BKE_curvemapping_blend_read(reader, &eqcmd.curve_mapping);
|
||||
}
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_SoundEqualizer = {
|
||||
/*idname*/ "SoundEqualizer",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Equalizer"),
|
||||
/*struct_name*/ "SoundEqualizerModifierData",
|
||||
/*struct_size*/ sizeof(SoundEqualizerModifierData),
|
||||
/*init_data*/ sound_equalizermodifier_init_data,
|
||||
/*free_data*/ sound_equalizermodifier_free,
|
||||
/*copy_data*/ sound_equalizermodifier_copy_data,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ sound_equalizermodifier_register,
|
||||
/*blend_write*/ sound_equalizermodifier_write,
|
||||
/*blend_read*/ sound_equalizermodifier_read,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,79 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void pitchmodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
PitchModifierData *pmd = reinterpret_cast<PitchModifierData *>(smd);
|
||||
pmd->mode = ePitchMode::PITCH_MODE_SEMITONES;
|
||||
pmd->semitones = 0;
|
||||
pmd->cents = 0;
|
||||
pmd->ratio = 1;
|
||||
pmd->preserve_formant = false;
|
||||
pmd->quality = ePitchQuality::PITCH_QUALITY_HIGH;
|
||||
}
|
||||
|
||||
static void pitchmodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
|
||||
col.prop(ptr, "mode", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
int mode = RNA_enum_get(ptr, "mode");
|
||||
if (mode == ePitchMode::PITCH_MODE_SEMITONES) {
|
||||
col.prop(ptr, "semitones", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "cents", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else if (mode == ePitchMode::PITCH_MODE_RATIO) {
|
||||
col.prop(ptr, "ratio", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
col.prop(ptr, "preserve_formant", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "quality", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
|
||||
static void pitchmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Pitch, pitchmodifier_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Pitch = {
|
||||
/*idname*/ "Pitch",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Pitch"),
|
||||
/*struct_name*/ "PitchModifierData",
|
||||
/*struct_size*/ sizeof(PitchModifierData),
|
||||
/*init_data*/ pitchmodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ pitchmodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,377 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct AvgLogLum {
|
||||
const SequencerTonemapModifierData *tmmd;
|
||||
float al;
|
||||
float auto_key;
|
||||
float lav;
|
||||
float3 cav;
|
||||
float igm;
|
||||
};
|
||||
|
||||
static void tonemapmodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
SequencerTonemapModifierData *tmmd = reinterpret_cast<SequencerTonemapModifierData *>(smd);
|
||||
/* Same as tone-map compositor node. */
|
||||
tmmd->type = SEQ_TONEMAP_RD_PHOTORECEPTOR;
|
||||
tmmd->key = 0.18f;
|
||||
tmmd->offset = 1.0f;
|
||||
tmmd->gamma = 1.0f;
|
||||
tmmd->intensity = 0.0f;
|
||||
tmmd->contrast = 0.0f;
|
||||
tmmd->adaptation = 1.0f;
|
||||
tmmd->correction = 0.0f;
|
||||
}
|
||||
|
||||
/* Convert chunk of float image pixels to scene linear space, in-place. */
|
||||
static void pixels_to_scene_linear_float(const ColorSpace *colorspace,
|
||||
float4 *pixels,
|
||||
int64_t count)
|
||||
{
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
reinterpret_cast<float *>(pixels), int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
/* Convert chunk of byte image pixels to scene linear space, into a destination array. */
|
||||
static void pixels_to_scene_linear_byte(const ColorSpace *colorspace,
|
||||
const uchar *pixels,
|
||||
float4 *dst,
|
||||
int64_t count)
|
||||
{
|
||||
const uchar *bptr = pixels;
|
||||
float4 *dst_ptr = dst;
|
||||
for (int64_t i = 0; i < count; i++) {
|
||||
straight_uchar_to_premul_float(*dst_ptr, bptr);
|
||||
bptr += 4;
|
||||
dst_ptr++;
|
||||
}
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
reinterpret_cast<float *>(dst), int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
static void scene_linear_to_image_chunk_byte(float4 *src, ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
reinterpret_cast<float *>(src), int(range.size()), 1, 4, colorspace);
|
||||
const float4 *src_ptr = src;
|
||||
uchar *bptr = ibuf->byte_data_for_write();
|
||||
for (const int64_t idx : range) {
|
||||
premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
|
||||
src_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
struct AreaLuminance {
|
||||
int64_t pixel_count = 0;
|
||||
double sum = 0.0f;
|
||||
float3 color_sum = {0, 0, 0};
|
||||
double log_sum = 0.0;
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
};
|
||||
|
||||
static void scene_linear_to_image_chunk_float(ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->float_buffer.colorspace;
|
||||
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_data_for_write());
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
reinterpret_cast<float *>(fptr + range.first()), int(range.size()), 1, 4, colorspace);
|
||||
}
|
||||
|
||||
template<typename MaskSampler>
|
||||
static void tonemap_simple(
|
||||
float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
|
||||
{
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = *scene_linear;
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz() * avg.al;
|
||||
float3 d = pixel + avg.tmmd->offset;
|
||||
pixel.x /= (d.x == 0.0f) ? 1.0f : d.x;
|
||||
pixel.y /= (d.y == 0.0f) ? 1.0f : d.y;
|
||||
pixel.z /= (d.z == 0.0f) ? 1.0f : d.z;
|
||||
const float igm = avg.igm;
|
||||
if (igm != 0.0f) {
|
||||
pixel.x = powf(math::max(pixel.x, 0.0f), igm);
|
||||
pixel.y = powf(math::max(pixel.y, 0.0f), igm);
|
||||
pixel.z = powf(math::max(pixel.z, 0.0f), igm);
|
||||
}
|
||||
|
||||
/* Apply mask. */
|
||||
float4 result(pixel.x, pixel.y, pixel.z, input.w);
|
||||
mask.apply_mask(input, result);
|
||||
*scene_linear = result;
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename MaskSampler>
|
||||
static void tonemap_rd_photoreceptor(
|
||||
float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg)
|
||||
{
|
||||
const float f = expf(-avg.tmmd->intensity);
|
||||
const float m = (avg.tmmd->contrast > 0.0f) ? avg.tmmd->contrast :
|
||||
(0.3f + 0.7f * powf(avg.auto_key, 1.4f));
|
||||
const float ic = 1.0f - avg.tmmd->correction, ia = 1.0f - avg.tmmd->adaptation;
|
||||
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = *scene_linear;
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz();
|
||||
const float L = IMB_colormanagement_get_luminance(pixel);
|
||||
float I_l = pixel.x + ic * (L - pixel.x);
|
||||
float I_g = avg.cav.x + ic * (avg.lav - avg.cav.x);
|
||||
float I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.x /= std::max(pixel.x + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.y + ic * (L - pixel.y);
|
||||
I_g = avg.cav.y + ic * (avg.lav - avg.cav.y);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.y /= std::max(pixel.y + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.z + ic * (L - pixel.z);
|
||||
I_g = avg.cav.z + ic * (avg.lav - avg.cav.z);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.z /= std::max(pixel.z + powf(f * I_a, m), 1.0e-30f);
|
||||
|
||||
/* Apply mask. */
|
||||
float4 result(pixel.x, pixel.y, pixel.z, input.w);
|
||||
mask.apply_mask(input, result);
|
||||
*scene_linear = result;
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TonemapApplyOp {
|
||||
AreaLuminance lum;
|
||||
AvgLogLum data;
|
||||
eModTonemapType type;
|
||||
ImBuf *ibuf;
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
const IndexRange pixel_range(y_range.first() * image_x, y_range.size() * image_x);
|
||||
if constexpr (std::is_same_v<ImageT, float>) {
|
||||
/* Float pixels: no need for temporary storage. Luminance calculation already converted
|
||||
* data to scene linear. */
|
||||
float4 *pixels = (float4 *)(image + y_range.first() * image_x * 4);
|
||||
if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(pixels, mask, image_x, y_range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(pixels, mask, image_x, y_range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_float(this->ibuf, pixel_range);
|
||||
}
|
||||
else {
|
||||
/* Byte pixels: temporary storage for scene linear pixel values. */
|
||||
Array<float4> scene_linear(pixel_range.size());
|
||||
pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
|
||||
ibuf->byte_data() + pixel_range.first() * 4,
|
||||
scene_linear.data(),
|
||||
pixel_range.size());
|
||||
if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(scene_linear.data(), mask, image_x, y_range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(scene_linear.data(), mask, image_x, y_range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_byte(scene_linear.data(), this->ibuf, pixel_range);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void tonemap_calc_chunk_luminance(const int width,
|
||||
const IndexRange y_range,
|
||||
const float4 *scene_linear,
|
||||
AreaLuminance &r_lum)
|
||||
{
|
||||
for ([[maybe_unused]] const int y : y_range) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float4 pixel = *scene_linear;
|
||||
r_lum.pixel_count++;
|
||||
float L = IMB_colormanagement_get_luminance(pixel);
|
||||
r_lum.sum += L;
|
||||
r_lum.color_sum.x += pixel.x;
|
||||
r_lum.color_sum.y += pixel.y;
|
||||
r_lum.color_sum.z += pixel.z;
|
||||
r_lum.log_sum += logf(math::max(L, 0.0f) + 1e-5f);
|
||||
r_lum.max = math::max(r_lum.max, L);
|
||||
r_lum.min = math::min(r_lum.min, L);
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AreaLuminance tonemap_calc_input_luminance(ImBuf *ibuf)
|
||||
{
|
||||
float *float_data = ibuf->float_data_for_write();
|
||||
AreaLuminance lum;
|
||||
lum = threading::parallel_reduce(
|
||||
IndexRange(ibuf->y),
|
||||
32,
|
||||
lum,
|
||||
/* Calculate luminance for a chunk. */
|
||||
[&](const IndexRange y_range, const AreaLuminance &init) {
|
||||
AreaLuminance lum = init;
|
||||
const int64_t chunk_size = y_range.size() * ibuf->x;
|
||||
/* For float images, convert to scene-linear in place. The rest
|
||||
* of tone-mapper can then continue with scene-linear values. */
|
||||
if (float_data != nullptr) {
|
||||
float4 *fptr = reinterpret_cast<float4 *>(float_data);
|
||||
fptr += y_range.first() * ibuf->x;
|
||||
pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
|
||||
tonemap_calc_chunk_luminance(ibuf->x, y_range, fptr, lum);
|
||||
}
|
||||
else {
|
||||
const uchar *bptr = ibuf->byte_data() + y_range.first() * ibuf->x * 4;
|
||||
Array<float4> scene_linear(chunk_size);
|
||||
pixels_to_scene_linear_byte(
|
||||
ibuf->byte_buffer.colorspace, bptr, scene_linear.data(), chunk_size);
|
||||
tonemap_calc_chunk_luminance(ibuf->x, y_range, scene_linear.data(), lum);
|
||||
}
|
||||
return lum;
|
||||
},
|
||||
/* Reduce luminance results. */
|
||||
[&](const AreaLuminance &a, const AreaLuminance &b) {
|
||||
AreaLuminance res;
|
||||
res.pixel_count = a.pixel_count + b.pixel_count;
|
||||
res.sum = a.sum + b.sum;
|
||||
res.color_sum = a.color_sum + b.color_sum;
|
||||
res.log_sum = a.log_sum + b.log_sum;
|
||||
res.min = math::min(a.min, b.min);
|
||||
res.max = math::max(a.max, b.max);
|
||||
return res;
|
||||
});
|
||||
return lum;
|
||||
}
|
||||
|
||||
static void tonemapmodifier_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModTonemap", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const SequencerTonemapModifierData *tmmd =
|
||||
reinterpret_cast<const SequencerTonemapModifierData *>(smd);
|
||||
|
||||
TonemapApplyOp op;
|
||||
op.type = tmmd->type;
|
||||
op.ibuf = context.result.image;
|
||||
op.lum = tonemap_calc_input_luminance(context.result.image);
|
||||
if (op.lum.pixel_count == 0) {
|
||||
return; /* Strip is zero size or off-screen. */
|
||||
}
|
||||
|
||||
op.data.tmmd = tmmd;
|
||||
op.data.lav = op.lum.sum / op.lum.pixel_count;
|
||||
op.data.cav.x = op.lum.color_sum.x / op.lum.pixel_count;
|
||||
op.data.cav.y = op.lum.color_sum.y / op.lum.pixel_count;
|
||||
op.data.cav.z = op.lum.color_sum.z / op.lum.pixel_count;
|
||||
float maxl = log(double(op.lum.max) + 1e-5f);
|
||||
float minl = log(double(op.lum.min) + 1e-5f);
|
||||
float avl = op.lum.log_sum / op.lum.pixel_count;
|
||||
op.data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
|
||||
float al = exp(double(avl));
|
||||
op.data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
|
||||
op.data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
|
||||
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
const int tonemap_type = RNA_enum_get(ptr, "tonemap_type");
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
col.prop(ptr, "tonemap_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
if (tonemap_type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
col.prop(ptr, "intensity", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "adaptation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "correction", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else if (tonemap_type == SEQ_TONEMAP_RH_SIMPLE) {
|
||||
col.prop(ptr, "key", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "gamma", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemapmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Tonemap, tonemapmodifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Tonemap = {
|
||||
/*idname*/ "Tonemap",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Tonemap"),
|
||||
/*struct_name*/ "SequencerTonemapModifierData",
|
||||
/*struct_size*/ sizeof(SequencerTonemapModifierData),
|
||||
/*init_data*/ tonemapmodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ tonemapmodifier_apply,
|
||||
/*panel_register*/ tonemapmodifier_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,122 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "PRF_profile.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void whiteBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
WhiteBalanceModifierData *cbmd = reinterpret_cast<WhiteBalanceModifierData *>(smd);
|
||||
copy_v3_fl(cbmd->white_value, 1.0f);
|
||||
}
|
||||
|
||||
struct WhiteBalanceApplyOp {
|
||||
float multiplier[3];
|
||||
|
||||
template<typename ImageT, typename MaskSampler>
|
||||
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
|
||||
{
|
||||
image += y_range.first() * image_x * 4;
|
||||
for (int64_t y : y_range) {
|
||||
mask.begin_row(y);
|
||||
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
#if 0
|
||||
mul_v3_v3(result, multiplier);
|
||||
#else
|
||||
/* similar to division without the clipping */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
/* Prevent pow argument from being negative. This whole math
|
||||
* breaks down overall with any HDR colors; would be good to
|
||||
* revisit and do something more proper. */
|
||||
float f = max_ff(1.0f - input[i], 0.0f);
|
||||
result[i] = 1.0f - powf(f, this->multiplier[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
mask.apply_mask(input, result);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void whiteBalance_apply(ModifierApplyContext &context, StripModifierData *smd)
|
||||
{
|
||||
PRF_scope_with_name("SeqModWhiteBalance", ProfileCategory::Draw);
|
||||
ensure_ibuf_is_sequencer_space(context.render_data.scene, context.result.image, false);
|
||||
ImBuf *mask = modifier_render_mask_input(context, *smd);
|
||||
|
||||
const WhiteBalanceModifierData *data = reinterpret_cast<const WhiteBalanceModifierData *>(smd);
|
||||
|
||||
WhiteBalanceApplyOp op;
|
||||
op.multiplier[0] = (data->white_value[0] != 0.0f) ? 1.0f / data->white_value[0] : FLT_MAX;
|
||||
op.multiplier[1] = (data->white_value[1] != 0.0f) ? 1.0f / data->white_value[1] : FLT_MAX;
|
||||
op.multiplier[2] = (data->white_value[2] != 0.0f) ? 1.0f / data->white_value[2] : FLT_MAX;
|
||||
apply_modifier_op(op, context.result.image, mask, context.transform);
|
||||
if (mask != nullptr) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void whiteBalance_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
layout.prop(ptr, "white_value", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
if (ui::Layout *mask_input_layout = layout.panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, *mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void whiteBalance_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_WhiteBalance, whiteBalance_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_WhiteBalance = {
|
||||
/*idname*/ "WhiteBalance",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "White Balance"),
|
||||
/*struct_name*/ "WhiteBalanceModifierData",
|
||||
/*struct_size*/ sizeof(WhiteBalanceModifierData),
|
||||
/*init_data*/ whiteBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ whiteBalance_apply,
|
||||
/*panel_register*/ whiteBalance_register,
|
||||
/*blend_write*/ nullptr,
|
||||
/*blend_read*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,731 @@
|
||||
/* SPDX-FileCopyrightText: 2012-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_mask_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
#include "DNA_space_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_idprop.hh"
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_modifiertypes.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
static bool modifier_has_persistent_uid(const Strip &strip, int uid)
|
||||
{
|
||||
for (StripModifierData &smd : strip.modifiers) {
|
||||
if (smd.persistent_uid == uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd)
|
||||
{
|
||||
uint64_t hash = get_default_hash(StringRef(smd.name));
|
||||
RandomNumberGenerator rng{uint32_t(hash)};
|
||||
while (true) {
|
||||
const int new_uid = rng.get_int32();
|
||||
if (new_uid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (modifier_has_persistent_uid(strip, new_uid)) {
|
||||
continue;
|
||||
}
|
||||
smd.persistent_uid = new_uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip)
|
||||
{
|
||||
Set<int> uids;
|
||||
int modifiers_num = 0;
|
||||
for (StripModifierData &smd : strip.modifiers) {
|
||||
if (smd.persistent_uid <= 0) {
|
||||
return false;
|
||||
}
|
||||
uids.add(smd.persistent_uid);
|
||||
modifiers_num++;
|
||||
}
|
||||
if (uids.size() != modifiers_num) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void modifier_ops_extra_draw(bContext *C, ui::Layout *layout, void *smd_v)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
Strip *strip = seq::select_active_get(sequencer_scene);
|
||||
if (!strip) {
|
||||
return;
|
||||
}
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_v);
|
||||
PointerRNA mod_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, RNA_StripModifier, smd);
|
||||
|
||||
PointerRNA op_ptr;
|
||||
/* Duplicate. */
|
||||
op_ptr = layout->op("SEQUENCER_OT_strip_modifier_duplicate",
|
||||
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate"),
|
||||
ICON_DUPLICATE);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
/* Copy to selected. */
|
||||
op_ptr = layout->op("SEQUENCER_OT_strip_modifier_copy",
|
||||
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"),
|
||||
0);
|
||||
RNA_enum_set(&op_ptr, "type", /*SEQ_MODIFIER_COPY_APPEND*/ 1);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
|
||||
layout->separator();
|
||||
|
||||
/* Move to first. */
|
||||
{
|
||||
ui::Layout &row = layout->row(false);
|
||||
op_ptr = row.op("SEQUENCER_OT_strip_modifier_move_to_index",
|
||||
IFACE_("Move to First"),
|
||||
ICON_TRIA_UP,
|
||||
wm::OpCallContext::InvokeDefault,
|
||||
UI_ITEM_NONE);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&op_ptr, "index", 0);
|
||||
row.enabled_set(smd->prev != nullptr);
|
||||
}
|
||||
|
||||
/* Move to last. */
|
||||
{
|
||||
ui::Layout &row = layout->row(false);
|
||||
op_ptr = row.op("SEQUENCER_OT_strip_modifier_move_to_index",
|
||||
IFACE_("Move to Last"),
|
||||
ICON_TRIA_DOWN,
|
||||
wm::OpCallContext::InvokeDefault,
|
||||
UI_ITEM_NONE);
|
||||
RNA_string_set(&op_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&op_ptr, "index", strip->modifiers.count() - 1);
|
||||
row.enabled_set(smd->next != nullptr);
|
||||
}
|
||||
|
||||
if (smd->type == eSeqModifierType_Compositor) {
|
||||
layout->separator();
|
||||
layout->prop(&mod_ptr, "show_group_selector", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
static void modifier_panel_header(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
ui::Layout &layout = *panel->layout;
|
||||
|
||||
/* Don't use #modifier_panel_get_property_pointers, we don't want to lock the header. */
|
||||
PointerRNA *ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(ptr->data);
|
||||
|
||||
ui::panel_context_pointer_set(panel, "modifier", ptr);
|
||||
|
||||
/* Modifier Icon. */
|
||||
ui::Layout *sub = &layout.row(true);
|
||||
sub->emboss_set(ui::EmbossType::None);
|
||||
PointerRNA active_op_ptr = sub->op(
|
||||
"SEQUENCER_OT_strip_modifier_set_active", "", RNA_struct_ui_icon(ptr->type));
|
||||
RNA_string_set(&active_op_ptr, "modifier", smd->name);
|
||||
|
||||
ui::Layout &row = layout.row(true);
|
||||
|
||||
/* Modifier Name.
|
||||
* Count how many buttons are added to the header to check if there is enough space. */
|
||||
int buttons_number = 0;
|
||||
ui::Layout &name_row = row.row(true);
|
||||
|
||||
if (!smd->is_type_sound()) {
|
||||
sub = &row.row(true);
|
||||
sub->prop(ptr, "show_preview", UI_ITEM_NONE, "", ICON_NONE);
|
||||
buttons_number++;
|
||||
}
|
||||
|
||||
sub = &row.row(true);
|
||||
sub->prop(ptr, "enable", UI_ITEM_NONE, "", ICON_NONE);
|
||||
buttons_number++;
|
||||
|
||||
/* Extra operators menu. */
|
||||
row.menu_fn("", ICON_DOWNARROW_HLT, modifier_ops_extra_draw, smd);
|
||||
|
||||
/* Delete button. */
|
||||
sub = &row.row(false);
|
||||
sub->emboss_set(ui::EmbossType::None);
|
||||
PointerRNA remove_op_ptr = sub->op("SEQUENCER_OT_strip_modifier_remove", "", ICON_X);
|
||||
RNA_string_set(&remove_op_ptr, "name", smd->name);
|
||||
buttons_number++;
|
||||
|
||||
bool display_name = (panel->sizex / UI_UNIT_X - buttons_number > 5) || (panel->sizex == 0);
|
||||
if (display_name) {
|
||||
name_row.prop(ptr, "name", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
else {
|
||||
row.alignment_set(ui::LayoutAlign::Right);
|
||||
}
|
||||
|
||||
/* Extra padding for delete button. */
|
||||
layout.separator();
|
||||
}
|
||||
|
||||
void draw_mask_input_type_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
Editing *ed = seq::editing_get(sequencer_scene);
|
||||
|
||||
const int input_mask_type = RNA_enum_get(ptr, "input_mask_type");
|
||||
|
||||
layout.use_property_split_set(true);
|
||||
|
||||
ui::Layout &col = layout.column(false);
|
||||
ui::Layout *row = &col.row(true);
|
||||
row->prop(ptr, "input_mask_type", ui::ITEM_R_EXPAND, IFACE_("Type"), ICON_NONE);
|
||||
|
||||
if (input_mask_type == STRIP_MASK_INPUT_STRIP) {
|
||||
PointerRNA sequences_object = RNA_pointer_create_discrete(
|
||||
&sequencer_scene->id, RNA_SequenceEditor, ed);
|
||||
col.prop_search(
|
||||
ptr, "input_mask_strip", &sequences_object, "strips_all", IFACE_("Mask"), ICON_NONE);
|
||||
}
|
||||
else {
|
||||
col.prop(ptr, "input_mask_id", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
row = &col.row(true);
|
||||
row->prop(ptr, "mask_time", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_ui_poll(const bContext *C, PanelType * /*pt*/)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
if (!sequencer_scene) {
|
||||
return false;
|
||||
}
|
||||
Strip *active_strip = seq::select_active_get(sequencer_scene);
|
||||
return active_strip != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a modifier to the index it's moved to after a drag and drop.
|
||||
*/
|
||||
static void modifier_reorder(bContext *C, Panel *panel, const int new_index)
|
||||
{
|
||||
PointerRNA *smd_ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_ptr->data);
|
||||
|
||||
wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_strip_modifier_move_to_index", false);
|
||||
PointerRNA props_ptr = WM_operator_properties_create_ptr(ot);
|
||||
RNA_string_set(&props_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&props_ptr, "index", new_index);
|
||||
WM_operator_name_call_ptr(C, ot, wm::OpCallContext::InvokeDefault, &props_ptr, nullptr);
|
||||
WM_operator_properties_free(&props_ptr);
|
||||
}
|
||||
|
||||
static short get_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
PointerRNA *smd_ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_ptr->data);
|
||||
return smd->ui_expand_flag;
|
||||
}
|
||||
|
||||
static void set_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel, short expand_flag)
|
||||
{
|
||||
PointerRNA *smd_ptr = ui::panel_custom_data_get(panel);
|
||||
StripModifierData *smd = static_cast<StripModifierData *>(smd_ptr->data);
|
||||
smd->ui_expand_flag = expand_flag;
|
||||
}
|
||||
|
||||
PanelType *modifier_panel_register(ARegionType *region_type,
|
||||
const eStripModifierType type,
|
||||
PanelDrawFn draw)
|
||||
{
|
||||
PanelType *panel_type = MEM_new_zeroed<PanelType>(__func__);
|
||||
|
||||
modifier_type_panel_id(type, panel_type->idname);
|
||||
STRNCPY_UTF8(panel_type->label, "");
|
||||
STRNCPY_UTF8(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
|
||||
STRNCPY_UTF8(panel_type->active_property, "is_active");
|
||||
STRNCPY_UTF8(panel_type->context, "strip_modifier");
|
||||
|
||||
panel_type->draw_header = modifier_panel_header;
|
||||
panel_type->draw = draw;
|
||||
panel_type->poll = modifier_ui_poll;
|
||||
|
||||
/* Give the panel the special flag that says it was built here and corresponds to a
|
||||
* modifier rather than a #PanelType. */
|
||||
panel_type->flag = PANEL_TYPE_HEADER_EXPAND | PANEL_TYPE_INSTANCED;
|
||||
panel_type->reorder = modifier_reorder;
|
||||
panel_type->get_list_data_expand_flag = get_strip_modifier_expand_flag;
|
||||
panel_type->set_list_data_expand_flag = set_strip_modifier_expand_flag;
|
||||
|
||||
BLI_addtail(®ion_type->paneltypes, panel_type);
|
||||
|
||||
return panel_type;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
float4 load_pixel_premul(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
straight_uchar_to_premul_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
float4 load_pixel_premul(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
void store_pixel_premul(float4 pix, uchar *ptr)
|
||||
{
|
||||
premul_float_to_straight_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
void store_pixel_premul(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
float4 load_pixel_raw(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
rgba_uchar_to_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
float4 load_pixel_raw(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
void store_pixel_raw(float4 pix, uchar *ptr)
|
||||
{
|
||||
rgba_float_to_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
void store_pixel_raw(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
ImBuf *modifier_render_mask_input(const ModifierApplyContext &context,
|
||||
const StripModifierData &smd)
|
||||
{
|
||||
ImBuf *mask = nullptr;
|
||||
|
||||
if (smd.mask_input_type == STRIP_MASK_INPUT_STRIP) {
|
||||
if (smd.mask_strip) {
|
||||
mask = seq_render_strip(&context.render_data,
|
||||
&context.render_state,
|
||||
smd.mask_strip,
|
||||
context.timeline_frame)
|
||||
.image;
|
||||
}
|
||||
}
|
||||
else if (smd.mask_input_type == STRIP_MASK_INPUT_ID) {
|
||||
int frame_offset = 0;
|
||||
if (smd.mask_time == STRIP_MASK_TIME_RELATIVE) {
|
||||
frame_offset = context.strip.start;
|
||||
}
|
||||
else if (smd.mask_time == STRIP_MASK_TIME_ABSOLUTE) {
|
||||
frame_offset = smd.mask_id ? smd.mask_id->sfra : 0;
|
||||
}
|
||||
|
||||
/* Note that we do not request mask to be float image: if it is that is
|
||||
* fine, but if it is a byte image then we also just take that without
|
||||
* extra memory allocations or conversions. All modifiers are expected
|
||||
* to handle mask being either type. */
|
||||
mask = seq_render_mask(context.render_data.depsgraph,
|
||||
context.render_data.rectx,
|
||||
context.render_data.recty,
|
||||
smd.mask_id,
|
||||
context.timeline_frame - frame_offset,
|
||||
false);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Public Modifier Functions
|
||||
* \{ */
|
||||
|
||||
static StripModifierTypeInfo *modifiersTypes[NUM_STRIP_MODIFIER_TYPES] = {nullptr};
|
||||
|
||||
static void modifier_types_init(StripModifierTypeInfo *types[])
|
||||
{
|
||||
#define INIT_TYPE(typeName) (types[eSeqModifierType_##typeName] = &seqModifierType_##typeName)
|
||||
INIT_TYPE(None);
|
||||
INIT_TYPE(BrightContrast);
|
||||
INIT_TYPE(ColorBalance);
|
||||
INIT_TYPE(Compositor);
|
||||
INIT_TYPE(Curves);
|
||||
INIT_TYPE(HueCorrect);
|
||||
INIT_TYPE(Mask);
|
||||
INIT_TYPE(SoundEqualizer);
|
||||
INIT_TYPE(Pitch);
|
||||
INIT_TYPE(Echo);
|
||||
INIT_TYPE(Tonemap);
|
||||
INIT_TYPE(WhiteBalance);
|
||||
#undef INIT_TYPE
|
||||
}
|
||||
|
||||
void modifiers_init()
|
||||
{
|
||||
modifier_types_init(modifiersTypes);
|
||||
}
|
||||
|
||||
const StripModifierTypeInfo *modifier_type_info_get(eStripModifierType type)
|
||||
{
|
||||
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
|
||||
return nullptr;
|
||||
}
|
||||
return modifiersTypes[type];
|
||||
}
|
||||
|
||||
StripModifierData *modifier_new(Strip *strip, const char *name, eStripModifierType type)
|
||||
{
|
||||
StripModifierData *smd;
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
|
||||
|
||||
smd = static_cast<StripModifierData *>(MEM_new_zeroed(smti->struct_size, "sequence modifier"));
|
||||
|
||||
smd->type = type;
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED | STRIP_MODIFIER_FLAG_SHOW_PREVIEW;
|
||||
smd->ui_expand_flag |= UI_PANEL_DATA_EXPAND_ROOT;
|
||||
smd->runtime = MEM_new<StripModifierDataRuntime>(__func__);
|
||||
|
||||
if (!name || !name[0]) {
|
||||
STRNCPY_UTF8(smd->name, CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name));
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(smd->name, name);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip->modifiers, smd);
|
||||
|
||||
if (ELEM(strip->type, STRIP_TYPE_SOUND, STRIP_TYPE_SOUND_HD)) {
|
||||
strip->runtime->sound_modifiers_count++;
|
||||
}
|
||||
|
||||
modifier_unique_name(strip, smd);
|
||||
|
||||
if (smti->init_data) {
|
||||
smti->init_data(smd);
|
||||
}
|
||||
|
||||
modifier_set_active(strip, smd);
|
||||
|
||||
return smd;
|
||||
}
|
||||
|
||||
bool modifier_remove(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
if (BLI_findindex(&strip->modifiers, smd) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
/* Prefer the next modifier but use the previous if this modifier is the last in the list. */
|
||||
if (smd->next != nullptr) {
|
||||
modifier_set_active(strip, smd->next);
|
||||
}
|
||||
else if (smd->prev != nullptr) {
|
||||
modifier_set_active(strip, smd->prev);
|
||||
}
|
||||
}
|
||||
|
||||
BLI_remlink(&strip->modifiers, smd);
|
||||
modifier_free(smd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void modifier_clear(Strip *strip)
|
||||
{
|
||||
StripModifierData *smd, *smd_next;
|
||||
|
||||
for (smd = static_cast<StripModifierData *>(strip->modifiers.first); smd; smd = smd_next) {
|
||||
smd_next = smd->next;
|
||||
modifier_free(smd);
|
||||
}
|
||||
|
||||
strip->modifiers.clear_no_delete();
|
||||
}
|
||||
|
||||
void modifier_free(StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
if (smti && smti->free_data) {
|
||||
smti->free_data(smd);
|
||||
}
|
||||
|
||||
if (smd->runtime) {
|
||||
MEM_delete(smd->runtime);
|
||||
}
|
||||
if (smd->system_properties != nullptr) {
|
||||
IDP_FreeProperty_ex(smd->system_properties, false);
|
||||
}
|
||||
|
||||
MEM_delete(smd);
|
||||
}
|
||||
|
||||
void modifier_unique_name(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
BLI_uniquename(&strip->modifiers,
|
||||
smd,
|
||||
CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name),
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(smd->name));
|
||||
}
|
||||
|
||||
StripModifierData *modifier_find_by_name(Strip *strip, const char *name)
|
||||
{
|
||||
return static_cast<StripModifierData *>(
|
||||
BLI_findstring(&(strip->modifiers), name, offsetof(StripModifierData, name)));
|
||||
}
|
||||
|
||||
static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeline_frame)
|
||||
{
|
||||
using namespace blender::seq;
|
||||
|
||||
if (smd->mask_strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const bool strip_has_ended_skip = smd->mask_input_type == STRIP_MASK_INPUT_STRIP &&
|
||||
smd->mask_time == STRIP_MASK_TIME_RELATIVE &&
|
||||
!smd->mask_strip->intersects_frame(scene, timeline_frame);
|
||||
const bool missing_data_skip = !strip_has_valid_data(smd->mask_strip) ||
|
||||
media_presence_is_missing(scene, smd->mask_strip);
|
||||
|
||||
return strip_has_ended_skip || missing_data_skip;
|
||||
}
|
||||
|
||||
void modifier_apply_stack(ModifierApplyContext &context)
|
||||
{
|
||||
if (context.strip.modifiers.first == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (StripModifierData &smd : context.strip.modifiers) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd.type);
|
||||
|
||||
/* could happen if modifier is being removed or not exists in current version of blender */
|
||||
if (!smti) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool show_preview = (smd.flag & STRIP_MODIFIER_FLAG_SHOW_PREVIEW) != 0;
|
||||
const bool show_render = (smd.flag & STRIP_MODIFIER_FLAG_MUTE) == 0;
|
||||
|
||||
if (context.render_data.render && !show_render) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!context.render_data.render && !show_preview) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (smti->apply && !skip_modifier(context.render_data.scene, &smd, context.timeline_frame)) {
|
||||
smti->apply(context, &smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src, const int flag)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
|
||||
StripModifierData *mod_new = MEM_dupalloc(mod_src);
|
||||
|
||||
mod_new->system_properties = nullptr;
|
||||
if (mod_src->system_properties) {
|
||||
mod_new->system_properties = IDP_CopyProperty_ex(mod_src->system_properties, flag);
|
||||
}
|
||||
|
||||
mod_new->runtime = MEM_new<StripModifierDataRuntime>(__func__);
|
||||
|
||||
if (smti && smti->copy_data) {
|
||||
smti->copy_data(mod_new, mod_src);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip_dst.modifiers, mod_new);
|
||||
BLI_uniquename(&strip_dst.modifiers,
|
||||
mod_new,
|
||||
"Strip Modifier",
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(StripModifierData::name));
|
||||
return mod_new;
|
||||
}
|
||||
|
||||
void modifier_list_copy(Strip *strip_new, Strip *strip, const int flag)
|
||||
{
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
modifier_copy(*strip_new, &smd, flag);
|
||||
}
|
||||
}
|
||||
|
||||
bool strip_supports_modifiers(const Strip *strip)
|
||||
{
|
||||
return (strip->type != STRIP_TYPE_SOUND);
|
||||
}
|
||||
|
||||
bool modifier_move_to_index(Strip *strip, StripModifierData *smd, const int new_index)
|
||||
{
|
||||
const int current_index = BLI_findindex(&strip->modifiers, smd);
|
||||
return BLI_listbase_move_index(&strip->modifiers, current_index, new_index);
|
||||
}
|
||||
|
||||
StripModifierData *modifier_get_active(const Strip *strip)
|
||||
{
|
||||
/* In debug mode, check for only one active modifier. */
|
||||
#ifndef NDEBUG
|
||||
int active_count = 0;
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
if (smd.flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
active_count++;
|
||||
}
|
||||
}
|
||||
BLI_assert(ELEM(active_count, 0, 1));
|
||||
#endif
|
||||
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
if (smd.flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
return &smd;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void modifier_set_active(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
for (StripModifierData &smd_iter : strip->modifiers) {
|
||||
smd_iter.flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
|
||||
if (smd != nullptr) {
|
||||
BLI_assert(BLI_findindex(&strip->modifiers, smd) != -1);
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_type_panel_id(eStripModifierType type, char *r_idname)
|
||||
{
|
||||
const StripModifierTypeInfo *mti = modifier_type_info_get(type);
|
||||
BLI_string_join(
|
||||
r_idname, sizeof(PanelType::idname), STRIP_MODIFIER_TYPE_PANEL_PREFIX, mti->idname);
|
||||
}
|
||||
|
||||
void foreach_strip_modifier_id(Strip *strip, const FunctionRef<void(ID *)> fn)
|
||||
{
|
||||
for (StripModifierData &smd : strip->modifiers) {
|
||||
if (smd.mask_id) {
|
||||
fn(reinterpret_cast<ID *>(smd.mask_id));
|
||||
}
|
||||
if (smd.type == eSeqModifierType_Compositor) {
|
||||
auto *modifier_data = reinterpret_cast<SequencerCompositorModifierData *>(&smd);
|
||||
if (modifier_data->node_group) {
|
||||
fn(reinterpret_cast<ID *>(modifier_data->node_group));
|
||||
}
|
||||
}
|
||||
if (smd.system_properties) {
|
||||
IDP_foreach_property(smd.system_properties, IDP_TYPE_FILTER_ID, [&](IDProperty *id_prop) {
|
||||
fn((ID *)id_prop->data.pointer);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name .blend File I/O
|
||||
* \{ */
|
||||
|
||||
void modifier_blend_write(BlendWriter *writer, ListBaseT<StripModifierData> *modbase)
|
||||
{
|
||||
for (StripModifierData &smd : *modbase) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd.type);
|
||||
|
||||
if (smti) {
|
||||
if (smd.system_properties) {
|
||||
IDP_BlendWrite(writer, smd.system_properties);
|
||||
}
|
||||
|
||||
writer->write_struct_by_name(smti->struct_name, &smd);
|
||||
if (smti->blend_write) {
|
||||
smti->blend_write(writer, &smd);
|
||||
}
|
||||
}
|
||||
else {
|
||||
writer->write_struct(&smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_blend_read_data(BlendDataReader *reader, ListBaseT<StripModifierData> *lb)
|
||||
{
|
||||
BLO_read_struct_list(reader, StripModifierData, lb);
|
||||
|
||||
for (StripModifierData &smd : *lb) {
|
||||
BLO_read_struct(reader, IDProperty, &smd.system_properties);
|
||||
IDP_BlendDataRead(reader, &smd.system_properties);
|
||||
|
||||
if (smd.mask_strip) {
|
||||
BLO_read_struct(reader, Strip, &smd.mask_strip);
|
||||
}
|
||||
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd.type);
|
||||
if (smti && smti->blend_read) {
|
||||
smti->blend_read(reader, &smd);
|
||||
}
|
||||
smd.runtime = MEM_new<StripModifierDataRuntime>(__func__);
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -0,0 +1,353 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
#include "BLI_math_interp.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct bContext;
|
||||
struct ARegionType;
|
||||
struct ImBuf;
|
||||
struct Strip;
|
||||
struct Panel;
|
||||
struct PanelType;
|
||||
struct PointerRNA;
|
||||
|
||||
namespace ui {
|
||||
struct Layout;
|
||||
} // namespace ui
|
||||
|
||||
namespace seq {
|
||||
|
||||
struct RenderData;
|
||||
struct SeqRenderState;
|
||||
struct SeqResult;
|
||||
|
||||
struct ModifierApplyContext {
|
||||
ModifierApplyContext(const RenderData &render_data,
|
||||
SeqRenderState &render_state,
|
||||
const Strip &strip,
|
||||
const float3x3 &transform,
|
||||
const float3x3 &transform_comp_result,
|
||||
const float timeline_frame,
|
||||
SeqResult &result)
|
||||
: render_data(render_data),
|
||||
render_state(render_state),
|
||||
strip(strip),
|
||||
transform(transform),
|
||||
transform_comp_result(transform_comp_result),
|
||||
timeline_frame(timeline_frame),
|
||||
result(result)
|
||||
{
|
||||
}
|
||||
const RenderData &render_data;
|
||||
SeqRenderState &render_state;
|
||||
const Strip &strip;
|
||||
|
||||
/* Transformation from strip image local pixel coordinates to the
|
||||
* full render area pixel coordinates.This is used to sample
|
||||
* modifier masks (since masks are in full render area space). */
|
||||
const float3x3 transform;
|
||||
/* Transformation to apply when sampling masks in compositor modifier. */
|
||||
const float3x3 transform_comp_result;
|
||||
/* Timeline frame at which the modifiers are being applied at. */
|
||||
const float timeline_frame;
|
||||
SeqResult &result;
|
||||
};
|
||||
|
||||
void modifier_apply_stack(ModifierApplyContext &context);
|
||||
|
||||
ImBuf *modifier_render_mask_input(const ModifierApplyContext &context,
|
||||
const StripModifierData &smd);
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip);
|
||||
|
||||
void draw_mask_input_type_settings(const bContext *C, ui::Layout &layout, PointerRNA *ptr);
|
||||
|
||||
bool modifier_ui_poll(const bContext *C, PanelType *pt);
|
||||
|
||||
using PanelDrawFn = void (*)(const bContext *, Panel *);
|
||||
|
||||
PanelType *modifier_panel_register(ARegionType *region_type,
|
||||
const eStripModifierType type,
|
||||
PanelDrawFn draw);
|
||||
|
||||
float4 load_pixel_premul(const uchar *ptr);
|
||||
float4 load_pixel_premul(const float *ptr);
|
||||
void store_pixel_premul(const float4 pix, uchar *ptr);
|
||||
void store_pixel_premul(const float4 pix, float *ptr);
|
||||
float4 load_pixel_raw(const uchar *ptr);
|
||||
float4 load_pixel_raw(const float *ptr);
|
||||
void store_pixel_raw(const float4 pix, uchar *ptr);
|
||||
void store_pixel_raw(const float4 pix, float *ptr);
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: no mask is present. */
|
||||
struct MaskSamplerNone {
|
||||
void begin_row(int64_t /*y*/) {}
|
||||
void apply_mask(const float4 /*input*/, float4 & /*result*/) {}
|
||||
float load_mask_min()
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: floating point mask,
|
||||
* same size as input, no transform. */
|
||||
struct MaskSamplerDirectFloat {
|
||||
MaskSamplerDirectFloat(const ImBuf *mask) : mask(mask)
|
||||
{
|
||||
BLI_assert(mask && mask->float_data());
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
BLI_assert(y >= 0 && y < mask->y);
|
||||
ptr = mask->float_data() + y * mask->x * 4;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float3 m(this->ptr);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->ptr += 4;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float r = std::min({this->ptr[0], this->ptr[1], this->ptr[2]});
|
||||
this->ptr += 4;
|
||||
return r;
|
||||
}
|
||||
const float *ptr = nullptr;
|
||||
const ImBuf *mask;
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: byte mask,
|
||||
* same size as input, no transform. */
|
||||
struct MaskSamplerDirectByte {
|
||||
MaskSamplerDirectByte(const ImBuf *mask) : mask(mask)
|
||||
{
|
||||
BLI_assert(mask && mask->byte_data());
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
BLI_assert(y >= 0 && y < mask->y);
|
||||
ptr = mask->byte_data() + y * mask->x * 4;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float3 m;
|
||||
rgb_uchar_to_float(m, this->ptr);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->ptr += 4;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float r = float(std::min({this->ptr[0], this->ptr[1], this->ptr[2]})) * (1.0f / 255.0f);
|
||||
this->ptr += 4;
|
||||
return r;
|
||||
}
|
||||
const uchar *ptr = nullptr;
|
||||
const ImBuf *mask;
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: floating point mask,
|
||||
* sample mask with a transform. */
|
||||
struct MaskSamplerTransformedFloat {
|
||||
MaskSamplerTransformedFloat(const ImBuf *mask, const float3x3 &transform)
|
||||
: mask(mask), transform(transform)
|
||||
{
|
||||
BLI_assert(mask && mask->float_data());
|
||||
start_uv = transform.location().xy();
|
||||
add_x = transform.x_axis().xy();
|
||||
add_y = transform.y_axis().xy();
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
this->cur_y = y;
|
||||
this->cur_x = 0;
|
||||
/* Sample at pixel centers. */
|
||||
this->cur_uv_row = this->start_uv + (y + 0.5f) * this->add_y + 0.5f * this->add_x;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
float4 m;
|
||||
math::interpolate_bilinear_border_fl(
|
||||
this->mask->float_data(), m, this->mask->x, this->mask->y, 4, uv.x, uv.y);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->cur_x++;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
float4 m;
|
||||
math::interpolate_bilinear_border_fl(
|
||||
this->mask->float_data(), m, this->mask->x, this->mask->y, 4, uv.x, uv.y);
|
||||
float r = std::min({m.x, m.y, m.z});
|
||||
this->cur_x++;
|
||||
return r;
|
||||
}
|
||||
int64_t cur_x = 0, cur_y = 0;
|
||||
const ImBuf *mask;
|
||||
const float3x3 transform;
|
||||
float2 start_uv, add_x, add_y;
|
||||
float2 cur_uv_row;
|
||||
};
|
||||
|
||||
/* Mask sampler for #apply_modifier_op: byte mask,
|
||||
* sample mask with a transform. */
|
||||
struct MaskSamplerTransformedByte {
|
||||
MaskSamplerTransformedByte(const ImBuf *mask, const float3x3 &transform)
|
||||
: mask(mask), transform(transform)
|
||||
{
|
||||
BLI_assert(mask && mask->byte_data());
|
||||
start_uv = transform.location().xy();
|
||||
add_x = transform.x_axis().xy();
|
||||
add_y = transform.y_axis().xy();
|
||||
}
|
||||
void begin_row(int64_t y)
|
||||
{
|
||||
this->cur_y = y;
|
||||
this->cur_x = 0;
|
||||
/* Sample at pixel centers. */
|
||||
this->cur_uv_row = this->start_uv + (y + 0.5f) * this->add_y + 0.5f * this->add_x;
|
||||
}
|
||||
void apply_mask(const float4 input, float4 &result)
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
uchar4 mb = math::interpolate_bilinear_border_byte(
|
||||
this->mask->byte_data(), this->mask->x, this->mask->y, uv.x, uv.y);
|
||||
float3 m;
|
||||
rgb_uchar_to_float(m, mb);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
this->cur_x++;
|
||||
}
|
||||
float load_mask_min()
|
||||
{
|
||||
float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f;
|
||||
uchar4 m = math::interpolate_bilinear_border_byte(
|
||||
this->mask->byte_data(), this->mask->x, this->mask->y, uv.x, uv.y);
|
||||
float r = float(std::min({m.x, m.y, m.z})) * (1.0f / 255.0f);
|
||||
this->cur_x++;
|
||||
return r;
|
||||
}
|
||||
int64_t cur_x = 0, cur_y = 0;
|
||||
const ImBuf *mask;
|
||||
const float3x3 transform;
|
||||
float2 start_uv, add_x, add_y;
|
||||
float2 cur_uv_row;
|
||||
};
|
||||
|
||||
/* Given `T` that implements an `apply` function:
|
||||
*
|
||||
* template <typename ImageT, typename MaskSampler>
|
||||
* void apply(ImageT* image, MaskSampler &mask, int image_x, IndexRange y_range);
|
||||
*
|
||||
* this function calls the apply() function in parallel
|
||||
* chunks of the image to process, and with needed
|
||||
* uchar or float ImageT types, and with appropriate MaskSampler
|
||||
* instantiated, depending on whether the mask exists, data type
|
||||
* of the mask, and whether it needs a transformation or can be
|
||||
* sampled directly.
|
||||
*
|
||||
* Both input and mask images are expected to have
|
||||
* 4 (RGBA) color channels. Input is modified. */
|
||||
template<typename T>
|
||||
void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask, const float3x3 &mask_transform)
|
||||
{
|
||||
if (ibuf == nullptr) {
|
||||
return;
|
||||
}
|
||||
BLI_assert_msg(ibuf->channels == 0 || ibuf->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
BLI_assert_msg(mask == nullptr || mask->channels == 0 || mask->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
const bool direct_mask_sampling = mask == nullptr || (mask->x == ibuf->x && mask->y == ibuf->y &&
|
||||
math::is_identity(mask_transform));
|
||||
const int image_x = ibuf->x;
|
||||
uchar *image_byte = ibuf->byte_data_for_write();
|
||||
float *image_float = ibuf->float_data_for_write();
|
||||
threading::parallel_for(IndexRange(ibuf->y), 16, [&](IndexRange y_range) {
|
||||
const uchar *mask_byte = mask ? mask->byte_data() : nullptr;
|
||||
const float *mask_float = mask ? mask->float_data() : nullptr;
|
||||
|
||||
/* Instantiate the needed processing function based on image/mask
|
||||
* data types. */
|
||||
if (image_byte) {
|
||||
if (mask_byte) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectByte sampler(mask);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedByte sampler(mask, mask_transform);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else if (mask_float) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectFloat sampler(mask);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedFloat sampler(mask, mask_transform);
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MaskSamplerNone sampler;
|
||||
op.apply(image_byte, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else if (image_float) {
|
||||
if (mask_byte) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectByte sampler(mask);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedByte sampler(mask, mask_transform);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else if (mask_float) {
|
||||
if (direct_mask_sampling) {
|
||||
MaskSamplerDirectFloat sampler(mask);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
else {
|
||||
MaskSamplerTransformedFloat sampler(mask, mask_transform);
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
else {
|
||||
MaskSamplerNone sampler;
|
||||
op.apply(image_float, sampler, image_x, y_range);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user