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
283 lines
8.1 KiB
C++
283 lines
8.1 KiB
C++
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup sequencer
|
|
*/
|
|
|
|
#include "BLI_math_base.h"
|
|
#include "BLI_rect.h"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "COM_context.hh"
|
|
#include "COM_domain.hh"
|
|
#include "COM_evaluator.hh"
|
|
|
|
#include "DNA_sequence_types.h"
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "IMB_colormanagement.hh"
|
|
|
|
#include "SEQ_modifier.hh"
|
|
#include "SEQ_modifiertypes.hh"
|
|
#include "SEQ_render.hh"
|
|
#include "SEQ_transform.hh"
|
|
|
|
#include "UI_interface.hh"
|
|
#include "UI_interface_layout.hh"
|
|
|
|
#include "RNA_access.hh"
|
|
|
|
#include "modifier.hh"
|
|
#include "render.hh"
|
|
|
|
namespace blender::seq {
|
|
|
|
class CompositorContext : public compositor::Context {
|
|
private:
|
|
const RenderData &render_data_;
|
|
const SequencerCompositorModifierData *modifier_data_;
|
|
|
|
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)
|
|
: compositor::Context(),
|
|
render_data_(render_data),
|
|
modifier_data_(modifier_data),
|
|
image_buffer_(image_buffer),
|
|
mask_buffer_(mask_buffer),
|
|
xform_(float3x3::identity())
|
|
{
|
|
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));
|
|
}
|
|
}
|
|
|
|
float2 get_result_translation() const
|
|
{
|
|
return result_translation_;
|
|
}
|
|
|
|
const Scene &get_scene() const override
|
|
{
|
|
return *render_data_.scene;
|
|
}
|
|
|
|
const bNodeTree &get_node_tree() const override
|
|
{
|
|
return *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph, modifier_data_->node_group);
|
|
}
|
|
|
|
compositor::OutputTypes needed_outputs() const override
|
|
{
|
|
compositor::OutputTypes needed_outputs = compositor::OutputTypes::Composite;
|
|
if (!render_data_.for_render) {
|
|
needed_outputs |= compositor::OutputTypes::Viewer;
|
|
}
|
|
return needed_outputs;
|
|
}
|
|
|
|
bool treat_viewer_as_compositor_output() const override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool use_context_bounds_for_input_output() const override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Bounds<int2> get_compositing_region() const override
|
|
{
|
|
return Bounds<int2>(int2(0), int2(image_buffer_->x, image_buffer_->y));
|
|
}
|
|
|
|
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);
|
|
* 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;
|
|
}
|
|
|
|
compositor::Result get_viewer_output(compositor::Domain domain,
|
|
bool /*is_data*/,
|
|
compositor::ResultPrecision /*precision*/) override
|
|
{
|
|
/* Within compositor modifier, output and viewer output function the same. */
|
|
return get_output(domain);
|
|
}
|
|
|
|
compositor::Result get_input(StringRef name) override
|
|
{
|
|
compositor::Result result = this->create_result(compositor::ResultType::Color);
|
|
|
|
if (name == "Image") {
|
|
result.wrap_external(image_buffer_->float_buffer.data,
|
|
int2(image_buffer_->x, image_buffer_->y));
|
|
}
|
|
else if (name == "Mask" && mask_buffer_) {
|
|
result.wrap_external(mask_buffer_->float_buffer.data,
|
|
int2(mask_buffer_->x, mask_buffer_->y));
|
|
result.set_transformation(xform_);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool use_gpu() const override
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
static void compositor_modifier_init_data(StripModifierData *strip_modifier_data)
|
|
{
|
|
SequencerCompositorModifierData *modifier_data =
|
|
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
|
|
modifier_data->node_group = nullptr;
|
|
}
|
|
|
|
static bool is_linear_float_buffer(ImBuf *image_buffer)
|
|
{
|
|
return image_buffer->float_buffer.data &&
|
|
IMB_colormanagement_space_is_scene_linear(image_buffer->float_buffer.colorspace);
|
|
}
|
|
|
|
static bool ensure_linear_float_buffer(ImBuf *ibuf)
|
|
{
|
|
if (!ibuf) {
|
|
return false;
|
|
}
|
|
|
|
/* Already have scene linear float pixels, nothing to do. */
|
|
if (is_linear_float_buffer(ibuf)) {
|
|
return true;
|
|
}
|
|
|
|
if (ibuf->float_buffer.data == nullptr) {
|
|
IMB_float_from_byte(ibuf);
|
|
}
|
|
else {
|
|
const char *from_colorspace = IMB_colormanagement_get_float_colorspace(ibuf);
|
|
const char *to_colorspace = IMB_colormanagement_role_colorspace_name_get(
|
|
COLOR_ROLE_SCENE_LINEAR);
|
|
IMB_colormanagement_transform_float(ibuf->float_buffer.data,
|
|
ibuf->x,
|
|
ibuf->y,
|
|
ibuf->channels,
|
|
from_colorspace,
|
|
to_colorspace,
|
|
true);
|
|
IMB_colormanagement_assign_float_colorspace(ibuf, to_colorspace);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void compositor_modifier_apply(ModifierApplyContext &context,
|
|
StripModifierData *strip_modifier_data,
|
|
ImBuf *mask)
|
|
{
|
|
const SequencerCompositorModifierData *modifier_data =
|
|
reinterpret_cast<SequencerCompositorModifierData *>(strip_modifier_data);
|
|
if (!modifier_data->node_group) {
|
|
return;
|
|
}
|
|
|
|
ImBuf *linear_mask = mask;
|
|
if (mask && !is_linear_float_buffer(mask)) {
|
|
linear_mask = IMB_dupImBuf(mask);
|
|
ensure_linear_float_buffer(linear_mask);
|
|
}
|
|
|
|
const bool was_float_linear = ensure_linear_float_buffer(context.image);
|
|
const bool was_byte = context.image->float_buffer.data == nullptr;
|
|
|
|
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);
|
|
}
|
|
|
|
if (was_float_linear) {
|
|
return;
|
|
}
|
|
|
|
if (was_byte) {
|
|
IMB_byte_from_float(context.image);
|
|
IMB_free_float_pixels(context.image);
|
|
}
|
|
else {
|
|
seq_imbuf_to_sequencer_space(context.render_data.scene, context.image, true);
|
|
}
|
|
}
|
|
|
|
static void compositor_modifier_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);
|
|
|
|
uiTemplateID(layout,
|
|
C,
|
|
ptr,
|
|
"node_group",
|
|
"NODE_OT_new_compositor_sequencer_node_group",
|
|
nullptr,
|
|
nullptr);
|
|
|
|
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 compositor_modifier_register(ARegionType *region_type)
|
|
{
|
|
modifier_panel_register(
|
|
region_type, eSeqModifierType_Compositor, compositor_modifier_panel_draw);
|
|
}
|
|
|
|
StripModifierTypeInfo seqModifierType_Compositor = {
|
|
/*idname*/ "Compositor",
|
|
/*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Compositor"),
|
|
/*struct_name*/ "SequencerCompositorModifierData",
|
|
/*struct_size*/ sizeof(SequencerCompositorModifierData),
|
|
/*init_data*/ compositor_modifier_init_data,
|
|
/*free_data*/ nullptr,
|
|
/*copy_data*/ nullptr,
|
|
/*apply*/ compositor_modifier_apply,
|
|
/*panel_register*/ compositor_modifier_register,
|
|
};
|
|
|
|
}; // namespace blender::seq
|