From 866fcd0a0971625294a3c509f5aef22be4b10d40 Mon Sep 17 00:00:00 2001 From: Falk David Date: Thu, 4 Sep 2025 15:01:57 +0200 Subject: [PATCH] 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 --- .../datafiles/userdef/userdef_default_theme.c | 2 +- scripts/startup/bl_ui/space_sequencer.py | 99 +- scripts/startup/bl_ui/space_userpref.py | 3 + .../blender/blenkernel/BKE_blender_version.h | 2 +- .../blenloader/intern/versioning_260.cc | 2 +- .../blenloader/intern/versioning_280.cc | 4 +- .../blenloader/intern/versioning_300.cc | 2 +- .../blenloader/intern/versioning_420.cc | 2 +- .../blenloader/intern/versioning_500.cc | 16 + .../blenloader/intern/versioning_userdef.cc | 4 + .../tests/blendfile_loading_base_test.cc | 3 + .../eval/deg_eval_runtime_backup_sequence.cc | 4 +- .../blender/editors/include/UI_interface_c.hh | 1 + .../blender/editors/include/UI_resources.hh | 1 + .../blender/editors/interface/CMakeLists.txt | 2 + .../editors/interface/interface_panel.cc | 2 +- source/blender/editors/interface/resources.cc | 3 + .../interface_template_strip_modifiers.cc | 90 + .../space_sequencer/sequencer_intern.hh | 2 + .../space_sequencer/sequencer_modifier.cc | 119 ++ .../editors/space_sequencer/sequencer_ops.cc | 2 + .../space_sequencer/space_sequencer.cc | 10 + source/blender/makesdna/DNA_sequence_types.h | 24 +- source/blender/makesdna/DNA_theme_types.h | 2 +- source/blender/makesrna/intern/rna_color.cc | 2 +- .../blender/makesrna/intern/rna_sequencer.cc | 138 +- source/blender/makesrna/intern/rna_ui_api.cc | 4 + source/blender/makesrna/intern/rna_userdef.cc | 18 +- source/blender/sequencer/CMakeLists.txt | 16 +- source/blender/sequencer/SEQ_modifier.hh | 22 + source/blender/sequencer/SEQ_modifiertypes.hh | 27 + source/blender/sequencer/intern/modifier.cc | 1492 ----------------- source/blender/sequencer/intern/modifier.hh | 17 - .../modifiers/MOD_brightness_contrast.cc | 115 ++ .../intern/modifiers/MOD_color_balance.cc | 371 ++++ .../sequencer/intern/modifiers/MOD_curves.cc | 121 ++ .../intern/modifiers/MOD_hue_correct.cc | 148 ++ .../sequencer/intern/modifiers/MOD_mask.cc | 108 ++ .../sequencer/intern/modifiers/MOD_none.cc | 29 + .../intern/modifiers/MOD_sound_equalizer.cc | 72 + .../sequencer/intern/modifiers/MOD_tonemap.cc | 393 +++++ .../intern/modifiers/MOD_white_balance.cc | 108 ++ .../sequencer/intern/modifiers/modifier.cc | 683 ++++++++ .../sequencer/intern/modifiers/modifier.hh | 105 ++ source/blender/sequencer/intern/render.cc | 2 +- source/blender/sequencer/intern/sequencer.cc | 2 +- source/blender/sequencer/intern/sound.cc | 2 +- source/creator/CMakeLists.txt | 1 + source/creator/creator.cc | 3 + 49 files changed, 2742 insertions(+), 1658 deletions(-) create mode 100644 source/blender/editors/interface/templates/interface_template_strip_modifiers.cc create mode 100644 source/blender/sequencer/SEQ_modifiertypes.hh delete mode 100644 source/blender/sequencer/intern/modifier.cc delete mode 100644 source/blender/sequencer/intern/modifier.hh create mode 100644 source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_color_balance.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_curves.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_mask.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_none.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_sound_equalizer.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_tonemap.cc create mode 100644 source/blender/sequencer/intern/modifiers/MOD_white_balance.cc create mode 100644 source/blender/sequencer/intern/modifiers/modifier.cc create mode 100644 source/blender/sequencer/intern/modifiers/modifier.hh diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index 3963fc6599b..b6e2a3d3c45 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -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, diff --git a/scripts/startup/bl_ui/space_sequencer.py b/scripts/startup/bl_ui/space_sequencer.py index a96969bf088..7adb385cd11 100644 --- a/scripts/startup/bl_ui/space_sequencer.py +++ b/scripts/startup/bl_ui/space_sequencer.py @@ -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): diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index d527936772a..0dafaa04047 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -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") diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 6d4ca36cec1..2512c7d5722 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -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 diff --git a/source/blender/blenloader/intern/versioning_260.cc b/source/blender/blenloader/intern/versioning_260.cc index f02e6780963..42f46938f9c 100644 --- a/source/blender/blenloader/intern/versioning_260.cc +++ b/source/blender/blenloader/intern/versioning_260.cc @@ -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; diff --git a/source/blender/blenloader/intern/versioning_280.cc b/source/blender/blenloader/intern/versioning_280.cc index 322dc6d8ac3..2b06d64c237 100644 --- a/source/blender/blenloader/intern/versioning_280.cc +++ b/source/blender/blenloader/intern/versioning_280.cc @@ -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); } diff --git a/source/blender/blenloader/intern/versioning_300.cc b/source/blender/blenloader/intern/versioning_300.cc index 6144e5fe00a..f5cd3ae8388 100644 --- a/source/blender/blenloader/intern/versioning_300.cc +++ b/source/blender/blenloader/intern/versioning_300.cc @@ -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++) { diff --git a/source/blender/blenloader/intern/versioning_420.cc b/source/blender/blenloader/intern/versioning_420.cc index 841c47a478a..4bd3731a064 100644 --- a/source/blender/blenloader/intern/versioning_420.cc +++ b/source/blender/blenloader/intern/versioning_420.cc @@ -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); diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index 6e8ad8af1a8..064212efec9 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -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(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. diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index 2206df5b4a2..30ac8b9965b 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -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. diff --git a/source/blender/blenloader/tests/blendfile_loading_base_test.cc b/source/blender/blenloader/tests/blendfile_loading_base_test.cc index 43818295fcf..808bd1aa9ff 100644 --- a/source/blender/blenloader/tests/blendfile_loading_base_test.cc +++ b/source/blender/blenloader/tests/blendfile_loading_base_test.cc @@ -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(); diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc index c2c1b758934..e88cfda5521 100644 --- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc +++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_sequence.cc @@ -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; diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 11155ca7a74..695c90f3f78 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -2380,6 +2380,7 @@ void uiTemplatePathBuilder(uiLayout *layout, PointerRNA *root_ptr, std::optional 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. */ diff --git a/source/blender/editors/include/UI_resources.hh b/source/blender/editors/include/UI_resources.hh index 5f0dae78146..74f1f39f0f5 100644 --- a/source/blender/editors/include/UI_resources.hh +++ b/source/blender/editors/include/UI_resources.hh @@ -76,6 +76,7 @@ enum ThemeColorID { TH_PANEL_BACK, TH_PANEL_SUB_BACK, TH_PANEL_OUTLINE, + TH_PANEL_ACTIVE, TH_BUTBACK, diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 18482439cd7..d53c6fb62a8 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -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 ) diff --git a/source/blender/editors/interface/interface_panel.cc b/source/blender/editors/interface/interface_panel.cc index 641907e2f04..dc3a384e9f3 100644 --- a/source/blender/editors/interface/interface_panel.cc +++ b/source/blender/editors/interface/interface_panel.cc @@ -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. */ } diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index a11254cd633..156b4a3a40d 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -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; diff --git a/source/blender/editors/interface/templates/interface_template_strip_modifiers.cc b/source/blender/editors/interface/templates/interface_template_strip_modifiers.cc new file mode 100644 index 00000000000..b1bb361f186 --- /dev/null +++ b/source/blender/editors/interface/templates/interface_template_strip_modifiers.cc @@ -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(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(__func__); + *md_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_StripModifier, smd); + + UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel = static_cast(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(__func__); + *md_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_StripModifier, smd); + UI_panel_custom_data_set(panel, md_ptr); + + panel = panel->next; + } + } +} diff --git a/source/blender/editors/space_sequencer/sequencer_intern.hh b/source/blender/editors/space_sequencer/sequencer_intern.hh index a2898ee109a..c34480da10d 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.hh +++ b/source/blender/editors/space_sequencer/sequencer_intern.hh @@ -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` */ diff --git a/source/blender/editors/space_sequencer/sequencer_modifier.cc b/source/blender/editors/space_sequencer/sequencer_modifier.cc index f863a076f72..043c855c82e 100644 --- a/source/blender/editors/space_sequencer/sequencer_modifier.cc +++ b/source/blender/editors/space_sequencer/sequencer_modifier.cc @@ -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 diff --git a/source/blender/editors/space_sequencer/sequencer_ops.cc b/source/blender/editors/space_sequencer/sequencer_ops.cc index 0a16538f9ef..2daa7d0811a 100644 --- a/source/blender/editors/space_sequencer/sequencer_ops.cc +++ b/source/blender/editors/space_sequencer/sequencer_ops.cc @@ -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 */ diff --git a/source/blender/editors/space_sequencer/space_sequencer.cc b/source/blender/editors/space_sequencer/space_sequencer.cc index d383c5f1013..201e8e2d0f3 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.cc +++ b/source/blender/editors/space_sequencer/space_sequencer.cc @@ -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("spacetype sequencer tools region"); diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 108f7fb668c..2d6b3f2cac5 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -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 { diff --git a/source/blender/makesdna/DNA_theme_types.h b/source/blender/makesdna/DNA_theme_types.h index 507a098f01b..5e540a02ace 100644 --- a/source/blender/makesdna/DNA_theme_types.h +++ b/source/blender/makesdna/DNA_theme_types.h @@ -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; diff --git a/source/blender/makesrna/intern/rna_color.cc b/source/blender/makesrna/intern/rna_color.cc index 9790834e2fa..2ac2250018f 100644 --- a/source/blender/makesrna/intern/rna_color.cc +++ b/source/blender/makesrna/intern/rna_color.cc @@ -81,7 +81,7 @@ static bool seq_update_modifier_curve(Strip *strip, void *user_data) * curve mapping. */ SeqCurveMappingUpdateData *data = static_cast(user_data); LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) { - if (smd->type == seqModifierType_Curves) { + if (smd->type == eSeqModifierType_Curves) { CurvesModifierData *cmd = reinterpret_cast(smd); if (&cmd->curve_mapping == data->curve) { blender::seq::relations_invalidate_cache(data->scene, strip); diff --git a/source/blender/makesrna/intern/rna_sequencer.cc b/source/blender/makesrna/intern/rna_sequencer.cc index 4b4db628870..92595a0d2fa 100644 --- a/source/blender/makesrna/intern/rna_sequencer.cc +++ b/source/blender/makesrna/intern/rna_sequencer.cc @@ -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(); + 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(); + StripModifierData *smd = value.data_as(); + + 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(arg_pt); @@ -1207,7 +1235,7 @@ static bool colbalance_seq_cmp_fn(Strip *strip, void *arg_pt) for (StripModifierData *smd = static_cast(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(); + + 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) diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index 385920bf0f7..38fe4aebb2a 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -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"); diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 6e809e8f673..f21386fdfac 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -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) diff --git a/source/blender/sequencer/CMakeLists.txt b/source/blender/sequencer/CMakeLists.txt index 5f31c79b1c7..d2af2e43836 100644 --- a/source/blender/sequencer/CMakeLists.txt +++ b/source/blender/sequencer/CMakeLists.txt @@ -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 diff --git a/source/blender/sequencer/SEQ_modifier.hh b/source/blender/sequencer/SEQ_modifier.hh index 687534391d9..1058baab3e5 100644 --- a/source/blender/sequencer/SEQ_modifier.hh +++ b/source/blender/sequencer/SEQ_modifier.hh @@ -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 diff --git a/source/blender/sequencer/SEQ_modifiertypes.hh b/source/blender/sequencer/SEQ_modifiertypes.hh new file mode 100644 index 00000000000..3928052d789 --- /dev/null +++ b/source/blender/sequencer/SEQ_modifiertypes.hh @@ -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 diff --git a/source/blender/sequencer/intern/modifier.cc b/source/blender/sequencer/intern/modifier.cc deleted file mode 100644 index 745ad296785..00000000000 --- a/source/blender/sequencer/intern/modifier.cc +++ /dev/null @@ -1,1492 +0,0 @@ -/* SPDX-FileCopyrightText: 2012-2024 Blender Authors - * - * SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup bke - */ - -#include -#include -#include - -#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 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(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(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 - * 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 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 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 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 - 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 - 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 - 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 - 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 - 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) { - /* 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) { - /* 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(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(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(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 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 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(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(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( - 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(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 diff --git a/source/blender/sequencer/intern/modifier.hh b/source/blender/sequencer/intern/modifier.hh deleted file mode 100644 index 10f65068738..00000000000 --- a/source/blender/sequencer/intern/modifier.hh +++ /dev/null @@ -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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc b/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc new file mode 100644 index 00000000000..7fd72e5cfc7 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc @@ -0,0 +1,115 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup sequencer + */ + +#include + +#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 + 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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc b/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc new file mode 100644 index 00000000000..de9674e92ac --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc @@ -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 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 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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_curves.cc b/source/blender/sequencer/intern/modifiers/MOD_curves.cc new file mode 100644 index 00000000000..acd9e33704f --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_curves.cc @@ -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 + 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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc b/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc new file mode 100644 index 00000000000..5defbf00e2d --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc @@ -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 + 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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_mask.cc b/source/blender/sequencer/intern/modifiers/MOD_mask.cc new file mode 100644 index 00000000000..88d9d8827a7 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_mask.cc @@ -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 + 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) { + /* 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) { + /* 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(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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_none.cc b/source/blender/sequencer/intern/modifiers/MOD_none.cc new file mode 100644 index 00000000000..e972b45cef8 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_none.cc @@ -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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_sound_equalizer.cc b/source/blender/sequencer/intern/modifiers/MOD_sound_equalizer.cc new file mode 100644 index 00000000000..4dc682a50b1 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_sound_equalizer.cc @@ -0,0 +1,72 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup sequencer + */ + +#include + +#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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc b/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc new file mode 100644 index 00000000000..71798e9b503 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc @@ -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(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(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 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 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 diff --git a/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc b/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc new file mode 100644 index 00000000000..ff7f3866e72 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc @@ -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 + 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 diff --git a/source/blender/sequencer/intern/modifiers/modifier.cc b/source/blender/sequencer/intern/modifiers/modifier.cc new file mode 100644 index 00000000000..49d11929570 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/modifier.cc @@ -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 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(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(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(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(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(__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(®ion_type->paneltypes, panel_type); + + return panel_type; +} + +/* -------------------------------------------------------------------- */ + +float4 load_pixel_premul(const uchar *ptr) +{ + float4 res; + straight_uchar_to_premul_float(res, ptr); + return res; +} + +float4 load_pixel_premul(const float *ptr) +{ + return float4(ptr); +} + +void store_pixel_premul(float4 pix, uchar *ptr) +{ + premul_float_to_straight_uchar(ptr, pix); +} + +void store_pixel_premul(float4 pix, float *ptr) +{ + *reinterpret_cast(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(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(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(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( + 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(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 diff --git a/source/blender/sequencer/intern/modifiers/modifier.hh b/source/blender/sequencer/intern/modifiers/modifier.hh new file mode 100644 index 00000000000..c32bba636b2 --- /dev/null +++ b/source/blender/sequencer/intern/modifiers/modifier.hh @@ -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 + * 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 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 diff --git a/source/blender/sequencer/intern/render.cc b/source/blender/sequencer/intern/render.cc index 1c4b102f071..0a94f13d88f 100644 --- a/source/blender/sequencer/intern/render.cc +++ b/source/blender/sequencer/intern/render.cc @@ -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; } } diff --git a/source/blender/sequencer/intern/sequencer.cc b/source/blender/sequencer/intern/sequencer.cc index ff455f10a6a..73544bf6ff5 100644 --- a/source/blender/sequencer/intern/sequencer.cc +++ b/source/blender/sequencer/intern/sequencer.cc @@ -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" diff --git a/source/blender/sequencer/intern/sound.cc b/source/blender/sequencer/intern/sound.cc index 58ed1fcfa02..14433eeed98 100644 --- a/source/blender/sequencer/intern/sound.cc +++ b/source/blender/sequencer/intern/sound.cc @@ -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) diff --git a/source/creator/CMakeLists.txt b/source/creator/CMakeLists.txt index 395e6b30044..f710fe33618 100644 --- a/source/creator/CMakeLists.txt +++ b/source/creator/CMakeLists.txt @@ -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 ) diff --git a/source/creator/creator.cc b/source/creator/creator.cc index 9850b2c91b9..59587d28824 100644 --- a/source/creator/creator.cc +++ b/source/creator/creator.cc @@ -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();