Files
test/source/blender/sequencer/intern/modifiers/MOD_curves.cc
Aras Pranckevicius 8a74f7c0b0 VSE: Execute modifiers in strip-local space (#145688)
Currently when a strip has a transform that does not fill the whole
render area, first the image of the strip is transformed, and then
any modifiers are applied on that. This is mostly in the new
Compositor modifier, where procedural textures, gradients, image
coordinates "stick to the screen" instead of following the transformed
strip.

This changes the behavior so that first the modifiers are applied
to the strip image, and then the strip is transformed. This is
potentially a visually breaking change:
- This can alter visual look of existing strip, especially if they are
  scaled. Previous behavior was first scale filtering, then modifier;
  now it is first modifier, then scale filtering.
- Most obvious change is Compositor modifier (which is new in 5.0).
- Compositor modifier can actually expand the input image (e.g. Blur
  node with "expand bounds" option set), and that works.
- Note that Masks continue to be applied in global/screen space. There
  can be small look differences with rotated/scaled strips that use
  masks, due to Mask application now needing to do filtered mask image
  lookups.
- If anyone needs previous behavior (modifier is applied on the
  "whole screen"), they can put transformed strip into a meta strip,
  and apply the modifier on the meta strip itself.

Compositor modifier examples with images in the PR.

Pull Request: https://projects.blender.org/blender/blender/pulls/146181
2025-10-07 13:51:41 +02:00

142 lines
4.0 KiB
C++

/* 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 MaskSampler>
void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range)
{
image += y_range.first() * image_x * 4;
for (int64_t y : y_range) {
mask.begin_row(y);
for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
float4 input = load_pixel_premul(image);
float4 result;
BKE_curvemapping_evaluate_premulRGBF(this->curve_mapping, result, input);
result.w = input.w;
mask.apply_mask(input, result);
store_pixel_premul(result, image);
image += 4;
}
}
}
};
static void curves_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
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, float3x3(transform));
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, 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 curves_register(ARegionType *region_type)
{
modifier_panel_register(region_type, eSeqModifierType_Curves, curves_panel_draw);
}
static void curves_write(BlendWriter *writer, const StripModifierData *smd)
{
const CurvesModifierData *cmd = reinterpret_cast<const CurvesModifierData *>(smd);
BKE_curvemapping_blend_write(writer, &cmd->curve_mapping);
}
static void curves_read(BlendDataReader *reader, StripModifierData *smd)
{
CurvesModifierData *cmd = reinterpret_cast<CurvesModifierData *>(smd);
BKE_curvemapping_blend_read(reader, &cmd->curve_mapping);
}
StripModifierTypeInfo seqModifierType_Curves = {
/*idname*/ "Curves",
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Curves"),
/*struct_name*/ "CurvesModifierData",
/*struct_size*/ sizeof(CurvesModifierData),
/*init_data*/ curves_init_data,
/*free_data*/ curves_free_data,
/*copy_data*/ curves_copy_data,
/*apply*/ curves_apply,
/*panel_register*/ curves_register,
/*blend_write*/ curves_write,
/*blend_read*/ curves_read,
};
}; // namespace blender::seq