Fix #147636: VSE compositor modifier output translation has no effect

The final image produced by the compositor can have domain translation
on it (e.g. caused by a Translate or Transform node). Similar to how
the regular compositor viewer node remembers the output domain
translation, do the same in the compositor modifier.

Bubble back that translation up to VSE rendering code, where it is
then added to regular strip transform.

In order to make this "bubble up" part easier, refactored modifiers
so that instead of soup of parameters they all get a struct
ModifierApplyContext with all the relevant data.

Added a new VSE render test that covers various compositor
transformation nodes (translate, rotate, transform, corner pin).

Pull Request: https://projects.blender.org/blender/blender/pulls/147695
This commit is contained in:
Aras Pranckevicius
2025-10-09 18:36:44 +02:00
committed by Aras Pranckevicius
parent e60c5475ca
commit 1973bad86c
14 changed files with 105 additions and 114 deletions

View File

@@ -23,9 +23,7 @@ struct ID;
namespace blender::seq {
struct SeqRenderState;
struct StripScreenQuad;
struct RenderData;
struct ModifierApplyContext;
struct StripModifierTypeInfo {
/**
@@ -56,12 +54,7 @@ struct StripModifierTypeInfo {
void (*copy_data)(StripModifierData *smd, StripModifierData *target);
/* Apply modifier on an image buffer. */
void (*apply)(const RenderData *render_data,
const Strip *strip,
const float transform[3][3],
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask);
void (*apply)(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask);
/** Register the panel types for the modifier's UI. */
void (*panel_register)(ARegionType *region_type);

View File

@@ -52,11 +52,8 @@ struct BrightContrastApplyOp {
}
};
static void brightcontrast_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
static void brightcontrast_apply(ModifierApplyContext &context,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const BrightContrastModifierData *bcmd = (BrightContrastModifierData *)smd;
@@ -81,7 +78,7 @@ static void brightcontrast_apply(const RenderData * /*render_data*/,
op.add = op.mul * brightness + delta;
}
apply_modifier_op(op, ibuf, mask, float3x3(transform));
apply_modifier_op(op, context.image, mask, context.transform);
}
static void brightcontrast_panel_draw(const bContext *C, Panel *panel)

View File

@@ -254,18 +254,13 @@ static void colorBalance_init_data(StripModifierData *smd)
}
}
static void colorBalance_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
static void colorBalance_apply(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask)
{
const ColorBalanceModifierData *cbmd = (const ColorBalanceModifierData *)smd;
ColorBalanceApplyOp op;
op.init(*cbmd, ibuf->byte_buffer.data != nullptr);
apply_modifier_op(op, ibuf, mask, float3x3(transform));
op.init(*cbmd, context.image->byte_buffer.data != nullptr);
apply_modifier_op(op, context.image, mask, context.transform);
}
static void colorBalance_panel_draw(const bContext *C, Panel *panel)

View File

