VSE: Update Strip Modifier UI
This PR updates the VSE strip modifiers interface. It now uses the same design as the object modifiers. Changes: * Except for the "Mask Input" subpanel, the modifier UIs are unchanged. * Modifiers can now be rearranged using drag & drop. * Additionally, there is now an active strip modifier. This is exposed though python via `strip.modifiers.active`. This is in part for !139634 which needs the concept of an active modifier. Notes: * The `modifier.cc` file included all the implementation of all modifiers. With the addition of a another new callback in this PR, this file was getting quite big so I split everything out into individual files for all modifiers. The modifiers are getting registered at launch. * The modifier panels are getting added using a UI template (`template_strip_modifiers`) very similar to the object modifiers. Pull Request: https://projects.blender.org/blender/blender/pulls/145367
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -3022,6 +3022,22 @@ void blo_do_versions_500(FileData *fd, Library * /*lib*/, Main *bmain)
|
||||
}
|
||||
}
|
||||
|
||||
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 74)) {
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
if (scene->ed != nullptr) {
|
||||
/* Set the first strip modifier as the active one and uncollapse the root panel. */
|
||||
blender::seq::for_each_callback(&scene->ed->seqbase, [&](Strip *strip) -> bool {
|
||||
seq::modifier_set_active(strip,
|
||||
static_cast<StripModifierData *>(strip->modifiers.first));
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
smd->layout_panel_open_flag |= UI_PANEL_DATA_EXPAND_ROOT;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always bump subversion in BKE_blender_version.h when adding versioning
|
||||
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2380,6 +2380,7 @@ void uiTemplatePathBuilder(uiLayout *layout,
|
||||
PointerRNA *root_ptr,
|
||||
std::optional<blender::StringRefNull> text);
|
||||
void uiTemplateModifiers(uiLayout *layout, bContext *C);
|
||||
void uiTemplateStripModifiers(uiLayout *layout, bContext *C);
|
||||
/**
|
||||
* Check if the shader effect panels don't match the data and rebuild the panels if so.
|
||||
*/
|
||||
|
||||
@@ -76,6 +76,7 @@ enum ThemeColorID {
|
||||
TH_PANEL_BACK,
|
||||
TH_PANEL_SUB_BACK,
|
||||
TH_PANEL_OUTLINE,
|
||||
TH_PANEL_ACTIVE,
|
||||
|
||||
TH_BUTBACK,
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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. */
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup edinterface
|
||||
*
|
||||
* Template for building the panel layout for the active strip's modifiers.
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_select.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
static void strip_modifier_panel_id(void *smd_link, char *r_name)
|
||||
{
|
||||
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_link);
|
||||
blender::seq::modifier_type_panel_id(eStripModifierType(smd->type), r_name);
|
||||
}
|
||||
|
||||
void uiTemplateStripModifiers(uiLayout * /*layout*/, bContext *C)
|
||||
{
|
||||
using namespace blender;
|
||||
ARegion *region = CTX_wm_region(C);
|
||||
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
if (!sequencer_scene) {
|
||||
return;
|
||||
}
|
||||
Strip *active_strip = seq::select_active_get(sequencer_scene);
|
||||
BLI_assert(active_strip != nullptr);
|
||||
ListBase *modifiers = &active_strip->modifiers;
|
||||
|
||||
const bool panels_match = UI_panel_list_matches_data(region, modifiers, strip_modifier_panel_id);
|
||||
|
||||
if (!panels_match) {
|
||||
UI_panels_free_instanced(C, region);
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, modifiers) {
|
||||
const seq::StripModifierTypeInfo *mti = seq::modifier_type_info_get(smd->type);
|
||||
if (mti->panel_register == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char panel_idname[MAX_NAME];
|
||||
strip_modifier_panel_id(smd, panel_idname);
|
||||
|
||||
/* Create custom data RNA pointer. */
|
||||
PointerRNA *md_ptr = MEM_new<PointerRNA>(__func__);
|
||||
*md_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_StripModifier, smd);
|
||||
|
||||
UI_panel_add_instanced(C, region, ®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<Panel *>(region->panels.first);
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, modifiers) {
|
||||
const seq::StripModifierTypeInfo *mti = seq::modifier_type_info_get(smd->type);
|
||||
if (mti->panel_register == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Move to the next instanced panel corresponding to the next modifier. */
|
||||
while ((panel->type == nullptr) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) {
|
||||
panel = panel->next;
|
||||
/* There shouldn't be fewer panels than modifiers with UIs. */
|
||||
BLI_assert(panel != nullptr);
|
||||
}
|
||||
|
||||
PointerRNA *md_ptr = MEM_new<PointerRNA>(__func__);
|
||||
*md_ptr = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_StripModifier, smd);
|
||||
UI_panel_custom_data_set(panel, md_ptr);
|
||||
|
||||
panel = panel->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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` */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "WM_message.hh"
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_offscreen.hh"
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_retiming.hh"
|
||||
@@ -1211,6 +1212,15 @@ void ED_spacetype_sequencer()
|
||||
art->draw = sequencer_buttons_region_draw;
|
||||
BLI_addhead(&st->regiontypes, art);
|
||||
|
||||
/* Register the panel types from strip modifiers. The actual panels are built per strip modifier
|
||||
* rather than per modifier type. */
|
||||
for (int i = 0; i < NUM_STRIP_MODIFIER_TYPES; i++) {
|
||||
const seq::StripModifierTypeInfo *mti = seq::modifier_type_info_get(i);
|
||||
if (mti != nullptr && mti->panel_register != nullptr) {
|
||||
mti->panel_register(art);
|
||||
}
|
||||
}
|
||||
|
||||
sequencer_buttons_register(art);
|
||||
/* Toolbar. */
|
||||
art = MEM_callocN<ARegionType>("spacetype sequencer tools region");
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ static bool seq_update_modifier_curve(Strip *strip, void *user_data)
|
||||
* curve mapping. */
|
||||
SeqCurveMappingUpdateData *data = static_cast<SeqCurveMappingUpdateData *>(user_data);
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
if (smd->type == seqModifierType_Curves) {
|
||||
if (smd->type == eSeqModifierType_Curves) {
|
||||
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
|
||||
if (&cmd->curve_mapping == data->curve) {
|
||||
blender::seq::relations_invalidate_cache(data->scene, strip);
|
||||
|
||||
@@ -42,16 +42,16 @@ struct EffectInfo {
|
||||
/* These wrap strangely, disable formatting for fixed indentation and wrapping. */
|
||||
/* clang-format off */
|
||||
#define RNA_ENUM_SEQUENCER_VIDEO_MODIFIER_TYPE_ITEMS \
|
||||
{seqModifierType_BrightContrast, "BRIGHT_CONTRAST", ICON_NONE, "Brightness/Contrast", ""}, \
|
||||
{seqModifierType_ColorBalance, "COLOR_BALANCE", ICON_NONE, "Color Balance", ""}, \
|
||||
{seqModifierType_Curves, "CURVES", ICON_NONE, "Curves", ""}, \
|
||||
{seqModifierType_HueCorrect, "HUE_CORRECT", ICON_NONE, "Hue Correct", ""}, \
|
||||
{seqModifierType_Mask, "MASK", ICON_NONE, "Mask", ""}, \
|
||||
{seqModifierType_Tonemap, "TONEMAP", ICON_NONE, "Tone Map", ""}, \
|
||||
{seqModifierType_WhiteBalance, "WHITE_BALANCE", ICON_NONE, "White Balance", ""}
|
||||
{eSeqModifierType_BrightContrast, "BRIGHT_CONTRAST", ICON_NONE, "Brightness/Contrast", ""}, \
|
||||
{eSeqModifierType_ColorBalance, "COLOR_BALANCE", ICON_NONE, "Color Balance", ""}, \
|
||||
{eSeqModifierType_Curves, "CURVES", ICON_NONE, "Curves", ""}, \
|
||||
{eSeqModifierType_HueCorrect, "HUE_CORRECT", ICON_NONE, "Hue Correct", ""}, \
|
||||
{eSeqModifierType_Mask, "MASK", ICON_NONE, "Mask", ""}, \
|
||||
{eSeqModifierType_Tonemap, "TONEMAP", ICON_NONE, "Tone Map", ""}, \
|
||||
{eSeqModifierType_WhiteBalance, "WHITE_BALANCE", ICON_NONE, "White Balance", ""}
|
||||
|
||||
#define RNA_ENUM_SEQUENCER_AUDIO_MODIFIER_TYPE_ITEMS \
|
||||
{seqModifierType_SoundEqualizer, "SOUND_EQUALIZER", ICON_NONE, "Sound Equalizer", ""}
|
||||
{eSeqModifierType_SoundEqualizer, "SOUND_EQUALIZER", ICON_NONE, "Sound Equalizer", ""}
|
||||
/* clang-format on */
|
||||
|
||||
const EnumPropertyItem rna_enum_strip_modifier_type_items[] = {
|
||||
@@ -663,6 +663,34 @@ static void rna_Strip_use_proxy_set(PointerRNA *ptr, bool value)
|
||||
blender::seq::proxy_set(strip, value != 0);
|
||||
}
|
||||
|
||||
static PointerRNA rna_Strip_active_modifier_get(PointerRNA *ptr)
|
||||
{
|
||||
const Strip *strip = ptr->data_as<Strip>();
|
||||
StripModifierData *smd = blender::seq::modifier_get_active(strip);
|
||||
return RNA_pointer_create_with_parent(*ptr, &RNA_StripModifier, smd);
|
||||
}
|
||||
|
||||
static void rna_Strip_active_modifier_set(PointerRNA *ptr, PointerRNA value, ReportList *reports)
|
||||
{
|
||||
Strip *strip = ptr->data_as<Strip>();
|
||||
StripModifierData *smd = value.data_as<StripModifierData>();
|
||||
|
||||
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, ptr->owner_id);
|
||||
|
||||
if (RNA_pointer_is_null(&value)) {
|
||||
blender::seq::modifier_set_active(strip, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (BLI_findindex(&strip->modifiers, smd) == -1) {
|
||||
BKE_reportf(
|
||||
reports, RPT_ERROR, "Modifier \"%s\" is not in the strip's modifier list", smd->name);
|
||||
return;
|
||||
}
|
||||
|
||||
blender::seq::modifier_set_active(strip, smd);
|
||||
}
|
||||
|
||||
static bool transform_strip_cmp_fn(Strip *strip, void *arg_pt)
|
||||
{
|
||||
StripSearchData *data = static_cast<StripSearchData *>(arg_pt);
|
||||
@@ -1207,7 +1235,7 @@ static bool colbalance_seq_cmp_fn(Strip *strip, void *arg_pt)
|
||||
for (StripModifierData *smd = static_cast<StripModifierData *>(strip->modifiers.first); smd;
|
||||
smd = smd->next)
|
||||
{
|
||||
if (smd->type == seqModifierType_ColorBalance) {
|
||||
if (smd->type == eSeqModifierType_ColorBalance) {
|
||||
ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd;
|
||||
|
||||
if (&cbmd->color_balance == data->data) {
|
||||
@@ -1400,19 +1428,19 @@ static StructRNA *rna_StripModifier_refine(PointerRNA *ptr)
|
||||
StripModifierData *smd = (StripModifierData *)ptr->data;
|
||||
|
||||
switch (smd->type) {
|
||||
case seqModifierType_ColorBalance:
|
||||
case eSeqModifierType_ColorBalance:
|
||||
return &RNA_ColorBalanceModifier;
|
||||
case seqModifierType_Curves:
|
||||
case eSeqModifierType_Curves:
|
||||
return &RNA_CurvesModifier;
|
||||
case seqModifierType_HueCorrect:
|
||||
case eSeqModifierType_HueCorrect:
|
||||
return &RNA_HueCorrectModifier;
|
||||
case seqModifierType_BrightContrast:
|
||||
case eSeqModifierType_BrightContrast:
|
||||
return &RNA_BrightContrastModifier;
|
||||
case seqModifierType_WhiteBalance:
|
||||
case eSeqModifierType_WhiteBalance:
|
||||
return &RNA_WhiteBalanceModifier;
|
||||
case seqModifierType_Tonemap:
|
||||
case eSeqModifierType_Tonemap:
|
||||
return &RNA_SequencerTonemapModifierData;
|
||||
case seqModifierType_SoundEqualizer:
|
||||
case eSeqModifierType_SoundEqualizer:
|
||||
return &RNA_SoundEqualizerModifier;
|
||||
default:
|
||||
return &RNA_StripModifier;
|
||||
@@ -1470,6 +1498,24 @@ static void rna_StripModifier_name_set(PointerRNA *ptr, const char *value)
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_StripModifier_is_active_set(PointerRNA *ptr, bool value)
|
||||
{
|
||||
StripModifierData *smd = ptr->data_as<StripModifierData>();
|
||||
|
||||
if (value) {
|
||||
/* Disable the active flag of all other modifiers. */
|
||||
for (StripModifierData *prev_smd = smd->prev; prev_smd != nullptr; prev_smd = prev_smd->prev) {
|
||||
prev_smd->flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
for (StripModifierData *next_smd = smd->next; next_smd != nullptr; next_smd = next_smd->next) {
|
||||
next_smd->flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, ptr->owner_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_StripModifier_update(Main *bmain, Scene * /*scene*/, PointerRNA *ptr)
|
||||
{
|
||||
/* strip from other scenes could be modified, so using active scene is not reliable */
|
||||
@@ -2124,6 +2170,7 @@ static const EnumPropertyItem blend_mode_items[] = {
|
||||
static void rna_def_strip_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
@@ -2143,7 +2190,7 @@ static void rna_def_strip_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
parm = RNA_def_enum(func,
|
||||
"type",
|
||||
rna_enum_strip_modifier_type_items,
|
||||
seqModifierType_ColorBalance,
|
||||
eSeqModifierType_ColorBalance,
|
||||
"",
|
||||
"Modifier type to add");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
@@ -2164,6 +2211,17 @@ static void rna_def_strip_modifiers(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
func = RNA_def_function(srna, "clear", "rna_Strip_modifier_clear");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
|
||||
RNA_def_function_ui_description(func, "Remove all modifiers from the strip");
|
||||
|
||||
/* Active modifier. */
|
||||
prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "StripModifier");
|
||||
RNA_def_property_pointer_funcs(
|
||||
prop, "rna_Strip_active_modifier_get", "rna_Strip_active_modifier_set", nullptr, nullptr);
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_ui_text(prop, "Active Modifier", "The active strip modifier in the list");
|
||||
RNA_def_property_update(prop, NC_OBJECT | ND_MODIFIER, nullptr);
|
||||
}
|
||||
|
||||
static void rna_def_strip(BlenderRNA *brna)
|
||||
@@ -3710,6 +3768,19 @@ static void rna_def_effects(BlenderRNA *brna)
|
||||
}
|
||||
}
|
||||
|
||||
static void rna_def_modifier_panel_open_prop(StructRNA *srna, const char *identifier, const int id)
|
||||
{
|
||||
BLI_assert(id >= 0);
|
||||
BLI_assert(id < sizeof(StripModifierData::layout_panel_open_flag) * 8);
|
||||
|
||||
PropertyRNA *prop;
|
||||
prop = RNA_def_property(srna, identifier, PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
|
||||
RNA_def_property_boolean_sdna(
|
||||
prop, nullptr, "modifier.layout_panel_open_flag", (int64_t(1) << id));
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, nullptr);
|
||||
}
|
||||
|
||||
static void rna_def_modifier(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
@@ -3759,17 +3830,23 @@ static void rna_def_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_ui_icon(prop, ICON_HIDE_OFF, -1);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "enable", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_negative_sdna(prop, nullptr, "flag", STRIP_MODIFIER_FLAG_MUTE);
|
||||
RNA_def_property_ui_text(prop, "Enable", "Enable this modifier");
|
||||
RNA_def_property_ui_icon(prop, ICON_HIDE_ON, 1);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "show_expanded", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", STRIP_MODIFIER_FLAG_EXPANDED);
|
||||
RNA_def_property_boolean_sdna(
|
||||
prop, nullptr, "layout_panel_open_flag", UI_PANEL_DATA_EXPAND_ROOT);
|
||||
RNA_def_property_ui_text(prop, "Expanded", "Mute expanded settings for the modifier");
|
||||
RNA_def_property_ui_icon(prop, ICON_RIGHTARROW, 1);
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, nullptr);
|
||||
|
||||
prop = RNA_def_property(srna, "input_mask_type", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_sdna(prop, nullptr, "mask_input_type");
|
||||
RNA_def_property_enum_items(prop, mask_input_type_items);
|
||||
RNA_def_property_ui_text(prop, "Mask Input Type", "Type of input data used for mask");
|
||||
RNA_def_property_ui_text(prop, "Type", "Type of input data used for mask");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "mask_time", PROP_ENUM, PROP_NONE);
|
||||
@@ -3791,6 +3868,15 @@ static void rna_def_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Mask", "Mask ID used as mask input for the modifier");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
prop = RNA_def_property(srna, "is_active", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", STRIP_MODIFIER_FLAG_ACTIVE);
|
||||
RNA_def_property_boolean_funcs(prop, nullptr, "rna_StripModifier_is_active_set");
|
||||
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
|
||||
RNA_def_property_ui_text(prop, "Is Active", "This modifier is active");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
}
|
||||
|
||||
static void rna_def_colorbalance_modifier(BlenderRNA *brna)
|
||||
@@ -3812,6 +3898,8 @@ static void rna_def_colorbalance_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_float_default(prop, 1.0f);
|
||||
RNA_def_property_ui_text(prop, "Multiply Colors", "Multiply the intensity of each pixel");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
|
||||
}
|
||||
|
||||
static void rna_def_whitebalance_modifier(BlenderRNA *brna)
|
||||
@@ -3829,6 +3917,8 @@ static void rna_def_whitebalance_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_float_sdna(prop, nullptr, "white_value");
|
||||
RNA_def_property_ui_text(prop, "White Value", "This color defines white in the strip");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
|
||||
}
|
||||
|
||||
static void rna_def_curves_modifier(BlenderRNA *brna)
|
||||
@@ -3845,6 +3935,8 @@ static void rna_def_curves_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_struct_type(prop, "CurveMapping");
|
||||
RNA_def_property_ui_text(prop, "Curve Mapping", "");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
|
||||
}
|
||||
|
||||
static void rna_def_hue_modifier(BlenderRNA *brna)
|
||||
@@ -3861,6 +3953,8 @@ static void rna_def_hue_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_struct_type(prop, "CurveMapping");
|
||||
RNA_def_property_ui_text(prop, "Curve Mapping", "");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
|
||||
}
|
||||
|
||||
static void rna_def_brightcontrast_modifier(BlenderRNA *brna)
|
||||
@@ -3884,6 +3978,8 @@ static void rna_def_brightcontrast_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_range(prop, -100.0f, 100.0f);
|
||||
RNA_def_property_ui_text(prop, "Contrast", "Adjust the difference in luminosity between pixels");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
|
||||
}
|
||||
|
||||
static void rna_def_tonemap_modifier(BlenderRNA *brna)
|
||||
@@ -3946,6 +4042,8 @@ static void rna_def_tonemap_modifier(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(
|
||||
prop, "Color Correction", "If 0, same for all channels; if 1, each independent");
|
||||
RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_StripModifier_update");
|
||||
|
||||
rna_def_modifier_panel_open_prop(srna, "open_mask_input_panel", 1);
|
||||
}
|
||||
|
||||
static void rna_def_modifiers(BlenderRNA *brna)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
27
source/blender/sequencer/SEQ_modifiertypes.hh
Normal file
27
source/blender/sequencer/SEQ_modifiertypes.hh
Normal file
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* ****************** Type structures for all modifiers ****************** */
|
||||
|
||||
extern StripModifierTypeInfo seqModifierType_None;
|
||||
extern StripModifierTypeInfo seqModifierType_BrightContrast;
|
||||
extern StripModifierTypeInfo seqModifierType_ColorBalance;
|
||||
extern StripModifierTypeInfo seqModifierType_Curves;
|
||||
extern StripModifierTypeInfo seqModifierType_HueCorrect;
|
||||
extern StripModifierTypeInfo seqModifierType_Mask;
|
||||
extern StripModifierTypeInfo seqModifierType_SoundEqualizer;
|
||||
extern StripModifierTypeInfo seqModifierType_Tonemap;
|
||||
extern StripModifierTypeInfo seqModifierType_WhiteBalance;
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -1,1492 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2012-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_geom.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_mask_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sound.hh"
|
||||
#include "SEQ_time.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
static bool modifier_has_persistent_uid(const Strip &strip, int uid)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) {
|
||||
if (smd->persistent_uid == uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd)
|
||||
{
|
||||
uint64_t hash = blender::get_default_hash(blender::StringRef(smd.name));
|
||||
blender::RandomNumberGenerator rng{uint32_t(hash)};
|
||||
while (true) {
|
||||
const int new_uid = rng.get_int32();
|
||||
if (new_uid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (modifier_has_persistent_uid(strip, new_uid)) {
|
||||
continue;
|
||||
}
|
||||
smd.persistent_uid = new_uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip)
|
||||
{
|
||||
Set<int> uids;
|
||||
int modifiers_num = 0;
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) {
|
||||
if (smd->persistent_uid <= 0) {
|
||||
return false;
|
||||
}
|
||||
uids.add(smd->persistent_uid);
|
||||
modifiers_num++;
|
||||
}
|
||||
if (uids.size() != modifiers_num) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static float4 load_pixel_premul(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
straight_uchar_to_premul_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
static float4 load_pixel_premul(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
static void store_pixel_premul(float4 pix, uchar *ptr)
|
||||
{
|
||||
premul_float_to_straight_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
static void store_pixel_premul(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
static float4 load_pixel_raw(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
rgba_uchar_to_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
static float4 load_pixel_raw(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
static void store_pixel_raw(float4 pix, uchar *ptr)
|
||||
{
|
||||
rgba_float_to_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
static void store_pixel_raw(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
/* Byte mask */
|
||||
static void apply_and_advance_mask(float4 input, float4 &result, const uchar *&mask)
|
||||
{
|
||||
float3 m;
|
||||
rgb_uchar_to_float(m, mask);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
mask += 4;
|
||||
}
|
||||
|
||||
/* Float mask */
|
||||
static void apply_and_advance_mask(float4 input, float4 &result, const float *&mask)
|
||||
{
|
||||
float3 m(mask);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
mask += 4;
|
||||
}
|
||||
|
||||
/* No mask */
|
||||
static void apply_and_advance_mask(float4 /*input*/, float4 & /*result*/, const void *& /*mask*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* Given `T` that implements an `apply` function:
|
||||
*
|
||||
* template <typename ImageT, typename MaskT>
|
||||
* void apply(ImageT* image, const MaskT* mask, IndexRange size);
|
||||
*
|
||||
* this function calls the apply() function in parallel
|
||||
* chunks of the image to process, and with needed
|
||||
* uchar, float or void types (void is used for mask, when there is
|
||||
* no masking). Both input and mask images are expected to have
|
||||
* 4 (RGBA) color channels. Input is modified. */
|
||||
template<typename T> static void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask)
|
||||
{
|
||||
if (ibuf == nullptr) {
|
||||
return;
|
||||
}
|
||||
BLI_assert_msg(ibuf->channels == 0 || ibuf->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
BLI_assert_msg(mask == nullptr || mask->channels == 0 || mask->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
|
||||
threading::parallel_for(IndexRange(size_t(ibuf->x) * ibuf->y), 32 * 1024, [&](IndexRange range) {
|
||||
uchar *image_byte = ibuf->byte_buffer.data;
|
||||
float *image_float = ibuf->float_buffer.data;
|
||||
const uchar *mask_byte = mask ? mask->byte_buffer.data : nullptr;
|
||||
const float *mask_float = mask ? mask->float_buffer.data : nullptr;
|
||||
const void *mask_none = nullptr;
|
||||
int64_t offset = range.first() * 4;
|
||||
|
||||
/* Instantiate the needed processing function based on image/mask
|
||||
* data types. */
|
||||
if (image_byte) {
|
||||
if (mask_byte) {
|
||||
op.apply(image_byte + offset, mask_byte + offset, range);
|
||||
}
|
||||
else if (mask_float) {
|
||||
op.apply(image_byte + offset, mask_float + offset, range);
|
||||
}
|
||||
else {
|
||||
op.apply(image_byte + offset, mask_none, range);
|
||||
}
|
||||
}
|
||||
else if (image_float) {
|
||||
if (mask_byte) {
|
||||
op.apply(image_float + offset, mask_byte + offset, range);
|
||||
}
|
||||
else if (mask_float) {
|
||||
op.apply(image_float + offset, mask_float + offset, range);
|
||||
}
|
||||
else {
|
||||
op.apply(image_float + offset, mask_none, range);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* \a timeline_frame is offset by \a fra_offset only in case we are using a real mask.
|
||||
*/
|
||||
static ImBuf *modifier_render_mask_input(const RenderData *context,
|
||||
int mask_input_type,
|
||||
Strip *mask_strip,
|
||||
Mask *mask_id,
|
||||
int timeline_frame,
|
||||
int fra_offset)
|
||||
{
|
||||
ImBuf *mask_input = nullptr;
|
||||
|
||||
if (mask_input_type == STRIP_MASK_INPUT_STRIP) {
|
||||
if (mask_strip) {
|
||||
SeqRenderState state;
|
||||
mask_input = seq_render_strip(context, &state, mask_strip, timeline_frame);
|
||||
}
|
||||
}
|
||||
else if (mask_input_type == STRIP_MASK_INPUT_ID) {
|
||||
/* Note that we do not request mask to be float image: if it is that is
|
||||
* fine, but if it is a byte image then we also just take that without
|
||||
* extra memory allocations or conversions. All modifiers are expected
|
||||
* to handle mask being either type. */
|
||||
mask_input = seq_render_mask(context, mask_id, timeline_frame - fra_offset, false);
|
||||
}
|
||||
|
||||
return mask_input;
|
||||
}
|
||||
|
||||
static ImBuf *modifier_mask_get(StripModifierData *smd,
|
||||
const RenderData *context,
|
||||
int timeline_frame,
|
||||
int fra_offset)
|
||||
{
|
||||
return modifier_render_mask_input(
|
||||
context, smd->mask_input_type, smd->mask_strip, smd->mask_id, timeline_frame, fra_offset);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Color Balance Modifier
|
||||
* \{ */
|
||||
|
||||
/* Lift-Gamma-Gain math. NOTE: lift is actually (2-lift). */
|
||||
static float color_balance_lgg(
|
||||
float in, const float lift, const float gain, const float gamma, const float mul)
|
||||
{
|
||||
float x = (((in - 1.0f) * lift) + 1.0f) * gain;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, gamma) * mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Slope-Offset-Power (ASC CDL) math, see https://en.wikipedia.org/wiki/ASC_CDL */
|
||||
static float color_balance_sop(
|
||||
float in, const float slope, const float offset, const float power, float mul)
|
||||
{
|
||||
float x = in * slope + offset;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, power);
|
||||
x *= mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a larger lookup table than 256 possible byte values: due to alpha
|
||||
* pre-multiplication, dark values with low alphas might need more precision.
|
||||
*/
|
||||
static constexpr int CB_TABLE_SIZE = 1024;
|
||||
|
||||
static void make_cb_table_lgg(
|
||||
float lift, float gain, float gamma, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_lgg(x, lift, gain, gamma, mul);
|
||||
}
|
||||
}
|
||||
|
||||
static void make_cb_table_sop(
|
||||
float slope, float offset, float power, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_sop(x, slope, offset, power, mul);
|
||||
}
|
||||
}
|
||||
|
||||
struct ColorBalanceApplyOp {
|
||||
int method;
|
||||
float3 lift, gain, gamma;
|
||||
float3 slope, offset, power;
|
||||
float multiplier;
|
||||
float lut[3][CB_TABLE_SIZE];
|
||||
|
||||
/* Apply on a byte image via a table lookup. */
|
||||
template<typename MaskT> void apply(uchar *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
int p0 = int(input.x * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p1 = int(input.y * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p2 = int(input.z * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
result.x = this->lut[0][p0];
|
||||
result.y = this->lut[1][p1];
|
||||
result.z = this->lut[2][p2];
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply on a float image by doing full math. */
|
||||
template<typename MaskT> void apply(float *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Lift/Gamma/Gain */
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_lgg(
|
||||
input.x, this->lift.x, this->gain.x, this->gamma.x, this->multiplier);
|
||||
result.y = color_balance_lgg(
|
||||
input.y, this->lift.y, this->gain.y, this->gamma.y, this->multiplier);
|
||||
result.z = color_balance_lgg(
|
||||
input.z, this->lift.z, this->gain.z, this->gamma.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
/* Slope/Offset/Power */
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_sop(
|
||||
input.x, this->slope.x, this->offset.x, this->power.x, this->multiplier);
|
||||
result.y = color_balance_sop(
|
||||
input.y, this->slope.y, this->offset.y, this->power.y, this->multiplier);
|
||||
result.z = color_balance_sop(
|
||||
input.z, this->slope.z, this->offset.z, this->power.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void init_lgg(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN);
|
||||
|
||||
this->lift = 2.0f - float3(data.lift);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_LIFT) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
/* tweak to give more subtle results
|
||||
* values above 1.0 are scaled */
|
||||
if (this->lift[c] > 1.0f) {
|
||||
this->lift[c] = powf(this->lift[c] - 1.0f, 2.0f) + 1.0f;
|
||||
}
|
||||
this->lift[c] = 2.0f - this->lift[c];
|
||||
}
|
||||
}
|
||||
|
||||
this->gain = float3(data.gain);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_GAIN) {
|
||||
this->gain = math::rcp(math::max(this->gain, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->gamma = float3(data.gamma);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_GAMMA)) {
|
||||
this->gamma = math::rcp(math::max(this->gamma, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init_sop(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER);
|
||||
|
||||
this->slope = float3(data.slope);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_SLOPE) {
|
||||
this->slope = math::rcp(math::max(this->slope, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->offset = float3(data.offset) - 1.0f;
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_OFFSET) {
|
||||
this->offset = -this->offset;
|
||||
}
|
||||
|
||||
this->power = float3(data.power);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_POWER)) {
|
||||
this->power = math::rcp(math::max(this->power, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init(const ColorBalanceModifierData &data, bool byte_image)
|
||||
{
|
||||
this->multiplier = data.color_multiply;
|
||||
this->method = data.color_balance.method;
|
||||
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
init_lgg(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_lgg(
|
||||
this->lift[c], this->gain[c], this->gamma[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
init_sop(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_sop(
|
||||
this->slope[c], this->offset[c], this->power[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void colorBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd;
|
||||
|
||||
cbmd->color_multiply = 1.0f;
|
||||
cbmd->color_balance.method = 0;
|
||||
|
||||
for (int c = 0; c < 3; c++) {
|
||||
cbmd->color_balance.lift[c] = 1.0f;
|
||||
cbmd->color_balance.gamma[c] = 1.0f;
|
||||
cbmd->color_balance.gain[c] = 1.0f;
|
||||
cbmd->color_balance.slope[c] = 1.0f;
|
||||
cbmd->color_balance.offset[c] = 1.0f;
|
||||
cbmd->color_balance.power[c] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const ColorBalanceModifierData *cbmd = (const ColorBalanceModifierData *)smd;
|
||||
|
||||
ColorBalanceApplyOp op;
|
||||
op.init(*cbmd, ibuf->byte_buffer.data != nullptr);
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name White Balance Modifier
|
||||
* \{ */
|
||||
|
||||
static void whiteBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
WhiteBalanceModifierData *cbmd = (WhiteBalanceModifierData *)smd;
|
||||
copy_v3_fl(cbmd->white_value, 1.0f);
|
||||
}
|
||||
|
||||
struct WhiteBalanceApplyOp {
|
||||
float multiplier[3];
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
#if 0
|
||||
mul_v3_v3(result, multiplier);
|
||||
#else
|
||||
/* similar to division without the clipping */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
/* Prevent pow argument from being negative. This whole math
|
||||
* breaks down overall with any HDR colors; would be good to
|
||||
* revisit and do something more proper. */
|
||||
float f = max_ff(1.0f - input[i], 0.0f);
|
||||
result[i] = 1.0f - powf(f, this->multiplier[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void whiteBalance_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const WhiteBalanceModifierData *data = (const WhiteBalanceModifierData *)smd;
|
||||
|
||||
WhiteBalanceApplyOp op;
|
||||
op.multiplier[0] = (data->white_value[0] != 0.0f) ? 1.0f / data->white_value[0] : FLT_MAX;
|
||||
op.multiplier[1] = (data->white_value[1] != 0.0f) ? 1.0f / data->white_value[1] : FLT_MAX;
|
||||
op.multiplier[2] = (data->white_value[2] != 0.0f) ? 1.0f / data->white_value[2] : FLT_MAX;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Curves Modifier
|
||||
* \{ */
|
||||
|
||||
static void curves_init_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_set_defaults(&cmd->curve_mapping, 4, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
}
|
||||
|
||||
static void curves_free_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_free_data(&cmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void curves_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
CurvesModifierData *cmd_target = (CurvesModifierData *)target;
|
||||
|
||||
BKE_curvemapping_copy_data(&cmd_target->curve_mapping, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct CurvesApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void curves_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
const float black[3] = {0.0f, 0.0f, 0.0f};
|
||||
const float white[3] = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
BKE_curvemapping_init(&cmd->curve_mapping);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, false);
|
||||
BKE_curvemapping_set_black_white(&cmd->curve_mapping, black, white);
|
||||
|
||||
CurvesApplyOp op;
|
||||
op.curve_mapping = &cmd->curve_mapping;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Hue Correct Modifier
|
||||
* \{ */
|
||||
|
||||
static void hue_correct_init_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
int c;
|
||||
|
||||
BKE_curvemapping_set_defaults(&hcmd->curve_mapping, 1, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
hcmd->curve_mapping.preset = CURVE_PRESET_MID8;
|
||||
|
||||
for (c = 0; c < 3; c++) {
|
||||
CurveMap *cuma = &hcmd->curve_mapping.cm[c];
|
||||
BKE_curvemap_reset(
|
||||
cuma, &hcmd->curve_mapping.clipr, hcmd->curve_mapping.preset, CURVEMAP_SLOPE_POSITIVE);
|
||||
}
|
||||
/* use wrapping for all hue correct modifiers */
|
||||
hcmd->curve_mapping.flag |= CUMA_USE_WRAPPING;
|
||||
/* default to showing Saturation */
|
||||
hcmd->curve_mapping.cur = 1;
|
||||
}
|
||||
|
||||
static void hue_correct_free_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_free_data(&hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void hue_correct_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
HueCorrectModifierData *hcmd_target = (HueCorrectModifierData *)target;
|
||||
|
||||
BKE_curvemapping_copy_data(&hcmd_target->curve_mapping, &hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct HueCorrectApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
|
||||
float3 hsv;
|
||||
rgb_to_hsv(input.x, input.y, input.z, &hsv.x, &hsv.y, &hsv.z);
|
||||
|
||||
/* adjust hue, scaling returned default 0.5 up to 1 */
|
||||
float f;
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 0, hsv.x);
|
||||
hsv.x += f - 0.5f;
|
||||
|
||||
/* adjust saturation, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 1, hsv.x);
|
||||
hsv.y *= (f * 2.0f);
|
||||
|
||||
/* adjust value, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 2, hsv.x);
|
||||
hsv.z *= (f * 2.0f);
|
||||
|
||||
hsv.x = hsv.x - floorf(hsv.x); /* mod 1.0 */
|
||||
hsv.y = math::clamp(hsv.y, 0.0f, 1.0f);
|
||||
|
||||
/* convert back to rgb */
|
||||
hsv_to_rgb(hsv.x, hsv.y, hsv.z, &result.x, &result.y, &result.z);
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void hue_correct_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_init(&hcmd->curve_mapping);
|
||||
|
||||
HueCorrectApplyOp op;
|
||||
op.curve_mapping = &hcmd->curve_mapping;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Brightness/Contrast Modifier
|
||||
* \{ */
|
||||
|
||||
struct BrightContrastApplyOp {
|
||||
float mul;
|
||||
float add;
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
|
||||
float4 result;
|
||||
result = input * this->mul + this->add;
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void brightcontrast_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const BrightContrastModifierData *bcmd = (BrightContrastModifierData *)smd;
|
||||
|
||||
BrightContrastApplyOp op;
|
||||
|
||||
/* The algorithm is by Werner D. Streidt
|
||||
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
|
||||
* Extracted from OpenCV `demhist.cpp`. */
|
||||
const float brightness = bcmd->bright / 100.0f;
|
||||
const float contrast = bcmd->contrast;
|
||||
float delta = contrast / 200.0f;
|
||||
|
||||
if (contrast > 0) {
|
||||
op.mul = 1.0f - delta * 2.0f;
|
||||
op.mul = 1.0f / max_ff(op.mul, FLT_EPSILON);
|
||||
op.add = op.mul * (brightness - delta);
|
||||
}
|
||||
else {
|
||||
delta *= -1;
|
||||
op.mul = max_ff(1.0f - delta * 2.0f, 0.0f);
|
||||
op.add = op.mul * brightness + delta;
|
||||
}
|
||||
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Mask Modifier
|
||||
* \{ */
|
||||
|
||||
static float load_mask_min(const uchar *&mask)
|
||||
{
|
||||
float m = float(min_iii(mask[0], mask[1], mask[2])) * (1.0f / 255.0f);
|
||||
mask += 4;
|
||||
return m;
|
||||
}
|
||||
static float load_mask_min(const float *&mask)
|
||||
{
|
||||
float m = min_fff(mask[0], mask[1], mask[2]);
|
||||
mask += 4;
|
||||
return m;
|
||||
}
|
||||
static float load_mask_min(const void *& /*mask*/)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
struct MaskApplyOp {
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float m = load_mask_min(mask);
|
||||
|
||||
if constexpr (std::is_same_v<ImageT, uchar>) {
|
||||
/* Byte buffer is straight, so only affect on alpha itself, this is
|
||||
* the only way to alpha-over byte strip after applying mask modifier. */
|
||||
image[3] = uchar(image[3] * m);
|
||||
}
|
||||
else if constexpr (std::is_same_v<ImageT, float>) {
|
||||
/* Float buffers are premultiplied, so need to premul color as well to make it
|
||||
* easy to alpha-over masked strip. */
|
||||
float4 pix(image);
|
||||
pix *= m;
|
||||
*reinterpret_cast<float4 *>(image) = pix;
|
||||
}
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void maskmodifier_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData * /*smd*/,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
if (mask == nullptr || (mask->byte_buffer.data == nullptr && mask->float_buffer.data == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MaskApplyOp op;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
|
||||
/* Image has gained transparency. */
|
||||
ibuf->planes = R_IMF_PLANES_RGBA;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Tonemap Modifier
|
||||
* \{ */
|
||||
|
||||
struct AvgLogLum {
|
||||
const SequencerTonemapModifierData *tmmd;
|
||||
float al;
|
||||
float auto_key;
|
||||
float lav;
|
||||
float3 cav;
|
||||
float igm;
|
||||
};
|
||||
|
||||
static void tonemapmodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
SequencerTonemapModifierData *tmmd = (SequencerTonemapModifierData *)smd;
|
||||
/* Same as tone-map compositor node. */
|
||||
tmmd->type = SEQ_TONEMAP_RD_PHOTORECEPTOR;
|
||||
tmmd->key = 0.18f;
|
||||
tmmd->offset = 1.0f;
|
||||
tmmd->gamma = 1.0f;
|
||||
tmmd->intensity = 0.0f;
|
||||
tmmd->contrast = 0.0f;
|
||||
tmmd->adaptation = 1.0f;
|
||||
tmmd->correction = 0.0f;
|
||||
}
|
||||
|
||||
/* Convert chunk of float image pixels to scene linear space, in-place. */
|
||||
static void pixels_to_scene_linear_float(const ColorSpace *colorspace,
|
||||
float4 *pixels,
|
||||
int64_t count)
|
||||
{
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
(float *)(pixels), int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
/* Convert chunk of byte image pixels to scene linear space, into a destination array. */
|
||||
static void pixels_to_scene_linear_byte(const ColorSpace *colorspace,
|
||||
const uchar *pixels,
|
||||
float4 *dst,
|
||||
int64_t count)
|
||||
{
|
||||
const uchar *bptr = pixels;
|
||||
float4 *dst_ptr = dst;
|
||||
for (int64_t i = 0; i < count; i++) {
|
||||
straight_uchar_to_premul_float(*dst_ptr, bptr);
|
||||
bptr += 4;
|
||||
dst_ptr++;
|
||||
}
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
(float *)dst, int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
static void scene_linear_to_image_chunk_float(ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->float_buffer.colorspace;
|
||||
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
(float *)(fptr + range.first()), int(range.size()), 1, 4, colorspace);
|
||||
}
|
||||
|
||||
static void scene_linear_to_image_chunk_byte(float4 *src, ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
(float *)src, int(range.size()), 1, 4, colorspace);
|
||||
const float4 *src_ptr = src;
|
||||
uchar *bptr = ibuf->byte_buffer.data;
|
||||
for (const int64_t idx : range) {
|
||||
premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
|
||||
src_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemap_simple(float4 *scene_linear,
|
||||
ImBuf *mask,
|
||||
IndexRange range,
|
||||
const AvgLogLum &avg)
|
||||
{
|
||||
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
|
||||
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
|
||||
|
||||
int64_t index = 0;
|
||||
for (const int64_t pixel_index : range) {
|
||||
float4 input = scene_linear[index];
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz() * avg.al;
|
||||
float3 d = pixel + avg.tmmd->offset;
|
||||
pixel.x /= (d.x == 0.0f) ? 1.0f : d.x;
|
||||
pixel.y /= (d.y == 0.0f) ? 1.0f : d.y;
|
||||
pixel.z /= (d.z == 0.0f) ? 1.0f : d.z;
|
||||
const float igm = avg.igm;
|
||||
if (igm != 0.0f) {
|
||||
pixel.x = powf(math::max(pixel.x, 0.0f), igm);
|
||||
pixel.y = powf(math::max(pixel.y, 0.0f), igm);
|
||||
pixel.z = powf(math::max(pixel.z, 0.0f), igm);
|
||||
}
|
||||
|
||||
/* Apply mask. */
|
||||
if (mask != nullptr) {
|
||||
float3 msk(1.0f);
|
||||
if (mask_byte != nullptr) {
|
||||
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
|
||||
}
|
||||
else if (mask_float != nullptr) {
|
||||
msk = mask_float[pixel_index].xyz();
|
||||
}
|
||||
pixel = math::interpolate(input.xyz(), pixel, msk);
|
||||
}
|
||||
|
||||
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemap_rd_photoreceptor(float4 *scene_linear,
|
||||
ImBuf *mask,
|
||||
IndexRange range,
|
||||
const AvgLogLum &avg)
|
||||
{
|
||||
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
|
||||
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
|
||||
|
||||
const float f = expf(-avg.tmmd->intensity);
|
||||
const float m = (avg.tmmd->contrast > 0.0f) ? avg.tmmd->contrast :
|
||||
(0.3f + 0.7f * powf(avg.auto_key, 1.4f));
|
||||
const float ic = 1.0f - avg.tmmd->correction, ia = 1.0f - avg.tmmd->adaptation;
|
||||
|
||||
int64_t index = 0;
|
||||
for (const int64_t pixel_index : range) {
|
||||
float4 input = scene_linear[index];
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz();
|
||||
const float L = IMB_colormanagement_get_luminance(pixel);
|
||||
float I_l = pixel.x + ic * (L - pixel.x);
|
||||
float I_g = avg.cav.x + ic * (avg.lav - avg.cav.x);
|
||||
float I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.x /= std::max(pixel.x + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.y + ic * (L - pixel.y);
|
||||
I_g = avg.cav.y + ic * (avg.lav - avg.cav.y);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.y /= std::max(pixel.y + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.z + ic * (L - pixel.z);
|
||||
I_g = avg.cav.z + ic * (avg.lav - avg.cav.z);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.z /= std::max(pixel.z + powf(f * I_a, m), 1.0e-30f);
|
||||
|
||||
/* Apply mask. */
|
||||
if (mask != nullptr) {
|
||||
float3 msk(1.0f);
|
||||
if (mask_byte != nullptr) {
|
||||
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
|
||||
}
|
||||
else if (mask_float != nullptr) {
|
||||
msk = mask_float[pixel_index].xyz();
|
||||
}
|
||||
pixel = math::interpolate(input.xyz(), pixel, msk);
|
||||
}
|
||||
|
||||
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_point_inside_quad(const StripScreenQuad &quad, int x, int y)
|
||||
{
|
||||
float2 pt(x + 0.5f, y + 0.5f);
|
||||
return isect_point_quad_v2(pt, quad.v0, quad.v1, quad.v2, quad.v3);
|
||||
}
|
||||
|
||||
struct AreaLuminance {
|
||||
int64_t pixel_count = 0;
|
||||
double sum = 0.0f;
|
||||
float3 color_sum = {0, 0, 0};
|
||||
double log_sum = 0.0;
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
};
|
||||
|
||||
static void tonemap_calc_chunk_luminance(const StripScreenQuad &quad,
|
||||
const bool all_pixels_inside_quad,
|
||||
const int width,
|
||||
const IndexRange y_range,
|
||||
const float4 *scene_linear,
|
||||
AreaLuminance &r_lum)
|
||||
{
|
||||
for (const int y : y_range) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (all_pixels_inside_quad || is_point_inside_quad(quad, x, y)) {
|
||||
float4 pixel = *scene_linear;
|
||||
r_lum.pixel_count++;
|
||||
float L = IMB_colormanagement_get_luminance(pixel);
|
||||
r_lum.sum += L;
|
||||
r_lum.color_sum.x += pixel.x;
|
||||
r_lum.color_sum.y += pixel.y;
|
||||
r_lum.color_sum.z += pixel.z;
|
||||
r_lum.log_sum += logf(math::max(L, 0.0f) + 1e-5f);
|
||||
r_lum.max = math::max(r_lum.max, L);
|
||||
r_lum.min = math::min(r_lum.min, L);
|
||||
}
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AreaLuminance tonemap_calc_input_luminance(const StripScreenQuad &quad, const ImBuf *ibuf)
|
||||
{
|
||||
/* Pixels outside the pre-transform strip area are ignored for luminance calculations.
|
||||
* If strip area covers whole image, we can trivially accept all pixels. */
|
||||
const bool all_pixels_inside_quad = is_point_inside_quad(quad, 0, 0) &&
|
||||
is_point_inside_quad(quad, ibuf->x - 1, 0) &&
|
||||
is_point_inside_quad(quad, 0, ibuf->y - 1) &&
|
||||
is_point_inside_quad(quad, ibuf->x - 1, ibuf->y - 1);
|
||||
|
||||
AreaLuminance lum;
|
||||
lum = threading::parallel_reduce(
|
||||
IndexRange(ibuf->y),
|
||||
32,
|
||||
lum,
|
||||
/* Calculate luminance for a chunk. */
|
||||
[&](const IndexRange y_range, const AreaLuminance &init) {
|
||||
AreaLuminance lum = init;
|
||||
const int64_t chunk_size = y_range.size() * ibuf->x;
|
||||
/* For float images, convert to scene-linear in place. The rest
|
||||
* of tone-mapper can then continue with scene-linear values. */
|
||||
if (ibuf->float_buffer.data != nullptr) {
|
||||
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
|
||||
fptr += y_range.first() * ibuf->x;
|
||||
pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
|
||||
tonemap_calc_chunk_luminance(quad, all_pixels_inside_quad, ibuf->x, y_range, fptr, lum);
|
||||
}
|
||||
else {
|
||||
const uchar *bptr = ibuf->byte_buffer.data + y_range.first() * ibuf->x * 4;
|
||||
Array<float4> scene_linear(chunk_size);
|
||||
pixels_to_scene_linear_byte(
|
||||
ibuf->byte_buffer.colorspace, bptr, scene_linear.data(), chunk_size);
|
||||
tonemap_calc_chunk_luminance(
|
||||
quad, all_pixels_inside_quad, ibuf->x, y_range, scene_linear.data(), lum);
|
||||
}
|
||||
return lum;
|
||||
},
|
||||
/* Reduce luminance results. */
|
||||
[&](const AreaLuminance &a, const AreaLuminance &b) {
|
||||
AreaLuminance res;
|
||||
res.pixel_count = a.pixel_count + b.pixel_count;
|
||||
res.sum = a.sum + b.sum;
|
||||
res.color_sum = a.color_sum + b.color_sum;
|
||||
res.log_sum = a.log_sum + b.log_sum;
|
||||
res.min = math::min(a.min, b.min);
|
||||
res.max = math::max(a.max, b.max);
|
||||
return res;
|
||||
});
|
||||
return lum;
|
||||
}
|
||||
|
||||
static void tonemapmodifier_apply(const StripScreenQuad &quad,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const SequencerTonemapModifierData *tmmd = (const SequencerTonemapModifierData *)smd;
|
||||
|
||||
AreaLuminance lum = tonemap_calc_input_luminance(quad, ibuf);
|
||||
if (lum.pixel_count == 0) {
|
||||
return; /* Strip is zero size or off-screen. */
|
||||
}
|
||||
|
||||
AvgLogLum data;
|
||||
data.tmmd = tmmd;
|
||||
data.lav = lum.sum / lum.pixel_count;
|
||||
data.cav.x = lum.color_sum.x / lum.pixel_count;
|
||||
data.cav.y = lum.color_sum.y / lum.pixel_count;
|
||||
data.cav.z = lum.color_sum.z / lum.pixel_count;
|
||||
float maxl = log(double(lum.max) + 1e-5f);
|
||||
float minl = log(double(lum.min) + 1e-5f);
|
||||
float avl = lum.log_sum / lum.pixel_count;
|
||||
data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
|
||||
float al = exp(double(avl));
|
||||
data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
|
||||
data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
|
||||
|
||||
threading::parallel_for(
|
||||
IndexRange(int64_t(ibuf->x) * ibuf->y), 64 * 1024, [&](IndexRange range) {
|
||||
if (ibuf->float_buffer.data != nullptr) {
|
||||
/* Float pixels: no need for temporary storage. Luminance calculation already converted
|
||||
* data to scene linear. */
|
||||
float4 *pixels = (float4 *)(ibuf->float_buffer.data) + range.first();
|
||||
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(pixels, mask, range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(pixels, mask, range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_float(ibuf, range);
|
||||
}
|
||||
else {
|
||||
/* Byte pixels: temporary storage for scene linear pixel values. */
|
||||
Array<float4> scene_linear(range.size());
|
||||
pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
|
||||
ibuf->byte_buffer.data + range.first() * 4,
|
||||
scene_linear.data(),
|
||||
range.size());
|
||||
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(scene_linear.data(), mask, range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(scene_linear.data(), mask, range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_byte(scene_linear.data(), ibuf, range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Public Modifier Functions
|
||||
* \{ */
|
||||
|
||||
static StripModifierTypeInfo modifiersTypes[NUM_STRIP_MODIFIER_TYPES] = {
|
||||
{}, /* First entry is unused. */
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color Balance"),
|
||||
/*struct_name*/ "ColorBalanceModifierData",
|
||||
/*struct_size*/ sizeof(ColorBalanceModifierData),
|
||||
/*init_data*/ colorBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ colorBalance_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Curves"),
|
||||
/*struct_name*/ "CurvesModifierData",
|
||||
/*struct_size*/ sizeof(CurvesModifierData),
|
||||
/*init_data*/ curves_init_data,
|
||||
/*free_data*/ curves_free_data,
|
||||
/*copy_data*/ curves_copy_data,
|
||||
/*apply*/ curves_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Hue Correct"),
|
||||
/*struct_name*/ "HueCorrectModifierData",
|
||||
/*struct_size*/ sizeof(HueCorrectModifierData),
|
||||
/*init_data*/ hue_correct_init_data,
|
||||
/*free_data*/ hue_correct_free_data,
|
||||
/*copy_data*/ hue_correct_copy_data,
|
||||
/*apply*/ hue_correct_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Brightness/Contrast"),
|
||||
/*struct_name*/ "BrightContrastModifierData",
|
||||
/*struct_size*/ sizeof(BrightContrastModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ brightcontrast_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Mask"),
|
||||
/*struct_name*/ "SequencerMaskModifierData",
|
||||
/*struct_size*/ sizeof(SequencerMaskModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ maskmodifier_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "White Balance"),
|
||||
/*struct_name*/ "WhiteBalanceModifierData",
|
||||
/*struct_size*/ sizeof(WhiteBalanceModifierData),
|
||||
/*init_data*/ whiteBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ whiteBalance_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Tonemap"),
|
||||
/*struct_name*/ "SequencerTonemapModifierData",
|
||||
/*struct_size*/ sizeof(SequencerTonemapModifierData),
|
||||
/*init_data*/ tonemapmodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ tonemapmodifier_apply,
|
||||
},
|
||||
{
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Equalizer"),
|
||||
/*struct_name*/ "SoundEqualizerModifierData",
|
||||
/*struct_size*/ sizeof(SoundEqualizerModifierData),
|
||||
/*init_data*/ sound_equalizermodifier_init_data,
|
||||
/*free_data*/ sound_equalizermodifier_free,
|
||||
/*copy_data*/ sound_equalizermodifier_copy_data,
|
||||
/*apply*/ nullptr,
|
||||
},
|
||||
};
|
||||
|
||||
const StripModifierTypeInfo *modifier_type_info_get(int type)
|
||||
{
|
||||
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
|
||||
return nullptr;
|
||||
}
|
||||
return &modifiersTypes[type];
|
||||
}
|
||||
|
||||
StripModifierData *modifier_new(Strip *strip, const char *name, int type)
|
||||
{
|
||||
StripModifierData *smd;
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
|
||||
|
||||
smd = static_cast<StripModifierData *>(MEM_callocN(smti->struct_size, "sequence modifier"));
|
||||
|
||||
smd->type = type;
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED;
|
||||
|
||||
if (!name || !name[0]) {
|
||||
STRNCPY_UTF8(smd->name, CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name));
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(smd->name, name);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip->modifiers, smd);
|
||||
|
||||
modifier_unique_name(strip, smd);
|
||||
|
||||
if (smti->init_data) {
|
||||
smti->init_data(smd);
|
||||
}
|
||||
|
||||
return smd;
|
||||
}
|
||||
|
||||
bool modifier_remove(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
if (BLI_findindex(&strip->modifiers, smd) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BLI_remlink(&strip->modifiers, smd);
|
||||
modifier_free(smd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void modifier_clear(Strip *strip)
|
||||
{
|
||||
StripModifierData *smd, *smd_next;
|
||||
|
||||
for (smd = static_cast<StripModifierData *>(strip->modifiers.first); smd; smd = smd_next) {
|
||||
smd_next = smd->next;
|
||||
modifier_free(smd);
|
||||
}
|
||||
|
||||
BLI_listbase_clear(&strip->modifiers);
|
||||
}
|
||||
|
||||
void modifier_free(StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
if (smti && smti->free_data) {
|
||||
smti->free_data(smd);
|
||||
}
|
||||
|
||||
MEM_freeN(smd);
|
||||
}
|
||||
|
||||
void modifier_unique_name(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
BLI_uniquename(&strip->modifiers,
|
||||
smd,
|
||||
CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name),
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(smd->name));
|
||||
}
|
||||
|
||||
StripModifierData *modifier_find_by_name(Strip *strip, const char *name)
|
||||
{
|
||||
return static_cast<StripModifierData *>(
|
||||
BLI_findstring(&(strip->modifiers), name, offsetof(StripModifierData, name)));
|
||||
}
|
||||
|
||||
static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeline_frame)
|
||||
{
|
||||
using namespace blender::seq;
|
||||
|
||||
if (smd->mask_strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const bool strip_has_ended_skip = smd->mask_input_type == STRIP_MASK_INPUT_STRIP &&
|
||||
smd->mask_time == STRIP_MASK_TIME_RELATIVE &&
|
||||
!time_strip_intersects_frame(
|
||||
scene, smd->mask_strip, timeline_frame);
|
||||
const bool missing_data_skip = !strip_has_valid_data(smd->mask_strip) ||
|
||||
media_presence_is_missing(scene, smd->mask_strip);
|
||||
|
||||
return strip_has_ended_skip || missing_data_skip;
|
||||
}
|
||||
|
||||
void modifier_apply_stack(const RenderData *context,
|
||||
const Strip *strip,
|
||||
ImBuf *ibuf,
|
||||
int timeline_frame)
|
||||
{
|
||||
const StripScreenQuad quad = get_strip_screen_quad(context, strip);
|
||||
|
||||
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
|
||||
render_imbuf_from_sequencer_space(context->scene, ibuf);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
/* could happen if modifier is being removed or not exists in current version of blender */
|
||||
if (!smti) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* modifier is muted, do nothing */
|
||||
if (smd->flag & STRIP_MODIFIER_FLAG_MUTE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (smti->apply && !skip_modifier(context->scene, smd, timeline_frame)) {
|
||||
int frame_offset;
|
||||
if (smd->mask_time == STRIP_MASK_TIME_RELATIVE) {
|
||||
frame_offset = strip->start;
|
||||
}
|
||||
else /* if (smd->mask_time == STRIP_MASK_TIME_ABSOLUTE) */ {
|
||||
frame_offset = smd->mask_id ? ((Mask *)smd->mask_id)->sfra : 0;
|
||||
}
|
||||
|
||||
ImBuf *mask = modifier_mask_get(smd, context, timeline_frame, frame_offset);
|
||||
smti->apply(quad, smd, ibuf, mask);
|
||||
if (mask) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
|
||||
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
|
||||
}
|
||||
}
|
||||
|
||||
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
|
||||
StripModifierData *mod_new = static_cast<StripModifierData *>(MEM_dupallocN(mod_src));
|
||||
|
||||
if (smti && smti->copy_data) {
|
||||
smti->copy_data(mod_new, mod_src);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip_dst.modifiers, mod_new);
|
||||
BLI_uniquename(&strip_dst.modifiers,
|
||||
mod_new,
|
||||
"Strip Modifier",
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(StripModifierData::name));
|
||||
return mod_new;
|
||||
}
|
||||
|
||||
void modifier_list_copy(Strip *strip_new, Strip *strip)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
modifier_copy(*strip_new, smd);
|
||||
}
|
||||
}
|
||||
|
||||
int sequence_supports_modifiers(Strip *strip)
|
||||
{
|
||||
return (strip->type != STRIP_TYPE_SOUND_RAM);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name .blend File I/O
|
||||
* \{ */
|
||||
|
||||
void modifier_blend_write(BlendWriter *writer, ListBase *modbase)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, modbase) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
if (smti) {
|
||||
BLO_write_struct_by_name(writer, smti->struct_name, smd);
|
||||
|
||||
if (smd->type == seqModifierType_Curves) {
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == seqModifierType_HueCorrect) {
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_write(writer, &hcmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == seqModifierType_SoundEqualizer) {
|
||||
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
|
||||
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
|
||||
BLO_write_struct_by_name(writer, "EQCurveMappingData", eqcmd);
|
||||
BKE_curvemapping_blend_write(writer, &eqcmd->curve_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLO_write_struct(writer, StripModifierData, smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
|
||||
{
|
||||
BLO_read_struct_list(reader, StripModifierData, lb);
|
||||
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, lb) {
|
||||
if (smd->mask_strip) {
|
||||
BLO_read_struct(reader, Strip, &smd->mask_strip);
|
||||
}
|
||||
|
||||
if (smd->type == seqModifierType_Curves) {
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == seqModifierType_HueCorrect) {
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_read(reader, &hcmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == seqModifierType_SoundEqualizer) {
|
||||
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
|
||||
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
|
||||
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
|
||||
BKE_curvemapping_blend_read(reader, &eqcmd->curve_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -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
|
||||
@@ -0,0 +1,115 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <cfloat>
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_vector.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_modifiertypes.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct BrightContrastApplyOp {
|
||||
float mul;
|
||||
float add;
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
|
||||
float4 result;
|
||||
result = input * this->mul + this->add;
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void brightcontrast_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const BrightContrastModifierData *bcmd = (BrightContrastModifierData *)smd;
|
||||
|
||||
BrightContrastApplyOp op;
|
||||
|
||||
/* The algorithm is by Werner D. Streidt
|
||||
* (http://visca.com/ffactory/archives/5-99/msg00021.html)
|
||||
* Extracted from OpenCV `demhist.cpp`. */
|
||||
const float brightness = bcmd->bright / 100.0f;
|
||||
const float contrast = bcmd->contrast;
|
||||
float delta = contrast / 200.0f;
|
||||
|
||||
if (contrast > 0) {
|
||||
op.mul = 1.0f - delta * 2.0f;
|
||||
op.mul = 1.0f / max_ff(op.mul, FLT_EPSILON);
|
||||
op.add = op.mul * (brightness - delta);
|
||||
}
|
||||
else {
|
||||
delta *= -1;
|
||||
op.mul = max_ff(1.0f - delta * 2.0f, 0.0f);
|
||||
op.add = op.mul * brightness + delta;
|
||||
}
|
||||
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
static void brightcontrast_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
|
||||
layout->prop(ptr, "bright", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout->prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
if (uiLayout *mask_input_layout = layout->panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void brightcontrast_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_BrightContrast, brightcontrast_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_BrightContrast = {
|
||||
/*idname*/ "BrightContrast",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Brightness/Contrast"),
|
||||
/*struct_name*/ "BrightContrastModifierData",
|
||||
/*struct_size*/ sizeof(BrightContrastModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ brightcontrast_apply,
|
||||
/*panel_register*/ brightcontrast_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
371
source/blender/sequencer/intern/modifiers/MOD_color_balance.cc
Normal file
371
source/blender/sequencer/intern/modifiers/MOD_color_balance.cc
Normal file
@@ -0,0 +1,371 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_modifiertypes.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* Lift-Gamma-Gain math. NOTE: lift is actually (2-lift). */
|
||||
static float color_balance_lgg(
|
||||
float in, const float lift, const float gain, const float gamma, const float mul)
|
||||
{
|
||||
float x = (((in - 1.0f) * lift) + 1.0f) * gain;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, gamma) * mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Slope-Offset-Power (ASC CDL) math, see https://en.wikipedia.org/wiki/ASC_CDL */
|
||||
static float color_balance_sop(
|
||||
float in, const float slope, const float offset, const float power, float mul)
|
||||
{
|
||||
float x = in * slope + offset;
|
||||
|
||||
/* prevent NaN */
|
||||
x = std::max(x, 0.0f);
|
||||
|
||||
x = powf(x, power);
|
||||
x *= mul;
|
||||
CLAMP(x, FLT_MIN, FLT_MAX);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a larger lookup table than 256 possible byte values: due to alpha
|
||||
* pre-multiplication, dark values with low alphas might need more precision.
|
||||
*/
|
||||
static constexpr int CB_TABLE_SIZE = 1024;
|
||||
|
||||
static void make_cb_table_lgg(
|
||||
float lift, float gain, float gamma, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_lgg(x, lift, gain, gamma, mul);
|
||||
}
|
||||
}
|
||||
|
||||
static void make_cb_table_sop(
|
||||
float slope, float offset, float power, float mul, float r_table[CB_TABLE_SIZE])
|
||||
{
|
||||
for (int i = 0; i < CB_TABLE_SIZE; i++) {
|
||||
float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
|
||||
r_table[i] = color_balance_sop(x, slope, offset, power, mul);
|
||||
}
|
||||
}
|
||||
|
||||
struct ColorBalanceApplyOp {
|
||||
int method;
|
||||
float3 lift, gain, gamma;
|
||||
float3 slope, offset, power;
|
||||
float multiplier;
|
||||
float lut[3][CB_TABLE_SIZE];
|
||||
|
||||
/* Apply on a byte image via a table lookup. */
|
||||
template<typename MaskT> void apply(uchar *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
int p0 = int(input.x * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p1 = int(input.y * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
int p2 = int(input.z * (CB_TABLE_SIZE - 1.0f) + 0.5f);
|
||||
result.x = this->lut[0][p0];
|
||||
result.y = this->lut[1][p1];
|
||||
result.z = this->lut[2][p2];
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply on a float image by doing full math. */
|
||||
template<typename MaskT> void apply(float *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Lift/Gamma/Gain */
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_lgg(
|
||||
input.x, this->lift.x, this->gain.x, this->gamma.x, this->multiplier);
|
||||
result.y = color_balance_lgg(
|
||||
input.y, this->lift.y, this->gain.y, this->gamma.y, this->multiplier);
|
||||
result.z = color_balance_lgg(
|
||||
input.z, this->lift.z, this->gain.z, this->gamma.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
/* Slope/Offset/Power */
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.x = color_balance_sop(
|
||||
input.x, this->slope.x, this->offset.x, this->power.x, this->multiplier);
|
||||
result.y = color_balance_sop(
|
||||
input.y, this->slope.y, this->offset.y, this->power.y, this->multiplier);
|
||||
result.z = color_balance_sop(
|
||||
input.z, this->slope.z, this->offset.z, this->power.z, this->multiplier);
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
void init_lgg(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN);
|
||||
|
||||
this->lift = 2.0f - float3(data.lift);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_LIFT) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
/* tweak to give more subtle results
|
||||
* values above 1.0 are scaled */
|
||||
if (this->lift[c] > 1.0f) {
|
||||
this->lift[c] = powf(this->lift[c] - 1.0f, 2.0f) + 1.0f;
|
||||
}
|
||||
this->lift[c] = 2.0f - this->lift[c];
|
||||
}
|
||||
}
|
||||
|
||||
this->gain = float3(data.gain);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_GAIN) {
|
||||
this->gain = math::rcp(math::max(this->gain, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->gamma = float3(data.gamma);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_GAMMA)) {
|
||||
this->gamma = math::rcp(math::max(this->gamma, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init_sop(const StripColorBalance &data)
|
||||
{
|
||||
BLI_assert(data.method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER);
|
||||
|
||||
this->slope = float3(data.slope);
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_SLOPE) {
|
||||
this->slope = math::rcp(math::max(this->slope, float3(1.0e-6f)));
|
||||
}
|
||||
|
||||
this->offset = float3(data.offset) - 1.0f;
|
||||
if (data.flag & SEQ_COLOR_BALANCE_INVERSE_OFFSET) {
|
||||
this->offset = -this->offset;
|
||||
}
|
||||
|
||||
this->power = float3(data.power);
|
||||
if (!(data.flag & SEQ_COLOR_BALANCE_INVERSE_POWER)) {
|
||||
this->power = math::rcp(math::max(this->power, float3(1.0e-6f)));
|
||||
}
|
||||
}
|
||||
|
||||
void init(const ColorBalanceModifierData &data, bool byte_image)
|
||||
{
|
||||
this->multiplier = data.color_multiply;
|
||||
this->method = data.color_balance.method;
|
||||
|
||||
if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
init_lgg(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_lgg(
|
||||
this->lift[c], this->gain[c], this->gamma[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
init_sop(data.color_balance);
|
||||
if (byte_image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
make_cb_table_sop(
|
||||
this->slope[c], this->offset[c], this->power[c], this->multiplier, this->lut[c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void colorBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
ColorBalanceModifierData *cbmd = (ColorBalanceModifierData *)smd;
|
||||
|
||||
cbmd->color_multiply = 1.0f;
|
||||
cbmd->color_balance.method = 0;
|
||||
|
||||
for (int c = 0; c < 3; c++) {
|
||||
cbmd->color_balance.lift[c] = 1.0f;
|
||||
cbmd->color_balance.gamma[c] = 1.0f;
|
||||
cbmd->color_balance.gain[c] = 1.0f;
|
||||
cbmd->color_balance.slope[c] = 1.0f;
|
||||
cbmd->color_balance.offset[c] = 1.0f;
|
||||
cbmd->color_balance.power[c] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const ColorBalanceModifierData *cbmd = (const ColorBalanceModifierData *)smd;
|
||||
|
||||
ColorBalanceApplyOp op;
|
||||
op.init(*cbmd, ibuf->byte_buffer.data != nullptr);
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
static void colorBalance_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
PointerRNA color_balance = RNA_pointer_get(ptr, "color_balance");
|
||||
const int correction_method = RNA_enum_get(&color_balance, "correction_method");
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
|
||||
layout->prop(ptr, "color_multiply", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
layout->prop(&color_balance, "correction_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
uiLayout &flow = layout->grid_flow(true, 0, true, false, false);
|
||||
flow.use_property_split_set(false);
|
||||
if (correction_method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
|
||||
/* Split into separate scopes to be able to reuse "split" and "col" variable names. */
|
||||
{
|
||||
uiLayout &split = flow.column(false).split(0.35f, false);
|
||||
uiLayout &col = split.column(true);
|
||||
col.label("Lift", ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "lift", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(&color_balance, "invert_lift", UI_ITEM_NONE, "Invert", ICON_ARROW_LEFTRIGHT);
|
||||
uiTemplateColorPicker(&split, &color_balance, "lift", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
uiLayout &split = flow.column(false).split(0.35f, false);
|
||||
uiLayout &col = split.column(true);
|
||||
col.label("Gamma", ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "gamma", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(&color_balance, "invert_gamma", UI_ITEM_NONE, "Invert", ICON_ARROW_LEFTRIGHT);
|
||||
uiTemplateColorPicker(&split, &color_balance, "gamma", true, false, true, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
uiLayout &split = flow.column(false).split(0.35f, false);
|
||||
uiLayout &col = split.column(true);
|
||||
col.label("Gain", ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "gain", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(&color_balance, "invert_gain", UI_ITEM_NONE, "Invert", ICON_ARROW_LEFTRIGHT);
|
||||
uiTemplateColorPicker(&split, &color_balance, "gain", true, false, true, true);
|
||||
}
|
||||
}
|
||||
else if (correction_method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
|
||||
{
|
||||
uiLayout &split = flow.column(false).split(0.35f, false);
|
||||
uiLayout &col = split.column(true);
|
||||
col.label("Offset", ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "offset", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(&color_balance, "invert_offset", UI_ITEM_NONE, "Invert", ICON_ARROW_LEFTRIGHT);
|
||||
uiTemplateColorPicker(&split, &color_balance, "offset", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
uiLayout &split = flow.column(false).split(0.35f, false);
|
||||
uiLayout &col = split.column(true);
|
||||
col.label("Power", ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "power", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(&color_balance, "invert_power", UI_ITEM_NONE, "Invert", ICON_ARROW_LEFTRIGHT);
|
||||
uiTemplateColorPicker(&split, &color_balance, "power", true, false, false, true);
|
||||
col.separator();
|
||||
}
|
||||
{
|
||||
uiLayout &split = flow.column(false).split(0.35f, false);
|
||||
uiLayout &col = split.column(true);
|
||||
col.label("Slope", ICON_NONE);
|
||||
col.separator();
|
||||
col.separator();
|
||||
col.prop(&color_balance, "slope", UI_ITEM_NONE, "", ICON_NONE);
|
||||
col.prop(&color_balance, "invert_slope", UI_ITEM_NONE, "Invert", ICON_ARROW_LEFTRIGHT);
|
||||
uiTemplateColorPicker(&split, &color_balance, "slope", true, false, false, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
if (uiLayout *mask_input_layout = layout->panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void colorBalance_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_ColorBalance, colorBalance_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_ColorBalance = {
|
||||
/*idname*/ "ColorBalance",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color Balance"),
|
||||
/*struct_name*/ "ColorBalanceModifierData",
|
||||
/*struct_size*/ sizeof(ColorBalanceModifierData),
|
||||
/*init_data*/ colorBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ colorBalance_apply,
|
||||
/*panel_register*/ colorBalance_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
121
source/blender/sequencer/intern/modifiers/MOD_curves.cc
Normal file
121
source/blender/sequencer/intern/modifiers/MOD_curves.cc
Normal file
@@ -0,0 +1,121 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_c.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void curves_init_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_set_defaults(&cmd->curve_mapping, 4, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
}
|
||||
|
||||
static void curves_free_data(StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_free_data(&cmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void curves_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
CurvesModifierData *cmd_target = (CurvesModifierData *)target;
|
||||
|
||||
BKE_curvemapping_copy_data(&cmd_target->curve_mapping, &cmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct CurvesApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
|
||||
result.w = input.w;
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void curves_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
const float black[3] = {0.0f, 0.0f, 0.0f};
|
||||
const float white[3] = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
BKE_curvemapping_init(&cmd->curve_mapping);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, false);
|
||||
BKE_curvemapping_set_black_white(&cmd->curve_mapping, black, white);
|
||||
|
||||
CurvesApplyOp op;
|
||||
op.curve_mapping = &cmd->curve_mapping;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
|
||||
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
|
||||
}
|
||||
|
||||
static void curves_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
uiTemplateCurveMapping(layout, ptr, "curve_mapping", 'c', false, false, false, true);
|
||||
|
||||
if (uiLayout *mask_input_layout = layout->panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void curves_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Curves, curves_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Curves = {
|
||||
/*idname*/ "Curves",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Curves"),
|
||||
/*struct_name*/ "CurvesModifierData",
|
||||
/*struct_size*/ sizeof(CurvesModifierData),
|
||||
/*init_data*/ curves_init_data,
|
||||
/*free_data*/ curves_free_data,
|
||||
/*copy_data*/ curves_copy_data,
|
||||
/*apply*/ curves_apply,
|
||||
/*panel_register*/ curves_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
148
source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc
Normal file
148
source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc
Normal file
@@ -0,0 +1,148 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_color.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_curve_enums.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void hue_correct_init_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
int c;
|
||||
|
||||
BKE_curvemapping_set_defaults(&hcmd->curve_mapping, 1, 0.0f, 0.0f, 1.0f, 1.0f, HD_AUTO);
|
||||
hcmd->curve_mapping.preset = CURVE_PRESET_MID8;
|
||||
|
||||
for (c = 0; c < 3; c++) {
|
||||
CurveMap *cuma = &hcmd->curve_mapping.cm[c];
|
||||
BKE_curvemap_reset(
|
||||
cuma, &hcmd->curve_mapping.clipr, hcmd->curve_mapping.preset, CURVEMAP_SLOPE_POSITIVE);
|
||||
}
|
||||
/* use wrapping for all hue correct modifiers */
|
||||
hcmd->curve_mapping.flag |= CUMA_USE_WRAPPING;
|
||||
/* default to showing Saturation */
|
||||
hcmd->curve_mapping.cur = 1;
|
||||
}
|
||||
|
||||
static void hue_correct_free_data(StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_free_data(&hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
static void hue_correct_copy_data(StripModifierData *target, StripModifierData *smd)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
HueCorrectModifierData *hcmd_target = (HueCorrectModifierData *)target;
|
||||
|
||||
BKE_curvemapping_copy_data(&hcmd_target->curve_mapping, &hcmd->curve_mapping);
|
||||
}
|
||||
|
||||
struct HueCorrectApplyOp {
|
||||
const CurveMapping *curve_mapping;
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
/* NOTE: arguably incorrect usage of "raw" values, should be un-premultiplied.
|
||||
* Not changing behavior for now, but would be good to fix someday. */
|
||||
float4 input = load_pixel_raw(image);
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
|
||||
float3 hsv;
|
||||
rgb_to_hsv(input.x, input.y, input.z, &hsv.x, &hsv.y, &hsv.z);
|
||||
|
||||
/* adjust hue, scaling returned default 0.5 up to 1 */
|
||||
float f;
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 0, hsv.x);
|
||||
hsv.x += f - 0.5f;
|
||||
|
||||
/* adjust saturation, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 1, hsv.x);
|
||||
hsv.y *= (f * 2.0f);
|
||||
|
||||
/* adjust value, scaling returned default 0.5 up to 1 */
|
||||
f = BKE_curvemapping_evaluateF(this->curve_mapping, 2, hsv.x);
|
||||
hsv.z *= (f * 2.0f);
|
||||
|
||||
hsv.x = hsv.x - floorf(hsv.x); /* mod 1.0 */
|
||||
hsv.y = math::clamp(hsv.y, 0.0f, 1.0f);
|
||||
|
||||
/* convert back to rgb */
|
||||
hsv_to_rgb(hsv.x, hsv.y, hsv.z, &result.x, &result.y, &result.z);
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_raw(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void hue_correct_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_init(&hcmd->curve_mapping);
|
||||
|
||||
HueCorrectApplyOp op;
|
||||
op.curve_mapping = &hcmd->curve_mapping;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
static void hue_correct_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
uiTemplateCurveMapping(layout, ptr, "curve_mapping", 'h', false, false, false, false);
|
||||
|
||||
if (uiLayout *mask_input_layout = layout->panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void hue_correct_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_HueCorrect, hue_correct_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_HueCorrect = {
|
||||
/*idname*/ "HueCorrect",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Hue Correct"),
|
||||
/*struct_name*/ "HueCorrectModifierData",
|
||||
/*struct_size*/ sizeof(HueCorrectModifierData),
|
||||
/*init_data*/ hue_correct_init_data,
|
||||
/*free_data*/ hue_correct_free_data,
|
||||
/*copy_data*/ hue_correct_copy_data,
|
||||
/*apply*/ hue_correct_apply,
|
||||
/*panel_register*/ hue_correct_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
108
source/blender/sequencer/intern/modifiers/MOD_mask.cc
Normal file
108
source/blender/sequencer/intern/modifiers/MOD_mask.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static float load_mask_min(const uchar *&mask)
|
||||
{
|
||||
float m = float(min_iii(mask[0], mask[1], mask[2])) * (1.0f / 255.0f);
|
||||
mask += 4;
|
||||
return m;
|
||||
}
|
||||
static float load_mask_min(const float *&mask)
|
||||
{
|
||||
float m = min_fff(mask[0], mask[1], mask[2]);
|
||||
mask += 4;
|
||||
return m;
|
||||
}
|
||||
static float load_mask_min(const void *& /*mask*/)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
struct MaskApplyOp {
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float m = load_mask_min(mask);
|
||||
|
||||
if constexpr (std::is_same_v<ImageT, uchar>) {
|
||||
/* Byte buffer is straight, so only affect on alpha itself, this is
|
||||
* the only way to alpha-over byte strip after applying mask modifier. */
|
||||
image[3] = uchar(image[3] * m);
|
||||
}
|
||||
else if constexpr (std::is_same_v<ImageT, float>) {
|
||||
/* Float buffers are premultiplied, so need to premul color as well to make it
|
||||
* easy to alpha-over masked strip. */
|
||||
float4 pix(image);
|
||||
pix *= m;
|
||||
*reinterpret_cast<float4 *>(image) = pix;
|
||||
}
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void maskmodifier_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData * /*smd*/,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
if (mask == nullptr || (mask->byte_buffer.data == nullptr && mask->float_buffer.data == nullptr))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MaskApplyOp op;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
|
||||
/* Image has gained transparency. */
|
||||
ibuf->planes = R_IMF_PLANES_RGBA;
|
||||
}
|
||||
|
||||
static void maskmodifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
draw_mask_input_type_settings(C, layout, ptr);
|
||||
}
|
||||
|
||||
static void maskmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Mask, maskmodifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Mask = {
|
||||
/*idname*/ "Mask",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Mask"),
|
||||
/*struct_name*/ "SequencerMaskModifierData",
|
||||
/*struct_size*/ sizeof(SequencerMaskModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ maskmodifier_apply,
|
||||
/*panel_register*/ maskmodifier_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
29
source/blender/sequencer/intern/modifiers/MOD_none.cc
Normal file
29
source/blender/sequencer/intern/modifiers/MOD_none.cc
Normal file
@@ -0,0 +1,29 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
StripModifierTypeInfo seqModifierType_None = {
|
||||
/*idname*/ "None",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "None"),
|
||||
/*struct_name*/ "StripModifierData",
|
||||
/*struct_size*/ sizeof(StripModifierData),
|
||||
/*init_data*/ nullptr,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ nullptr,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_sound.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void sound_equalizermodifier_draw(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
|
||||
uiLayout &flow = layout->grid_flow(true, 0, true, false, false);
|
||||
RNA_BEGIN (ptr, sound_eq, "graphics") {
|
||||
PointerRNA curve_mapping = RNA_pointer_get(&sound_eq, "curve_mapping");
|
||||
const float clip_min_x = RNA_float_get(&curve_mapping, "clip_min_x");
|
||||
const float clip_max_x = RNA_float_get(&curve_mapping, "clip_max_x");
|
||||
|
||||
uiLayout &col = flow.column(false);
|
||||
uiLayout &split = col.split(0.4f, false);
|
||||
split.label(fmt::format("{:.2f}", clip_min_x), ICON_NONE);
|
||||
split.label("Hz", ICON_NONE);
|
||||
split.alignment_set(ui::LayoutAlign::Right);
|
||||
split.label(fmt::format("{:.2f}", clip_max_x), ICON_NONE);
|
||||
uiTemplateCurveMapping(&col, &sound_eq, "curve_mapping", 0, false, true, true, false);
|
||||
uiLayout &row = col.row(false);
|
||||
row.alignment_set(ui::LayoutAlign::Center);
|
||||
row.label("dB", ICON_NONE);
|
||||
}
|
||||
RNA_END;
|
||||
}
|
||||
|
||||
static void sound_equalizermodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(
|
||||
region_type, eSeqModifierType_SoundEqualizer, sound_equalizermodifier_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_SoundEqualizer = {
|
||||
/*idname*/ "SoundEqualizer",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Equalizer"),
|
||||
/*struct_name*/ "SoundEqualizerModifierData",
|
||||
/*struct_size*/ sizeof(SoundEqualizerModifierData),
|
||||
/*init_data*/ sound_equalizermodifier_init_data,
|
||||
/*free_data*/ sound_equalizermodifier_free,
|
||||
/*copy_data*/ sound_equalizermodifier_copy_data,
|
||||
/*apply*/ nullptr,
|
||||
/*panel_register*/ sound_equalizermodifier_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
393
source/blender/sequencer/intern/modifiers/MOD_tonemap.cc
Normal file
393
source/blender/sequencer/intern/modifiers/MOD_tonemap.cc
Normal file
@@ -0,0 +1,393 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_math_geom.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct AvgLogLum {
|
||||
const SequencerTonemapModifierData *tmmd;
|
||||
float al;
|
||||
float auto_key;
|
||||
float lav;
|
||||
float3 cav;
|
||||
float igm;
|
||||
};
|
||||
|
||||
static void tonemapmodifier_init_data(StripModifierData *smd)
|
||||
{
|
||||
SequencerTonemapModifierData *tmmd = (SequencerTonemapModifierData *)smd;
|
||||
/* Same as tone-map compositor node. */
|
||||
tmmd->type = SEQ_TONEMAP_RD_PHOTORECEPTOR;
|
||||
tmmd->key = 0.18f;
|
||||
tmmd->offset = 1.0f;
|
||||
tmmd->gamma = 1.0f;
|
||||
tmmd->intensity = 0.0f;
|
||||
tmmd->contrast = 0.0f;
|
||||
tmmd->adaptation = 1.0f;
|
||||
tmmd->correction = 0.0f;
|
||||
}
|
||||
|
||||
/* Convert chunk of float image pixels to scene linear space, in-place. */
|
||||
static void pixels_to_scene_linear_float(const ColorSpace *colorspace,
|
||||
float4 *pixels,
|
||||
int64_t count)
|
||||
{
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
(float *)(pixels), int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
/* Convert chunk of byte image pixels to scene linear space, into a destination array. */
|
||||
static void pixels_to_scene_linear_byte(const ColorSpace *colorspace,
|
||||
const uchar *pixels,
|
||||
float4 *dst,
|
||||
int64_t count)
|
||||
{
|
||||
const uchar *bptr = pixels;
|
||||
float4 *dst_ptr = dst;
|
||||
for (int64_t i = 0; i < count; i++) {
|
||||
straight_uchar_to_premul_float(*dst_ptr, bptr);
|
||||
bptr += 4;
|
||||
dst_ptr++;
|
||||
}
|
||||
IMB_colormanagement_colorspace_to_scene_linear(
|
||||
(float *)dst, int(count), 1, 4, colorspace, false);
|
||||
}
|
||||
|
||||
static void scene_linear_to_image_chunk_float(ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->float_buffer.colorspace;
|
||||
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
(float *)(fptr + range.first()), int(range.size()), 1, 4, colorspace);
|
||||
}
|
||||
|
||||
static void scene_linear_to_image_chunk_byte(float4 *src, ImBuf *ibuf, IndexRange range)
|
||||
{
|
||||
const ColorSpace *colorspace = ibuf->byte_buffer.colorspace;
|
||||
IMB_colormanagement_scene_linear_to_colorspace(
|
||||
(float *)src, int(range.size()), 1, 4, colorspace);
|
||||
const float4 *src_ptr = src;
|
||||
uchar *bptr = ibuf->byte_buffer.data;
|
||||
for (const int64_t idx : range) {
|
||||
premul_float_to_straight_uchar(bptr + idx * 4, *src_ptr);
|
||||
src_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemap_simple(float4 *scene_linear,
|
||||
ImBuf *mask,
|
||||
IndexRange range,
|
||||
const AvgLogLum &avg)
|
||||
{
|
||||
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
|
||||
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
|
||||
|
||||
int64_t index = 0;
|
||||
for (const int64_t pixel_index : range) {
|
||||
float4 input = scene_linear[index];
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz() * avg.al;
|
||||
float3 d = pixel + avg.tmmd->offset;
|
||||
pixel.x /= (d.x == 0.0f) ? 1.0f : d.x;
|
||||
pixel.y /= (d.y == 0.0f) ? 1.0f : d.y;
|
||||
pixel.z /= (d.z == 0.0f) ? 1.0f : d.z;
|
||||
const float igm = avg.igm;
|
||||
if (igm != 0.0f) {
|
||||
pixel.x = powf(math::max(pixel.x, 0.0f), igm);
|
||||
pixel.y = powf(math::max(pixel.y, 0.0f), igm);
|
||||
pixel.z = powf(math::max(pixel.z, 0.0f), igm);
|
||||
}
|
||||
|
||||
/* Apply mask. */
|
||||
if (mask != nullptr) {
|
||||
float3 msk(1.0f);
|
||||
if (mask_byte != nullptr) {
|
||||
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
|
||||
}
|
||||
else if (mask_float != nullptr) {
|
||||
msk = mask_float[pixel_index].xyz();
|
||||
}
|
||||
pixel = math::interpolate(input.xyz(), pixel, msk);
|
||||
}
|
||||
|
||||
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemap_rd_photoreceptor(float4 *scene_linear,
|
||||
ImBuf *mask,
|
||||
IndexRange range,
|
||||
const AvgLogLum &avg)
|
||||
{
|
||||
const float4 *mask_float = mask != nullptr ? (const float4 *)mask->float_buffer.data : nullptr;
|
||||
const uchar4 *mask_byte = mask != nullptr ? (const uchar4 *)mask->byte_buffer.data : nullptr;
|
||||
|
||||
const float f = expf(-avg.tmmd->intensity);
|
||||
const float m = (avg.tmmd->contrast > 0.0f) ? avg.tmmd->contrast :
|
||||
(0.3f + 0.7f * powf(avg.auto_key, 1.4f));
|
||||
const float ic = 1.0f - avg.tmmd->correction, ia = 1.0f - avg.tmmd->adaptation;
|
||||
|
||||
int64_t index = 0;
|
||||
for (const int64_t pixel_index : range) {
|
||||
float4 input = scene_linear[index];
|
||||
|
||||
/* Apply correction. */
|
||||
float3 pixel = input.xyz();
|
||||
const float L = IMB_colormanagement_get_luminance(pixel);
|
||||
float I_l = pixel.x + ic * (L - pixel.x);
|
||||
float I_g = avg.cav.x + ic * (avg.lav - avg.cav.x);
|
||||
float I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.x /= std::max(pixel.x + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.y + ic * (L - pixel.y);
|
||||
I_g = avg.cav.y + ic * (avg.lav - avg.cav.y);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.y /= std::max(pixel.y + powf(f * I_a, m), 1.0e-30f);
|
||||
I_l = pixel.z + ic * (L - pixel.z);
|
||||
I_g = avg.cav.z + ic * (avg.lav - avg.cav.z);
|
||||
I_a = I_l + ia * (I_g - I_l);
|
||||
pixel.z /= std::max(pixel.z + powf(f * I_a, m), 1.0e-30f);
|
||||
|
||||
/* Apply mask. */
|
||||
if (mask != nullptr) {
|
||||
float3 msk(1.0f);
|
||||
if (mask_byte != nullptr) {
|
||||
rgb_uchar_to_float(msk, mask_byte[pixel_index]);
|
||||
}
|
||||
else if (mask_float != nullptr) {
|
||||
msk = mask_float[pixel_index].xyz();
|
||||
}
|
||||
pixel = math::interpolate(input.xyz(), pixel, msk);
|
||||
}
|
||||
|
||||
scene_linear[index] = float4(pixel.x, pixel.y, pixel.z, input.w);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_point_inside_quad(const StripScreenQuad &quad, int x, int y)
|
||||
{
|
||||
float2 pt(x + 0.5f, y + 0.5f);
|
||||
return isect_point_quad_v2(pt, quad.v0, quad.v1, quad.v2, quad.v3);
|
||||
}
|
||||
|
||||
struct AreaLuminance {
|
||||
int64_t pixel_count = 0;
|
||||
double sum = 0.0f;
|
||||
float3 color_sum = {0, 0, 0};
|
||||
double log_sum = 0.0;
|
||||
float min = FLT_MAX;
|
||||
float max = -FLT_MAX;
|
||||
};
|
||||
|
||||
static void tonemap_calc_chunk_luminance(const StripScreenQuad &quad,
|
||||
const bool all_pixels_inside_quad,
|
||||
const int width,
|
||||
const IndexRange y_range,
|
||||
const float4 *scene_linear,
|
||||
AreaLuminance &r_lum)
|
||||
{
|
||||
for (const int y : y_range) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (all_pixels_inside_quad || is_point_inside_quad(quad, x, y)) {
|
||||
float4 pixel = *scene_linear;
|
||||
r_lum.pixel_count++;
|
||||
float L = IMB_colormanagement_get_luminance(pixel);
|
||||
r_lum.sum += L;
|
||||
r_lum.color_sum.x += pixel.x;
|
||||
r_lum.color_sum.y += pixel.y;
|
||||
r_lum.color_sum.z += pixel.z;
|
||||
r_lum.log_sum += logf(math::max(L, 0.0f) + 1e-5f);
|
||||
r_lum.max = math::max(r_lum.max, L);
|
||||
r_lum.min = math::min(r_lum.min, L);
|
||||
}
|
||||
scene_linear++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static AreaLuminance tonemap_calc_input_luminance(const StripScreenQuad &quad, const ImBuf *ibuf)
|
||||
{
|
||||
/* Pixels outside the pre-transform strip area are ignored for luminance calculations.
|
||||
* If strip area covers whole image, we can trivially accept all pixels. */
|
||||
const bool all_pixels_inside_quad = is_point_inside_quad(quad, 0, 0) &&
|
||||
is_point_inside_quad(quad, ibuf->x - 1, 0) &&
|
||||
is_point_inside_quad(quad, 0, ibuf->y - 1) &&
|
||||
is_point_inside_quad(quad, ibuf->x - 1, ibuf->y - 1);
|
||||
|
||||
AreaLuminance lum;
|
||||
lum = threading::parallel_reduce(
|
||||
IndexRange(ibuf->y),
|
||||
32,
|
||||
lum,
|
||||
/* Calculate luminance for a chunk. */
|
||||
[&](const IndexRange y_range, const AreaLuminance &init) {
|
||||
AreaLuminance lum = init;
|
||||
const int64_t chunk_size = y_range.size() * ibuf->x;
|
||||
/* For float images, convert to scene-linear in place. The rest
|
||||
* of tone-mapper can then continue with scene-linear values. */
|
||||
if (ibuf->float_buffer.data != nullptr) {
|
||||
float4 *fptr = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
|
||||
fptr += y_range.first() * ibuf->x;
|
||||
pixels_to_scene_linear_float(ibuf->float_buffer.colorspace, fptr, chunk_size);
|
||||
tonemap_calc_chunk_luminance(quad, all_pixels_inside_quad, ibuf->x, y_range, fptr, lum);
|
||||
}
|
||||
else {
|
||||
const uchar *bptr = ibuf->byte_buffer.data + y_range.first() * ibuf->x * 4;
|
||||
Array<float4> scene_linear(chunk_size);
|
||||
pixels_to_scene_linear_byte(
|
||||
ibuf->byte_buffer.colorspace, bptr, scene_linear.data(), chunk_size);
|
||||
tonemap_calc_chunk_luminance(
|
||||
quad, all_pixels_inside_quad, ibuf->x, y_range, scene_linear.data(), lum);
|
||||
}
|
||||
return lum;
|
||||
},
|
||||
/* Reduce luminance results. */
|
||||
[&](const AreaLuminance &a, const AreaLuminance &b) {
|
||||
AreaLuminance res;
|
||||
res.pixel_count = a.pixel_count + b.pixel_count;
|
||||
res.sum = a.sum + b.sum;
|
||||
res.color_sum = a.color_sum + b.color_sum;
|
||||
res.log_sum = a.log_sum + b.log_sum;
|
||||
res.min = math::min(a.min, b.min);
|
||||
res.max = math::max(a.max, b.max);
|
||||
return res;
|
||||
});
|
||||
return lum;
|
||||
}
|
||||
|
||||
static void tonemapmodifier_apply(const StripScreenQuad &quad,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const SequencerTonemapModifierData *tmmd = (const SequencerTonemapModifierData *)smd;
|
||||
|
||||
AreaLuminance lum = tonemap_calc_input_luminance(quad, ibuf);
|
||||
if (lum.pixel_count == 0) {
|
||||
return; /* Strip is zero size or off-screen. */
|
||||
}
|
||||
|
||||
AvgLogLum data;
|
||||
data.tmmd = tmmd;
|
||||
data.lav = lum.sum / lum.pixel_count;
|
||||
data.cav.x = lum.color_sum.x / lum.pixel_count;
|
||||
data.cav.y = lum.color_sum.y / lum.pixel_count;
|
||||
data.cav.z = lum.color_sum.z / lum.pixel_count;
|
||||
float maxl = log(double(lum.max) + 1e-5f);
|
||||
float minl = log(double(lum.min) + 1e-5f);
|
||||
float avl = lum.log_sum / lum.pixel_count;
|
||||
data.auto_key = (maxl > minl) ? ((maxl - avl) / (maxl - minl)) : 1.0f;
|
||||
float al = exp(double(avl));
|
||||
data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
|
||||
data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
|
||||
|
||||
threading::parallel_for(
|
||||
IndexRange(int64_t(ibuf->x) * ibuf->y), 64 * 1024, [&](IndexRange range) {
|
||||
if (ibuf->float_buffer.data != nullptr) {
|
||||
/* Float pixels: no need for temporary storage. Luminance calculation already converted
|
||||
* data to scene linear. */
|
||||
float4 *pixels = (float4 *)(ibuf->float_buffer.data) + range.first();
|
||||
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(pixels, mask, range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(pixels, mask, range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_float(ibuf, range);
|
||||
}
|
||||
else {
|
||||
/* Byte pixels: temporary storage for scene linear pixel values. */
|
||||
Array<float4> scene_linear(range.size());
|
||||
pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace,
|
||||
ibuf->byte_buffer.data + range.first() * 4,
|
||||
scene_linear.data(),
|
||||
range.size());
|
||||
if (tmmd->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
tonemap_rd_photoreceptor(scene_linear.data(), mask, range, data);
|
||||
}
|
||||
else {
|
||||
BLI_assert(tmmd->type == SEQ_TONEMAP_RH_SIMPLE);
|
||||
tonemap_simple(scene_linear.data(), mask, range, data);
|
||||
}
|
||||
scene_linear_to_image_chunk_byte(scene_linear.data(), ibuf, range);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
const int tonemap_type = RNA_enum_get(ptr, "tonemap_type");
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
|
||||
uiLayout &col = layout->column(false);
|
||||
col.prop(ptr, "tonemap_type", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
if (tonemap_type == SEQ_TONEMAP_RD_PHOTORECEPTOR) {
|
||||
col.prop(ptr, "intensity", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "contrast", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "adaptation", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "correction", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else if (tonemap_type == SEQ_TONEMAP_RH_SIMPLE) {
|
||||
col.prop(ptr, "key", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "offset", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
col.prop(ptr, "gamma", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
|
||||
if (uiLayout *mask_input_layout = layout->panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void tonemapmodifier_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_Tonemap, tonemapmodifier_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_Tonemap = {
|
||||
/*idname*/ "Tonemap",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Tonemap"),
|
||||
/*struct_name*/ "SequencerTonemapModifierData",
|
||||
/*struct_size*/ sizeof(SequencerTonemapModifierData),
|
||||
/*init_data*/ tonemapmodifier_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ tonemapmodifier_apply,
|
||||
/*panel_register*/ tonemapmodifier_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
108
source/blender/sequencer/intern/modifiers/MOD_white_balance.cc
Normal file
108
source/blender/sequencer/intern/modifiers/MOD_white_balance.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static void whiteBalance_init_data(StripModifierData *smd)
|
||||
{
|
||||
WhiteBalanceModifierData *cbmd = (WhiteBalanceModifierData *)smd;
|
||||
copy_v3_fl(cbmd->white_value, 1.0f);
|
||||
}
|
||||
|
||||
struct WhiteBalanceApplyOp {
|
||||
float multiplier[3];
|
||||
|
||||
template<typename ImageT, typename MaskT>
|
||||
void apply(ImageT *image, const MaskT *mask, IndexRange size)
|
||||
{
|
||||
for ([[maybe_unused]] int64_t i : size) {
|
||||
float4 input = load_pixel_premul(image);
|
||||
|
||||
float4 result;
|
||||
result.w = input.w;
|
||||
#if 0
|
||||
mul_v3_v3(result, multiplier);
|
||||
#else
|
||||
/* similar to division without the clipping */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
/* Prevent pow argument from being negative. This whole math
|
||||
* breaks down overall with any HDR colors; would be good to
|
||||
* revisit and do something more proper. */
|
||||
float f = max_ff(1.0f - input[i], 0.0f);
|
||||
result[i] = 1.0f - powf(f, this->multiplier[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
apply_and_advance_mask(input, result, mask);
|
||||
store_pixel_premul(result, image);
|
||||
image += 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void whiteBalance_apply(const StripScreenQuad & /*quad*/,
|
||||
StripModifierData *smd,
|
||||
ImBuf *ibuf,
|
||||
ImBuf *mask)
|
||||
{
|
||||
const WhiteBalanceModifierData *data = (const WhiteBalanceModifierData *)smd;
|
||||
|
||||
WhiteBalanceApplyOp op;
|
||||
op.multiplier[0] = (data->white_value[0] != 0.0f) ? 1.0f / data->white_value[0] : FLT_MAX;
|
||||
op.multiplier[1] = (data->white_value[1] != 0.0f) ? 1.0f / data->white_value[1] : FLT_MAX;
|
||||
op.multiplier[2] = (data->white_value[2] != 0.0f) ? 1.0f / data->white_value[2] : FLT_MAX;
|
||||
apply_modifier_op(op, ibuf, mask);
|
||||
}
|
||||
|
||||
static void whiteBalance_panel_draw(const bContext *C, Panel *panel)
|
||||
{
|
||||
uiLayout *layout = panel->layout;
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
|
||||
layout->prop(ptr, "white_value", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
|
||||
if (uiLayout *mask_input_layout = layout->panel_prop(
|
||||
C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
|
||||
{
|
||||
draw_mask_input_type_settings(C, mask_input_layout, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
static void whiteBalance_register(ARegionType *region_type)
|
||||
{
|
||||
modifier_panel_register(region_type, eSeqModifierType_WhiteBalance, whiteBalance_panel_draw);
|
||||
}
|
||||
|
||||
StripModifierTypeInfo seqModifierType_WhiteBalance = {
|
||||
/*idname*/ "WhiteBalance",
|
||||
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "White Balance"),
|
||||
/*struct_name*/ "WhiteBalanceModifierData",
|
||||
/*struct_size*/ sizeof(WhiteBalanceModifierData),
|
||||
/*init_data*/ whiteBalance_init_data,
|
||||
/*free_data*/ nullptr,
|
||||
/*copy_data*/ nullptr,
|
||||
/*apply*/ whiteBalance_apply,
|
||||
/*panel_register*/ whiteBalance_register,
|
||||
};
|
||||
|
||||
}; // namespace blender::seq
|
||||
683
source/blender/sequencer/intern/modifiers/modifier.cc
Normal file
683
source/blender/sequencer/intern/modifiers/modifier.cc
Normal file
@@ -0,0 +1,683 @@
|
||||
/* SPDX-FileCopyrightText: 2012-2024 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_string_utf8.h"
|
||||
#include "BLI_string_utils.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "DNA_mask_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_screen.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_prototypes.hh"
|
||||
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_modifiertypes.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_select.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "UI_interface.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "BLO_read_write.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
|
||||
#include "modifier.hh"
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
static bool modifier_has_persistent_uid(const Strip &strip, int uid)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) {
|
||||
if (smd->persistent_uid == uid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd)
|
||||
{
|
||||
uint64_t hash = blender::get_default_hash(blender::StringRef(smd.name));
|
||||
blender::RandomNumberGenerator rng{uint32_t(hash)};
|
||||
while (true) {
|
||||
const int new_uid = rng.get_int32();
|
||||
if (new_uid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (modifier_has_persistent_uid(strip, new_uid)) {
|
||||
continue;
|
||||
}
|
||||
smd.persistent_uid = new_uid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip)
|
||||
{
|
||||
Set<int> uids;
|
||||
int modifiers_num = 0;
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) {
|
||||
if (smd->persistent_uid <= 0) {
|
||||
return false;
|
||||
}
|
||||
uids.add(smd->persistent_uid);
|
||||
modifiers_num++;
|
||||
}
|
||||
if (uids.size() != modifiers_num) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void modifier_panel_header(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
uiLayout *row, *sub, *name_row;
|
||||
uiLayout *layout = panel->layout;
|
||||
|
||||
/* Don't use #modifier_panel_get_property_pointers, we don't want to lock the header. */
|
||||
PointerRNA *ptr = UI_panel_custom_data_get(panel);
|
||||
StripModifierData *smd = reinterpret_cast<StripModifierData *>(ptr->data);
|
||||
|
||||
UI_panel_context_pointer_set(panel, "modifier", ptr);
|
||||
|
||||
/* Modifier Icon. */
|
||||
sub = &layout->row(true);
|
||||
sub->emboss_set(blender::ui::EmbossType::None);
|
||||
PointerRNA active_op_ptr = sub->op(
|
||||
"SEQUENCER_OT_strip_modifier_set_active", "", RNA_struct_ui_icon(ptr->type));
|
||||
RNA_string_set(&active_op_ptr, "modifier", smd->name);
|
||||
|
||||
row = &layout->row(true);
|
||||
|
||||
/* Modifier Name.
|
||||
* Count how many buttons are added to the header to check if there is enough space. */
|
||||
int buttons_number = 0;
|
||||
name_row = &row->row(true);
|
||||
|
||||
sub = &row->row(true);
|
||||
sub->prop(ptr, "enable", UI_ITEM_NONE, "", ICON_NONE);
|
||||
buttons_number++;
|
||||
|
||||
/* Delete button. */
|
||||
sub = &row->row(false);
|
||||
sub->emboss_set(blender::ui::EmbossType::None);
|
||||
PointerRNA remove_op_ptr = sub->op("SEQUENCER_OT_strip_modifier_remove", "", ICON_X);
|
||||
RNA_string_set(&remove_op_ptr, "name", smd->name);
|
||||
buttons_number++;
|
||||
|
||||
bool display_name = (panel->sizex / UI_UNIT_X - buttons_number > 5) || (panel->sizex == 0);
|
||||
if (display_name) {
|
||||
name_row->prop(ptr, "name", UI_ITEM_NONE, "", ICON_NONE);
|
||||
}
|
||||
else {
|
||||
row->alignment_set(blender::ui::LayoutAlign::Right);
|
||||
}
|
||||
|
||||
/* Extra padding for delete button. */
|
||||
layout->separator();
|
||||
}
|
||||
|
||||
void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
Editing *ed = seq::editing_get(sequencer_scene);
|
||||
uiLayout *row, *col;
|
||||
|
||||
const int input_mask_type = RNA_enum_get(ptr, "input_mask_type");
|
||||
|
||||
layout->use_property_split_set(true);
|
||||
|
||||
col = &layout->column(false);
|
||||
row = &col->row(true);
|
||||
row->prop(ptr, "input_mask_type", UI_ITEM_R_EXPAND, "Type", ICON_NONE);
|
||||
|
||||
if (input_mask_type == STRIP_MASK_INPUT_STRIP) {
|
||||
MetaStack *ms = meta_stack_active_get(ed);
|
||||
PointerRNA sequences_object;
|
||||
if (ms) {
|
||||
sequences_object = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_MetaStrip, ms);
|
||||
}
|
||||
else {
|
||||
sequences_object = RNA_pointer_create_discrete(
|
||||
&sequencer_scene->id, &RNA_SequenceEditor, ed);
|
||||
}
|
||||
col->prop_search(ptr, "input_mask_strip", &sequences_object, "strips", "Mask", ICON_NONE);
|
||||
}
|
||||
else {
|
||||
col->prop(ptr, "input_mask_id", UI_ITEM_NONE, std::nullopt, ICON_NONE);
|
||||
row = &col->row(true);
|
||||
row->prop(ptr, "mask_time", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
bool modifier_ui_poll(const bContext *C, PanelType * /*pt*/)
|
||||
{
|
||||
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
|
||||
if (!sequencer_scene) {
|
||||
return false;
|
||||
}
|
||||
Strip *active_strip = seq::select_active_get(sequencer_scene);
|
||||
return active_strip != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a modifier to the index it's moved to after a drag and drop.
|
||||
*/
|
||||
static void modifier_reorder(bContext *C, Panel *panel, const int new_index)
|
||||
{
|
||||
PointerRNA *smd_ptr = UI_panel_custom_data_get(panel);
|
||||
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_ptr->data);
|
||||
|
||||
PointerRNA props_ptr;
|
||||
wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_strip_modifier_move_to_index", false);
|
||||
WM_operator_properties_create_ptr(&props_ptr, ot);
|
||||
RNA_string_set(&props_ptr, "modifier", smd->name);
|
||||
RNA_int_set(&props_ptr, "index", new_index);
|
||||
WM_operator_name_call_ptr(C, ot, blender::wm::OpCallContext::InvokeDefault, &props_ptr, nullptr);
|
||||
WM_operator_properties_free(&props_ptr);
|
||||
}
|
||||
|
||||
static short get_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel)
|
||||
{
|
||||
PointerRNA *smd_ptr = UI_panel_custom_data_get(panel);
|
||||
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_ptr->data);
|
||||
return smd->layout_panel_open_flag;
|
||||
}
|
||||
|
||||
static void set_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel, short expand_flag)
|
||||
{
|
||||
PointerRNA *smd_ptr = UI_panel_custom_data_get(panel);
|
||||
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_ptr->data);
|
||||
smd->layout_panel_open_flag = expand_flag;
|
||||
}
|
||||
|
||||
PanelType *modifier_panel_register(ARegionType *region_type,
|
||||
const eStripModifierType type,
|
||||
PanelDrawFn draw)
|
||||
{
|
||||
PanelType *panel_type = MEM_callocN<PanelType>(__func__);
|
||||
|
||||
modifier_type_panel_id(type, panel_type->idname);
|
||||
STRNCPY_UTF8(panel_type->label, "");
|
||||
STRNCPY_UTF8(panel_type->category, "Modifiers");
|
||||
STRNCPY_UTF8(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
|
||||
STRNCPY_UTF8(panel_type->active_property, "is_active");
|
||||
|
||||
panel_type->draw_header = modifier_panel_header;
|
||||
panel_type->draw = draw;
|
||||
panel_type->poll = modifier_ui_poll;
|
||||
|
||||
/* Give the panel the special flag that says it was built here and corresponds to a
|
||||
* modifier rather than a #PanelType. */
|
||||
panel_type->flag = PANEL_TYPE_HEADER_EXPAND | PANEL_TYPE_INSTANCED;
|
||||
panel_type->reorder = modifier_reorder;
|
||||
panel_type->get_list_data_expand_flag = get_strip_modifier_expand_flag;
|
||||
panel_type->set_list_data_expand_flag = set_strip_modifier_expand_flag;
|
||||
|
||||
BLI_addtail(®ion_type->paneltypes, panel_type);
|
||||
|
||||
return panel_type;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
float4 load_pixel_premul(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
straight_uchar_to_premul_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
float4 load_pixel_premul(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
void store_pixel_premul(float4 pix, uchar *ptr)
|
||||
{
|
||||
premul_float_to_straight_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
void store_pixel_premul(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
float4 load_pixel_raw(const uchar *ptr)
|
||||
{
|
||||
float4 res;
|
||||
rgba_uchar_to_float(res, ptr);
|
||||
return res;
|
||||
}
|
||||
|
||||
float4 load_pixel_raw(const float *ptr)
|
||||
{
|
||||
return float4(ptr);
|
||||
}
|
||||
|
||||
void store_pixel_raw(float4 pix, uchar *ptr)
|
||||
{
|
||||
rgba_float_to_uchar(ptr, pix);
|
||||
}
|
||||
|
||||
void store_pixel_raw(float4 pix, float *ptr)
|
||||
{
|
||||
*reinterpret_cast<float4 *>(ptr) = pix;
|
||||
}
|
||||
|
||||
/* Byte mask */
|
||||
void apply_and_advance_mask(float4 input, float4 &result, const uchar *&mask)
|
||||
{
|
||||
float3 m;
|
||||
rgb_uchar_to_float(m, mask);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
mask += 4;
|
||||
}
|
||||
|
||||
/* Float mask */
|
||||
void apply_and_advance_mask(float4 input, float4 &result, const float *&mask)
|
||||
{
|
||||
float3 m(mask);
|
||||
result.x = math::interpolate(input.x, result.x, m.x);
|
||||
result.y = math::interpolate(input.y, result.y, m.y);
|
||||
result.z = math::interpolate(input.z, result.z, m.z);
|
||||
mask += 4;
|
||||
}
|
||||
|
||||
/* No mask */
|
||||
void apply_and_advance_mask(float4 /*input*/, float4 & /*result*/, const void *& /*mask*/) {}
|
||||
|
||||
/**
|
||||
* \a timeline_frame is offset by \a fra_offset only in case we are using a real mask.
|
||||
*/
|
||||
static ImBuf *modifier_render_mask_input(const RenderData *context,
|
||||
int mask_input_type,
|
||||
Strip *mask_strip,
|
||||
Mask *mask_id,
|
||||
int timeline_frame,
|
||||
int fra_offset)
|
||||
{
|
||||
ImBuf *mask_input = nullptr;
|
||||
|
||||
if (mask_input_type == STRIP_MASK_INPUT_STRIP) {
|
||||
if (mask_strip) {
|
||||
SeqRenderState state;
|
||||
mask_input = seq_render_strip(context, &state, mask_strip, timeline_frame);
|
||||
}
|
||||
}
|
||||
else if (mask_input_type == STRIP_MASK_INPUT_ID) {
|
||||
/* Note that we do not request mask to be float image: if it is that is
|
||||
* fine, but if it is a byte image then we also just take that without
|
||||
* extra memory allocations or conversions. All modifiers are expected
|
||||
* to handle mask being either type. */
|
||||
mask_input = seq_render_mask(context, mask_id, timeline_frame - fra_offset, false);
|
||||
}
|
||||
|
||||
return mask_input;
|
||||
}
|
||||
|
||||
static ImBuf *modifier_mask_get(StripModifierData *smd,
|
||||
const RenderData *context,
|
||||
int timeline_frame,
|
||||
int fra_offset)
|
||||
{
|
||||
return modifier_render_mask_input(
|
||||
context, smd->mask_input_type, smd->mask_strip, smd->mask_id, timeline_frame, fra_offset);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Public Modifier Functions
|
||||
* \{ */
|
||||
|
||||
static StripModifierTypeInfo *modifiersTypes[NUM_STRIP_MODIFIER_TYPES] = {nullptr};
|
||||
|
||||
static void modifier_types_init(StripModifierTypeInfo *types[])
|
||||
{
|
||||
#define INIT_TYPE(typeName) (types[eSeqModifierType_##typeName] = &seqModifierType_##typeName)
|
||||
INIT_TYPE(None);
|
||||
INIT_TYPE(BrightContrast);
|
||||
INIT_TYPE(ColorBalance);
|
||||
INIT_TYPE(Curves);
|
||||
INIT_TYPE(HueCorrect);
|
||||
INIT_TYPE(Mask);
|
||||
INIT_TYPE(SoundEqualizer);
|
||||
INIT_TYPE(Tonemap);
|
||||
INIT_TYPE(WhiteBalance);
|
||||
#undef INIT_TYPE
|
||||
}
|
||||
|
||||
void modifiers_init()
|
||||
{
|
||||
modifier_types_init(modifiersTypes);
|
||||
}
|
||||
|
||||
const StripModifierTypeInfo *modifier_type_info_get(int type)
|
||||
{
|
||||
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
|
||||
return nullptr;
|
||||
}
|
||||
return modifiersTypes[type];
|
||||
}
|
||||
|
||||
StripModifierData *modifier_new(Strip *strip, const char *name, int type)
|
||||
{
|
||||
StripModifierData *smd;
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
|
||||
|
||||
smd = static_cast<StripModifierData *>(MEM_callocN(smti->struct_size, "sequence modifier"));
|
||||
|
||||
smd->type = type;
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED;
|
||||
smd->layout_panel_open_flag |= UI_PANEL_DATA_EXPAND_ROOT;
|
||||
|
||||
if (!name || !name[0]) {
|
||||
STRNCPY_UTF8(smd->name, CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name));
|
||||
}
|
||||
else {
|
||||
STRNCPY_UTF8(smd->name, name);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip->modifiers, smd);
|
||||
|
||||
modifier_unique_name(strip, smd);
|
||||
|
||||
if (smti->init_data) {
|
||||
smti->init_data(smd);
|
||||
}
|
||||
|
||||
modifier_set_active(strip, smd);
|
||||
|
||||
return smd;
|
||||
}
|
||||
|
||||
bool modifier_remove(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
if (BLI_findindex(&strip->modifiers, smd) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BLI_remlink(&strip->modifiers, smd);
|
||||
modifier_free(smd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void modifier_clear(Strip *strip)
|
||||
{
|
||||
StripModifierData *smd, *smd_next;
|
||||
|
||||
for (smd = static_cast<StripModifierData *>(strip->modifiers.first); smd; smd = smd_next) {
|
||||
smd_next = smd->next;
|
||||
modifier_free(smd);
|
||||
}
|
||||
|
||||
BLI_listbase_clear(&strip->modifiers);
|
||||
}
|
||||
|
||||
void modifier_free(StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
if (smti && smti->free_data) {
|
||||
smti->free_data(smd);
|
||||
}
|
||||
|
||||
MEM_freeN(smd);
|
||||
}
|
||||
|
||||
void modifier_unique_name(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
BLI_uniquename(&strip->modifiers,
|
||||
smd,
|
||||
CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name),
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(smd->name));
|
||||
}
|
||||
|
||||
StripModifierData *modifier_find_by_name(Strip *strip, const char *name)
|
||||
{
|
||||
return static_cast<StripModifierData *>(
|
||||
BLI_findstring(&(strip->modifiers), name, offsetof(StripModifierData, name)));
|
||||
}
|
||||
|
||||
static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeline_frame)
|
||||
{
|
||||
using namespace blender::seq;
|
||||
|
||||
if (smd->mask_strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const bool strip_has_ended_skip = smd->mask_input_type == STRIP_MASK_INPUT_STRIP &&
|
||||
smd->mask_time == STRIP_MASK_TIME_RELATIVE &&
|
||||
!time_strip_intersects_frame(
|
||||
scene, smd->mask_strip, timeline_frame);
|
||||
const bool missing_data_skip = !strip_has_valid_data(smd->mask_strip) ||
|
||||
media_presence_is_missing(scene, smd->mask_strip);
|
||||
|
||||
return strip_has_ended_skip || missing_data_skip;
|
||||
}
|
||||
|
||||
void modifier_apply_stack(const RenderData *context,
|
||||
const Strip *strip,
|
||||
ImBuf *ibuf,
|
||||
int timeline_frame)
|
||||
{
|
||||
const StripScreenQuad quad = get_strip_screen_quad(context, strip);
|
||||
|
||||
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
|
||||
render_imbuf_from_sequencer_space(context->scene, ibuf);
|
||||
}
|
||||
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
/* could happen if modifier is being removed or not exists in current version of blender */
|
||||
if (!smti) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* modifier is muted, do nothing */
|
||||
if (smd->flag & STRIP_MODIFIER_FLAG_MUTE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (smti->apply && !skip_modifier(context->scene, smd, timeline_frame)) {
|
||||
int frame_offset;
|
||||
if (smd->mask_time == STRIP_MASK_TIME_RELATIVE) {
|
||||
frame_offset = strip->start;
|
||||
}
|
||||
else /* if (smd->mask_time == STRIP_MASK_TIME_ABSOLUTE) */ {
|
||||
frame_offset = smd->mask_id ? ((Mask *)smd->mask_id)->sfra : 0;
|
||||
}
|
||||
|
||||
ImBuf *mask = modifier_mask_get(smd, context, timeline_frame, frame_offset);
|
||||
smti->apply(quad, smd, ibuf, mask);
|
||||
if (mask) {
|
||||
IMB_freeImBuf(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
|
||||
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
|
||||
}
|
||||
}
|
||||
|
||||
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src)
|
||||
{
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
|
||||
StripModifierData *mod_new = static_cast<StripModifierData *>(MEM_dupallocN(mod_src));
|
||||
|
||||
if (smti && smti->copy_data) {
|
||||
smti->copy_data(mod_new, mod_src);
|
||||
}
|
||||
|
||||
BLI_addtail(&strip_dst.modifiers, mod_new);
|
||||
BLI_uniquename(&strip_dst.modifiers,
|
||||
mod_new,
|
||||
"Strip Modifier",
|
||||
'.',
|
||||
offsetof(StripModifierData, name),
|
||||
sizeof(StripModifierData::name));
|
||||
return mod_new;
|
||||
}
|
||||
|
||||
void modifier_list_copy(Strip *strip_new, Strip *strip)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
modifier_copy(*strip_new, smd);
|
||||
}
|
||||
}
|
||||
|
||||
int sequence_supports_modifiers(Strip *strip)
|
||||
{
|
||||
return (strip->type != STRIP_TYPE_SOUND_RAM);
|
||||
}
|
||||
|
||||
bool modifier_move_to_index(Strip *strip, StripModifierData *smd, const int new_index)
|
||||
{
|
||||
const int current_index = BLI_findindex(&strip->modifiers, smd);
|
||||
return BLI_listbase_move_index(&strip->modifiers, current_index, new_index);
|
||||
}
|
||||
|
||||
StripModifierData *modifier_get_active(const Strip *strip)
|
||||
{
|
||||
/* In debug mode, check for only one active modifier. */
|
||||
#ifndef NDEBUG
|
||||
int active_count = 0;
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
active_count++;
|
||||
}
|
||||
}
|
||||
BLI_assert(ELEM(active_count, 0, 1));
|
||||
#endif
|
||||
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
|
||||
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
|
||||
return smd;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void modifier_set_active(Strip *strip, StripModifierData *smd)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd_iter, &strip->modifiers) {
|
||||
smd_iter->flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
|
||||
if (smd != nullptr) {
|
||||
BLI_assert(BLI_findindex(&strip->modifiers, smd) != -1);
|
||||
smd->flag |= STRIP_MODIFIER_FLAG_ACTIVE;
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_type_panel_id(eStripModifierType type, char *r_idname)
|
||||
{
|
||||
const StripModifierTypeInfo *mti = modifier_type_info_get(type);
|
||||
BLI_string_join(
|
||||
r_idname, sizeof(PanelType::idname), STRIP_MODIFIER_TYPE_PANEL_PREFIX, mti->idname);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name .blend File I/O
|
||||
* \{ */
|
||||
|
||||
void modifier_blend_write(BlendWriter *writer, ListBase *modbase)
|
||||
{
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, modbase) {
|
||||
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
|
||||
|
||||
if (smti) {
|
||||
BLO_write_struct_by_name(writer, smti->struct_name, smd);
|
||||
|
||||
if (smd->type == eSeqModifierType_Curves) {
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == eSeqModifierType_HueCorrect) {
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_write(writer, &hcmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == eSeqModifierType_SoundEqualizer) {
|
||||
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
|
||||
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
|
||||
BLO_write_struct_by_name(writer, "EQCurveMappingData", eqcmd);
|
||||
BKE_curvemapping_blend_write(writer, &eqcmd->curve_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
BLO_write_struct(writer, StripModifierData, smd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
|
||||
{
|
||||
BLO_read_struct_list(reader, StripModifierData, lb);
|
||||
|
||||
LISTBASE_FOREACH (StripModifierData *, smd, lb) {
|
||||
if (smd->mask_strip) {
|
||||
BLO_read_struct(reader, Strip, &smd->mask_strip);
|
||||
}
|
||||
|
||||
if (smd->type == eSeqModifierType_Curves) {
|
||||
CurvesModifierData *cmd = (CurvesModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == eSeqModifierType_HueCorrect) {
|
||||
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
|
||||
|
||||
BKE_curvemapping_blend_read(reader, &hcmd->curve_mapping);
|
||||
}
|
||||
else if (smd->type == eSeqModifierType_SoundEqualizer) {
|
||||
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
|
||||
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
|
||||
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
|
||||
BKE_curvemapping_blend_read(reader, &eqcmd->curve_mapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
} // namespace blender::seq
|
||||
105
source/blender/sequencer/intern/modifiers/modifier.hh
Normal file
105
source/blender/sequencer/intern/modifiers/modifier.hh
Normal file
@@ -0,0 +1,105 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
struct bContext;
|
||||
struct ARegionType;
|
||||
struct Strip;
|
||||
struct uiLayout;
|
||||
struct Panel;
|
||||
struct PanelType;
|
||||
struct PointerRNA;
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
bool modifier_persistent_uids_are_valid(const Strip &strip);
|
||||
|
||||
void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr);
|
||||
|
||||
bool modifier_ui_poll(const bContext *C, PanelType *pt);
|
||||
|
||||
using PanelDrawFn = void (*)(const bContext *, Panel *);
|
||||
|
||||
PanelType *modifier_panel_register(ARegionType *region_type,
|
||||
const eStripModifierType type,
|
||||
PanelDrawFn draw);
|
||||
|
||||
float4 load_pixel_premul(const uchar *ptr);
|
||||
float4 load_pixel_premul(const float *ptr);
|
||||
void store_pixel_premul(const float4 pix, uchar *ptr);
|
||||
void store_pixel_premul(const float4 pix, float *ptr);
|
||||
float4 load_pixel_raw(const uchar *ptr);
|
||||
float4 load_pixel_raw(const float *ptr);
|
||||
void store_pixel_raw(const float4 pix, uchar *ptr);
|
||||
void store_pixel_raw(const float4 pix, float *ptr);
|
||||
void apply_and_advance_mask(const float4 input, float4 &result, const uchar *&mask);
|
||||
void apply_and_advance_mask(const float4 input, float4 &result, const float *&mask);
|
||||
void apply_and_advance_mask(const float4 input, float4 &result, const void *&mask);
|
||||
|
||||
/* Given `T` that implements an `apply` function:
|
||||
*
|
||||
* template <typename ImageT, typename MaskT>
|
||||
* void apply(ImageT* image, const MaskT* mask, IndexRange size);
|
||||
*
|
||||
* this function calls the apply() function in parallel
|
||||
* chunks of the image to process, and with needed
|
||||
* uchar, float or void types (void is used for mask, when there is
|
||||
* no masking). Both input and mask images are expected to have
|
||||
* 4 (RGBA) color channels. Input is modified. */
|
||||
template<typename T> void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask)
|
||||
{
|
||||
if (ibuf == nullptr) {
|
||||
return;
|
||||
}
|
||||
BLI_assert_msg(ibuf->channels == 0 || ibuf->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
BLI_assert_msg(mask == nullptr || mask->channels == 0 || mask->channels == 4,
|
||||
"Sequencer only supports 4 channel images");
|
||||
|
||||
threading::parallel_for(IndexRange(size_t(ibuf->x) * ibuf->y), 32 * 1024, [&](IndexRange range) {
|
||||
uchar *image_byte = ibuf->byte_buffer.data;
|
||||
float *image_float = ibuf->float_buffer.data;
|
||||
const uchar *mask_byte = mask ? mask->byte_buffer.data : nullptr;
|
||||
const float *mask_float = mask ? mask->float_buffer.data : nullptr;
|
||||
const void *mask_none = nullptr;
|
||||
int64_t offset = range.first() * 4;
|
||||
|
||||
/* Instantiate the needed processing function based on image/mask
|
||||
* data types. */
|
||||
if (image_byte) {
|
||||
if (mask_byte) {
|
||||
op.apply(image_byte + offset, mask_byte + offset, range);
|
||||
}
|
||||
else if (mask_float) {
|
||||
op.apply(image_byte + offset, mask_float + offset, range);
|
||||
}
|
||||
else {
|
||||
op.apply(image_byte + offset, mask_none, range);
|
||||
}
|
||||
}
|
||||
else if (image_float) {
|
||||
if (mask_byte) {
|
||||
op.apply(image_float + offset, mask_byte + offset, range);
|
||||
}
|
||||
else if (mask_float) {
|
||||
op.apply(image_float + offset, mask_float + offset, range);
|
||||
}
|
||||
else {
|
||||
op.apply(image_float + offset, mask_none, range);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user