Files
test/source/blender/sequencer/intern/modifiers/modifier.cc
Falk David 866fcd0a09 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
2025-09-04 15:01:57 +02:00

684 lines
20 KiB
C++

/* SPDX-FileCopyrightText: 2012-2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BLI_array.hh"
#include "BLI_hash.hh"
#include "BLI_listbase.h"
#include "BLI_rand.hh"
#include "BLI_set.hh"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "BLI_task.hh"
#include "BLT_translation.hh"
#include "DNA_mask_types.h"
#include "DNA_sequence_types.h"
#include "BKE_colortools.hh"
#include "BKE_screen.hh"
#include "IMB_colormanagement.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "SEQ_modifier.hh"
#include "SEQ_modifiertypes.hh"
#include "SEQ_render.hh"
#include "SEQ_select.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_time.hh"
#include "SEQ_utils.hh"
#include "UI_interface.hh"
#include "UI_interface_layout.hh"
#include "BLO_read_write.hh"
#include "WM_api.hh"
#include "modifier.hh"
#include "render.hh"
namespace blender::seq {
/* -------------------------------------------------------------------- */
static bool modifier_has_persistent_uid(const Strip &strip, int uid)
{
LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) {
if (smd->persistent_uid == uid) {
return true;
}
}
return false;
}
void modifier_persistent_uid_init(const Strip &strip, StripModifierData &smd)
{
uint64_t hash = blender::get_default_hash(blender::StringRef(smd.name));
blender::RandomNumberGenerator rng{uint32_t(hash)};
while (true) {
const int new_uid = rng.get_int32();
if (new_uid <= 0) {
continue;
}
if (modifier_has_persistent_uid(strip, new_uid)) {
continue;
}
smd.persistent_uid = new_uid;
break;
}
}
bool modifier_persistent_uids_are_valid(const Strip &strip)
{
Set<int> uids;
int modifiers_num = 0;
LISTBASE_FOREACH (StripModifierData *, smd, &strip.modifiers) {
if (smd->persistent_uid <= 0) {
return false;
}
uids.add(smd->persistent_uid);
modifiers_num++;
}
if (uids.size() != modifiers_num) {
return false;
}
return true;
}
static void modifier_panel_header(const bContext * /*C*/, Panel *panel)
{
uiLayout *row, *sub, *name_row;
uiLayout *layout = panel->layout;
/* Don't use #modifier_panel_get_property_pointers, we don't want to lock the header. */
PointerRNA *ptr = UI_panel_custom_data_get(panel);
StripModifierData *smd = reinterpret_cast<StripModifierData *>(ptr->data);
UI_panel_context_pointer_set(panel, "modifier", ptr);
/* Modifier Icon. */
sub = &layout->row(true);
sub->emboss_set(blender::ui::EmbossType::None);
PointerRNA active_op_ptr = sub->op(
"SEQUENCER_OT_strip_modifier_set_active", "", RNA_struct_ui_icon(ptr->type));
RNA_string_set(&active_op_ptr, "modifier", smd->name);
row = &layout->row(true);
/* Modifier Name.
* Count how many buttons are added to the header to check if there is enough space. */
int buttons_number = 0;
name_row = &row->row(true);
sub = &row->row(true);
sub->prop(ptr, "enable", UI_ITEM_NONE, "", ICON_NONE);
buttons_number++;
/* Delete button. */
sub = &row->row(false);
sub->emboss_set(blender::ui::EmbossType::None);
PointerRNA remove_op_ptr = sub->op("SEQUENCER_OT_strip_modifier_remove", "", ICON_X);
RNA_string_set(&remove_op_ptr, "name", smd->name);
buttons_number++;
bool display_name = (panel->sizex / UI_UNIT_X - buttons_number > 5) || (panel->sizex == 0);
if (display_name) {
name_row->prop(ptr, "name", UI_ITEM_NONE, "", ICON_NONE);
}
else {
row->alignment_set(blender::ui::LayoutAlign::Right);
}
/* Extra padding for delete button. */
layout->separator();
}
void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr)
{
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
Editing *ed = seq::editing_get(sequencer_scene);
uiLayout *row, *col;
const int input_mask_type = RNA_enum_get(ptr, "input_mask_type");
layout->use_property_split_set(true);
col = &layout->column(false);
row = &col->row(true);
row->prop(ptr, "input_mask_type", UI_ITEM_R_EXPAND, "Type", ICON_NONE);
if (input_mask_type == STRIP_MASK_INPUT_STRIP) {
MetaStack *ms = meta_stack_active_get(ed);
PointerRNA sequences_object;
if (ms) {
sequences_object = RNA_pointer_create_discrete(&sequencer_scene->id, &RNA_MetaStrip, ms);
}
else {
sequences_object = RNA_pointer_create_discrete(
&sequencer_scene->id, &RNA_SequenceEditor, ed);
}
col->prop_search(ptr, "input_mask_strip", &sequences_object, "strips", "Mask", ICON_NONE);
}
else {
col->prop(ptr, "input_mask_id", UI_ITEM_NONE, std::nullopt, ICON_NONE);
row = &col->row(true);
row->prop(ptr, "mask_time", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
}
bool modifier_ui_poll(const bContext *C, PanelType * /*pt*/)
{
Scene *sequencer_scene = CTX_data_sequencer_scene(C);
if (!sequencer_scene) {
return false;
}
Strip *active_strip = seq::select_active_get(sequencer_scene);
return active_strip != nullptr;
}
/**
* Move a modifier to the index it's moved to after a drag and drop.
*/
static void modifier_reorder(bContext *C, Panel *panel, const int new_index)
{
PointerRNA *smd_ptr = UI_panel_custom_data_get(panel);
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_ptr->data);
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find("SEQUENCER_OT_strip_modifier_move_to_index", false);
WM_operator_properties_create_ptr(&props_ptr, ot);
RNA_string_set(&props_ptr, "modifier", smd->name);
RNA_int_set(&props_ptr, "index", new_index);
WM_operator_name_call_ptr(C, ot, blender::wm::OpCallContext::InvokeDefault, &props_ptr, nullptr);
WM_operator_properties_free(&props_ptr);
}
static short get_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel)
{
PointerRNA *smd_ptr = UI_panel_custom_data_get(panel);
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_ptr->data);
return smd->layout_panel_open_flag;
}
static void set_strip_modifier_expand_flag(const bContext * /*C*/, Panel *panel, short expand_flag)
{
PointerRNA *smd_ptr = UI_panel_custom_data_get(panel);
StripModifierData *smd = reinterpret_cast<StripModifierData *>(smd_ptr->data);
smd->layout_panel_open_flag = expand_flag;
}
PanelType *modifier_panel_register(ARegionType *region_type,
const eStripModifierType type,
PanelDrawFn draw)
{
PanelType *panel_type = MEM_callocN<PanelType>(__func__);
modifier_type_panel_id(type, panel_type->idname);
STRNCPY_UTF8(panel_type->label, "");
STRNCPY_UTF8(panel_type->category, "Modifiers");
STRNCPY_UTF8(panel_type->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
STRNCPY_UTF8(panel_type->active_property, "is_active");
panel_type->draw_header = modifier_panel_header;
panel_type->draw = draw;
panel_type->poll = modifier_ui_poll;
/* Give the panel the special flag that says it was built here and corresponds to a
* modifier rather than a #PanelType. */
panel_type->flag = PANEL_TYPE_HEADER_EXPAND | PANEL_TYPE_INSTANCED;
panel_type->reorder = modifier_reorder;
panel_type->get_list_data_expand_flag = get_strip_modifier_expand_flag;
panel_type->set_list_data_expand_flag = set_strip_modifier_expand_flag;
BLI_addtail(&region_type->paneltypes, panel_type);
return panel_type;
}
/* -------------------------------------------------------------------- */
float4 load_pixel_premul(const uchar *ptr)
{
float4 res;
straight_uchar_to_premul_float(res, ptr);
return res;
}
float4 load_pixel_premul(const float *ptr)
{
return float4(ptr);
}
void store_pixel_premul(float4 pix, uchar *ptr)
{
premul_float_to_straight_uchar(ptr, pix);
}
void store_pixel_premul(float4 pix, float *ptr)
{
*reinterpret_cast<float4 *>(ptr) = pix;
}
float4 load_pixel_raw(const uchar *ptr)
{
float4 res;
rgba_uchar_to_float(res, ptr);
return res;
}
float4 load_pixel_raw(const float *ptr)
{
return float4(ptr);
}
void store_pixel_raw(float4 pix, uchar *ptr)
{
rgba_float_to_uchar(ptr, pix);
}
void store_pixel_raw(float4 pix, float *ptr)
{
*reinterpret_cast<float4 *>(ptr) = pix;
}
/* Byte mask */
void apply_and_advance_mask(float4 input, float4 &result, const uchar *&mask)
{
float3 m;
rgb_uchar_to_float(m, mask);
result.x = math::interpolate(input.x, result.x, m.x);
result.y = math::interpolate(input.y, result.y, m.y);
result.z = math::interpolate(input.z, result.z, m.z);
mask += 4;
}
/* Float mask */
void apply_and_advance_mask(float4 input, float4 &result, const float *&mask)
{
float3 m(mask);
result.x = math::interpolate(input.x, result.x, m.x);
result.y = math::interpolate(input.y, result.y, m.y);
result.z = math::interpolate(input.z, result.z, m.z);
mask += 4;
}
/* No mask */
void apply_and_advance_mask(float4 /*input*/, float4 & /*result*/, const void *& /*mask*/) {}
/**
* \a timeline_frame is offset by \a fra_offset only in case we are using a real mask.
*/
static ImBuf *modifier_render_mask_input(const RenderData *context,
int mask_input_type,
Strip *mask_strip,
Mask *mask_id,
int timeline_frame,
int fra_offset)
{
ImBuf *mask_input = nullptr;
if (mask_input_type == STRIP_MASK_INPUT_STRIP) {
if (mask_strip) {
SeqRenderState state;
mask_input = seq_render_strip(context, &state, mask_strip, timeline_frame);
}
}
else if (mask_input_type == STRIP_MASK_INPUT_ID) {
/* Note that we do not request mask to be float image: if it is that is
* fine, but if it is a byte image then we also just take that without
* extra memory allocations or conversions. All modifiers are expected
* to handle mask being either type. */
mask_input = seq_render_mask(context, mask_id, timeline_frame - fra_offset, false);
}
return mask_input;
}
static ImBuf *modifier_mask_get(StripModifierData *smd,
const RenderData *context,
int timeline_frame,
int fra_offset)
{
return modifier_render_mask_input(
context, smd->mask_input_type, smd->mask_strip, smd->mask_id, timeline_frame, fra_offset);
}
/* -------------------------------------------------------------------- */
/** \name Public Modifier Functions
* \{ */
static StripModifierTypeInfo *modifiersTypes[NUM_STRIP_MODIFIER_TYPES] = {nullptr};
static void modifier_types_init(StripModifierTypeInfo *types[])
{
#define INIT_TYPE(typeName) (types[eSeqModifierType_##typeName] = &seqModifierType_##typeName)
INIT_TYPE(None);
INIT_TYPE(BrightContrast);
INIT_TYPE(ColorBalance);
INIT_TYPE(Curves);
INIT_TYPE(HueCorrect);
INIT_TYPE(Mask);
INIT_TYPE(SoundEqualizer);
INIT_TYPE(Tonemap);
INIT_TYPE(WhiteBalance);
#undef INIT_TYPE
}
void modifiers_init()
{
modifier_types_init(modifiersTypes);
}
const StripModifierTypeInfo *modifier_type_info_get(int type)
{
if (type <= 0 || type >= NUM_STRIP_MODIFIER_TYPES) {
return nullptr;
}
return modifiersTypes[type];
}
StripModifierData *modifier_new(Strip *strip, const char *name, int type)
{
StripModifierData *smd;
const StripModifierTypeInfo *smti = modifier_type_info_get(type);
smd = static_cast<StripModifierData *>(MEM_callocN(smti->struct_size, "sequence modifier"));
smd->type = type;
smd->flag |= STRIP_MODIFIER_FLAG_EXPANDED;
smd->layout_panel_open_flag |= UI_PANEL_DATA_EXPAND_ROOT;
if (!name || !name[0]) {
STRNCPY_UTF8(smd->name, CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name));
}
else {
STRNCPY_UTF8(smd->name, name);
}
BLI_addtail(&strip->modifiers, smd);
modifier_unique_name(strip, smd);
if (smti->init_data) {
smti->init_data(smd);
}
modifier_set_active(strip, smd);
return smd;
}
bool modifier_remove(Strip *strip, StripModifierData *smd)
{
if (BLI_findindex(&strip->modifiers, smd) == -1) {
return false;
}
BLI_remlink(&strip->modifiers, smd);
modifier_free(smd);
return true;
}
void modifier_clear(Strip *strip)
{
StripModifierData *smd, *smd_next;
for (smd = static_cast<StripModifierData *>(strip->modifiers.first); smd; smd = smd_next) {
smd_next = smd->next;
modifier_free(smd);
}
BLI_listbase_clear(&strip->modifiers);
}
void modifier_free(StripModifierData *smd)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
if (smti && smti->free_data) {
smti->free_data(smd);
}
MEM_freeN(smd);
}
void modifier_unique_name(Strip *strip, StripModifierData *smd)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
BLI_uniquename(&strip->modifiers,
smd,
CTX_DATA_(BLT_I18NCONTEXT_ID_SEQUENCE, smti->name),
'.',
offsetof(StripModifierData, name),
sizeof(smd->name));
}
StripModifierData *modifier_find_by_name(Strip *strip, const char *name)
{
return static_cast<StripModifierData *>(
BLI_findstring(&(strip->modifiers), name, offsetof(StripModifierData, name)));
}
static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeline_frame)
{
using namespace blender::seq;
if (smd->mask_strip == nullptr) {
return false;
}
const bool strip_has_ended_skip = smd->mask_input_type == STRIP_MASK_INPUT_STRIP &&
smd->mask_time == STRIP_MASK_TIME_RELATIVE &&
!time_strip_intersects_frame(
scene, smd->mask_strip, timeline_frame);
const bool missing_data_skip = !strip_has_valid_data(smd->mask_strip) ||
media_presence_is_missing(scene, smd->mask_strip);
return strip_has_ended_skip || missing_data_skip;
}
void modifier_apply_stack(const RenderData *context,
const Strip *strip,
ImBuf *ibuf,
int timeline_frame)
{
const StripScreenQuad quad = get_strip_screen_quad(context, strip);
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
render_imbuf_from_sequencer_space(context->scene, ibuf);
}
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
/* could happen if modifier is being removed or not exists in current version of blender */
if (!smti) {
continue;
}
/* modifier is muted, do nothing */
if (smd->flag & STRIP_MODIFIER_FLAG_MUTE) {
continue;
}
if (smti->apply && !skip_modifier(context->scene, smd, timeline_frame)) {
int frame_offset;
if (smd->mask_time == STRIP_MASK_TIME_RELATIVE) {
frame_offset = strip->start;
}
else /* if (smd->mask_time == STRIP_MASK_TIME_ABSOLUTE) */ {
frame_offset = smd->mask_id ? ((Mask *)smd->mask_id)->sfra : 0;
}
ImBuf *mask = modifier_mask_get(smd, context, timeline_frame, frame_offset);
smti->apply(quad, smd, ibuf, mask);
if (mask) {
IMB_freeImBuf(mask);
}
}
}
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
seq_imbuf_to_sequencer_space(context->scene, ibuf, false);
}
}
StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src)
{
const StripModifierTypeInfo *smti = modifier_type_info_get(mod_src->type);
StripModifierData *mod_new = static_cast<StripModifierData *>(MEM_dupallocN(mod_src));
if (smti && smti->copy_data) {
smti->copy_data(mod_new, mod_src);
}
BLI_addtail(&strip_dst.modifiers, mod_new);
BLI_uniquename(&strip_dst.modifiers,
mod_new,
"Strip Modifier",
'.',
offsetof(StripModifierData, name),
sizeof(StripModifierData::name));
return mod_new;
}
void modifier_list_copy(Strip *strip_new, Strip *strip)
{
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
modifier_copy(*strip_new, smd);
}
}
int sequence_supports_modifiers(Strip *strip)
{
return (strip->type != STRIP_TYPE_SOUND_RAM);
}
bool modifier_move_to_index(Strip *strip, StripModifierData *smd, const int new_index)
{
const int current_index = BLI_findindex(&strip->modifiers, smd);
return BLI_listbase_move_index(&strip->modifiers, current_index, new_index);
}
StripModifierData *modifier_get_active(const Strip *strip)
{
/* In debug mode, check for only one active modifier. */
#ifndef NDEBUG
int active_count = 0;
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
active_count++;
}
}
BLI_assert(ELEM(active_count, 0, 1));
#endif
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (smd->flag & STRIP_MODIFIER_FLAG_ACTIVE) {
return smd;
}
}
return nullptr;
}
void modifier_set_active(Strip *strip, StripModifierData *smd)
{
LISTBASE_FOREACH (StripModifierData *, smd_iter, &strip->modifiers) {
smd_iter->flag &= ~STRIP_MODIFIER_FLAG_ACTIVE;
}
if (smd != nullptr) {
BLI_assert(BLI_findindex(&strip->modifiers, smd) != -1);
smd->flag |= STRIP_MODIFIER_FLAG_ACTIVE;
}
}
void modifier_type_panel_id(eStripModifierType type, char *r_idname)
{
const StripModifierTypeInfo *mti = modifier_type_info_get(type);
BLI_string_join(
r_idname, sizeof(PanelType::idname), STRIP_MODIFIER_TYPE_PANEL_PREFIX, mti->idname);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name .blend File I/O
* \{ */
void modifier_blend_write(BlendWriter *writer, ListBase *modbase)
{
LISTBASE_FOREACH (StripModifierData *, smd, modbase) {
const StripModifierTypeInfo *smti = modifier_type_info_get(smd->type);
if (smti) {
BLO_write_struct_by_name(writer, smti->struct_name, smd);
if (smd->type == eSeqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_blend_write(writer, &hcmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_SoundEqualizer) {
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
BLO_write_struct_by_name(writer, "EQCurveMappingData", eqcmd);
BKE_curvemapping_blend_write(writer, &eqcmd->curve_mapping);
}
}
}
else {
BLO_write_struct(writer, StripModifierData, smd);
}
}
}
void modifier_blend_read_data(BlendDataReader *reader, ListBase *lb)
{
BLO_read_struct_list(reader, StripModifierData, lb);
LISTBASE_FOREACH (StripModifierData *, smd, lb) {
if (smd->mask_strip) {
BLO_read_struct(reader, Strip, &smd->mask_strip);
}
if (smd->type == eSeqModifierType_Curves) {
CurvesModifierData *cmd = (CurvesModifierData *)smd;
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_HueCorrect) {
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
BKE_curvemapping_blend_read(reader, &hcmd->curve_mapping);
}
else if (smd->type == eSeqModifierType_SoundEqualizer) {
SoundEqualizerModifierData *semd = (SoundEqualizerModifierData *)smd;
BLO_read_struct_list(reader, EQCurveMappingData, &semd->graphics);
LISTBASE_FOREACH (EQCurveMappingData *, eqcmd, &semd->graphics) {
BKE_curvemapping_blend_read(reader, &eqcmd->curve_mapping);
}
}
}
}
/** \} */
} // namespace blender::seq