Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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(&region_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

View File

@@ -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