From 8a74f7c0b0faab6c3c2eebad28d2ea8fff16b0cc Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Tue, 7 Oct 2025 13:51:41 +0200 Subject: [PATCH] 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 --- source/blender/compositor/COM_context.hh | 8 +- .../engines/compositor/compositor_engine.cc | 2 +- .../nodes/node_composite_group_input.cc | 8 +- .../nodes/node_composite_group_output.cc | 15 +- .../composite/nodes/node_composite_viewer.cc | 8 +- source/blender/render/intern/compositor.cc | 2 +- source/blender/sequencer/SEQ_modifier.hh | 11 +- .../modifiers/MOD_brightness_contrast.cc | 33 +- .../intern/modifiers/MOD_color_balance.cc | 113 +++--- .../intern/modifiers/MOD_compositor.cc | 44 ++- .../sequencer/intern/modifiers/MOD_curves.cc | 29 +- .../intern/modifiers/MOD_hue_correct.cc | 63 ++-- .../sequencer/intern/modifiers/MOD_mask.cc | 63 ++-- .../sequencer/intern/modifiers/MOD_tonemap.cc | 339 ++++++++---------- .../intern/modifiers/MOD_white_balance.cc | 45 +-- .../sequencer/intern/modifiers/modifier.cc | 60 +--- .../sequencer/intern/modifiers/modifier.hh | 248 ++++++++++++- source/blender/sequencer/intern/render.cc | 177 +++++---- source/blender/sequencer/intern/render.hh | 8 +- .../effects/mod_comp_blur_expand.blend | 3 + .../reference/adjustment_layer_stack.png | 4 +- .../effects/reference/mod_bright_contrast.png | 4 +- .../effects/reference/mod_colorbalance.png | 4 +- .../effects/reference/mod_comp.png | 4 +- .../reference/mod_comp_blur_expand.png | 3 + .../effects/reference/mod_curves.png | 4 +- .../effects/reference/mod_huecorrect.png | 4 +- .../effects/reference/mod_mask.png | 4 +- .../effects/reference/mod_mask_id.png | 4 +- .../effects/reference/mod_mask_id_rot.png | 4 +- .../effects/reference/mod_tonemap.png | 4 +- .../effects/reference/mod_whitebalance.png | 4 +- .../effects/reference/text_outline_stack.png | 4 +- 33 files changed, 798 insertions(+), 532 deletions(-) create mode 100644 tests/files/sequence_editing/effects/mod_comp_blur_expand.blend create mode 100644 tests/files/sequence_editing/effects/reference/mod_comp_blur_expand.png diff --git a/source/blender/compositor/COM_context.hh b/source/blender/compositor/COM_context.hh index e193ece6190..f93b60d0a8f 100644 --- a/source/blender/compositor/COM_context.hh +++ b/source/blender/compositor/COM_context.hh @@ -66,7 +66,7 @@ class Context { virtual Bounds get_compositing_region() const = 0; /* Get the result where the result of the compositor should be written. */ - virtual Result get_output() = 0; + virtual Result get_output(Domain domain) = 0; /* Get the result where the result of the compositor viewer should be written, given the domain * of the result to be viewed, its precision, and whether the output is a non-color data image @@ -102,6 +102,12 @@ class Context { * or support for viewers. */ virtual bool treat_viewer_as_compositor_output() const; + /* True if the compositor input/output should use output region/bounds setup in the context. */ + virtual bool use_context_bounds_for_input_output() const + { + return true; + } + /* Populates the given meta data from the render stamp information of the given render pass. */ virtual void populate_meta_data_for_pass(const Scene *scene, int view_layer_id, diff --git a/source/blender/draw/engines/compositor/compositor_engine.cc b/source/blender/draw/engines/compositor/compositor_engine.cc index 7fc38094854..c5964976c6d 100644 --- a/source/blender/draw/engines/compositor/compositor_engine.cc +++ b/source/blender/draw/engines/compositor/compositor_engine.cc @@ -110,7 +110,7 @@ class Context : public compositor::Context { .value_or(Bounds(int2(0))); } - compositor::Result get_output() override + compositor::Result get_output(compositor::Domain /*domain*/) override { compositor::Result result = this->create_result(compositor::ResultType::Color, compositor::ResultPrecision::Half); diff --git a/source/blender/nodes/composite/nodes/node_composite_group_input.cc b/source/blender/nodes/composite/nodes/node_composite_group_input.cc index 660691f21bd..8df3746c74c 100644 --- a/source/blender/nodes/composite/nodes/node_composite_group_input.cc +++ b/source/blender/nodes/composite/nodes/node_composite_group_input.cc @@ -67,6 +67,7 @@ class GroupInputOperation : public NodeOperation { else { this->execute_pass_cpu(pass, result); } + result.set_transformation(pass.domain().transformation); } void execute_pass_gpu(const Result &pass, Result &result) @@ -125,9 +126,12 @@ class GroupInputOperation : public NodeOperation { * compositing region into an appropriately sized result. */ const int2 lower_bound = this->context().get_compositing_region().min; - result.allocate_texture(Domain(this->context().get_compositing_region_size())); + const int2 size = this->context().use_context_bounds_for_input_output() ? + this->context().get_compositing_region_size() : + pass.domain().size; + result.allocate_texture(size); - parallel_for(result.domain().size, [&](const int2 texel) { + parallel_for(size, [&](const int2 texel) { result.store_pixel_generic_type(texel, pass.load_pixel_generic_type(texel + lower_bound)); }); } diff --git a/source/blender/nodes/composite/nodes/node_composite_group_output.cc b/source/blender/nodes/composite/nodes/node_composite_group_output.cc index e1b4004968a..29a42915747 100644 --- a/source/blender/nodes/composite/nodes/node_composite_group_output.cc +++ b/source/blender/nodes/composite/nodes/node_composite_group_output.cc @@ -68,7 +68,7 @@ class GroupOutputOperation : public NodeOperation { float4 color = image.get_single_value(); const Domain domain = this->compute_domain(); - Result output = this->context().get_output(); + Result output = this->context().get_output(domain); if (this->context().use_gpu()) { GPU_texture_clear(output, GPU_DATA_FLOAT, color); } @@ -90,7 +90,7 @@ class GroupOutputOperation : public NodeOperation { void execute_copy_gpu(const Result &image) { const Domain domain = this->compute_domain(); - Result output = this->context().get_output(); + Result output = this->context().get_output(domain); gpu::Shader *shader = this->context().get_shader("compositor_write_output", output.precision()); @@ -114,9 +114,11 @@ class GroupOutputOperation : public NodeOperation { void execute_copy_cpu(const Result &image) { const Domain domain = this->compute_domain(); - Result output = this->context().get_output(); + Result output = this->context().get_output(domain); - const Bounds bounds = this->context().get_compositing_region(); + const Bounds bounds = this->context().use_context_bounds_for_input_output() ? + this->context().get_compositing_region() : + Bounds(int2(0, 0), domain.size); parallel_for(domain.size, [&](const int2 texel) { const int2 output_texel = texel + bounds.min; if (output_texel.x > bounds.max.x || output_texel.y > bounds.max.y) { @@ -130,7 +132,10 @@ class GroupOutputOperation : public NodeOperation { * applied. */ Domain compute_domain() override { - return Domain(this->context().get_compositing_region_size()); + if (this->context().use_context_bounds_for_input_output()) { + return Domain(this->context().get_compositing_region_size()); + } + return NodeOperation::compute_domain(); } }; diff --git a/source/blender/nodes/composite/nodes/node_composite_viewer.cc b/source/blender/nodes/composite/nodes/node_composite_viewer.cc index c222eed4d1d..1f02ba0e900 100644 --- a/source/blender/nodes/composite/nodes/node_composite_viewer.cc +++ b/source/blender/nodes/composite/nodes/node_composite_viewer.cc @@ -144,7 +144,9 @@ class ViewerOperation : public NodeOperation { { /* Viewers are treated as composite outputs that should be in the bounds of the compositing * region. */ - if (this->context().treat_viewer_as_compositor_output()) { + if (this->context().treat_viewer_as_compositor_output() && + this->context().use_context_bounds_for_input_output()) + { return this->context().get_compositing_region(); } @@ -156,7 +158,9 @@ class ViewerOperation : public NodeOperation { { /* Viewers are treated as composite outputs that should be in the domain of the compositing * region. */ - if (context().treat_viewer_as_compositor_output()) { + if (this->context().treat_viewer_as_compositor_output() && + this->context().use_context_bounds_for_input_output()) + { return Domain(context().get_compositing_region_size()); } diff --git a/source/blender/render/intern/compositor.cc b/source/blender/render/intern/compositor.cc index 6ebf20d1145..7d3aa9cb164 100644 --- a/source/blender/render/intern/compositor.cc +++ b/source/blender/render/intern/compositor.cc @@ -170,7 +170,7 @@ class Context : public compositor::Context { return Bounds(int2(0), this->get_render_size()); } - compositor::Result get_output() override + compositor::Result get_output(compositor::Domain /*domain*/) override { const int2 render_size = get_render_size(); if (output_result_.is_allocated()) { diff --git a/source/blender/sequencer/SEQ_modifier.hh b/source/blender/sequencer/SEQ_modifier.hh index a4b884e930a..030b627d258 100644 --- a/source/blender/sequencer/SEQ_modifier.hh +++ b/source/blender/sequencer/SEQ_modifier.hh @@ -55,10 +55,10 @@ struct StripModifierTypeInfo { /* copy data from one modifier to another */ void (*copy_data)(StripModifierData *smd, StripModifierData *target); - /* Apply modifier on an image buffer. - * quad contains four corners of the (pre-transform) strip rectangle in pixel space. */ + /* Apply modifier on an image buffer. */ void (*apply)(const RenderData *render_data, - const StripScreenQuad &quad, + const Strip *strip, + const float transform[3][3], StripModifierData *smd, ImBuf *ibuf, ImBuf *mask); @@ -82,11 +82,6 @@ void modifier_clear(Strip *strip); void modifier_free(StripModifierData *smd); void modifier_unique_name(Strip *strip, StripModifierData *smd); StripModifierData *modifier_find_by_name(Strip *strip, const char *name); -void modifier_apply_stack(const RenderData *context, - SeqRenderState *state, - const Strip *strip, - ImBuf *ibuf, - int timeline_frame); StripModifierData *modifier_copy(Strip &strip_dst, StripModifierData *mod_src); void modifier_list_copy(Strip *strip_new, Strip *strip); int sequence_supports_modifiers(Strip *strip); diff --git a/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc b/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc index a0a108f78dd..a5c0c77fc71 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_brightness_contrast.cc @@ -29,27 +29,32 @@ struct BrightContrastApplyOp { float mul; float add; - template - void apply(ImageT *image, const MaskT *mask, IndexRange size) + template + void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range) { - 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); + 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)) { + /* 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; + 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; + mask.apply_mask(input, result); + store_pixel_raw(result, image); + image += 4; + } } } }; static void brightcontrast_apply(const RenderData * /*render_data*/, - const StripScreenQuad & /*quad*/, + const Strip * /*strip*/, + const float transform[3][3], StripModifierData *smd, ImBuf *ibuf, ImBuf *mask) @@ -76,7 +81,7 @@ static void brightcontrast_apply(const RenderData * /*render_data*/, op.add = op.mul * brightness + delta; } - apply_modifier_op(op, ibuf, mask); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); } static void brightcontrast_panel_draw(const bContext *C, Panel *panel) diff --git a/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc b/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc index 998d784204f..bbaffdc958a 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_color_balance.cc @@ -85,69 +85,79 @@ struct ColorBalanceApplyOp { float lut[3][CB_TABLE_SIZE]; /* Apply on a byte image via a table lookup. */ - template void apply(uchar *image, const MaskT *mask, IndexRange size) + template + void apply(uchar *image, MaskSampler &mask, int image_x, IndexRange y_range) { - for ([[maybe_unused]] int64_t i : size) { - float4 input = load_pixel_premul(image); + 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; - 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; + 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; + mask.apply_mask(input, result); + store_pixel_premul(result, image); + image += 4; + } } } /* Apply on a float image by doing full math. */ - template void apply(float *image, const MaskT *mask, IndexRange size) + template + void apply(float *image, MaskSampler &mask, int image_x, IndexRange y_range) { - if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { - /* Lift/Gamma/Gain */ - for ([[maybe_unused]] int64_t i : size) { - float4 input = load_pixel_premul(image); + image += y_range.first() * image_x * 4; + for (int64_t y : y_range) { + mask.begin_row(y); + if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) { + /* Lift/Gamma/Gain */ + for ([[maybe_unused]] int64_t x : IndexRange(image_x)) { + 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; + 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; + mask.apply_mask(input, result); + 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); + else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) { + /* Slope/Offset/Power */ + for ([[maybe_unused]] int64_t x : IndexRange(image_x)) { + 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; + 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; + mask.apply_mask(input, result); + store_pixel_premul(result, image); + image += 4; + } + } + else { + BLI_assert_unreachable(); } - } - else { - BLI_assert_unreachable(); } } @@ -245,7 +255,8 @@ static void colorBalance_init_data(StripModifierData *smd) } static void colorBalance_apply(const RenderData * /*render_data*/, - const StripScreenQuad & /*quad*/, + const Strip * /*strip*/, + const float transform[3][3], StripModifierData *smd, ImBuf *ibuf, ImBuf *mask) @@ -254,7 +265,7 @@ static void colorBalance_apply(const RenderData * /*render_data*/, ColorBalanceApplyOp op; op.init(*cbmd, ibuf->byte_buffer.data != nullptr); - apply_modifier_op(op, ibuf, mask); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); } static void colorBalance_panel_draw(const bContext *C, Panel *panel) diff --git a/source/blender/sequencer/intern/modifiers/MOD_compositor.cc b/source/blender/sequencer/intern/modifiers/MOD_compositor.cc index 2a04a20ed77..1cca678ed6c 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_compositor.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_compositor.cc @@ -24,6 +24,7 @@ #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" @@ -42,18 +43,26 @@ class CompositorContext : public compositor::Context { ImBuf *image_buffer_; ImBuf *mask_buffer_; + float3x3 xform_; public: CompositorContext(const RenderData &render_data, const SequencerCompositorModifierData *modifier_data, ImBuf *image_buffer, - ImBuf *mask_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) + 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)); + } } const Scene &get_scene() const override @@ -80,24 +89,45 @@ class CompositorContext : public compositor::Context { return true; } + bool use_context_bounds_for_input_output() const override + { + return false; + } + Bounds get_compositing_region() const override { return Bounds(int2(0), int2(image_buffer_->x, image_buffer_->y)); } - compositor::Result get_output() override + compositor::Result get_output(compositor::Domain domain) 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; } - compositor::Result get_viewer_output(compositor::Domain /*domain*/, + compositor::Result get_viewer_output(compositor::Domain domain, 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; @@ -114,6 +144,7 @@ class CompositorContext : public compositor::Context { 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; @@ -169,7 +200,8 @@ static bool ensure_linear_float_buffer(ImBuf *ibuf) } static void compositor_modifier_apply(const RenderData *render_data, - const StripScreenQuad & /*quad*/, + const Strip *strip, + const float /*transform*/[3][3], StripModifierData *strip_modifier_data, ImBuf *image_buffer, ImBuf *mask) @@ -189,7 +221,7 @@ static void compositor_modifier_apply(const RenderData *render_data, const bool was_float_linear = ensure_linear_float_buffer(image_buffer); const bool was_byte = image_buffer->float_buffer.data == nullptr; - CompositorContext context(*render_data, modifier_data, image_buffer, linear_mask); + CompositorContext context(*render_data, modifier_data, image_buffer, linear_mask, strip); compositor::Evaluator evaluator(context); evaluator.evaluate(); diff --git a/source/blender/sequencer/intern/modifiers/MOD_curves.cc b/source/blender/sequencer/intern/modifiers/MOD_curves.cc index 4b7b5d835af..f2d9c942c23 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_curves.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_curves.cc @@ -48,25 +48,30 @@ static void curves_copy_data(StripModifierData *target, StripModifierData *smd) struct CurvesApplyOp { const CurveMapping *curve_mapping; - template - void apply(ImageT *image, const MaskT *mask, IndexRange size) + template + void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range) { - for ([[maybe_unused]] int64_t i : size) { - float4 input = load_pixel_premul(image); + 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; + 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; + mask.apply_mask(input, result); + store_pixel_premul(result, image); + image += 4; + } } } }; static void curves_apply(const RenderData * /*render_data*/, - const StripScreenQuad & /*quad*/, + const Strip * /*strip*/, + const float transform[3][3], StripModifierData *smd, ImBuf *ibuf, ImBuf *mask) @@ -83,7 +88,7 @@ static void curves_apply(const RenderData * /*render_data*/, CurvesApplyOp op; op.curve_mapping = &cmd->curve_mapping; - apply_modifier_op(op, ibuf, mask); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); BKE_curvemapping_premultiply(&cmd->curve_mapping, true); } diff --git a/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc b/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc index 88687d52621..4d4a506f8c1 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_hue_correct.cc @@ -61,47 +61,52 @@ static void hue_correct_copy_data(StripModifierData *target, StripModifierData * struct HueCorrectApplyOp { const CurveMapping *curve_mapping; - template - void apply(ImageT *image, const MaskT *mask, IndexRange size) + template + void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range) { - 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; + 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)) { + /* 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); + 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 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 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); + /* 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); + 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); + /* 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; + mask.apply_mask(input, result); + store_pixel_raw(result, image); + image += 4; + } } } }; static void hue_correct_apply(const RenderData * /*render_data*/, - const StripScreenQuad & /*quad*/, + const Strip * /*strip*/, + const float transform[3][3], StripModifierData *smd, ImBuf *ibuf, ImBuf *mask) @@ -112,7 +117,7 @@ static void hue_correct_apply(const RenderData * /*render_data*/, HueCorrectApplyOp op; op.curve_mapping = &hcmd->curve_mapping; - apply_modifier_op(op, ibuf, mask); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); } static void hue_correct_panel_draw(const bContext *C, Panel *panel) diff --git a/source/blender/sequencer/intern/modifiers/MOD_mask.cc b/source/blender/sequencer/intern/modifiers/MOD_mask.cc index 2474fe626f0..0f94c4abb95 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_mask.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_mask.cc @@ -7,6 +7,7 @@ */ #include "BLI_math_base.h" +#include "BLI_math_matrix.hh" #include "BLT_translation.hh" @@ -14,6 +15,8 @@ #include "DNA_sequence_types.h" #include "SEQ_modifier.hh" +#include "SEQ_render.hh" +#include "SEQ_transform.hh" #include "UI_interface.hh" #include "UI_interface_layout.hh" @@ -22,49 +25,37 @@ 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 - void apply(ImageT *image, const MaskT *mask, IndexRange size) + template + void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range) { - for ([[maybe_unused]] int64_t i : size) { - float m = load_mask_min(mask); + 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)) { + float m = mask.load_mask_min(); - if constexpr (std::is_same_v) { - /* 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); + if constexpr (std::is_same_v) { + /* 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) { + /* 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(image) = pix; + } + image += 4; } - else if constexpr (std::is_same_v) { - /* 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(image) = pix; - } - image += 4; } } }; -static void maskmodifier_apply(const RenderData * /*render_data*/, - const StripScreenQuad & /*quad*/, +static void maskmodifier_apply(const RenderData * /* render_data */, + const Strip * /*strip*/, + const float transform[3][3], StripModifierData * /*smd*/, ImBuf *ibuf, ImBuf *mask) @@ -75,7 +66,7 @@ static void maskmodifier_apply(const RenderData * /*render_data*/, } MaskApplyOp op; - apply_modifier_op(op, ibuf, mask); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); /* Image has gained transparency. */ ibuf->planes = R_IMF_PLANES_RGBA; diff --git a/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc b/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc index f4660f9d5eb..878df639f7e 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_tonemap.cc @@ -7,7 +7,6 @@ */ #include "BLI_array.hh" -#include "BLI_math_geom.h" #include "BLT_translation.hh" @@ -76,14 +75,6 @@ static void pixels_to_scene_linear_byte(const ColorSpace *colorspace, (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(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; @@ -97,104 +88,6 @@ static void scene_linear_to_image_chunk_byte(float4 *src, ImBuf *ibuf, IndexRang } } -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; @@ -204,41 +97,150 @@ struct AreaLuminance { 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) +static void scene_linear_to_image_chunk_float(ImBuf *ibuf, IndexRange range) { - 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); + const ColorSpace *colorspace = ibuf->float_buffer.colorspace; + float4 *fptr = reinterpret_cast(ibuf->float_buffer.data); + IMB_colormanagement_scene_linear_to_colorspace( + (float *)(fptr + range.first()), int(range.size()), 1, 4, colorspace); +} + +template +static void tonemap_simple( + float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg) +{ + for (int64_t y : y_range) { + mask.begin_row(y); + for ([[maybe_unused]] int64_t x : IndexRange(image_x)) { + float4 input = *scene_linear; + + /* 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. */ + float4 result(pixel.x, pixel.y, pixel.z, input.w); + mask.apply_mask(input, result); + *scene_linear = result; scene_linear++; } } } -static AreaLuminance tonemap_calc_input_luminance(const StripScreenQuad &quad, const ImBuf *ibuf) +template +static void tonemap_rd_photoreceptor( + float4 *scene_linear, MaskSampler &mask, int image_x, IndexRange y_range, const AvgLogLum &avg) { - /* 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); + 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; + for (int64_t y : y_range) { + mask.begin_row(y); + for ([[maybe_unused]] int64_t x : IndexRange(image_x)) { + float4 input = *scene_linear; + + /* 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. */ + float4 result(pixel.x, pixel.y, pixel.z, input.w); + mask.apply_mask(input, result); + *scene_linear = result; + scene_linear++; + } + } +} + +struct TonemapApplyOp { + AreaLuminance lum; + AvgLogLum data; + eModTonemapType type; + ImBuf *ibuf; + + template + void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range) + { + const IndexRange pixel_range(y_range.first() * image_x, y_range.size() * image_x); + if constexpr (std::is_same_v) { + /* Float pixels: no need for temporary storage. Luminance calculation already converted + * data to scene linear. */ + float4 *pixels = (float4 *)(image + y_range.first() * image_x * 4); + if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) { + tonemap_rd_photoreceptor(pixels, mask, image_x, y_range, data); + } + else { + BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE); + tonemap_simple(pixels, mask, image_x, y_range, data); + } + scene_linear_to_image_chunk_float(this->ibuf, pixel_range); + } + else { + /* Byte pixels: temporary storage for scene linear pixel values. */ + Array scene_linear(pixel_range.size()); + pixels_to_scene_linear_byte(ibuf->byte_buffer.colorspace, + ibuf->byte_buffer.data + pixel_range.first() * 4, + scene_linear.data(), + pixel_range.size()); + if (this->type == SEQ_TONEMAP_RD_PHOTORECEPTOR) { + tonemap_rd_photoreceptor(scene_linear.data(), mask, image_x, y_range, data); + } + else { + BLI_assert(this->type == SEQ_TONEMAP_RH_SIMPLE); + tonemap_simple(scene_linear.data(), mask, image_x, y_range, data); + } + scene_linear_to_image_chunk_byte(scene_linear.data(), this->ibuf, pixel_range); + } + } +}; + +static void tonemap_calc_chunk_luminance(const int width, + const IndexRange y_range, + const float4 *scene_linear, + AreaLuminance &r_lum) +{ + for ([[maybe_unused]] const int y : y_range) { + for (int x = 0; x < width; x++) { + 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 ImBuf *ibuf) +{ AreaLuminance lum; lum = threading::parallel_reduce( IndexRange(ibuf->y), @@ -254,15 +256,14 @@ static AreaLuminance tonemap_calc_input_luminance(const StripScreenQuad &quad, c float4 *fptr = reinterpret_cast(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); + tonemap_calc_chunk_luminance(ibuf->x, y_range, fptr, lum); } else { const uchar *bptr = ibuf->byte_buffer.data + y_range.first() * ibuf->x * 4; Array 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); + tonemap_calc_chunk_luminance(ibuf->x, y_range, scene_linear.data(), lum); } return lum; }, @@ -281,64 +282,36 @@ static AreaLuminance tonemap_calc_input_luminance(const StripScreenQuad &quad, c } static void tonemapmodifier_apply(const RenderData * /*render_data*/, - const StripScreenQuad &quad, + const Strip * /*strip*/, + const float transform[3][3], 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) { + TonemapApplyOp op; + op.type = eModTonemapType(tmmd->type); + op.ibuf = ibuf; + op.lum = tonemap_calc_input_luminance(ibuf); + if (op.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; + op.data.tmmd = tmmd; + op.data.lav = op.lum.sum / op.lum.pixel_count; + op.data.cav.x = op.lum.color_sum.x / op.lum.pixel_count; + op.data.cav.y = op.lum.color_sum.y / op.lum.pixel_count; + op.data.cav.z = op.lum.color_sum.z / op.lum.pixel_count; + float maxl = log(double(op.lum.max) + 1e-5f); + float minl = log(double(op.lum.min) + 1e-5f); + float avl = op.lum.log_sum / op.lum.pixel_count; + op.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); + op.data.al = (al == 0.0f) ? 0.0f : (tmmd->key / al); + op.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 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); - } - }); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); } static void tonemapmodifier_panel_draw(const bContext *C, Panel *panel) diff --git a/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc b/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc index e28e1d577c5..398f5bca1a0 100644 --- a/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc +++ b/source/blender/sequencer/intern/modifiers/MOD_white_balance.cc @@ -30,36 +30,41 @@ static void whiteBalance_init_data(StripModifierData *smd) struct WhiteBalanceApplyOp { float multiplier[3]; - template - void apply(ImageT *image, const MaskT *mask, IndexRange size) + template + void apply(ImageT *image, MaskSampler &mask, int image_x, IndexRange y_range) { - for ([[maybe_unused]] int64_t i : size) { - float4 input = load_pixel_premul(image); + 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; - result.w = input.w; + float4 result; + result.w = input.w; #if 0 - mul_v3_v3(result, multiplier); + 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]); - } + /* 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; + mask.apply_mask(input, result); + store_pixel_premul(result, image); + image += 4; + } } } }; static void whiteBalance_apply(const RenderData * /*render_data*/, - const StripScreenQuad & /*quad*/, + const Strip * /*strip*/, + const float transform[3][3], StripModifierData *smd, ImBuf *ibuf, ImBuf *mask) @@ -70,7 +75,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); + apply_modifier_op(op, ibuf, mask, float3x3(transform)); } static void whiteBalance_panel_draw(const bContext *C, Panel *panel) diff --git a/source/blender/sequencer/intern/modifiers/modifier.cc b/source/blender/sequencer/intern/modifiers/modifier.cc index 36d72f67779..26d6a73227c 100644 --- a/source/blender/sequencer/intern/modifiers/modifier.cc +++ b/source/blender/sequencer/intern/modifiers/modifier.cc @@ -35,6 +35,7 @@ #include "SEQ_select.hh" #include "SEQ_sequencer.hh" #include "SEQ_time.hh" +#include "SEQ_transform.hh" #include "SEQ_utils.hh" #include "UI_interface.hh" @@ -292,30 +293,6 @@ void store_pixel_raw(float4 pix, float *ptr) *reinterpret_cast(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. */ @@ -339,27 +316,17 @@ 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, mask_id, timeline_frame - fra_offset, false); + mask_input = seq_render_mask(context->depsgraph, + context->rectx, + context->recty, + mask_id, + timeline_frame - fra_offset, + false); } return mask_input; } -static ImBuf *modifier_mask_get(StripModifierData *smd, - const RenderData *context, - SeqRenderState *state, - int timeline_frame, - int fra_offset) -{ - return modifier_render_mask_input(context, - state, - smd->mask_input_type, - smd->mask_strip, - smd->mask_id, - timeline_frame, - fra_offset); -} - /* -------------------------------------------------------------------- */ /** \name Public Modifier Functions * \{ */ @@ -499,11 +466,10 @@ static bool skip_modifier(Scene *scene, const StripModifierData *smd, int timeli void modifier_apply_stack(const RenderData *context, SeqRenderState *state, const Strip *strip, + const float3x3 &transform, 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); } @@ -530,8 +496,14 @@ void modifier_apply_stack(const RenderData *context, frame_offset = smd->mask_id ? ((Mask *)smd->mask_id)->sfra : 0; } - ImBuf *mask = modifier_mask_get(smd, context, state, timeline_frame, frame_offset); - smti->apply(context, quad, smd, ibuf, mask); + ImBuf *mask = modifier_render_mask_input(context, + state, + smd->mask_input_type, + smd->mask_strip, + smd->mask_id, + timeline_frame, + frame_offset); + smti->apply(context, strip, transform.ptr(), smd, ibuf, mask); if (mask) { IMB_freeImBuf(mask); } diff --git a/source/blender/sequencer/intern/modifiers/modifier.hh b/source/blender/sequencer/intern/modifiers/modifier.hh index c32bba636b2..66b8d277ac3 100644 --- a/source/blender/sequencer/intern/modifiers/modifier.hh +++ b/source/blender/sequencer/intern/modifiers/modifier.hh @@ -8,6 +8,9 @@ * \ingroup sequencer */ +#include "BLI_math_color.h" +#include "BLI_math_interp.hh" +#include "BLI_math_matrix.hh" #include "BLI_math_vector.hh" #include "BLI_task.hh" @@ -15,6 +18,7 @@ struct bContext; struct ARegionType; +struct ImBuf; struct Strip; struct uiLayout; struct Panel; @@ -23,6 +27,19 @@ struct PointerRNA; 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); + bool modifier_persistent_uids_are_valid(const Strip &strip); void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr); @@ -43,21 +60,186 @@ 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); + +/* Mask sampler for #apply_modifier_op: no mask is present. */ +struct MaskSamplerNone { + void begin_row(int64_t /*y*/) {} + void apply_mask(const float4 /*input*/, float4 & /*result*/) {} + float load_mask_min() + { + return 0.0f; + } +}; + +/* Mask sampler for #apply_modifier_op: floating point mask, + * same size as input, no transform. */ +struct MaskSamplerDirectFloat { + MaskSamplerDirectFloat(const ImBuf *mask) : mask(mask) + { + BLI_assert(mask && mask->float_buffer.data); + } + void begin_row(int64_t y) + { + BLI_assert(y >= 0 && y < mask->y); + ptr = mask->float_buffer.data + y * mask->x * 4; + } + void apply_mask(const float4 input, float4 &result) + { + float3 m(this->ptr); + 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); + this->ptr += 4; + } + float load_mask_min() + { + float r = min_fff(this->ptr[0], this->ptr[1], this->ptr[2]); + this->ptr += 4; + return r; + } + const float *ptr = nullptr; + const ImBuf *mask; +}; + +/* Mask sampler for #apply_modifier_op: byte mask, + * same size as input, no transform. */ +struct MaskSamplerDirectByte { + MaskSamplerDirectByte(const ImBuf *mask) : mask(mask) + { + BLI_assert(mask && mask->byte_buffer.data); + } + void begin_row(int64_t y) + { + BLI_assert(y >= 0 && y < mask->y); + ptr = mask->byte_buffer.data + y * mask->x * 4; + } + void apply_mask(const float4 input, float4 &result) + { + float3 m; + rgb_uchar_to_float(m, this->ptr); + 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); + this->ptr += 4; + } + float load_mask_min() + { + float r = float(min_iii(this->ptr[0], this->ptr[1], this->ptr[2])) * (1.0f / 255.0f); + this->ptr += 4; + return r; + } + const uchar *ptr = nullptr; + const ImBuf *mask; +}; + +/* Mask sampler for #apply_modifier_op: floating point mask, + * sample mask with a transform. */ +struct MaskSamplerTransformedFloat { + MaskSamplerTransformedFloat(const ImBuf *mask, const float3x3 &transform) + : mask(mask), transform(transform) + { + BLI_assert(mask && mask->float_buffer.data); + start_uv = transform.location().xy(); + add_x = transform.x_axis().xy(); + add_y = transform.y_axis().xy(); + } + void begin_row(int64_t y) + { + this->cur_y = y; + this->cur_x = 0; + /* Sample at pixel centers. */ + this->cur_uv_row = this->start_uv + (y + 0.5f) * this->add_y + 0.5f * this->add_x; + } + void apply_mask(const float4 input, float4 &result) + { + float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f; + float4 m; + math::interpolate_bilinear_border_fl( + this->mask->float_buffer.data, m, this->mask->x, this->mask->y, 4, uv.x, uv.y); + 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); + this->cur_x++; + } + float load_mask_min() + { + float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f; + float4 m; + math::interpolate_bilinear_border_fl( + this->mask->float_buffer.data, m, this->mask->x, this->mask->y, 4, uv.x, uv.y); + float r = min_fff(m.x, m.y, m.z); + this->cur_x++; + return r; + } + int64_t cur_x = 0, cur_y = 0; + const ImBuf *mask; + const float3x3 transform; + float2 start_uv, add_x, add_y; + float2 cur_uv_row; +}; + +/* Mask sampler for #apply_modifier_op: byte mask, + * sample mask with a transform. */ +struct MaskSamplerTransformedByte { + MaskSamplerTransformedByte(const ImBuf *mask, const float3x3 &transform) + : mask(mask), transform(transform) + { + BLI_assert(mask && mask->byte_buffer.data); + start_uv = transform.location().xy(); + add_x = transform.x_axis().xy(); + add_y = transform.y_axis().xy(); + } + void begin_row(int64_t y) + { + this->cur_y = y; + this->cur_x = 0; + /* Sample at pixel centers. */ + this->cur_uv_row = this->start_uv + (y + 0.5f) * this->add_y + 0.5f * this->add_x; + } + void apply_mask(const float4 input, float4 &result) + { + float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f; + uchar4 mb = math::interpolate_bilinear_border_byte( + this->mask->byte_buffer.data, this->mask->x, this->mask->y, uv.x, uv.y); + float3 m; + rgb_uchar_to_float(m, mb); + 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); + this->cur_x++; + } + float load_mask_min() + { + float2 uv = this->cur_uv_row + this->cur_x * this->add_x - 0.5f; + uchar4 m = math::interpolate_bilinear_border_byte( + this->mask->byte_buffer.data, this->mask->x, this->mask->y, uv.x, uv.y); + float r = float(min_iii(m.x, m.y, m.z)) * (1.0f / 255.0f); + this->cur_x++; + return r; + } + int64_t cur_x = 0, cur_y = 0; + const ImBuf *mask; + const float3x3 transform; + float2 start_uv, add_x, add_y; + float2 cur_uv_row; +}; /* Given `T` that implements an `apply` function: * - * template - * void apply(ImageT* image, const MaskT* mask, IndexRange size); + * template + * void apply(ImageT* image, MaskSampler &mask, int image_x, IndexRange y_range); * * 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 + * uchar or float ImageT types, and with appropriate MaskSampler + * instantiated, depending on whether the mask exists, data type + * of the mask, and whether it needs a transformation or can be + * sampled directly. + * + * Both input and mask images are expected to have * 4 (RGBA) color channels. Input is modified. */ -template void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask) +template +void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask, const float3x3 &mask_transform) { if (ibuf == nullptr) { return; @@ -66,37 +248,67 @@ template void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mas "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) { + const bool direct_mask_sampling = mask == nullptr || (mask->x == ibuf->x && mask->y == ibuf->y && + math::is_identity(mask_transform)); + const int image_x = ibuf->x; + threading::parallel_for(IndexRange(ibuf->y), 16, [&](IndexRange y_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); + if (direct_mask_sampling) { + MaskSamplerDirectByte sampler(mask); + op.apply(image_byte, sampler, image_x, y_range); + } + else { + MaskSamplerTransformedByte sampler(mask, mask_transform); + op.apply(image_byte, sampler, image_x, y_range); + } } else if (mask_float) { - op.apply(image_byte + offset, mask_float + offset, range); + if (direct_mask_sampling) { + MaskSamplerDirectFloat sampler(mask); + op.apply(image_byte, sampler, image_x, y_range); + } + else { + MaskSamplerTransformedFloat sampler(mask, mask_transform); + op.apply(image_byte, sampler, image_x, y_range); + } } else { - op.apply(image_byte + offset, mask_none, range); + MaskSamplerNone sampler; + op.apply(image_byte, sampler, image_x, y_range); } } else if (image_float) { if (mask_byte) { - op.apply(image_float + offset, mask_byte + offset, range); + if (direct_mask_sampling) { + MaskSamplerDirectByte sampler(mask); + op.apply(image_float, sampler, image_x, y_range); + } + else { + MaskSamplerTransformedByte sampler(mask, mask_transform); + op.apply(image_float, sampler, image_x, y_range); + } } else if (mask_float) { - op.apply(image_float + offset, mask_float + offset, range); + if (direct_mask_sampling) { + MaskSamplerDirectFloat sampler(mask); + op.apply(image_float, sampler, image_x, y_range); + } + else { + MaskSamplerTransformedFloat sampler(mask, mask_transform); + op.apply(image_float, sampler, image_x, y_range); + } } else { - op.apply(image_float + offset, mask_none, range); + MaskSamplerNone sampler; + op.apply(image_float, sampler, image_x, y_range); } } }); diff --git a/source/blender/sequencer/intern/render.cc b/source/blender/sequencer/intern/render.cc index 454f8dfe798..563c19f45e6 100644 --- a/source/blender/sequencer/intern/render.cc +++ b/source/blender/sequencer/intern/render.cc @@ -71,6 +71,7 @@ #include "cache/intra_frame_cache.hh" #include "cache/source_image_cache.hh" #include "effects/effects.hh" +#include "modifiers/modifier.hh" #include "multiview.hh" #include "prefetch.hh" #include "proxy.hh" @@ -440,19 +441,21 @@ static bool seq_need_scale_to_render_size(const Strip *strip, bool is_proxy_imag return true; } -static float3x3 sequencer_image_crop_transform_matrix(const Scene *scene, - const Strip *strip, - const ImBuf *in, - const ImBuf *out, - const float image_scale_factor, - const float preview_scale_factor) +static float3x3 calc_strip_transform_matrix(const Scene *scene, + const Strip *strip, + const int in_x, + const int in_y, + const int out_x, + const int out_y, + const float image_scale_factor, + const float preview_scale_factor) { const StripTransform *transform = strip->data->transform; /* This value is intentionally kept as integer. Otherwise images with odd dimensions would * be translated to center of canvas by non-integer value, which would cause it to be * interpolated. Interpolation with 0 user defined translation is unwanted behavior. */ - const int3 image_center_offs((out->x - in->x) / 2, (out->y - in->y) / 2, 0); + const int3 image_center_offs((out_x - in_x) / 2, (out_y - in_y) / 2, 0); const float2 translation(transform->xofs * preview_scale_factor, transform->yofs * preview_scale_factor); @@ -461,12 +464,12 @@ static float3x3 sequencer_image_crop_transform_matrix(const Scene *scene, transform->scale_y * image_scale_factor); const float2 origin = image_transform_origin_get(scene, strip); - const float2 pivot(in->x * origin[0], in->y * origin[1]); + const float2 pivot(in_x * origin[0], in_y * origin[1]); const float3x3 matrix = math::from_loc_rot_scale( translation + float2(image_center_offs), rotation, scale); const float3x3 mat_pivot = math::from_origin_transform(matrix, pivot); - return math::invert(mat_pivot); + return mat_pivot; } static void sequencer_image_crop_init(const Strip *strip, @@ -531,17 +534,14 @@ static eIMBInterpolationFilterMode get_auto_filter(const StripTransform *transfo return IMB_FILTER_BILINEAR; } -static void sequencer_preprocess_transform_crop( - ImBuf *in, ImBuf *out, const RenderData *context, Strip *strip, const bool is_proxy_image) +static void sequencer_preprocess_transform_crop(ImBuf *in, + ImBuf *out, + const RenderData *context, + Strip *strip, + const float3x3 &matrix, + const bool do_scale_to_render_size, + const float preview_scale_factor) { - const Scene *scene = context->scene; - const float preview_scale_factor = get_render_scale_factor(*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; - - float3x3 matrix = sequencer_image_crop_transform_matrix( - scene, strip, in, out, image_scale_factor, preview_scale_factor); - /* Proxy image is smaller, so crop values must be corrected by proxy scale factor. * Proxy scale factor always matches preview_scale_factor. */ rctf source_crop; @@ -628,56 +628,26 @@ static ImBuf *input_preprocess(const RenderData *context, const bool is_proxy_image) { Scene *scene = context->scene; - ImBuf *preprocessed_ibuf = nullptr; /* Deinterlace. */ if ((strip->flag & SEQ_FILTERY) && !ELEM(strip->type, STRIP_TYPE_MOVIE, STRIP_TYPE_MOVIECLIP)) { - /* Change original image pointer to avoid another duplication in SEQ_USE_TRANSFORM. */ - preprocessed_ibuf = IMB_makeSingleUser(ibuf); - ibuf = preprocessed_ibuf; - - IMB_filtery(preprocessed_ibuf); - } - - if (sequencer_use_crop(strip) || sequencer_use_transform(strip) || context->rectx != ibuf->x || - context->recty != ibuf->y) - { - const int x = context->rectx; - const int y = context->recty; - preprocessed_ibuf = IMB_allocImBuf( - x, y, 32, ibuf->float_buffer.data ? IB_float_data : IB_byte_data); - - sequencer_preprocess_transform_crop(ibuf, preprocessed_ibuf, context, strip, is_proxy_image); - - seq_imbuf_assign_spaces(scene, preprocessed_ibuf); - IMB_metadata_copy(preprocessed_ibuf, ibuf); - IMB_freeImBuf(ibuf); - } - - /* Duplicate ibuf if we still have original. */ - if (preprocessed_ibuf == nullptr) { - preprocessed_ibuf = IMB_makeSingleUser(ibuf); - } - - if (strip->flag & SEQ_FLIPX) { - IMB_flipx(preprocessed_ibuf); - } - - if (strip->flag & SEQ_FLIPY) { - IMB_flipy(preprocessed_ibuf); + ibuf = IMB_makeSingleUser(ibuf); + IMB_filtery(ibuf); } if (strip->sat != 1.0f) { - IMB_saturation(preprocessed_ibuf, strip->sat); + ibuf = IMB_makeSingleUser(ibuf); + IMB_saturation(ibuf, strip->sat); } if (strip->flag & SEQ_MAKE_FLOAT) { - if (!preprocessed_ibuf->float_buffer.data) { - seq_imbuf_to_sequencer_space(scene, preprocessed_ibuf, true); + if (!ibuf->float_buffer.data) { + ibuf = IMB_makeSingleUser(ibuf); + seq_imbuf_to_sequencer_space(scene, ibuf, true); } - if (preprocessed_ibuf->byte_buffer.data) { - IMB_free_byte_pixels(preprocessed_ibuf); + if (ibuf->byte_buffer.data) { + IMB_free_byte_pixels(ibuf); } } @@ -687,15 +657,71 @@ static ImBuf *input_preprocess(const RenderData *context, } if (mul != 1.0f) { + ibuf = IMB_makeSingleUser(ibuf); const bool multiply_alpha = (strip->flag & SEQ_MULTIPLY_ALPHA); - multiply_ibuf(preprocessed_ibuf, mul, multiply_alpha); + multiply_ibuf(ibuf, mul, multiply_alpha); } + const float preview_scale_factor = get_render_scale_factor(*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; + if (strip->modifiers.first) { - modifier_apply_stack(context, state, strip, preprocessed_ibuf, timeline_frame); + ibuf = IMB_makeSingleUser(ibuf); + float3x3 matrix = calc_strip_transform_matrix(scene, + strip, + ibuf->x, + ibuf->y, + context->rectx, + context->recty, + image_scale_factor, + preview_scale_factor); + modifier_apply_stack(context, state, strip, matrix, ibuf, timeline_frame); } - return preprocessed_ibuf; + if (sequencer_use_crop(strip) || sequencer_use_transform(strip) || context->rectx != ibuf->x || + context->recty != ibuf->y) + { + const int x = context->rectx; + const int y = context->recty; + ImBuf *transformed_ibuf = IMB_allocImBuf( + x, y, 32, ibuf->float_buffer.data ? IB_float_data : IB_byte_data); + + /* Note: calculate matrix again; modifiers can actually change the image size. */ + float3x3 matrix = calc_strip_transform_matrix(scene, + strip, + ibuf->x, + ibuf->y, + context->rectx, + context->recty, + image_scale_factor, + preview_scale_factor); + matrix = math::invert(matrix); + sequencer_preprocess_transform_crop(ibuf, + transformed_ibuf, + context, + strip, + matrix, + do_scale_to_render_size, + preview_scale_factor); + + seq_imbuf_assign_spaces(scene, transformed_ibuf); + IMB_metadata_copy(transformed_ibuf, ibuf); + IMB_freeImBuf(ibuf); + ibuf = transformed_ibuf; + } + + if (strip->flag & SEQ_FLIPX) { + ibuf = IMB_makeSingleUser(ibuf); + IMB_flipx(ibuf); + } + + if (strip->flag & SEQ_FLIPY) { + ibuf = IMB_makeSingleUser(ibuf); + IMB_flipy(ibuf); + } + + return ibuf; } static ImBuf *seq_render_preprocess_ibuf(const RenderData *context, @@ -1261,7 +1287,12 @@ static ImBuf *seq_render_movieclip_strip(const RenderData *context, return ibuf; } -ImBuf *seq_render_mask(const RenderData *context, Mask *mask, float frame_index, bool make_float) +ImBuf *seq_render_mask(Depsgraph *depsgraph, + int width, + int height, + const Mask *mask, + float frame_index, + bool make_float) { /* TODO: add option to rasterize to alpha imbuf? */ ImBuf *ibuf = nullptr; @@ -1284,19 +1315,18 @@ ImBuf *seq_render_mask(const RenderData *context, Mask *mask, float frame_index, /* anim-data */ adt = BKE_animdata_from_id(&mask->id); const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( - context->depsgraph, mask->sfra + frame_index); + depsgraph, mask->sfra + frame_index); BKE_animsys_evaluate_animdata(&mask_temp->id, adt, &anim_eval_context, ADT_RECALC_ANIM, false); - maskbuf = MEM_malloc_arrayN(size_t(context->rectx) * size_t(context->recty), __func__); + maskbuf = MEM_malloc_arrayN(size_t(width) * size_t(height), __func__); mr_handle = BKE_maskrasterize_handle_new(); - BKE_maskrasterize_handle_init( - mr_handle, mask_temp, context->rectx, context->recty, true, true, true); + BKE_maskrasterize_handle_init(mr_handle, mask_temp, width, height, true, true, true); BKE_id_free(nullptr, &mask_temp->id); - BKE_maskrasterize_buffer(mr_handle, context->rectx, context->recty, maskbuf); + BKE_maskrasterize_buffer(mr_handle, width, height, maskbuf); BKE_maskrasterize_handle_free(mr_handle); @@ -1305,12 +1335,11 @@ ImBuf *seq_render_mask(const RenderData *context, Mask *mask, float frame_index, const float *fp_src; float *fp_dst; - ibuf = IMB_allocImBuf( - context->rectx, context->recty, 32, IB_float_data | IB_uninitialized_pixels); + ibuf = IMB_allocImBuf(width, height, 32, IB_float_data | IB_uninitialized_pixels); fp_src = maskbuf; fp_dst = ibuf->float_buffer.data; - i = context->rectx * context->recty; + i = width * height; while (--i) { fp_dst[0] = fp_dst[1] = fp_dst[2] = *fp_src; fp_dst[3] = 1.0f; @@ -1324,12 +1353,11 @@ ImBuf *seq_render_mask(const RenderData *context, Mask *mask, float frame_index, const float *fp_src; uchar *ub_dst; - ibuf = IMB_allocImBuf( - context->rectx, context->recty, 32, IB_byte_data | IB_uninitialized_pixels); + ibuf = IMB_allocImBuf(width, height, 32, IB_byte_data | IB_uninitialized_pixels); fp_src = maskbuf; ub_dst = ibuf->byte_buffer.data; - i = context->rectx * context->recty; + i = width * height; while (--i) { ub_dst[0] = ub_dst[1] = ub_dst[2] = uchar(*fp_src * 255.0f); /* already clamped */ ub_dst[3] = 255; @@ -1348,7 +1376,8 @@ static ImBuf *seq_render_mask_strip(const RenderData *context, Strip *strip, flo { bool make_float = (strip->flag & SEQ_MAKE_FLOAT) != 0; - return seq_render_mask(context, strip->mask, frame_index, make_float); + return seq_render_mask( + context->depsgraph, context->rectx, context->recty, strip->mask, frame_index, make_float); } static ImBuf *seq_render_scene_strip_ex(const RenderData *context, diff --git a/source/blender/sequencer/intern/render.hh b/source/blender/sequencer/intern/render.hh index 144fd51ebc6..0b3486283ad 100644 --- a/source/blender/sequencer/intern/render.hh +++ b/source/blender/sequencer/intern/render.hh @@ -12,6 +12,7 @@ #include "BLI_set.hh" #include "BLI_vector.hh" +struct Depsgraph; struct ImBuf; struct LinkNode; struct ListBase; @@ -55,7 +56,12 @@ ImBuf *seq_render_strip(const RenderData *context, /* Renders Mask into an image suitable for sequencer: * RGB channels contain mask intensity; alpha channel is opaque. */ -ImBuf *seq_render_mask(const RenderData *context, Mask *mask, float frame_index, bool make_float); +ImBuf *seq_render_mask(Depsgraph *depsgraph, + int width, + int height, + const Mask *mask, + float frame_index, + bool make_float); void seq_imbuf_assign_spaces(const Scene *scene, ImBuf *ibuf); StripScreenQuad get_strip_screen_quad(const RenderData *context, const Strip *strip); diff --git a/tests/files/sequence_editing/effects/mod_comp_blur_expand.blend b/tests/files/sequence_editing/effects/mod_comp_blur_expand.blend new file mode 100644 index 00000000000..7ba24eed799 --- /dev/null +++ b/tests/files/sequence_editing/effects/mod_comp_blur_expand.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1808a063f48e5aad3dc88dba4f2b7838426cad577f1c6053dbfee76ba0567a0c +size 84689 diff --git a/tests/files/sequence_editing/effects/reference/adjustment_layer_stack.png b/tests/files/sequence_editing/effects/reference/adjustment_layer_stack.png index ccaeb2a7c13..2b999103621 100644 --- a/tests/files/sequence_editing/effects/reference/adjustment_layer_stack.png +++ b/tests/files/sequence_editing/effects/reference/adjustment_layer_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f065a5161a2fab6f9085177fad4ff191f0f512f533d590225d60c154968c8242 -size 35622 +oid sha256:f352d108d2f26a528057460929b88dc08f38d589e6780cc4ea49a24a4dd87251 +size 35569 diff --git a/tests/files/sequence_editing/effects/reference/mod_bright_contrast.png b/tests/files/sequence_editing/effects/reference/mod_bright_contrast.png index 57bd8cee83b..5c5dfbd243a 100644 --- a/tests/files/sequence_editing/effects/reference/mod_bright_contrast.png +++ b/tests/files/sequence_editing/effects/reference/mod_bright_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e661dba85d768bda493724a3f5780d8c2ea7dc3381049d3267412f4416566e7e -size 72138 +oid sha256:735e3cb07369f3562fce65929a711a35f5e724e52ccbf409e59e4b48a26d4f64 +size 71547 diff --git a/tests/files/sequence_editing/effects/reference/mod_colorbalance.png b/tests/files/sequence_editing/effects/reference/mod_colorbalance.png index 69aac9f814b..afe3bc8ae8d 100644 --- a/tests/files/sequence_editing/effects/reference/mod_colorbalance.png +++ b/tests/files/sequence_editing/effects/reference/mod_colorbalance.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d4411c767210040941e09910ae220a1aecccb1b07271771ea73c823324d3947 -size 101220 +oid sha256:eaf52c0bf20112c81b8db3b267f66592baa65eef4441493be8759bee70fde6bf +size 101098 diff --git a/tests/files/sequence_editing/effects/reference/mod_comp.png b/tests/files/sequence_editing/effects/reference/mod_comp.png index 24824d89be8..3741a872caf 100644 --- a/tests/files/sequence_editing/effects/reference/mod_comp.png +++ b/tests/files/sequence_editing/effects/reference/mod_comp.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7b778d45609b65b11c4b92d1fafa6e167f303645391bd18b0a008d4243e95c8c -size 92110 +oid sha256:70a5f834ecc412e0effcf278bbc289713be8fb731c14f5eff3d54e50dac120e2 +size 101041 diff --git a/tests/files/sequence_editing/effects/reference/mod_comp_blur_expand.png b/tests/files/sequence_editing/effects/reference/mod_comp_blur_expand.png new file mode 100644 index 00000000000..13a679f5dc8 --- /dev/null +++ b/tests/files/sequence_editing/effects/reference/mod_comp_blur_expand.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:166101e96648151dced51a184192aeaf2606cf180f8a9577feffa63c3d17ad56 +size 83863 diff --git a/tests/files/sequence_editing/effects/reference/mod_curves.png b/tests/files/sequence_editing/effects/reference/mod_curves.png index 20fa7a48656..37ab1cc1108 100644 --- a/tests/files/sequence_editing/effects/reference/mod_curves.png +++ b/tests/files/sequence_editing/effects/reference/mod_curves.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13399f05acc8569f553e7fb88793c5070745987a5bbb1381e9e48b8a3abded27 -size 82200 +oid sha256:6558e390a7e5b9e3350817a6d476de22e3a439fe89941f9edde1b34955961d99 +size 82263 diff --git a/tests/files/sequence_editing/effects/reference/mod_huecorrect.png b/tests/files/sequence_editing/effects/reference/mod_huecorrect.png index 90c9881154f..b853fffa61f 100644 --- a/tests/files/sequence_editing/effects/reference/mod_huecorrect.png +++ b/tests/files/sequence_editing/effects/reference/mod_huecorrect.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8313b13866f4ede35ac87860e2bdc00c1b46d621457de98514672bc19ec28d84 -size 76894 +oid sha256:4b6c7e5507aa4f712f776748976f82b8f260aa32eea46159a0d057ce0ae71bcf +size 78087 diff --git a/tests/files/sequence_editing/effects/reference/mod_mask.png b/tests/files/sequence_editing/effects/reference/mod_mask.png index 45ee664a9f9..bb6fd020747 100644 --- a/tests/files/sequence_editing/effects/reference/mod_mask.png +++ b/tests/files/sequence_editing/effects/reference/mod_mask.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f216542e32174f7bcfc48c1d3ab1a56f9005490fa55408f314070fb512ff993 -size 60077 +oid sha256:ac388f3e9848be25b6a7855e92aa39e12614a25a1dde742cc8c0bea23d9285c6 +size 60240 diff --git a/tests/files/sequence_editing/effects/reference/mod_mask_id.png b/tests/files/sequence_editing/effects/reference/mod_mask_id.png index a0d47218168..221aa55f9b1 100644 --- a/tests/files/sequence_editing/effects/reference/mod_mask_id.png +++ b/tests/files/sequence_editing/effects/reference/mod_mask_id.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52907512c7ca43ccfa74281ad5a12718e34f0e96b09d5c9b2ad9552b97270639 -size 38805 +oid sha256:4e2cb03d2c99ff42c1138c0e96c04e5a8a214a57164138f495dd76c69c560ecd +size 40337 diff --git a/tests/files/sequence_editing/effects/reference/mod_mask_id_rot.png b/tests/files/sequence_editing/effects/reference/mod_mask_id_rot.png index fca10f0bf02..334da895954 100644 --- a/tests/files/sequence_editing/effects/reference/mod_mask_id_rot.png +++ b/tests/files/sequence_editing/effects/reference/mod_mask_id_rot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:765e6be0f6e0797ffe3c25838af9a11c4d3dc57d20540f112ebff6920f76e312 -size 50634 +oid sha256:371bd44dd4e0d589133d1e384aaafda7b95571a459c24dace70248c88551b929 +size 51698 diff --git a/tests/files/sequence_editing/effects/reference/mod_tonemap.png b/tests/files/sequence_editing/effects/reference/mod_tonemap.png index d96d4b74f0a..60434b8c3d8 100644 --- a/tests/files/sequence_editing/effects/reference/mod_tonemap.png +++ b/tests/files/sequence_editing/effects/reference/mod_tonemap.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:07efcbb21962e1e310be6fdb4f532b9f44ad46ab68ef4a527fde2237b9a28bb4 -size 212475 +oid sha256:313f5ac13389c0d4ef95fd66248bfb4660edf24f52f165dbe390464bd120e261 +size 212504 diff --git a/tests/files/sequence_editing/effects/reference/mod_whitebalance.png b/tests/files/sequence_editing/effects/reference/mod_whitebalance.png index 4a810f21672..e666115d15a 100644 --- a/tests/files/sequence_editing/effects/reference/mod_whitebalance.png +++ b/tests/files/sequence_editing/effects/reference/mod_whitebalance.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5473594c0e1a8b8360655eb1abedfcb871a0815470b22356804ecb7bea5e300f -size 76897 +oid sha256:d18134270c655456d42652cc72c6949f021b00396c3d7beea72abcbc1d0c39c1 +size 80096 diff --git a/tests/files/sequence_editing/effects/reference/text_outline_stack.png b/tests/files/sequence_editing/effects/reference/text_outline_stack.png index ab7f1831062..a2b45bb7da0 100644 --- a/tests/files/sequence_editing/effects/reference/text_outline_stack.png +++ b/tests/files/sequence_editing/effects/reference/text_outline_stack.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:71d00c0a6615c857cb641512d00300e293f292b5b4f3d37ba8af5ce99fc2abd6 -size 13266 +oid sha256:020d44b58a0bc7b1f64d9b70dec1ea8b7f17e26fc16ba1482889b42debb65db5 +size 13210