VSE: Update Strip Modifier UI

This PR updates the VSE strip modifiers interface.
It now uses the same design as the object modifiers.

Changes:

* Except for the "Mask Input" subpanel, the modifier UIs are unchanged.
* Modifiers can now be rearranged using drag & drop.
* Additionally, there is now an active strip modifier. This is exposed
   though python via `strip.modifiers.active`.

This is in part for !139634 which needs the concept of an active modifier.

Notes:

* The `modifier.cc` file included all the implementation of all modifiers.
   With the addition of a another new callback in this PR, this file was
   getting quite big so I split everything out into individual files for all
  modifiers. The modifiers are getting registered at launch.
* The modifier panels are getting added using a UI template
  (`template_strip_modifiers`) very similar to the object modifiers.

Pull Request: https://projects.blender.org/blender/blender/pulls/145367
This commit is contained in:
Falk David
2025-09-04 15:01:57 +02:00
committed by Falk David
parent 7f94b86038
commit 866fcd0a09
49 changed files with 2742 additions and 1658 deletions

View File

@@ -275,6 +275,7 @@ const bTheme U_theme_default = {
.panel_outline = RGBA(0xffffff11),
.panel_title = RGBA(0xe6e6e6ff),
.panel_text = RGBA(0xe6e6e6ff),
.panel_active = RGBA(0x4772b3ff),
},
.common = {
.anim = {
@@ -327,7 +328,6 @@ const bTheme U_theme_default = {
.header_text_hi = RGBA(0xffffffff),
.tab_back = RGBA(0x1d1d1d00),
.button = RGBA(0x303030ff),
.active = RGBA(0x4772b3ff),
.vertex_size = 3,
.outline_width = 1,
.facedot_size = 4,

View File

@@ -2977,6 +2977,7 @@ class SEQUENCER_PT_view_safe_areas_center_cut(SequencerButtonsPanel_Output, Pane
class SEQUENCER_PT_modifiers(SequencerButtonsPanel, Panel):
bl_label = "Modifiers"
bl_options = {'HIDE_HEADER'}
bl_category = "Modifiers"
def draw(self, context):
@@ -2996,103 +2997,7 @@ class SEQUENCER_PT_modifiers(SequencerButtonsPanel, Panel):
layout.operator_menu_enum("sequencer.strip_modifier_add", "type")
layout.operator("sequencer.strip_modifier_copy")
for mod in strip.modifiers:
box = layout.box()
row = box.row()
row.use_property_decorate = False
row.prop(mod, "show_expanded", text="", emboss=False)
row.prop(mod, "name", text="")
row.prop(mod, "mute", text="")
row.use_property_decorate = True
sub = row.row(align=True)
props = sub.operator("sequencer.strip_modifier_move", text="", icon='TRIA_UP')
props.name = mod.name
props.direction = 'UP'
props = sub.operator("sequencer.strip_modifier_move", text="", icon='TRIA_DOWN')
props.name = mod.name
props.direction = 'DOWN'
row.operator("sequencer.strip_modifier_remove", text="", icon='X', emboss=False).name = mod.name
if mod.show_expanded:
if sound is None:
if mod.type == 'COLOR_BALANCE':
box.prop(mod, "color_multiply")
draw_color_balance(box, mod.color_balance)
elif mod.type == 'CURVES':
box.template_curve_mapping(mod, "curve_mapping", type='COLOR', show_tone=True)
elif mod.type == 'HUE_CORRECT':
box.template_curve_mapping(mod, "curve_mapping", type='HUE')
elif mod.type == 'BRIGHT_CONTRAST':
col = box.column()
col.prop(mod, "bright")
col.prop(mod, "contrast")
elif mod.type == 'WHITE_BALANCE':
col = box.column()
col.prop(mod, "white_value")
elif mod.type == 'TONEMAP':
col = box.column()
col.prop(mod, "tonemap_type")
if mod.tonemap_type == 'RD_PHOTORECEPTOR':
col.prop(mod, "intensity")
col.prop(mod, "contrast")
col.prop(mod, "adaptation")
col.prop(mod, "correction")
elif mod.tonemap_type == 'RH_SIMPLE':
col.prop(mod, "key")
col.prop(mod, "offset")
col.prop(mod, "gamma")
box.separator(type='LINE')
col = box.column()
row = col.row()
row.prop(mod, "input_mask_type", expand=True)
if mod.input_mask_type == 'STRIP':
sequences_object = ed
if ed.meta_stack:
sequences_object = ed.meta_stack[-1]
col.prop_search(mod, "input_mask_strip", sequences_object, "strips", text="Mask")
else:
col.prop(mod, "input_mask_id")
row = col.row()
row.prop(mod, "mask_time", expand=True)
else:
if mod.type == 'SOUND_EQUALIZER':
# eq_row = box.row()
# eq_graphs = eq_row.operator_menu_enum("sequencer.strip_modifier_equalizer_redefine", "graphs")
# eq_graphs.name = mod.name
flow = box.grid_flow(
row_major=True,
columns=0,
even_columns=True,
even_rows=False,
align=False,
)
for sound_eq in mod.graphics:
col = flow.column()
box = col.box()
split = box.split(factor=0.4)
split.label(text="{:.2f}".format(sound_eq.curve_mapping.clip_min_x), translate=False)
split.label(text="Hz")
split.alignment = 'RIGHT'
split.label(text="{:.2f}".format(sound_eq.curve_mapping.clip_max_x), translate=False)
box.template_curve_mapping(
sound_eq,
"curve_mapping",
type='NONE',
levels=False,
brush=True,
use_negative_slope=True,
show_tone=False,
)
second_row = col.row()
second_row.label(text="dB")
second_row.alignment = 'CENTER'
layout.template_strip_modifiers()
class SEQUENCER_PT_annotation(AnnotationDataPanel, SequencerButtonsPanel_Output, Panel):

View File

@@ -1118,6 +1118,9 @@ class USERPREF_PT_theme_interface_panel(ThemePanel, CenterAlignMixIn, Panel):
col.prop(ui, "panel_back", text="Background")
col.prop(ui, "panel_sub_back", text="Sub-Panel")
col = col.column()
col.prop(ui, "panel_active", text="Active")
col = flow.column(align=True)
col.prop(ui, "panel_title", text="Title")
col.prop(ui, "panel_text", text="Text")

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 73
#define BLENDER_FILE_SUBVERSION 74
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@@ -1143,7 +1143,7 @@ static bool strip_colorbalance_update_cb(Strip *strip, void * /*user_data*/)
if (data && data->color_balance_legacy) {
StripModifierData *smd = blender::seq::modifier_new(
strip, nullptr, seqModifierType_ColorBalance);
strip, nullptr, eSeqModifierType_ColorBalance);
ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd;
cbmd->color_balance = *data->color_balance_legacy;

View File

@@ -780,11 +780,11 @@ static void do_version_curvemapping_walker(Main *bmain, void (*callback)(CurveMa
smd->type);
if (smti) {
if (smd->type == seqModifierType_Curves) {
if (smd->type == eSeqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
callback(&cmd->curve_mapping);
}
else if (smd->type == seqModifierType_HueCorrect) {
else if (smd->type == eSeqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
callback(&hcmd->curve_mapping);
}

View File

@@ -499,7 +499,7 @@ static bool do_versions_sequencer_color_tags(Strip *strip, void * /*user_data*/)
static bool do_versions_sequencer_color_balance_sop(Strip *strip, void * /*user_data*/)
{
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->type == seqModifierType_ColorBalance) {
if (smd->type == eSeqModifierType_ColorBalance) {
StripColorBalance *cb = &((ColorBalanceModifierData *)smd)->color_balance;
cb->method = SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN;
for (int i = 0; i < 3; i++) {

View File

@@ -616,7 +616,7 @@ static void hue_correct_set_wrapping(CurveMapping *curve_mapping)
static bool strip_hue_correct_set_wrapping(Strip *strip, void * /*user_data*/)
{
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->type == seqModifierType_HueCorrect) {
if (smd->type == eSeqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
CurveMapping *cumap = (CurveMapping *)&hcmd->curve_mapping;
hue_correct_set_wrapping(cumap);

View File

@@ -3022,6 +3022,22 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
}
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 74)) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
if (scene->ed != nullptr) {
/* Set the first strip modifier as the active one and uncollapse the root panel. */
blender::seq::for_each_callback(&scene->ed->seqbase, [&](Strip *strip) -> bool {
seq::modifier_set_active(strip,
static_cast<StripModifierData *>(strip->modifiers.first));
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
smd->layout_panel_open_flag |= UI_PANEL_DATA_EXPAND_ROOT;
}
return true;
});
}
}
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.

View File

@@ -399,6 +399,10 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
FROM_DEFAULT_V4_UCHAR(common.anim.long_key_selected);
}
if (!USER_VERSION_ATLEAST(500, 74)) {
FROM_DEFAULT_V4_UCHAR(tui.panel_active);
}
/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a USER_VERSION_ATLEAST check.

View File

@@ -41,6 +41,8 @@
#include "RNA_define.hh"
#include "SEQ_modifier.hh"
#include "WM_api.hh"
#include "wm.hh"
@@ -62,6 +64,7 @@ void BlendfileLoadingBaseTest::SetUpTestCase()
BKE_appdir_init();
IMB_init();
BKE_modifier_init();
blender::seq::modifiers_init();
DEG_register_node_types();
RNA_init();
blender::bke::node_system_init();

View File

@@ -28,7 +28,7 @@ void StripModifierDataBackup::reset()
void StripModifierDataBackup::init_from_modifier(StripModifierData *smd)
{
if (smd->type == seqModifierType_SoundEqualizer) {
if (smd->type == eSeqModifierType_SoundEqualizer) {
sound_in = smd->runtime.last_sound_in;
sound_out = smd->runtime.last_sound_out;
last_buf = smd->runtime.last_buf;
@@ -41,7 +41,7 @@ void StripModifierDataBackup::init_from_modifier(StripModifierData *smd)
void StripModifierDataBackup::restore_to_modifier(StripModifierData *smd)
{
if (smd->type == seqModifierType_SoundEqualizer) {
if (smd->type == eSeqModifierType_SoundEqualizer) {
smd->runtime.last_sound_in = sound_in;
smd->runtime.last_sound_out = sound_out;
smd->runtime.last_buf = last_buf;

View File

@@ -2380,6 +2380,7 @@ void uiTemplatePathBuilder(uiLayout *layout,
PointerRNA *root_ptr,
std::optional<blender::StringRefNull> text);
void uiTemplateModifiers(uiLayout *layout, bContext *C);
void uiTemplateStripModifiers(uiLayout *layout, bContext *C);
/**
* Check if the shader effect panels don't match the data and rebuild the panels if so.
*/

View File

@@ -76,6 +76,7 @@ enum ThemeColorID {
TH_PANEL_BACK,
TH_PANEL_SUB_BACK,
TH_PANEL_OUTLINE,
TH_PANEL_ACTIVE,
TH_BUTBACK,

View File

@@ -87,6 +87,7 @@ set(SRC
templates/interface_template_search_operator.cc
templates/interface_template_shader_fx.cc
templates/interface_template_status.cc
templates/interface_template_strip_modifiers.cc
templates/interface_templates.cc
interface_undo.cc
interface_utils.cc
@@ -133,6 +134,7 @@ set(LIB
PRIVATE bf::animrig
PRIVATE bf::nodes
PRIVATE bf::render
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
)

View File

@@ -1081,7 +1081,7 @@ static void panel_draw_border(const Panel *panel,
}
float color[4];
UI_GetThemeColor4fv(is_active ? TH_SELECT_ACTIVE : TH_PANEL_OUTLINE, color);
UI_GetThemeColor4fv(is_active ? TH_PANEL_ACTIVE : TH_PANEL_OUTLINE, color);
if (color[3] == 0.0f) {
return; /* No border to draw. */
}

View File

@@ -286,6 +286,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case TH_PANEL_OUTLINE:
cp = btheme->tui.panel_outline;
break;
case TH_PANEL_ACTIVE:
cp = btheme->tui.panel_active;
break;
case TH_BUTBACK:
cp = ts->button;

View File

@@ -0,0 +1,90 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*
* Template for building the panel layout for the active strip's modifiers.
*/
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "BKE_context.hh"
#include "BKE_screen.hh"
#include "BLI_listbase.h"
#include "SEQ_modifier.hh"
#include "SEQ_select.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
static void strip_modifier_panel_id(void *smd_link, char *r_name)
{
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_link);
blender::seq::modifier_type_panel_id(eStripModifierType(smd->type), r_name);
}
void uiTemplateStripModifiers(uiLayout * /*layout*/, bContext *C)
{
using namespace blender;
ARegion *region = CTX_wm_region(C);
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
if (!sequencer_scene) {
return;
}
Strip *active_strip = seq::select_active_get(sequencer_scene);
BLI_assert(active_strip != nullptr);
ListBase *modifiers = &active_strip->modifiers;
const bool panels_match = UI_panel_list_matches_data(region, modifiers, strip_modifier_panel_id);
if (!panels_match) {
UI_panels_free_instanced(C, region);
LISTBASE_FOREACH (StripModifierData *, smd, modifiers) {
const seq::StripModifierTypeInfo *mti = seq::modifier_type_info_get(smd->type);
if (mti->panel_register == nullptr) {
continue;
}
char panel_idname[MAX_NAME];
strip_modifier_panel_id(smd, panel_idname);
/* Create custom data RNA pointer. */
PointerRNA *md_ptr = MEM_new<PointerRNA>(__func__);
*md_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_StripModifier, smd);
UI_panel_add_instanced(C, region, &region->panels, panel_idname, md_ptr);
}
}
else {
/* Assuming there's only one group of instanced panels, update the custom data pointers. */
Panel *panel = static_cast<Panel *>(region->panels.first);
LISTBASE_FOREACH (StripModifierData *, smd, modifiers) {
const seq::StripModifierTypeInfo *mti = seq::modifier_type_info_get(smd->type);
if (mti->panel_register == nullptr) {
continue;
}
/* Move to the next instanced panel corresponding to the next modifier. */
while ((panel->type == nullptr) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) {
panel = panel->next;
/* There shouldn't be fewer panels than modifiers with UIs. */
BLI_assert(panel != nullptr);
}
PointerRNA *md_ptr = MEM_new<PointerRNA>(__func__);
*md_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_StripModifier, smd);
UI_panel_custom_data_set(panel, md_ptr);
panel = panel->next;
}
}
}

View File

@@ -313,6 +313,8 @@ void SEQUENCER_OT_strip_modifier_add(wmOperatorType *ot);
void SEQUENCER_OT_strip_modifier_remove(wmOperatorType *ot);
void SEQUENCER_OT_strip_modifier_move(wmOperatorType *ot);
void SEQUENCER_OT_strip_modifier_copy(wmOperatorType *ot);
void SEQUENCER_OT_strip_modifier_move_to_index(wmOperatorType *ot);
void SEQUENCER_OT_strip_modifier_set_active(wmOperatorType *ot);
void SEQUENCER_OT_strip_modifier_equalizer_redefine(wmOperatorType *ot);
/* `sequencer_view.cc` */

View File

@@ -20,6 +20,7 @@
#include "RNA_define.hh"
#include "RNA_enum_types.hh"
#include "RNA_prototypes.hh"
#include "SEQ_modifier.hh"
#include "SEQ_relations.hh"
@@ -27,6 +28,8 @@
#include "SEQ_sequencer.hh"
#include "SEQ_sound.hh"
#include "UI_interface_c.hh"
/* Own include. */
#include "sequencer_intern.hh"
@@ -386,4 +389,120 @@ void SEQUENCER_OT_strip_modifier_equalizer_redefine(wmOperatorType *ot)
/** \} */
/* ------------------------------------------------------------------- */
/** \name Move to Index Modifier Operator
* \{ */
static wmOperatorStatus modifier_move_to_index_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_sequencer_scene(C);
Strip *strip = seq::select_active_get(scene);
char name[MAX_NAME];
RNA_string_get(op->ptr, "modifier", name);
const int index = RNA_int_get(op->ptr, "index");
StripModifierData *smd = seq::modifier_find_by_name(strip, name);
if (!smd) {
return OPERATOR_CANCELLED;
}
if (!seq::modifier_move_to_index(strip, smd, index)) {
return OPERATOR_CANCELLED;
}
if (ELEM(strip->type, STRIP_TYPE_SOUND_RAM)) {
DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS | ID_RECALC_AUDIO);
}
else {
seq::relations_invalidate_cache(scene, strip);
}
WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static wmOperatorStatus modifier_move_to_index_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
BLI_assert(RNA_struct_property_is_set(op->ptr, "modifier"));
return modifier_move_to_index_exec(C, op);
}
void SEQUENCER_OT_strip_modifier_move_to_index(wmOperatorType *ot)
{
PropertyRNA *prop;
ot->name = "Move Active Strip Modifier to Index";
ot->description =
"Change the strip modifier's index in the stack so it evaluates after the set number of "
"others";
ot->idname = "SEQUENCER_OT_strip_modifier_move_to_index";
ot->invoke = modifier_move_to_index_invoke;
ot->exec = modifier_move_to_index_exec;
ot->poll = sequencer_strip_editable_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
prop = RNA_def_string(
ot->srna, "modifier", nullptr, MAX_NAME, "Modifier", "Name of the modifier to edit");
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_int(
ot->srna, "index", 0, 0, INT_MAX, "Index", "The index to move the modifier to", 0, INT_MAX);
}
/** \} */
/* ------------------------------------------------------------------- */
/** \name Set Active Modifier Operator
* \{ */
static wmOperatorStatus modifier_set_active_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_sequencer_scene(C);
Strip *strip = seq::select_active_get(scene);
char name[MAX_NAME];
RNA_string_get(op->ptr, "modifier", name);
StripModifierData *smd = seq::modifier_find_by_name(strip, name);
/* If there is no modifier set for this operator, clear the active modifier field. */
blender::seq::modifier_set_active(strip, smd);
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, scene);
return OPERATOR_FINISHED;
}
static wmOperatorStatus modifier_set_active_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
BLI_assert(RNA_struct_property_is_set(op->ptr, "modifier"));
return modifier_set_active_exec(C, op);
}
void SEQUENCER_OT_strip_modifier_set_active(wmOperatorType *ot)
{
ot->name = "Set Active Strip Modifier";
ot->description = "Activate the strip modifier to use as the context";
ot->idname = "SEQUENCER_OT_strip_modifier_set_active";
ot->invoke = modifier_set_active_invoke;
ot->exec = modifier_set_active_exec;
ot->poll = sequencer_strip_editable_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
ot->prop = RNA_def_string(
ot->srna, "modifier", nullptr, MAX_NAME, "Modifier", "Name of the strip modifier to edit");
RNA_def_property_flag(ot->prop, PROP_HIDDEN);
}
/** \} */
} // namespace blender::ed::vse

View File

@@ -124,6 +124,8 @@ void sequencer_operatortypes()
WM_operatortype_append(SEQUENCER_OT_strip_modifier_remove);
WM_operatortype_append(SEQUENCER_OT_strip_modifier_move);
WM_operatortype_append(SEQUENCER_OT_strip_modifier_copy);
WM_operatortype_append(SEQUENCER_OT_strip_modifier_move_to_index);
WM_operatortype_append(SEQUENCER_OT_strip_modifier_set_active);
WM_operatortype_append(SEQUENCER_OT_strip_modifier_equalizer_redefine);
/* sequencer_view.h */

View File

@@ -49,6 +49,7 @@
#include "WM_message.hh"
#include "SEQ_channels.hh"
#include "SEQ_modifier.hh"
#include "SEQ_offscreen.hh"
#include "SEQ_preview_cache.hh"
#include "SEQ_retiming.hh"
@@ -1211,6 +1212,15 @@ void ED_spacetype_sequencer()
art->draw = sequencer_buttons_region_draw;
BLI_addhead(&st->regiontypes, art);
/* Register the panel types from strip modifiers. The actual panels are built per strip modifier
* rather than per modifier type. */
for (int i = 0; i < NUM_STRIP_MODIFIER_TYPES; i++) {
const seq::StripModifierTypeInfo *mti = seq::modifier_type_info_get(i);
if (mti != nullptr && mti->panel_register != nullptr) {
mti->panel_register(art);
}
}
sequencer_buttons_register(art);
/* Toolbar. */
art = MEM_callocN<ARegionType>("spacetype sequencer tools region");

View File

@@ -560,7 +560,11 @@ typedef struct StripModifierData {
struct Mask *mask_id;
int persistent_uid;
char _pad[4];
/**
* Bits that can be used for open-states of layout panels in the modifier.
*/
uint16_t layout_panel_open_flag;
char _pad[2];
StripModifierDataRuntime runtime;
} StripModifierData;
@@ -872,14 +876,15 @@ enum {
/** #StripModifierData.type */
typedef enum eStripModifierType {
seqModifierType_ColorBalance = 1,
seqModifierType_Curves = 2,
seqModifierType_HueCorrect = 3,
seqModifierType_BrightContrast = 4,
seqModifierType_Mask = 5,
seqModifierType_WhiteBalance = 6,
seqModifierType_Tonemap = 7,
seqModifierType_SoundEqualizer = 8,
eSeqModifierType_None = 0,
eSeqModifierType_ColorBalance = 1,
eSeqModifierType_Curves = 2,
eSeqModifierType_HueCorrect = 3,
eSeqModifierType_BrightContrast = 4,
eSeqModifierType_Mask = 5,
eSeqModifierType_WhiteBalance = 6,
eSeqModifierType_Tonemap = 7,
eSeqModifierType_SoundEqualizer = 8,
/* Keep last. */
NUM_STRIP_MODIFIER_TYPES,
} eStripModifierType;
@@ -888,6 +893,7 @@ typedef enum eStripModifierType {
typedef enum eStripModifierFlag {
STRIP_MODIFIER_FLAG_MUTE = (1 << 0),
STRIP_MODIFIER_FLAG_EXPANDED = (1 << 1),
STRIP_MODIFIER_FLAG_ACTIVE = (1 << 2),
} eStripModifierFlag;
typedef enum eModMaskInput {

View File

@@ -247,7 +247,7 @@ typedef struct ThemeUI {
unsigned char panel_outline[4];
unsigned char panel_title[4];
unsigned char panel_text[4];
char _pad2[4];
unsigned char panel_active[4];
} ThemeUI;

View File

@@ -81,7 +81,7 @@ static bool seq_update_modifier_curve(Strip *strip, void *user_data)
* curve mapping. */
SeqCurveMappingUpdateData *data = static_cast<SeqCurveMappingUpdateData *>(user_data);
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->type == seqModifierType_Curves) {
if (smd->type == eSeqModifierType_Curves) {
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
if (&cmd->curve_mapping == data->curve) {
blender::seq::relations_invalidate_cache(data->scene, strip);

View File

@@ -42,16 +42,16 @@ struct EffectInfo {
/* These wrap strangely, disable formatting for fixed indentation and wrapping. */
/* clang-format off */
#define RNA_ENUM_SEQUENCER_VIDEO_MODIFIER_TYPE_ITEMS \
{seqModifierType_BrightContrast, "BRIGHT_CONTRAST", ICON_NONE, "Brightness/Contrast", ""}, \
{seqModifierType_ColorBalance, "COLOR_BALANCE", ICON_NONE, "Color Balance", ""}, \
{seqModifierType_Curves, "CURVES", ICON_NONE, "Curves", ""}, \
{seqModifierType_HueCorrect, "HUE_CORRECT", ICON_NONE, "Hue Correct", ""}, \
{seqModifierType_Mask, "MASK", ICON_NONE, "Mask", ""}, \
{seqModifierType_Tonemap, "TONEMAP", ICON_NONE, "Tone Map", ""}, \
{seqModifierType_WhiteBalance, "WHITE_BALANCE", ICON_NONE, "White Balance", ""}
{eSeqModifierType_BrightContrast, "BRIGHT_CONTRAST", ICON_NONE, "Brightness/Contrast", ""}, \
{eSeqModifierType_ColorBalance, "COLOR_BALANCE", ICON_NONE, "Color Balance", ""}, \
{eSeqModifierType_Curves, "CURVES", ICON_NONE, "Curves", ""}, \
{eSeqModifierType_HueCorrect, "HUE_CORRECT", ICON_NONE, "Hue Correct", ""}, \
{eSeqModifierType_Mask, "MASK", ICON_NONE, "Mask", ""}, \
{eSeqModifierType_Tonemap, "TONEMAP", ICON_NONE, "Tone Map", ""}, \
{eSeqModifierType_WhiteBalance, "WHITE_BALANCE", ICON_NONE, "White Balance", ""}
#define RNA_ENUM_SEQUENCER_AUDIO_MODIFIER_TYPE_ITEMS \
{seqModifierType_SoundEqualizer, "SOUND_EQUALIZER", ICON_NONE, "Sound Equalizer", ""}
{eSeqModifierType_SoundEqualizer, "SOUND_EQUALIZER", ICON_NONE, "Sound Equalizer", ""}
/* clang-format on */
const EnumPropertyItem rna_enum_strip_modifier_type_items[] = {
@@ -663,6 +663,34 @@ static void rna_Strip_use_proxy_set(PointerRNA *ptr, bool value)
blender::seq::proxy_set(strip, value != 0);
}
static PointerRNA rna_Strip_active_modifier_get(PointerRNA *ptr)
{
const Strip *strip = ptr->data_as<Strip>();
StripModifierData *smd = blender::seq::modifier_get_active(strip);
return RNA_pointer_create_with_parent(*ptr, &RNA_StripModifier, smd);
}
static void rna_Strip_active_modifier_set(PointerRNA *ptr, PointerRNA value, ReportList *reports)
{
Strip *strip = ptr->data_as<Strip>();
StripModifierData *smd = value.data_as<StripModifierData>();
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, ptr->owner_id);
if (RNA_pointer_is_null(&value)) {
blender::seq::modifier_set_active(strip, nullptr);
return;
}
if (BLI_findindex(&strip->modifiers, smd) == -1) {
BKE_reportf(
reports, RPT_ERROR, "Modifier \"%s\" is not in the strip's modifier list", smd->name);
return;
}
blender::seq::modifier_set_active(strip, smd);
}
static bool transform_strip_cmp_fn(Strip *strip, void *arg_pt)
{
StripSearchData *data = static_cast<StripSearchData *>(arg_pt);
@@ -1207,7 +1235,7 @@ static bool colbalance_seq_cmp_fn(Strip *strip, void *arg_pt)
for (StripModifierData *smd = static_cast<StripModifierData *>(strip->modifiers.first); smd;
smd = smd->next)
{
if (smd->type == seqModifierType_ColorBalance) {
if (smd->type == eSeqModifierType_ColorBalance) {
ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd;
if (&cbmd->color_balance == data->data) {
@@ -1400,19 +1428,19 @@ static StructRNA *rna_StripModifier_refine(PointerRNA *ptr)
StripModifierData *smd = (StripModifierData *)ptr->data;
switch (smd->type) {
case seqModifierType_ColorBalance:
case eSeqModifierType_ColorBalance:
return &RNA_ColorBalanceModifier;
case seqModifierType_Curves:
case eSeqModifierType_Curves:
return &RNA_CurvesModifier;
case seqModifierType_HueCorrect:
case eSeqModifierType_HueCorrect:
return &RNA_HueCorrectModifier;
case seqModifierType_BrightContrast:
case eSeqModifierType_BrightContrast:
return &RNA_BrightContrastModifier;
case seqModifierType_WhiteBalance:
case eSeqModifierType_WhiteBalance:
return &RNA_WhiteBalanceModifier;
case seqModifierType_Tonemap:
case eSeqModifierType_Tonemap:
return &RNA_SequencerTonemapModifierData;
case seqModifierType_SoundEqualizer:
case eSeqModifierType_SoundEqualizer:
return &RNA_SoundEqualizerModifier;
default:
return &RNA_StripModifier;
@@ -1470,6 +1498,24 @@ static void rna_StripModifier_name_set(PointerRNA *ptr, const char *value)
}
}
static void rna_StripModifier_is_active_set(PointerRNA *ptr, bool value)
{
StripModifierData *smd = ptr->data_as<StripModifierData>();
if (value) {
/* Disable the active flag of all other modifiers. */
for (StripModifierData *prev_smd = smd->prev; prev_smd != nullptr; prev_smd = prev_smd->prev) {
prev_smd->flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
}
for (StripModifierData *next_smd = smd->next; next_smd != nullptr; next_smd = next_smd->next) {
next_smd->flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
}
smd->flag |= STRIP_MODIFIER_FLAG_ACTIVE;
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, ptr->owner_id);
}
}
static void rna_StripModifier_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
{
/* strip from other scenes could be modified, so using active scene is not reliable */
@@ -2124,6 +2170,7 @@ static const EnumPropertyItem blend_mode_items[] = {
static void rna_def_strip_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
{
StructRNA *srna;
PropertyRNA *prop;
FunctionRNA *func;
PropertyRNA *parm;
@@ -2143,7 +2190,7 @@ static void rna_def_strip_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
parm = RNA_def_enum(func,
"type",
rna_enum_strip_modifier_type_items,
seqModifierType_ColorBalance,
eSeqModifierType_ColorBalance,
"",
"Modifier type to add");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
@@ -2164,6 +2211,17 @@ static void rna_def_strip_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
func = RNA_def_function(srna, "clear", "rna_Strip_modifier_clear");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Remove all modifiers from the strip");
/* Active modifier. */
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "StripModifier");
RNA_def_property_pointer_funcs(
prop, "rna_Strip_active_modifier_get", "rna_Strip_active_modifier_set", nullptr, nullptr);
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Active Modifier", "The active strip modifier in the list");
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, nullptr);
}
static void rna_def_strip(BlenderRNA *brna)
@@ -3710,6 +3768,19 @@ static void rna_def_effects(BlenderRNA *brna)
}
}
static void rna_def_modifier_panel_open_prop(StructRNA *srna, const char *identifier, const int id)
{
BLI_assert(id >= 0);
BLI_assert(id < sizeof(StripModifierData::layout_panel_open_flag) * 8);
PropertyRNA *prop;
prop = RNA_def_property(srna, identifier, PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_boolean_sdna(
prop, nullptr, "modifier.layout_panel_open_flag", (int64_t(1) << id));
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, nullptr);
}
static void rna_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -3759,17 +3830,23 @@ static void rna_def_modifier(BlenderRNA *brna)
RNA_def_property_ui_icon(prop, ICON_HIDE_OFF, -1);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
prop = RNA_def_property(srna, "enable", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, nullptr, "flag", STRIP_MODIFIER_FLAG_MUTE);
RNA_def_property_ui_text(prop, "Enable", "Enable this modifier");
RNA_def_property_ui_icon(prop, ICON_HIDE_ON, 1);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", STRIP_MODIFIER_FLAG_EXPANDED);
RNA_def_property_boolean_sdna(
prop, nullptr, "layout_panel_open_flag", UI_PANEL_DATA_EXPAND_ROOT);
RNA_def_property_ui_text(prop, "Expanded", "Mute expanded settings for the modifier");
RNA_def_property_ui_icon(prop, ICON_RIGHTARROW, 1);
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, nullptr);
prop = RNA_def_property(srna, "input_mask_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "mask_input_type");
RNA_def_property_enum_items(prop, mask_input_type_items);
RNA_def_property_ui_text(prop, "Mask Input Type", "Type of input data used for mask");
RNA_def_property_ui_text(prop, "Type", "Type of input data used for mask");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
prop = RNA_def_property(srna, "mask_time", PROP_ENUM, PROP_NONE);
@@ -3791,6 +3868,15 @@ static void rna_def_modifier(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Mask", "Mask ID used as mask input for the modifier");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
prop = RNA_def_property(srna, "is_active", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", STRIP_MODIFIER_FLAG_ACTIVE);
RNA_def_property_boolean_funcs(prop, nullptr, "rna_StripModifier_is_active_set");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
RNA_def_property_ui_text(prop, "Is Active", "This modifier is active");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
}
static void rna_def_colorbalance_modifier(BlenderRNA *brna)
@@ -3812,6 +3898,8 @@ static void rna_def_colorbalance_modifier(BlenderRNA *brna)
RNA_def_property_float_default(prop, 1.0f);
RNA_def_property_ui_text(prop, "Multiply Colors", "Multiply the intensity of each pixel");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
}
static void rna_def_whitebalance_modifier(BlenderRNA *brna)
@@ -3829,6 +3917,8 @@ static void rna_def_whitebalance_modifier(BlenderRNA *brna)
RNA_def_property_float_sdna(prop, nullptr, "white_value");
RNA_def_property_ui_text(prop, "White Value", "This color defines white in the strip");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
}
static void rna_def_curves_modifier(BlenderRNA *brna)
@@ -3845,6 +3935,8 @@ static void rna_def_curves_modifier(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop, "Curve Mapping", "");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
}
static void rna_def_hue_modifier(BlenderRNA *brna)
@@ -3861,6 +3953,8 @@ static void rna_def_hue_modifier(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "CurveMapping");
RNA_def_property_ui_text(prop, "Curve Mapping", "");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
}
static void rna_def_brightcontrast_modifier(BlenderRNA *brna)
@@ -3884,6 +3978,8 @@ static void rna_def_brightcontrast_modifier(BlenderRNA *brna)
RNA_def_property_range(prop, -100.0f, 100.0f);
RNA_def_property_ui_text(prop, "Contrast", "Adjust the difference in luminosity between pixels");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
}
static void rna_def_tonemap_modifier(BlenderRNA *brna)
@@ -3946,6 +4042,8 @@ static void rna_def_tonemap_modifier(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Color Correction", "If 0, same for all channels; if 1, each independent");
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
}
static void rna_def_modifiers(BlenderRNA *brna)

View File

@@ -1790,6 +1790,10 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Generates the UI layout for the modifier stack");
func = RNA_def_function(srna, "template_strip_modifiers", "uiTemplateStripModifiers");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Generates the UI layout for the strip modifier stack");
func = RNA_def_function(srna, "template_collection_exporters", "uiTemplateCollectionExporters");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Generates the UI layout for collection exporters");

View File

@@ -2004,6 +2004,12 @@ static void rna_def_userdef_theme_ui(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Panel Outline", "Color of the outline of top-level panels");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "panel_active", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(
prop, "Active Panel Outline", "Color of the outline of top-level panels that are active");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
/* Transparent Grid */
prop = RNA_def_property(srna, "transparent_checker_primary", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, nullptr, "transparent_checker_primary");
@@ -3565,12 +3571,6 @@ static void rna_def_userdef_theme_space_buts(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Search Match", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "active_modifier", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, nullptr, "active");
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Active Modifier Outline", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
rna_def_userdef_theme_spaces_main(srna);
rna_def_userdef_theme_spaces_region_main(srna);
}
@@ -3825,6 +3825,12 @@ static void rna_def_userdef_theme_space_seq(BlenderRNA *brna)
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Selected Text", "Text strip editing selection");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
prop = RNA_def_property(srna, "active_modifier", PROP_FLOAT, PROP_COLOR_GAMMA);
RNA_def_property_float_sdna(prop, nullptr, "active");
RNA_def_property_array(prop, 4);
RNA_def_property_ui_text(prop, "Active Modifier Outline", "");
RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
}
static void rna_def_userdef_theme_space_action(BlenderRNA *brna)

View File

@@ -5,6 +5,7 @@
set(INC
PUBLIC .
intern
../editors/include
../animrig
../makesrna
@@ -25,6 +26,7 @@ set(SRC
SEQ_effects.hh
SEQ_iterator.hh
SEQ_modifier.hh
SEQ_modifiertypes.hh
SEQ_offscreen.hh
SEQ_prefetch.hh
SEQ_preview_cache.hh
@@ -64,10 +66,19 @@ set(SRC
intern/effects/vse_effect_text.cc
intern/effects/vse_effect_transform.cc
intern/effects/vse_effect_wipe.cc
intern/modifiers/MOD_brightness_contrast.cc
intern/modifiers/MOD_color_balance.cc
intern/modifiers/MOD_curves.cc
intern/modifiers/MOD_hue_correct.cc
intern/modifiers/MOD_mask.cc
intern/modifiers/MOD_none.cc
intern/modifiers/MOD_sound_equalizer.cc
intern/modifiers/MOD_tonemap.cc
intern/modifiers/MOD_white_balance.cc
intern/modifiers/modifier.cc
intern/modifiers/modifier.hh
intern/iterator.cc
intern/media_presence.cc
intern/modifier.cc
intern/modifier.hh
intern/multiview.cc
intern/multiview.hh
intern/prefetch.cc
@@ -102,6 +113,7 @@ set(LIB
PRIVATE bf::blentranslation
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::extern::fmtlib
PRIVATE bf::gpu
PRIVATE bf::imbuf
PRIVATE bf::imbuf::movie

View File

@@ -8,6 +8,9 @@
* \ingroup sequencer
*/
#include "DNA_sequence_types.h"
struct ARegionType;
struct BlendDataReader;
struct BlendWriter;
struct ImBuf;
@@ -21,6 +24,12 @@ struct StripScreenQuad;
struct RenderData;
struct StripModifierTypeInfo {
/**
* A unique identifier for this modifier. Used to generate the panel id type name.
* See #seq::modifier_type_panel_id.
*/
char idname[/*MAX_NAME*/ 64];
/* default name for the modifier */
char name[/*MAX_NAME*/ 64];
@@ -45,8 +54,13 @@ struct StripModifierTypeInfo {
/* Apply modifier on an image buffer.
* quad contains four corners of the (pre-transform) strip rectangle in pixel space. */
void (*apply)(const StripScreenQuad &quad, StripModifierData *smd, ImBuf *ibuf, ImBuf *mask);
/** Register the panel types for the modifier's UI. */
void (*panel_register)(ARegionType *region_type);
};
void modifiers_init();
const StripModifierTypeInfo *modifier_type_info_get(int type);
StripModifierData *modifier_new(Strip *strip, const char *name, int type);
bool modifier_remove(Strip *strip, StripModifierData *smd);
@@ -66,4 +80,12 @@ void modifier_blend_write(BlendWriter *writer, ListBase *modbase);
void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb);
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd);
bool modifier_move_to_index(Strip *strip, StripModifierData *smd, int new_index);
StripModifierData *modifier_get_active(const Strip *strip);
void modifier_set_active(Strip *strip, StripModifierData *smd);
static constexpr char STRIP_MODIFIER_TYPE_PANEL_PREFIX[] = "STRIPMOD_PT_";
void modifier_type_panel_id(eStripModifierType type, char *r_idname);
} // namespace blender::seq

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#pragma once
#include "SEQ_modifier.hh"
namespace blender::seq {
/* ****************** Type structures for all modifiers ****************** */
extern StripModifierTypeInfo seqModifierType_None;
extern StripModifierTypeInfo seqModifierType_BrightContrast;
extern StripModifierTypeInfo seqModifierType_ColorBalance;
extern StripModifierTypeInfo seqModifierType_Curves;
extern StripModifierTypeInfo seqModifierType_HueCorrect;
extern StripModifierTypeInfo seqModifierType_Mask;
extern StripModifierTypeInfo seqModifierType_SoundEqualizer;
extern StripModifierTypeInfo seqModifierType_Tonemap;
extern StripModifierTypeInfo seqModifierType_WhiteBalance;
} // namespace blender::seq

View File

@@ -1,1492 +0,0 @@
/* SPDX-FileCopyrightText: 2012-2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <algorithm>
#include <cstddef>
#include <cstring>
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.hh"
#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 "BKE_colortools.hh"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "SEQ_modifier.hh"
#include "SEQ_render.hh"
#include "SEQ_sound.hh"
#include "SEQ_time.hh"
#include "SEQ_utils.hh"
#include "BLO_read_write.hh"
#include "modifier.hh"
#include "render.hh"
namespace blender::seq {
/* -------------------------------------------------------------------- */
static bool modifier_has_persistent_uid(const Strip &strip, int uid)
{
LISTBASE_FOREACH (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 = blender::get_default_hash(blender::StringRef(smd.name));
blender::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;
LISTBASE_FOREACH (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 float4 load_pixel_premul(const uchar *ptr)
{
float4 res;
straight_uchar_to_premul_float(res, ptr);
return res;
}
static float4 load_pixel_premul(const float *ptr)
{
return float4(ptr);
}
static void store_pixel_premul(float4 pix, uchar *ptr)
{
premul_float_to_straight_uchar(ptr, pix);
}
static void store_pixel_premul(float4 pix, float *ptr)
{
*reinterpret_cast<float4 *>(ptr) = pix;
}
static float4 load_pixel_raw(const uchar *ptr)
{
float4 res;
rgba_uchar_to_float(res, ptr);
return res;
}
static float4 load_pixel_raw(const float *ptr)
{
return float4(ptr);
}
static void store_pixel_raw(float4 pix, uchar *ptr)
{
rgba_float_to_uchar(ptr, pix);
}
static void store_pixel_raw(float4 pix, float *ptr)
{
*reinterpret_cast<float4 *>(ptr) = pix;
}
/* Byte mask */
static void apply_and_advance_mask(float4 input, float4 &result, const uchar *&mask)
{
float3 m;
rgb_uchar_to_float(m, mask);
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);
mask += 4;
}
/* Float mask */
static void apply_and_advance_mask(float4 input, float4 &result, const float *&mask)
{
float3 m(mask);
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);
mask += 4;
}
/* No mask */
static void apply_and_advance_mask(float4 /*input*/, float4 & /*result*/, const void *& /*mask*/)
{
}
/* Given `T` that implements an `apply` function:
*
* template <typename ImageT, typename MaskT>
* void apply(ImageT* image, const MaskT* mask, IndexRange size);
*
* this function calls the apply() function in parallel
* chunks of the image to process, and with needed
* uchar, float or void types (void is used for mask, when there is
* no masking). Both input and mask images are expected to have
* 4 (RGBA) color channels. Input is modified. */
template<typename T> static void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask)
{
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");
threading::parallel_for(IndexRange(size_t(ibuf->x) * ibuf->y), 32 * 1024, [&](IndexRange range) {
uchar *image_byte = ibuf->byte_buffer.data;
float *image_float = ibuf->float_buffer.data;
const uchar *mask_byte = mask ? mask->byte_buffer.data : nullptr;
const float *mask_float = mask ? mask->float_buffer.data : nullptr;
const void *mask_none = nullptr;
int64_t offset = range.first() * 4;
/* Instantiate the needed processing function based on image/mask
* data types. */
if (image_byte) {
if (mask_byte) {
op.apply(image_byte + offset, mask_byte + offset, range);
}
else if (mask_float) {
op.apply(image_byte + offset, mask_float + offset, range);
}
else {
op.apply(image_byte + offset, mask_none, range);
}
}
else if (image_float) {
if (mask_byte) {
op.apply(image_float + offset, mask_byte + offset, range);
}
else if (mask_float) {
op.apply(image_float + offset, mask_float + offset, range);
}
else {
op.apply(image_float + offset, mask_none, range);
}
}
});
}
/**
* \a timeline_frame is offset by \a fra_offset only in case we are using a real mask.
*/
static ImBuf *modifier_render_mask_input(const RenderData *context,
int mask_input_type,
Strip *mask_strip,
Mask *mask_id,
int timeline_frame,
int fra_offset)
{
ImBuf *mask_input = nullptr;
if (mask_input_type == STRIP_MASK_INPUT_STRIP) {
if (mask_strip) {
SeqRenderState state;
mask_input = seq_render_strip(context, &state, mask_strip, timeline_frame);
}
}
else if (mask_input_type == STRIP_MASK_INPUT_ID) {
/* 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_input = seq_render_mask(context, mask_id, timeline_frame - fra_offset, false);
}
return mask_input;
}
static ImBuf *modifier_mask_get(StripModifierData *smd,
const RenderData *context,
int timeline_frame,
int fra_offset)
{
return modifier_render_mask_input(
context, smd->mask_input_type, smd->mask_strip, smd->mask_id, timeline_frame, fra_offset);
}
/* -------------------------------------------------------------------- */
/** \name Color Balance Modifier
* \{ */
/* 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 MaskT> void apply(uchar *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
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;
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
/* Apply on a float image by doing full math. */
template<typename MaskT> void apply(float *image, const MaskT *mask, IndexRange size)
{
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
/* Lift/Gamma/Gain */
for ([[maybe_unused]] int64_t i : size) {
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;
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
/* Slope/Offset/Power */
for ([[maybe_unused]] int64_t i : size) {
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;
apply_and_advance_mask(input, result, mask);
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 = (ColorBalanceModifierData *)smd;
cbmd->color_multiply = 1.0f;
cbmd->color_balance.method = 0;
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(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const ColorBalanceModifierData *cbmd = (const ColorBalanceModifierData *)smd;
ColorBalanceApplyOp op;
op.init(*cbmd, ibuf->byte_buffer.data != nullptr);
apply_modifier_op(op, ibuf, mask);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name White Balance Modifier
* \{ */
static void whiteBalance_init_data(StripModifierData *smd)
{
WhiteBalanceModifierData *cbmd = (WhiteBalanceModifierData *)smd;
copy_v3_fl(cbmd->white_value, 1.0f);
}
struct WhiteBalanceApplyOp {
float multiplier[3];
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
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
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
};
static void whiteBalance_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const WhiteBalanceModifierData *data = (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, ibuf, mask);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Curves Modifier
* \{ */
static void curves_init_data(StripModifierData *smd)
{
CurvesModifierData *cmd = (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 = (CurvesModifierData *)smd;
BKE_curvemapping_free_data(&cmd->curve_mapping);
}
static void curves_copy_data(StripModifierData *target, StripModifierData *smd)
{
CurvesModifierData *cmd = (CurvesModifierData *)smd;
CurvesModifierData *cmd_target = (CurvesModifierData *)target;
BKE_curvemapping_copy_data(&cmd_target->curve_mapping, &cmd->curve_mapping);
}
struct CurvesApplyOp {
const CurveMapping *curve_mapping;
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
float4 input = load_pixel_premul(image);
float4 result;
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
result.w = input.w;
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
};
static void curves_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
CurvesModifierData *cmd = (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, ibuf, mask);
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Hue Correct Modifier
* \{ */
static void hue_correct_init_data(StripModifierData *smd)
{
HueCorrectModifierData *hcmd = (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, CURVEMAP_SLOPE_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 = (HueCorrectModifierData *)smd;
BKE_curvemapping_free_data(&hcmd->curve_mapping);
}
static void hue_correct_copy_data(StripModifierData *target, StripModifierData *smd)
{
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
HueCorrectModifierData *hcmd_target = (HueCorrectModifierData *)target;
BKE_curvemapping_copy_data(&hcmd_target->curve_mapping, &hcmd->curve_mapping);
}
struct HueCorrectApplyOp {
const CurveMapping *curve_mapping;
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
/* 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);
apply_and_advance_mask(input, result, mask);
store_pixel_raw(result, image);
image += 4;
}
}
};
static void hue_correct_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_init(&hcmd->curve_mapping);
HueCorrectApplyOp op;
op.curve_mapping = &hcmd->curve_mapping;
apply_modifier_op(op, ibuf, mask);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Brightness/Contrast Modifier
* \{ */
struct BrightContrastApplyOp {
float mul;
float add;
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
/* 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;
apply_and_advance_mask(input, result, mask);
store_pixel_raw(result, image);
image += 4;
}
}
};
static void brightcontrast_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const BrightContrastModifierData *bcmd = (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, ibuf, mask);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Mask Modifier
* \{ */
static float load_mask_min(const uchar *&mask)
{
float m = float(min_iii(mask[0], mask[1], mask[2])) * (1.0f / 255.0f);
mask += 4;
return m;
}
static float load_mask_min(const float *&mask)
{
float m = min_fff(mask[0], mask[1], mask[2]);
mask += 4;
return m;
}
static float load_mask_min(const void *& /*mask*/)
{
return 1.0f;
}
struct MaskApplyOp {
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
float m = load_mask_min(mask);
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 premultiplied, so need to premul 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(const StripScreenQuad & /*quad*/,
StripModifierData * /*smd*/,
ImBuf *ibuf,
ImBuf *mask)
{
if (mask == nullptr || (mask->byte_buffer.data == nullptr && mask->float_buffer.data == nullptr))
{
return;
}
MaskApplyOp op;
apply_modifier_op(op, ibuf, mask);
/* Image has gained transparency. */
ibuf->planes = R_IMF_PLANES_RGBA;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Tonemap Modifier
* \{ */
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 = (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(
(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(
(float *)dst, int(count), 1, 4, colorspace, false);
}
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_buffer.data);
IMB_colormanagement_scene_linear_to_colorspace(
(float *)(fptr + range.first()), int(range.size()), 1, 4, colorspace);
}
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(
(float *)src, int(range.size()), 1, 4, colorspace);
const float4 *src_ptr = src;
uchar *bptr = ibuf->byte_buffer.data;
for (const int64_t idx : range) {
premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
src_ptr++;
}
}
static void tonemap_simple(float4 *scene_linear,
ImBuf *mask,
IndexRange range,
const AvgLogLum &avg)
{
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
int64_t index = 0;
for (const int64_t pixel_index : range) {
float4 input = scene_linear[index];
/* 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. */
if (mask != nullptr) {
float3 msk(1.0f);
if (mask_byte != nullptr) {
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
}
else if (mask_float != nullptr) {
msk = mask_float[pixel_index].xyz();
}
pixel = math::interpolate(input.xyz(), pixel, msk);
}
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
index++;
}
}
static void tonemap_rd_photoreceptor(float4 *scene_linear,
ImBuf *mask,
IndexRange range,
const AvgLogLum &avg)
{
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
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;
int64_t index = 0;
for (const int64_t pixel_index : range) {
float4 input = scene_linear[index];
/* 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. */
if (mask != nullptr) {
float3 msk(1.0f);
if (mask_byte != nullptr) {
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
}
else if (mask_float != nullptr) {
msk = mask_float[pixel_index].xyz();
}
pixel = math::interpolate(input.xyz(), pixel, msk);
}
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
index++;
}
}
static bool is_point_inside_quad(const StripScreenQuad &quad, int x, int y)
{
float2 pt(x + 0.5f, y + 0.5f);
return isect_point_quad_v2(pt, quad.v0, quad.v1, quad.v2, quad.v3);
}
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 tonemap_calc_chunk_luminance(const StripScreenQuad &quad,
const bool all_pixels_inside_quad,
const int width,
const IndexRange y_range,
const float4 *scene_linear,
AreaLuminance &r_lum)
{
for (const int y : y_range) {
for (int x = 0; x < width; x++) {
if (all_pixels_inside_quad || is_point_inside_quad(quad, x, y)) {
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(const StripScreenQuad &quad, const ImBuf *ibuf)
{
/* Pixels outside the pre-transform strip area are ignored for luminance calculations.
* If strip area covers whole image, we can trivially accept all pixels. */
const bool all_pixels_inside_quad = is_point_inside_quad(quad, 0, 0) &&
is_point_inside_quad(quad, ibuf->x - 1, 0) &&
is_point_inside_quad(quad, 0, ibuf->y - 1) &&
is_point_inside_quad(quad, ibuf->x - 1, ibuf->y - 1);
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 (ibuf->float_buffer.data != nullptr) {
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
fptr += y_range.first() * ibuf->x;
pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
tonemap_calc_chunk_luminance(quad, all_pixels_inside_quad, ibuf->x, y_range, fptr, lum);
}
else {
const uchar *bptr = ibuf->byte_buffer.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(
quad, all_pixels_inside_quad, 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(const StripScreenQuad &quad,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const SequencerTonemapModifierData *tmmd = (const SequencerTonemapModifierData *)smd;
AreaLuminance lum = tonemap_calc_input_luminance(quad, ibuf);
if (lum.pixel_count == 0) {
return; /* Strip is zero size or off-screen. */
}
AvgLogLum data;
data.tmmd = tmmd;
data.lav = lum.sum / lum.pixel_count;
data.cav.x = lum.color_sum.x / lum.pixel_count;
data.cav.y = lum.color_sum.y / lum.pixel_count;
data.cav.z = lum.color_sum.z / lum.pixel_count;
float maxl = log(double(lum.max) + 1e-5f);
float minl = log(double(lum.min) + 1e-5f);
float avl = lum.log_sum / lum.pixel_count;
data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
float al = exp(double(avl));
data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
threading::parallel_for(
IndexRange(int64_t(ibuf->x) * ibuf->y), 64 * 1024, [&](IndexRange range) {
if (ibuf->float_buffer.data != nullptr) {
/* Float pixels: no need for temporary storage. Luminance calculation already converted
* data to scene linear. */
float4 *pixels = (float4 *)(ibuf->float_buffer.data) + range.first();
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
tonemap_rd_photoreceptor(pixels, mask, range, data);
}
else {
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
tonemap_simple(pixels, mask, range, data);
}
scene_linear_to_image_chunk_float(ibuf, range);
}
else {
/* Byte pixels: temporary storage for scene linear pixel values. */
Array<float4> scene_linear(range.size());
pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
ibuf->byte_buffer.data + range.first() * 4,
scene_linear.data(),
range.size());
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
tonemap_rd_photoreceptor(scene_linear.data(), mask, range, data);
}
else {
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
tonemap_simple(scene_linear.data(), mask, range, data);
}
scene_linear_to_image_chunk_byte(scene_linear.data(), ibuf, range);
}
});
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public Modifier Functions
* \{ */
static StripModifierTypeInfo modifiersTypes[NUM_STRIP_MODIFIER_TYPES] = {
{}, /* First entry is unused. */
{
/*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,
},
{
/*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,
},
{
/*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,
},
{
/*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,
},
{
/*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,
},
{
/*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,
},
{
/*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,
},
{
/*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,
},
};
const StripModifierTypeInfo *modifier_type_info_get(int type)
{
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
return nullptr;
}
return &modifiersTypes[type];
}
StripModifierData *modifier_new(Strip *strip, const char *name, int type)
{
StripModifierData *smd;
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
smd = static_cast<StripModifierData *>(MEM_callocN(smti->struct_size, "sequence modifier"));
smd->type = type;
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED;
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);
modifier_unique_name(strip, smd);
if (smti->init_data) {
smti->init_data(smd);
}
return smd;
}
bool modifier_remove(Strip *strip, StripModifierData *smd)
{
if (BLI_findindex(&strip->modifiers, smd) == -1) {
return false;
}
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);
}
BLI_listbase_clear(&strip->modifiers);
}
void modifier_free(StripModifierData *smd)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
if (smti && smti->free_data) {
smti->free_data(smd);
}
MEM_freeN(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 &&
!time_strip_intersects_frame(
scene, smd->mask_strip, 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(const RenderData *context,
const Strip *strip,
ImBuf *ibuf,
int timeline_frame)
{
const StripScreenQuad quad = get_strip_screen_quad(context, strip);
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
render_imbuf_from_sequencer_space(context->scene, ibuf);
}
LISTBASE_FOREACH (StripModifierData *, smd, &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;
}
/* modifier is muted, do nothing */
if (smd->flag & STRIP_MODIFIER_FLAG_MUTE) {
continue;
}
if (smti->apply && !skip_modifier(context->scene, smd, timeline_frame)) {
int frame_offset;
if (smd->mask_time == STRIP_MASK_TIME_RELATIVE) {
frame_offset = strip->start;
}
else /* if (smd->mask_time == STRIP_MASK_TIME_ABSOLUTE) */ {
frame_offset = smd->mask_id ? ((Mask *)smd->mask_id)->sfra : 0;
}
ImBuf *mask = modifier_mask_get(smd, context, timeline_frame, frame_offset);
smti->apply(quad, smd, ibuf, mask);
if (mask) {
IMB_freeImBuf(mask);
}
}
}
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
}
}
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
StripModifierData *mod_new = static_cast<StripModifierData *>(MEM_dupallocN(mod_src));
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)
{
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
modifier_copy(*strip_new, smd);
}
}
int sequence_supports_modifiers(Strip *strip)
{
return (strip->type != STRIP_TYPE_SOUND_RAM);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name .blend File I/O
* \{ */
void modifier_blend_write(BlendWriter *writer, ListBase *modbase)
{
LISTBASE_FOREACH (StripModifierData *, smd, modbase) {
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
if (smti) {
BLO_write_struct_by_name(writer, smti->struct_name, smd);
if (smd->type == seqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
}
else if (smd->type == seqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_blend_write(writer, &hcmd->curve_mapping);
}
else if (smd->type == seqModifierType_SoundEqualizer) {
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
BLO_write_struct_by_name(writer, "EQCurveMappingData", eqcmd);
BKE_curvemapping_blend_write(writer, &eqcmd->curve_mapping);
}
}
}
else {
BLO_write_struct(writer, StripModifierData, smd);
}
}
}
void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
{
BLO_read_struct_list(reader, StripModifierData, lb);
LISTBASE_FOREACH (StripModifierData *, smd, lb) {
if (smd->mask_strip) {
BLO_read_struct(reader, Strip, &smd->mask_strip);
}
if (smd->type == seqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
}
else if (smd->type == seqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_blend_read(reader, &hcmd->curve_mapping);
}
else if (smd->type == seqModifierType_SoundEqualizer) {
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
BKE_curvemapping_blend_read(reader, &eqcmd->curve_mapping);
}
}
}
}
/** \} */
} // namespace blender::seq

View File

@@ -1,17 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup sequencer
*/
struct Strip;
namespace blender::seq {
bool modifier_persistent_uids_are_valid(const Strip &strip);
} // namespace blender::seq

View File

@@ -0,0 +1,115 @@
/* 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 "SEQ_modifier.hh"
#include "SEQ_modifiertypes.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "modifier.hh"
namespace blender::seq {
struct BrightContrastApplyOp {
float mul;
float add;
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
/* 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;
apply_and_advance_mask(input, result, mask);
store_pixel_raw(result, image);
image += 4;
}
}
};
static void brightcontrast_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const BrightContrastModifierData *bcmd = (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, ibuf, mask);
}
static void brightcontrast_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *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 (uiLayout *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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,371 @@
/* 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 "SEQ_modifier.hh"
#include "SEQ_modifiertypes.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "RNA_access.hh"
#include "modifier.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 MaskT> void apply(uchar *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
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;
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
/* Apply on a float image by doing full math. */
template<typename MaskT> void apply(float *image, const MaskT *mask, IndexRange size)
{
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
/* Lift/Gamma/Gain */
for ([[maybe_unused]] int64_t i : size) {
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;
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
/* Slope/Offset/Power */
for ([[maybe_unused]] int64_t i : size) {
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;
apply_and_advance_mask(input, result, mask);
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 = (ColorBalanceModifierData *)smd;
cbmd->color_multiply = 1.0f;
cbmd->color_balance.method = 0;
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(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const ColorBalanceModifierData *cbmd = (const ColorBalanceModifierData *)smd;
ColorBalanceApplyOp op;
op.init(*cbmd, ibuf->byte_buffer.data != nullptr);
apply_modifier_op(op, ibuf, mask);
}
static void colorBalance_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *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);
uiLayout &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. */
{
uiLayout &split = flow.column(false).split(0.35f, false);
uiLayout &col = split.column(true);
col.label("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, "Invert", ICON_ARROW_LEFTRIGHT);
uiTemplateColorPicker(&split, &color_balance, "lift", true, false, false, true);
col.separator();
}
{
uiLayout &split = flow.column(false).split(0.35f, false);
uiLayout &col = split.column(true);
col.label("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, "Invert", ICON_ARROW_LEFTRIGHT);
uiTemplateColorPicker(&split, &color_balance, "gamma", true, false, true, true);
col.separator();
}
{
uiLayout &split = flow.column(false).split(0.35f, false);
uiLayout &col = split.column(true);
col.label("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, "Invert", ICON_ARROW_LEFTRIGHT);
uiTemplateColorPicker(&split, &color_balance, "gain", true, false, true, true);
}
}
else if (correction_method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
{
uiLayout &split = flow.column(false).split(0.35f, false);
uiLayout &col = split.column(true);
col.label("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, "Invert", ICON_ARROW_LEFTRIGHT);
uiTemplateColorPicker(&split, &color_balance, "offset", true, false, false, true);
col.separator();
}
{
uiLayout &split = flow.column(false).split(0.35f, false);
uiLayout &col = split.column(true);
col.label("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, "Invert", ICON_ARROW_LEFTRIGHT);
uiTemplateColorPicker(&split, &color_balance, "power", true, false, false, true);
col.separator();
}
{
uiLayout &split = flow.column(false).split(0.35f, false);
uiLayout &col = split.column(true);
col.label("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, "Invert", ICON_ARROW_LEFTRIGHT);
uiTemplateColorPicker(&split, &color_balance, "slope", true, false, false, true);
}
}
else {
BLI_assert_unreachable();
}
if (uiLayout *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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,121 @@
/* 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 "SEQ_modifier.hh"
#include "UI_interface.hh"
#include "UI_interface_c.hh"
#include "UI_interface_layout.hh"
#include "modifier.hh"
namespace blender::seq {
static void curves_init_data(StripModifierData *smd)
{
CurvesModifierData *cmd = (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 = (CurvesModifierData *)smd;
BKE_curvemapping_free_data(&cmd->curve_mapping);
}
static void curves_copy_data(StripModifierData *target, StripModifierData *smd)
{
CurvesModifierData *cmd = (CurvesModifierData *)smd;
CurvesModifierData *cmd_target = (CurvesModifierData *)target;
BKE_curvemapping_copy_data(&cmd_target->curve_mapping, &cmd->curve_mapping);
}
struct CurvesApplyOp {
const CurveMapping *curve_mapping;
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
float4 input = load_pixel_premul(image);
float4 result;
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
result.w = input.w;
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
};
static void curves_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
CurvesModifierData *cmd = (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, ibuf, mask);
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
}
static void curves_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = UI_panel_custom_data_get(panel);
uiTemplateCurveMapping(layout, ptr, "curve_mapping", 'c', false, false, false, true);
if (uiLayout *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);
}
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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,148 @@
/* 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 "SEQ_modifier.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "modifier.hh"
namespace blender::seq {
static void hue_correct_init_data(StripModifierData *smd)
{
HueCorrectModifierData *hcmd = (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, CURVEMAP_SLOPE_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 = (HueCorrectModifierData *)smd;
BKE_curvemapping_free_data(&hcmd->curve_mapping);
}
static void hue_correct_copy_data(StripModifierData *target, StripModifierData *smd)
{
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
HueCorrectModifierData *hcmd_target = (HueCorrectModifierData *)target;
BKE_curvemapping_copy_data(&hcmd_target->curve_mapping, &hcmd->curve_mapping);
}
struct HueCorrectApplyOp {
const CurveMapping *curve_mapping;
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
/* 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);
apply_and_advance_mask(input, result, mask);
store_pixel_raw(result, image);
image += 4;
}
}
};
static void hue_correct_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_init(&hcmd->curve_mapping);
HueCorrectApplyOp op;
op.curve_mapping = &hcmd->curve_mapping;
apply_modifier_op(op, ibuf, mask);
}
static void hue_correct_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = UI_panel_custom_data_get(panel);
uiTemplateCurveMapping(layout, ptr, "curve_mapping", 'h', false, false, false, false);
if (uiLayout *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);
}
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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,108 @@
/* 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_scene_types.h"
#include "DNA_sequence_types.h"
#include "SEQ_modifier.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "modifier.hh"
namespace blender::seq {
static float load_mask_min(const uchar *&mask)
{
float m = float(min_iii(mask[0], mask[1], mask[2])) * (1.0f / 255.0f);
mask += 4;
return m;
}
static float load_mask_min(const float *&mask)
{
float m = min_fff(mask[0], mask[1], mask[2]);
mask += 4;
return m;
}
static float load_mask_min(const void *& /*mask*/)
{
return 1.0f;
}
struct MaskApplyOp {
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
float m = load_mask_min(mask);
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 premultiplied, so need to premul 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(const StripScreenQuad & /*quad*/,
StripModifierData * /*smd*/,
ImBuf *ibuf,
ImBuf *mask)
{
if (mask == nullptr || (mask->byte_buffer.data == nullptr && mask->float_buffer.data == nullptr))
{
return;
}
MaskApplyOp op;
apply_modifier_op(op, ibuf, mask);
/* Image has gained transparency. */
ibuf->planes = R_IMF_PLANES_RGBA;
}
static void maskmodifier_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,29 @@
/* 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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,72 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include <fmt/format.h>
#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)
{
uiLayout *layout = panel->layout;
PointerRNA *ptr = UI_panel_custom_data_get(panel);
layout->use_property_split_set(true);
uiLayout &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");
uiLayout &col = flow.column(false);
uiLayout &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);
uiTemplateCurveMapping(&col, &sound_eq, "curve_mapping", 0, false, true, true, false);
uiLayout &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);
}
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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,393 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_array.hh"
#include "BLI_math_geom.h"
#include "BLT_translation.hh"
#include "DNA_sequence_types.h"
#include "IMB_colormanagement.hh"
#include "SEQ_modifier.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 = (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(
(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(
(float *)dst, int(count), 1, 4, colorspace, false);
}
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_buffer.data);
IMB_colormanagement_scene_linear_to_colorspace(
(float *)(fptr + range.first()), int(range.size()), 1, 4, colorspace);
}
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(
(float *)src, int(range.size()), 1, 4, colorspace);
const float4 *src_ptr = src;
uchar *bptr = ibuf->byte_buffer.data;
for (const int64_t idx : range) {
premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
src_ptr++;
}
}
static void tonemap_simple(float4 *scene_linear,
ImBuf *mask,
IndexRange range,
const AvgLogLum &avg)
{
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
int64_t index = 0;
for (const int64_t pixel_index : range) {
float4 input = scene_linear[index];
/* 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. */
if (mask != nullptr) {
float3 msk(1.0f);
if (mask_byte != nullptr) {
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
}
else if (mask_float != nullptr) {
msk = mask_float[pixel_index].xyz();
}
pixel = math::interpolate(input.xyz(), pixel, msk);
}
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
index++;
}
}
static void tonemap_rd_photoreceptor(float4 *scene_linear,
ImBuf *mask,
IndexRange range,
const AvgLogLum &avg)
{
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
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;
int64_t index = 0;
for (const int64_t pixel_index : range) {
float4 input = scene_linear[index];
/* 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. */
if (mask != nullptr) {
float3 msk(1.0f);
if (mask_byte != nullptr) {
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
}
else if (mask_float != nullptr) {
msk = mask_float[pixel_index].xyz();
}
pixel = math::interpolate(input.xyz(), pixel, msk);
}
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
index++;
}
}
static bool is_point_inside_quad(const StripScreenQuad &quad, int x, int y)
{
float2 pt(x + 0.5f, y + 0.5f);
return isect_point_quad_v2(pt, quad.v0, quad.v1, quad.v2, quad.v3);
}
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 tonemap_calc_chunk_luminance(const StripScreenQuad &quad,
const bool all_pixels_inside_quad,
const int width,
const IndexRange y_range,
const float4 *scene_linear,
AreaLuminance &r_lum)
{
for (const int y : y_range) {
for (int x = 0; x < width; x++) {
if (all_pixels_inside_quad || is_point_inside_quad(quad, x, y)) {
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(const StripScreenQuad &quad, const ImBuf *ibuf)
{
/* Pixels outside the pre-transform strip area are ignored for luminance calculations.
* If strip area covers whole image, we can trivially accept all pixels. */
const bool all_pixels_inside_quad = is_point_inside_quad(quad, 0, 0) &&
is_point_inside_quad(quad, ibuf->x - 1, 0) &&
is_point_inside_quad(quad, 0, ibuf->y - 1) &&
is_point_inside_quad(quad, ibuf->x - 1, ibuf->y - 1);
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 (ibuf->float_buffer.data != nullptr) {
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
fptr += y_range.first() * ibuf->x;
pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
tonemap_calc_chunk_luminance(quad, all_pixels_inside_quad, ibuf->x, y_range, fptr, lum);
}
else {
const uchar *bptr = ibuf->byte_buffer.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(
quad, all_pixels_inside_quad, 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(const StripScreenQuad &quad,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const SequencerTonemapModifierData *tmmd = (const SequencerTonemapModifierData *)smd;
AreaLuminance lum = tonemap_calc_input_luminance(quad, ibuf);
if (lum.pixel_count == 0) {
return; /* Strip is zero size or off-screen. */
}
AvgLogLum data;
data.tmmd = tmmd;
data.lav = lum.sum / lum.pixel_count;
data.cav.x = lum.color_sum.x / lum.pixel_count;
data.cav.y = lum.color_sum.y / lum.pixel_count;
data.cav.z = lum.color_sum.z / lum.pixel_count;
float maxl = log(double(lum.max) + 1e-5f);
float minl = log(double(lum.min) + 1e-5f);
float avl = lum.log_sum / lum.pixel_count;
data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
float al = exp(double(avl));
data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
threading::parallel_for(
IndexRange(int64_t(ibuf->x) * ibuf->y), 64 * 1024, [&](IndexRange range) {
if (ibuf->float_buffer.data != nullptr) {
/* Float pixels: no need for temporary storage. Luminance calculation already converted
* data to scene linear. */
float4 *pixels = (float4 *)(ibuf->float_buffer.data) + range.first();
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
tonemap_rd_photoreceptor(pixels, mask, range, data);
}
else {
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
tonemap_simple(pixels, mask, range, data);
}
scene_linear_to_image_chunk_float(ibuf, range);
}
else {
/* Byte pixels: temporary storage for scene linear pixel values. */
Array<float4> scene_linear(range.size());
pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
ibuf->byte_buffer.data + range.first() * 4,
scene_linear.data(),
range.size());
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
tonemap_rd_photoreceptor(scene_linear.data(), mask, range, data);
}
else {
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
tonemap_simple(scene_linear.data(), mask, range, data);
}
scene_linear_to_image_chunk_byte(scene_linear.data(), ibuf, range);
}
});
}
static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *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);
uiLayout &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 (uiLayout *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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,108 @@
/* 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 "SEQ_modifier.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "modifier.hh"
namespace blender::seq {
static void whiteBalance_init_data(StripModifierData *smd)
{
WhiteBalanceModifierData *cbmd = (WhiteBalanceModifierData *)smd;
copy_v3_fl(cbmd->white_value, 1.0f);
}
struct WhiteBalanceApplyOp {
float multiplier[3];
template<typename ImageT, typename MaskT>
void apply(ImageT *image, const MaskT *mask, IndexRange size)
{
for ([[maybe_unused]] int64_t i : size) {
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
apply_and_advance_mask(input, result, mask);
store_pixel_premul(result, image);
image += 4;
}
}
};
static void whiteBalance_apply(const StripScreenQuad & /*quad*/,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const WhiteBalanceModifierData *data = (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, ibuf, mask);
}
static void whiteBalance_panel_draw(const bContext *C, Panel *panel)
{
uiLayout *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 (uiLayout *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,
};
}; // namespace blender::seq

View File

@@ -0,0 +1,683 @@
/* 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 "BKE_colortools.hh"
#include "BKE_screen.hh"
#include "IMB_colormanagement.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_time.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)
{
LISTBASE_FOREACH (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 = blender::get_default_hash(blender::StringRef(smd.name));
blender::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;
LISTBASE_FOREACH (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_panel_header(const bContext * /*C*/, Panel *panel)
{
uiLayout *row, *sub, *name_row;
uiLayout *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 = reinterpret_cast<StripModifierData *>(ptr->data);
UI_panel_context_pointer_set(panel, "modifier", ptr);
/* Modifier Icon. */
sub = &layout->row(true);
sub->emboss_set(blender::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);
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;
name_row = &row->row(true);
sub = &row->row(true);
sub->prop(ptr, "enable", UI_ITEM_NONE, "", ICON_NONE);
buttons_number++;
/* Delete button. */
sub = &row->row(false);
sub->emboss_set(blender::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(blender::ui::LayoutAlign::Right);
}
/* Extra padding for delete button. */
layout->separator();
}
void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr)
{
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
Editing *ed = seq::editing_get(sequencer_scene);
uiLayout *row, *col;
const int input_mask_type = RNA_enum_get(ptr, "input_mask_type");
layout->use_property_split_set(true);
col = &layout->column(false);
row = &col->row(true);
row->prop(ptr, "input_mask_type", UI_ITEM_R_EXPAND, "Type", ICON_NONE);
if (input_mask_type == STRIP_MASK_INPUT_STRIP) {
MetaStack *ms = meta_stack_active_get(ed);
PointerRNA sequences_object;
if (ms) {
sequences_object = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_MetaStrip, ms);
}
else {
sequences_object = RNA_pointer_create_discrete(
&sequencer_scene->id, &RNA_SequenceEditor, ed);
}
col->prop_search(ptr, "input_mask_strip", &sequences_object, "strips", "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 = reinterpret_cast<StripModifierData *>(smd_ptr->data);
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_strip_modifier_move_to_index", false);
WM_operator_properties_create_ptr(&props_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, blender::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 = reinterpret_cast<StripModifierData *>(smd_ptr->data);
return smd->layout_panel_open_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 = reinterpret_cast<StripModifierData *>(smd_ptr->data);
smd->layout_panel_open_flag = expand_flag;
}
PanelType *modifier_panel_register(ARegionType *region_type,
const eStripModifierType type,
PanelDrawFn draw)
{
PanelType *panel_type = MEM_callocN<PanelType>(__func__);
modifier_type_panel_id(type, panel_type->idname);
STRNCPY_UTF8(panel_type->label, "");
STRNCPY_UTF8(panel_type->category, "Modifiers");
STRNCPY_UTF8(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
STRNCPY_UTF8(panel_type->active_property, "is_active");
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;
}
/* Byte mask */
void apply_and_advance_mask(float4 input, float4 &result, const uchar *&mask)
{
float3 m;
rgb_uchar_to_float(m, mask);
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);
mask += 4;
}
/* Float mask */
void apply_and_advance_mask(float4 input, float4 &result, const float *&mask)
{
float3 m(mask);
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);
mask += 4;
}
/* No mask */
void apply_and_advance_mask(float4 /*input*/, float4 & /*result*/, const void *& /*mask*/) {}
/**
* \a timeline_frame is offset by \a fra_offset only in case we are using a real mask.
*/
static ImBuf *modifier_render_mask_input(const RenderData *context,
int mask_input_type,
Strip *mask_strip,
Mask *mask_id,
int timeline_frame,
int fra_offset)
{
ImBuf *mask_input = nullptr;
if (mask_input_type == STRIP_MASK_INPUT_STRIP) {
if (mask_strip) {
SeqRenderState state;
mask_input = seq_render_strip(context, &state, mask_strip, timeline_frame);
}
}
else if (mask_input_type == STRIP_MASK_INPUT_ID) {
/* 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_input = seq_render_mask(context, mask_id, timeline_frame - fra_offset, false);
}
return mask_input;
}
static ImBuf *modifier_mask_get(StripModifierData *smd,
const RenderData *context,
int timeline_frame,
int fra_offset)
{
return modifier_render_mask_input(
context, smd->mask_input_type, smd->mask_strip, smd->mask_id, timeline_frame, fra_offset);
}
/* -------------------------------------------------------------------- */
/** \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(Curves);
INIT_TYPE(HueCorrect);
INIT_TYPE(Mask);
INIT_TYPE(SoundEqualizer);
INIT_TYPE(Tonemap);
INIT_TYPE(WhiteBalance);
#undef INIT_TYPE
}
void modifiers_init()
{
modifier_types_init(modifiersTypes);
}
const StripModifierTypeInfo *modifier_type_info_get(int type)
{
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
return nullptr;
}
return modifiersTypes[type];
}
StripModifierData *modifier_new(Strip *strip, const char *name, int type)
{
StripModifierData *smd;
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
smd = static_cast<StripModifierData *>(MEM_callocN(smti->struct_size, "sequence modifier"));
smd->type = type;
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED;
smd->layout_panel_open_flag |= UI_PANEL_DATA_EXPAND_ROOT;
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);
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;
}
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);
}
BLI_listbase_clear(&strip->modifiers);
}
void modifier_free(StripModifierData *smd)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
if (smti && smti->free_data) {
smti->free_data(smd);
}
MEM_freeN(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 &&
!time_strip_intersects_frame(
scene, smd->mask_strip, 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(const RenderData *context,
const Strip *strip,
ImBuf *ibuf,
int timeline_frame)
{
const StripScreenQuad quad = get_strip_screen_quad(context, strip);
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
render_imbuf_from_sequencer_space(context->scene, ibuf);
}
LISTBASE_FOREACH (StripModifierData *, smd, &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;
}
/* modifier is muted, do nothing */
if (smd->flag & STRIP_MODIFIER_FLAG_MUTE) {
continue;
}
if (smti->apply && !skip_modifier(context->scene, smd, timeline_frame)) {
int frame_offset;
if (smd->mask_time == STRIP_MASK_TIME_RELATIVE) {
frame_offset = strip->start;
}
else /* if (smd->mask_time == STRIP_MASK_TIME_ABSOLUTE) */ {
frame_offset = smd->mask_id ? ((Mask *)smd->mask_id)->sfra : 0;
}
ImBuf *mask = modifier_mask_get(smd, context, timeline_frame, frame_offset);
smti->apply(quad, smd, ibuf, mask);
if (mask) {
IMB_freeImBuf(mask);
}
}
}
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
}
}
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
StripModifierData *mod_new = static_cast<StripModifierData *>(MEM_dupallocN(mod_src));
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)
{
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
modifier_copy(*strip_new, smd);
}
}
int sequence_supports_modifiers(Strip *strip)
{
return (strip->type != STRIP_TYPE_SOUND_RAM);
}
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;
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
active_count++;
}
}
BLI_assert(ELEM(active_count, 0, 1));
#endif
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
return smd;
}
}
return nullptr;
}
void modifier_set_active(Strip *strip, StripModifierData *smd)
{
LISTBASE_FOREACH (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);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name .blend File I/O
* \{ */
void modifier_blend_write(BlendWriter *writer, ListBase *modbase)
{
LISTBASE_FOREACH (StripModifierData *, smd, modbase) {
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
if (smti) {
BLO_write_struct_by_name(writer, smti->struct_name, smd);
if (smd->type == eSeqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_blend_write(writer, &hcmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_SoundEqualizer) {
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
BLO_write_struct_by_name(writer, "EQCurveMappingData", eqcmd);
BKE_curvemapping_blend_write(writer, &eqcmd->curve_mapping);
}
}
}
else {
BLO_write_struct(writer, StripModifierData, smd);
}
}
}
void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
{
BLO_read_struct_list(reader, StripModifierData, lb);
LISTBASE_FOREACH (StripModifierData *, smd, lb) {
if (smd->mask_strip) {
BLO_read_struct(reader, Strip, &smd->mask_strip);
}
if (smd->type == eSeqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_blend_read(reader, &hcmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_SoundEqualizer) {
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
BKE_curvemapping_blend_read(reader, &eqcmd->curve_mapping);
}
}
}
}
/** \} */
} // namespace blender::seq

View File

@@ -0,0 +1,105 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup sequencer
*/
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "IMB_imbuf.hh"
struct bContext;
struct ARegionType;
struct Strip;
struct uiLayout;
struct Panel;
struct PanelType;
struct PointerRNA;
namespace blender::seq {
bool modifier_persistent_uids_are_valid(const Strip &strip);
void draw_mask_input_type_settings(const bContext *C, uiLayout *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);
void apply_and_advance_mask(const float4 input, float4 &result, const uchar *&mask);
void apply_and_advance_mask(const float4 input, float4 &result, const float *&mask);
void apply_and_advance_mask(const float4 input, float4 &result, const void *&mask);
/* Given `T` that implements an `apply` function:
*
* template <typename ImageT, typename MaskT>
* void apply(ImageT* image, const MaskT* mask, IndexRange size);
*
* this function calls the apply() function in parallel
* chunks of the image to process, and with needed
* uchar, float or void types (void is used for mask, when there is
* no masking). 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)
{
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");
threading::parallel_for(IndexRange(size_t(ibuf->x) * ibuf->y), 32 * 1024, [&](IndexRange range) {
uchar *image_byte = ibuf->byte_buffer.data;
float *image_float = ibuf->float_buffer.data;
const uchar *mask_byte = mask ? mask->byte_buffer.data : nullptr;
const float *mask_float = mask ? mask->float_buffer.data : nullptr;
const void *mask_none = nullptr;
int64_t offset = range.first() * 4;
/* Instantiate the needed processing function based on image/mask
* data types. */
if (image_byte) {
if (mask_byte) {
op.apply(image_byte + offset, mask_byte + offset, range);
}
else if (mask_float) {
op.apply(image_byte + offset, mask_float + offset, range);
}
else {
op.apply(image_byte + offset, mask_none, range);
}
}
else if (image_float) {
if (mask_byte) {
op.apply(image_float + offset, mask_byte + offset, range);
}
else if (mask_float) {
op.apply(image_float + offset, mask_float + offset, range);
}
else {
op.apply(image_float + offset, mask_none, range);
}
}
});
}
} // namespace blender::seq

View File

@@ -1840,7 +1840,7 @@ static bool is_opaque_alpha_over(const Strip *strip)
}
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
/* Assume result is not opaque if there is an enabled Mask modifier. */
if ((smd->flag & STRIP_MODIFIER_FLAG_MUTE) == 0 && smd->type == seqModifierType_Mask) {
if ((smd->flag & STRIP_MODIFIER_FLAG_MUTE) == 0 && smd->type == eSeqModifierType_Mask) {
return false;
}
}

View File

@@ -61,7 +61,7 @@
#include "cache/final_image_cache.hh"
#include "cache/intra_frame_cache.hh"
#include "cache/source_image_cache.hh"
#include "modifier.hh"
#include "modifiers/modifier.hh"
#include "prefetch.hh"
#include "sequencer.hh"
#include "utils.hh"

View File

@@ -40,7 +40,7 @@ namespace blender::seq {
/* Unlike _update_sound_ functions,
* these ones take info from audaspace to update sequence length! */
const SoundModifierWorkerInfo workersSoundModifiers[] = {
{seqModifierType_SoundEqualizer, sound_equalizermodifier_recreator}, {0, nullptr}};
{eSeqModifierType_SoundEqualizer, sound_equalizermodifier_recreator}, {0, nullptr}};
#ifdef WITH_CONVOLUTION
static bool sequencer_refresh_sound_length_recursive(Main *bmain, Scene *scene, ListBase *seqbase)

View File

@@ -20,6 +20,7 @@ set(LIB
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
PRIVATE bf::render
PRIVATE bf::sequencer
PRIVATE bf::windowmanager
PRIVATE bf::dependencies::optional::opencolorio
)

View File

@@ -69,6 +69,8 @@
#include "ED_datafiles.h"
#include "SEQ_modifier.hh"
#include "WM_api.hh"
#include "RNA_define.hh"
@@ -449,6 +451,7 @@ int main(int argc,
BKE_cpp_types_init();
BKE_idtype_init();
BKE_modifier_init();
blender::seq::modifiers_init();
BKE_shaderfx_init();
BKE_volumes_init();
DEG_register_node_types();