@@ -44,13 +44,14 @@ class CompositorContext : public compositor::Context {
ImBuf *image_buffer_;
ImBuf *mask_buffer_;
float3x3 xform_;
float2 result_translation_ = float2(0, 0);
public:
CompositorContext(const RenderData &render_data,
const SequencerCompositorModifierData *modifier_data,
ImBuf *image_buffer,
ImBuf *mask_buffer,
const Strip *strip)
const Strip &strip)
: compositor::Context(),
render_data_(render_data),
modifier_data_(modifier_data),
@@ -61,10 +62,15 @@ class CompositorContext : public compositor::Context {
if (mask_buffer) {
/* Note: do not use passed transform matrix since compositor coordinate
* space is not from the image corner, but rather centered on the image. */
xform_ = math::invert(image_transform_matrix_get(render_data.scene, strip));
xform_ = math::invert(image_transform_matrix_get(render_data.scene, &strip));
}
}
float2 get_result_translation() const
{
return result_translation_;
}
const Scene &get_scene() const override
{
return *render_data_.scene;
@@ -101,6 +107,7 @@ class CompositorContext : public compositor::Context {
compositor::Result get_output(compositor::Domain domain) override
{
result_translation_ = domain.transformation.location();
compositor::Result result = this->create_result(compositor::ResultType::Color);
if (domain.size.x != image_buffer_->x || domain.size.y != image_buffer_->y) {
/* Output size is different (e.g. image is blurred with expanded bounds);
@@ -119,18 +126,8 @@ class CompositorContext : public compositor::Context {
bool /*is_data*/,
compositor::ResultPrecision /*precision*/) override
{
compositor::Result result = this->create_result(compositor::ResultType::Color);
if (domain.size.x != image_buffer_->x || domain.size.y != image_buffer_->y) {
/* Output size is different (e.g. image is blurred with expanded bounds);
* need to allocate appropriately sized buffer. */
IMB_free_all_data(image_buffer_);
image_buffer_->x = domain.size.x;
image_buffer_->y = domain.size.y;
IMB_alloc_float_pixels(image_buffer_, 4, false);
}
result.wrap_external(image_buffer_->float_buffer.data,
int2(image_buffer_->x, image_buffer_->y));
return result;
/* Within compositor modifier, output and viewer output function the same. */
return get_output(domain);
}
compositor::Result get_input(StringRef name) override
@@ -199,11 +196,8 @@ static bool ensure_linear_float_buffer(ImBuf *ibuf)
return false;
}
static void compositor_modifier_apply(const RenderData *render_data,
const Strip *strip,
const float /*transform*/[3][3],
static void compositor_modifier_apply(ModifierApplyContext &context,
StripModifierData *strip_modifier_data,
ImBuf *image_buffer,
ImBuf *mask)
{
const SequencerCompositorModifierData *modifier_data =
@@ -218,13 +212,16 @@ static void compositor_modifier_apply(const RenderData *render_data,
ensure_linear_float_buffer(linear_mask);
}
const bool was_float_linear = ensure_linear_float_buffer(image_buffer);
const bool was_byte = image_buffer->float_buffer.data == nullptr;
const bool was_float_linear = ensure_linear_float_buffer(context.image);
const bool was_byte = context.image->float_buffer.data == nullptr;
CompositorContext context(*render_data, modifier_data, image_buffer, linear_mask, strip);
compositor::Evaluator evaluator(context);
CompositorContext com_context(
context.render_data, modifier_data, context.image, linear_mask, context.strip);
compositor::Evaluator evaluator(com_context);
evaluator.evaluate();
context.result_translation += com_context.get_result_translation();
if (mask != linear_mask) {
IMB_freeImBuf(linear_mask);
}
@@ -234,11 +231,11 @@ static void compositor_modifier_apply(const RenderData *render_data,
}
if (was_byte) {
IMB_byte_from_float(image_buffer);
IMB_free_float_pixels(image_buffer);
IMB_byte_from_float(context.image);
IMB_free_float_pixels(context.image);
}
else {
seq_imbuf_to_sequencer_space(render_data->scene, image_buffer, true);
seq_imbuf_to_sequencer_space(context.render_data.scene, context.image, true);
}
}

View File

@@ -69,12 +69,7 @@ struct CurvesApplyOp {
}
};
static void curves_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
static void curves_apply(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask)
{
CurvesModifierData *cmd = (CurvesModifierData *)smd;
@@ -88,7 +83,7 @@ static void curves_apply(const RenderData * /*render_data*/,
CurvesApplyOp op;
op.curve_mapping = &cmd->curve_mapping;
apply_modifier_op(op, ibuf, mask, float3x3(transform));
apply_modifier_op(op, context.image, mask, context.transform);
BKE_curvemapping_premultiply(&cmd->curve_mapping, true);
}

View File

@@ -104,12 +104,7 @@ struct HueCorrectApplyOp {
}
};
static void hue_correct_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
static void hue_correct_apply(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask)
{
HueCorrectModifierData *hcmd = (HueCorrectModifierData *)smd;
@@ -117,7 +112,7 @@ static void hue_correct_apply(const RenderData * /*render_data*/,
HueCorrectApplyOp op;
op.curve_mapping = &hcmd->curve_mapping;
apply_modifier_op(op, ibuf, mask, float3x3(transform));
apply_modifier_op(op, context.image, mask, context.transform);
}
static void hue_correct_panel_draw(const bContext *C, Panel *panel)

View File

@@ -53,11 +53,8 @@ struct MaskApplyOp {
}
};
static void maskmodifier_apply(const RenderData * /* render_data */,
const Strip * /*strip*/,
const float transform[3][3],
static void maskmodifier_apply(ModifierApplyContext &context,
StripModifierData * /*smd*/,
ImBuf *ibuf,
ImBuf *mask)
{
if (mask == nullptr || (mask->byte_buffer.data == nullptr && mask->float_buffer.data == nullptr))
@@ -66,10 +63,10 @@ static void maskmodifier_apply(const RenderData * /* render_data */,
}
MaskApplyOp op;
apply_modifier_op(op, ibuf, mask, float3x3(transform));
apply_modifier_op(op, context.image, mask, context.transform);
/* Image has gained transparency. */
ibuf->planes = R_IMF_PLANES_RGBA;
context.image->planes = R_IMF_PLANES_RGBA;
}
static void maskmodifier_panel_draw(const bContext *C, Panel *panel)

View File

@@ -281,19 +281,16 @@ static AreaLuminance tonemap_calc_input_luminance(const ImBuf *ibuf)
return lum;
}
static void tonemapmodifier_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
static void tonemapmodifier_apply(ModifierApplyContext &context,
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
{
const SequencerTonemapModifierData *tmmd = (const SequencerTonemapModifierData *)smd;
TonemapApplyOp op;
op.type = eModTonemapType(tmmd->type);
op.ibuf = ibuf;
op.lum = tonemap_calc_input_luminance(ibuf);
op.ibuf = context.image;
op.lum = tonemap_calc_input_luminance(context.image);
if (op.lum.pixel_count == 0) {
return; /* Strip is zero size or off-screen. */
}
@@ -311,7 +308,7 @@ static void tonemapmodifier_apply(const RenderData * /*render_data*/,
op.data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al);
op.data.igm = (tmmd->gamma == 0.0f) ? 1.0f : (1.0f / tmmd->gamma);
apply_modifier_op(op, ibuf, mask, float3x3(transform));
apply_modifier_op(op, context.image, mask, context.transform);
}
static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel)

View File

@@ -62,12 +62,7 @@ struct WhiteBalanceApplyOp {
}
};
static void whiteBalance_apply(const RenderData * /*render_data*/,
const Strip * /*strip*/,
const float transform[3][3],
StripModifierData *smd,
ImBuf *ibuf,
ImBuf *mask)
static void whiteBalance_apply(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask)
{
const WhiteBalanceModifierData *data = (const WhiteBalanceModifierData *)smd;
@@ -75,7 +70,7 @@ static void whiteBalance_apply(const RenderData * /*render_data*/,
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, float3x3(transform));
apply_modifier_op(op, context.image, mask, context.transform);
}
static void whiteBalance_panel_draw(const bContext *C, Panel *panel)

View File

@@ -297,8 +297,8 @@ void store_pixel_raw(float4 pix, float *ptr)
/**
* \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,
SeqRenderState *state,
static ImBuf *modifier_render_mask_input(const RenderData &context,
SeqRenderState &state,
int mask_input_type,
Strip *mask_strip,
Mask *mask_id,
@@ -309,7 +309,7 @@ static ImBuf *modifier_render_mask_input(const RenderData *context,
if (mask_input_type == STRIP_MASK_INPUT_STRIP) {
if (mask_strip) {
mask_input = seq_render_strip(context, state, mask_strip, timeline_frame);
mask_input = seq_render_strip(&context, &state, mask_strip, timeline_frame);
}
}
else if (mask_input_type == STRIP_MASK_INPUT_ID) {
@@ -317,9 +317,9 @@ static ImBuf *modifier_render_mask_input(const RenderData *context,
* 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->depsgraph,
context->rectx,
context->recty,
mask_input = seq_render_mask(context.depsgraph,
context.rectx,
context.recty,
mask_id,
timeline_frame - fra_offset,
false);
@@ -464,18 +464,17 @@ static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeli
return strip_has_ended_skip || missing_data_skip;
}
void modifier_apply_stack(const RenderData *context,
SeqRenderState *state,
const Strip *strip,
const float3x3 &transform,
ImBuf *ibuf,
int timeline_frame)
void modifier_apply_stack(ModifierApplyContext &context, int timeline_frame)
{
if (strip->modifiers.first && (strip->flag & SEQ_USE_LINEAR_MODIFIERS)) {
render_imbuf_from_sequencer_space(context->scene, ibuf);
if (context.strip.modifiers.first == nullptr) {
return;
}
LISTBASE_FOREACH (StripModifierData *, smd, &strip->modifiers) {
if (context.strip.flag & SEQ_USE_LINEAR_MODIFIERS) {
render_imbuf_from_sequencer_space(context.render_data.scene, context.image);
}
LISTBASE_FOREACH (StripModifierData *, smd, &context.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 */
@@ -488,31 +487,31 @@ void modifier_apply_stack(const RenderData *context,
continue;
}
if (smti->apply && !skip_modifier(context->scene, smd, timeline_frame)) {
if (smti->apply && !skip_modifier(context.render_data.scene, smd, timeline_frame)) {
int frame_offset;
if (smd->mask_time == STRIP_MASK_TIME_RELATIVE) {
frame_offset = strip->start;
frame_offset = context.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_render_mask_input(context,
state,
ImBuf *mask = modifier_render_mask_input(context.render_data,
context.render_state,
smd->mask_input_type,
smd->mask_strip,
smd->mask_id,
timeline_frame,
frame_offset);
smti->apply(context, strip, transform.ptr(), smd, ibuf, mask);
smti->apply(context, smd, 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);
if (context.strip.flag & SEQ_USE_LINEAR_MODIFIERS) {
seq_imbuf_to_sequencer_space(context.render_data.scene, context.image, false);
}
}

View File

@@ -30,15 +30,36 @@ namespace blender::seq {
struct RenderData;
struct SeqRenderState;
/* `transform` is transformation from strip image local pixel coordinates
* to the full render area pixel coordinates. This is used to sample
* modifier masks (since masks are in full render area space). */
void modifier_apply_stack(const RenderData *context,
SeqRenderState *state,
const Strip *strip,
const float3x3 &transform,
ImBuf *ibuf,
int timeline_frame);
struct ModifierApplyContext {
ModifierApplyContext(const RenderData &render_data,
SeqRenderState &render_state,
const Strip &strip,
const float3x3 &transform,
ImBuf *image)
: render_data(render_data),
render_state(render_state),
strip(strip),
transform(transform),
image(image)
{
}
const RenderData &render_data;
SeqRenderState &render_state;
const Strip &strip;
/* Transformation from strip image local pixel coordinates to the
* full render area pixel coordinates.This is used to sample
* modifier masks (since masks are in full render area space). */
const float3x3 transform;
ImBuf *const image;
/* How much the resulting image should be translated, in pixels.
* Compositor modifier can have some nodes that translate the output
* image. */
float2 result_translation = float2(0, 0);
};
void modifier_apply_stack(ModifierApplyContext &context, int timeline_frame);
bool modifier_persistent_uids_are_valid(const Strip &strip);

View File

@@ -666,6 +666,7 @@ static ImBuf *input_preprocess(const RenderData *context,
const bool do_scale_to_render_size = seq_need_scale_to_render_size(strip, is_proxy_image);
const float image_scale_factor = do_scale_to_render_size ? preview_scale_factor : 1.0f;
float2 modifier_translation = float2(0, 0);
if (strip->modifiers.first) {
ibuf = IMB_makeSingleUser(ibuf);
float3x3 matrix = calc_strip_transform_matrix(scene,
@@ -676,11 +677,13 @@ static ImBuf *input_preprocess(const RenderData *context,
context->recty,
image_scale_factor,
preview_scale_factor);
modifier_apply_stack(context, state, strip, matrix, ibuf, timeline_frame);
ModifierApplyContext mod_context(*context, *state, *strip, matrix, ibuf);
modifier_apply_stack(mod_context, timeline_frame);
modifier_translation = mod_context.result_translation;
}
if (sequencer_use_crop(strip) || sequencer_use_transform(strip) || context->rectx != ibuf->x ||
context->recty != ibuf->y)
context->recty != ibuf->y || modifier_translation != float2(0, 0))
{
const int x = context->rectx;
const int y = context->recty;
@@ -696,6 +699,7 @@ static ImBuf *input_preprocess(const RenderData *context,
context->recty,
image_scale_factor,
preview_scale_factor);
matrix *= math::from_location<float3x3>(modifier_translation);
matrix = math::invert(matrix);
sequencer_preprocess_transform_crop(ibuf,
transformed_ibuf,

Binary file not shown.

Binary file not shown.