Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_render.hh"
#include "effects.hh"
namespace blender::seq {
/* -------------------------------------------------------------------- */
/* Color Add Effect */
struct AddEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
int ifac = int(256.0f * fac);
for (int64_t idx = 0; idx < size; idx++) {
if constexpr (std::is_same_v<T, uchar>) {
const int f = ifac * int(src2[3]);
dst[0] = min_ii(src1[0] + ((f * src2[0]) >> 16), 255);
dst[1] = min_ii(src1[1] + ((f * src2[1]) >> 16), 255);
dst[2] = min_ii(src1[2] + ((f * src2[2]) >> 16), 255);
}
else {
const float f = (1.0f - (src1[3] * (1.0f - fac))) * src2[3];
dst[0] = src1[0] + f * src2[0];
dst[1] = src1[1] + f * src2[1];
dst[2] = src1[2] + f * src2[2];
}
dst[3] = src1[3];
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_add_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxAdd", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
AddEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
/* Destination uses alpha from src1 */
dst.is_opaque_before_transform = !src1.image->can_contain_alpha();
return dst;
}
/* -------------------------------------------------------------------- */
/* Color Subtract Effect */
struct SubEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
int ifac = int(256.0f * fac);
for (int64_t idx = 0; idx < size; idx++) {
if constexpr (std::is_same_v<T, uchar>) {
const int f = ifac * int(src2[3]);
dst[0] = max_ii(src1[0] - ((f * src2[0]) >> 16), 0);
dst[1] = max_ii(src1[1] - ((f * src2[1]) >> 16), 0);
dst[2] = max_ii(src1[2] - ((f * src2[2]) >> 16), 0);
}
else {
const float f = (1.0f - (src1[3] * (1.0f - fac))) * src2[3];
dst[0] = max_ff(src1[0] - f * src2[0], 0.0f);
dst[1] = max_ff(src1[1] - f * src2[1], 0.0f);
dst[2] = max_ff(src1[2] - f * src2[2], 0.0f);
}
dst[3] = src1[3];
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_sub_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxSub", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
SubEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
/* Destination uses alpha from src1 */
dst.is_opaque_before_transform = !src1.image->can_contain_alpha();
return dst;
}
/* -------------------------------------------------------------------- */
/* Multiply Effect */
struct MulEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
int ifac = int(256.0f * fac);
for (int64_t idx = 0; idx < size; idx++) {
/* Formula: `fac * (a * b) + (1-fac) * a => fac * a * (b - 1) + a` */
if constexpr (std::is_same_v<T, uchar>) {
dst[0] = src1[0] + ((ifac * src1[0] * (src2[0] - 255)) >> 16);
dst[1] = src1[1] + ((ifac * src1[1] * (src2[1] - 255)) >> 16);
dst[2] = src1[2] + ((ifac * src1[2] * (src2[2] - 255)) >> 16);
dst[3] = src1[3] + ((ifac * src1[3] * (src2[3] - 255)) >> 16);
}
else {
dst[0] = src1[0] + fac * src1[0] * (src2[0] - 1.0f);
dst[1] = src1[1] + fac * src1[1] * (src2[1] - 1.0f);
dst[2] = src1[2] + fac * src1[2] * (src2[2] - 1.0f);
dst[3] = src1[3] + fac * src1[3] * (src2[3] - 1.0f);
}
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_mul_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxMul", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
MulEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
return dst;
}
void add_effect_get_handle(EffectHandle &rval)
{
rval.execute = do_add_effect;
rval.early_out = early_out_mul_input2;
}
void sub_effect_get_handle(EffectHandle &rval)
{
rval.execute = do_sub_effect;
rval.early_out = early_out_mul_input2;
}
void mul_effect_get_handle(EffectHandle &rval)
{
rval.execute = do_mul_effect;
rval.early_out = early_out_mul_input2;
}
} // namespace blender::seq

View File

@@ -0,0 +1,92 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "PRF_profile.hh"
#include "DNA_sequence_types.h"
#include "SEQ_channels.hh"
#include "SEQ_render.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_utils.hh"
#include "effects.hh"
#include "render.hh"
namespace blender::seq {
static StripEarlyOut early_out_adjustment(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::NoInput;
}
static SeqResult do_adjustment_impl(const RenderData *context,
SeqRenderState *state,
Strip *strip,
float timeline_frame)
{
SeqResult out;
Editing *ed = context->scene->ed;
ListBaseT<Strip> *seqbasep = get_seqbase_by_strip(context->scene, strip);
ListBaseT<SeqTimelineChannel> *channels = get_channels_by_strip(ed, strip);
/* Clamp timeline_frame to strip range so it behaves as if it had "still frame" offset (last
* frame is static after end of strip). This is how most strips behave. This way transition
* effects that doesn't overlap or speed effect can't fail rendering outside of strip range. */
timeline_frame = clamp_i(
timeline_frame, strip->left_handle(), strip->right_handle(context->scene) - 1);
if (strip->channel > 1) {
out = seq_render_give_ibuf_seqbase(
context, state, timeline_frame, strip->channel - 1, channels, seqbasep);
}
/* Found nothing? Then work our way up the meta-strip stack, as this adjustment strip might be
* inside a nested meta-strip and affect strips below that meta-strip.
*
* NOTE: we should NOT walk past the stack level that the user is currently tabbed into,
* otherwise the adjustment layer can leak content from outside the meta context. */
if (!out.is_valid()) {
Strip *meta = lookup_meta_by_strip(ed, strip);
if (meta && meta != ed->current_meta_strip) {
out = do_adjustment_impl(context, state, meta, timeline_frame);
}
}
return out;
}
static SeqResult do_adjustment(const RenderData *context,
SeqRenderState *state,
Strip *strip,
float timeline_frame,
float /*fac*/,
const SeqResult & /*ibuf1*/,
const SeqResult & /*ibuf2*/)
{
PRF_scope_with_name("SeqFxAdjustment", ProfileCategory::Draw);
Editing *ed = context->scene->ed;
if (!ed || state->strips_in_progress.contains(strip)) {
return {};
}
state->strips_in_progress.add(strip);
SeqResult out = do_adjustment_impl(context, state, strip, timeline_frame);
state->strips_in_progress.remove(strip);
return out;
}
void adjustment_effect_get_handle(EffectHandle &rval)
{
rval.early_out = early_out_adjustment;
rval.execute = do_adjustment;
}
} // namespace blender::seq

View File

@@ -0,0 +1,415 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_math_color_blend.h"
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_render.hh"
#include "effects.hh"
namespace blender::seq {
/* -------------------------------------------------------------------- */
/* Alpha Over Effect */
static void init_alpha_over_or_under(Strip *strip)
{
Strip *input1 = strip->input1;
Strip *input2 = strip->input2;
strip->input2 = input1;
strip->input1 = input2;
}
static bool alpha_opaque(uchar alpha)
{
return alpha == 255;
}
static bool alpha_opaque(float alpha)
{
return alpha >= 1.0f;
}
/* dst = src1 over src2 (alpha from src1) */
struct AlphaOverEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
if (fac <= 0.0f) {
memcpy(dst, src2, sizeof(T) * 4 * size);
return;
}
for (int64_t idx = 0; idx < size; idx++) {
if (std::is_same_v<T, uchar> && src1[3] == 0) {
/* Optimization for fully transparent pixels: copy src2. Only do this for byte images;
* in floats alpha=0 can still have pure emissive color. */
memcpy(dst, src2, sizeof(T) * 4);
}
else if (fac == 1.0f && alpha_opaque(src1[3])) {
/* No change to `src1` as `fac == 1` and fully opaque. */
memcpy(dst, src1, sizeof(T) * 4);
}
else {
float4 col1 = load_premul_pixel(src1);
float mfac = 1.0f - fac * col1.w;
float4 col2 = load_premul_pixel(src2);
float4 col = fac * col1 + mfac * col2;
store_premul_pixel(col, dst);
}
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_alphaover_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxOver", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
AlphaOverEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
return dst;
}
/* -------------------------------------------------------------------- */
/* Alpha Under Effect */
/* dst = src1 under src2 (alpha from src2) */
struct AlphaUnderEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
if (fac <= 0.0f) {
memcpy(dst, src2, sizeof(T) * 4 * size);
return;
}
for (int64_t idx = 0; idx < size; idx++) {
if (src2[3] <= 0.0f && fac >= 1.0f) {
memcpy(dst, src1, sizeof(T) * 4);
}
else if (alpha_opaque(src2[3])) {
memcpy(dst, src2, sizeof(T) * 4);
}
else {
float4 col2 = load_premul_pixel(src2);
float mfac = fac * (1.0f - col2.w);
float4 col1 = load_premul_pixel(src1);
float4 col = mfac * col1 + col2;
store_premul_pixel(col, dst);
}
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_alphaunder_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxUnder", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
AlphaUnderEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
return dst;
}
/* -------------------------------------------------------------------- */
/* Blend Mode Effect */
/* blend_function has to be: void (T* dst, const T *src1, const T *src2) */
template<typename T, typename Func>
static void apply_blend_function(
float fac, int64_t size, const T *src1, const T *src2, T *dst, Func blend_function)
{
for (int64_t i = 0; i < size; i++) {
T achannel = src2[3];
(static_cast<T *>(const_cast<T *>(src2)))[3] = T(achannel * fac);
blend_function(dst, src1, src2);
(static_cast<T *>(const_cast<T *>(src2)))[3] = achannel;
dst[3] = src1[3];
src1 += 4;
src2 += 4;
dst += 4;
}
}
static void do_blend_effect_float(float fac,
int64_t size,
const float *rect1,
const float *rect2,
StripBlendMode btype,
float *out)
{
switch (btype) {
case STRIP_BLEND_ADD:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_add_float);
break;
case STRIP_BLEND_SUB:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_sub_float);
break;
case STRIP_BLEND_MUL:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_mul_float);
break;
case STRIP_BLEND_DARKEN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_darken_float);
break;
case STRIP_BLEND_COLOR_BURN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_burn_float);
break;
case STRIP_BLEND_LINEAR_BURN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearburn_float);
break;
case STRIP_BLEND_SCREEN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_screen_float);
break;
case STRIP_BLEND_LIGHTEN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_lighten_float);
break;
case STRIP_BLEND_DODGE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_dodge_float);
break;
case STRIP_BLEND_OVERLAY:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_overlay_float);
break;
case STRIP_BLEND_SOFT_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_softlight_float);
break;
case STRIP_BLEND_HARD_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hardlight_float);
break;
case STRIP_BLEND_PIN_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_pinlight_float);
break;
case STRIP_BLEND_LIN_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearlight_float);
break;
case STRIP_BLEND_VIVID_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_vividlight_float);
break;
case STRIP_BLEND_BLEND_COLOR:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_color_float);
break;
case STRIP_BLEND_HUE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hue_float);
break;
case STRIP_BLEND_SATURATION:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_saturation_float);
break;
case STRIP_BLEND_VALUE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_luminosity_float);
break;
case STRIP_BLEND_DIFFERENCE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_difference_float);
break;
case STRIP_BLEND_EXCLUSION:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_exclusion_float);
break;
default:
break;
}
}
static void do_blend_effect_byte(float fac,
int64_t size,
const uchar *rect1,
const uchar *rect2,
StripBlendMode btype,
uchar *out)
{
switch (btype) {
case STRIP_BLEND_ADD:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_add_byte);
break;
case STRIP_BLEND_SUB:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_sub_byte);
break;
case STRIP_BLEND_MUL:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_mul_byte);
break;
case STRIP_BLEND_DARKEN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_darken_byte);
break;
case STRIP_BLEND_COLOR_BURN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_burn_byte);
break;
case STRIP_BLEND_LINEAR_BURN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearburn_byte);
break;
case STRIP_BLEND_SCREEN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_screen_byte);
break;
case STRIP_BLEND_LIGHTEN:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_lighten_byte);
break;
case STRIP_BLEND_DODGE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_dodge_byte);
break;
case STRIP_BLEND_OVERLAY:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_overlay_byte);
break;
case STRIP_BLEND_SOFT_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_softlight_byte);
break;
case STRIP_BLEND_HARD_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hardlight_byte);
break;
case STRIP_BLEND_PIN_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_pinlight_byte);
break;
case STRIP_BLEND_LIN_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_linearlight_byte);
break;
case STRIP_BLEND_VIVID_LIGHT:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_vividlight_byte);
break;
case STRIP_BLEND_BLEND_COLOR:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_color_byte);
break;
case STRIP_BLEND_HUE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_hue_byte);
break;
case STRIP_BLEND_SATURATION:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_saturation_byte);
break;
case STRIP_BLEND_VALUE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_luminosity_byte);
break;
case STRIP_BLEND_DIFFERENCE:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_difference_byte);
break;
case STRIP_BLEND_EXCLUSION:
apply_blend_function(fac, size, rect1, rect2, out, blend_color_exclusion_byte);
break;
default:
break;
}
}
struct BlendModeEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
if constexpr (std::is_same_v<T, float>) {
do_blend_effect_float(this->factor, size, src1, src2, this->blend_mode, dst);
}
else {
do_blend_effect_byte(this->factor, size, src1, src2, this->blend_mode, dst);
}
}
StripBlendMode blend_mode;
float factor;
};
static SeqResult do_blend_mode_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxBlend", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
BlendModeEffectOp op;
op.factor = fac;
op.blend_mode = strip->blend_mode;
apply_effect_op(op, src1.image, src2.image, dst.image);
return dst;
}
/* -------------------------------------------------------------------- */
/* Color Mix Effect */
static void init_colormix_effect(Strip *strip)
{
ColorMixVars *data = MEM_new<ColorMixVars>("colormixvars");
strip->effectdata = data;
data->blend_effect = STRIP_BLEND_OVERLAY;
data->factor = 1.0f;
}
static void free_colormix_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
MEM_delete(static_cast<ColorMixVars *>(strip->effectdata));
strip->effectdata = nullptr;
}
}
static SeqResult do_colormix_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float /*fac*/,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxColorMix", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
const ColorMixVars *data = static_cast<const ColorMixVars *>(strip->effectdata);
BlendModeEffectOp op;
op.blend_mode = data->blend_effect;
op.factor = data->factor;
apply_effect_op(op, src1.image, src2.image, dst.image);
return dst;
}
void blend_mode_effect_get_handle(EffectHandle &rval)
{
rval.execute = do_blend_mode_effect;
rval.early_out = early_out_mul_input2;
}
void color_mix_effect_get_handle(EffectHandle &rval)
{
rval.init = init_colormix_effect;
rval.free = free_colormix_effect;
rval.execute = do_colormix_effect;
rval.early_out = early_out_mul_input2;
}
void alpha_over_effect_get_handle(EffectHandle &rval)
{
rval.init = init_alpha_over_or_under;
rval.execute = do_alphaover_effect;
rval.early_out = early_out_mul_input1;
}
void alpha_under_effect_get_handle(EffectHandle &rval)
{
rval.init = init_alpha_over_or_under;
rval.execute = do_alphaunder_effect;
}
} // namespace blender::seq

View File

@@ -0,0 +1,204 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BKE_node_runtime.hh"
#include "COM_domain.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_sequence_types.h"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_sequencer.hh"
#include "cache/compositor_cache.hh"
#include "compositor.hh"
#include "effects.hh"
namespace blender::seq {
class CompositorEffectContext : public CompositorContext {
bNodeTree *node_group_;
ImBuf *input_1_;
ImBuf *input_2_;
ImBuf *output_;
float factor_;
public:
CompositorEffectContext(compositor::StaticCacheManager &cache_manager,
const RenderData &render_data,
bNodeTree *node_tree,
ImBuf *input_1,
ImBuf *input_2,
ImBuf *output,
float factor,
const Strip &strip)
: CompositorContext(cache_manager, render_data, strip),
node_group_(node_tree),
input_1_(input_1),
input_2_(input_2),
output_(output),
factor_(factor)
{
}
compositor::Domain get_compositing_domain() const override
{
return compositor::Domain(int2(this->output_->x, this->output_->y));
}
void write_viewer(compositor::Result &viewer_result) override
{
write_viewer_impl(viewer_result, *this->output_);
}
void evaluate()
{
using namespace compositor;
const bNodeTree &node_group = *DEG_get_evaluated<bNodeTree>(render_data_.depsgraph,
node_group_);
const bke::DataBlockComputeContext compute_context(nullptr, this->get_scene().id);
NodeGroupOperation node_group_operation(*this,
node_group,
this->needed_outputs(),
node_group.active_viewer_key,
bke::NODE_INSTANCE_KEY_BASE,
compute_context);
set_output_refcount(node_group, node_group_operation);
/* Map the inputs to the operation. */
Vector<std::unique_ptr<Result>> inputs;
int float_counter = 0;
int color_counter = 0;
for (const bNodeTreeInterfaceSocket *input_socket : node_group.interface_inputs()) {
const bke::bNodeSocketType *typeinfo = input_socket->socket_typeinfo();
Result *input_result = nullptr;
if (typeinfo && typeinfo->type == SOCK_FLOAT && float_counter == 0) {
/* First float input is factor. */
input_result = new Result(this->create_result(ResultType::Float, ResultPrecision::Full));
input_result->allocate_single_value();
input_result->set_single_value(this->factor_);
float_counter++;
}
else if (color_counter == 0 && this->input_1_) {
/* First input image. */
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
create_result_from_input(*input_result, *this->input_1_);
color_counter++;
}
else if (color_counter == 1 && this->input_2_) {
/* Second input image. */
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
create_result_from_input(*input_result, *this->input_2_);
color_counter++;
}
else {
/* Unsupported sockets. */
input_result = new Result(this->create_result(ResultType::Color, ResultPrecision::Full));
input_result->allocate_invalid();
}
node_group_operation.map_input_to_result(input_socket->identifier, input_result);
inputs.append(std::unique_ptr<Result>(input_result));
}
node_group_operation.evaluate();
this->write_outputs(node_group, node_group_operation, *this->output_);
}
};
static SeqResult do_compositor_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxCompositor", ProfileCategory::Draw);
const int x = context->rectx;
const int y = context->recty;
SeqResult out;
out.image = IMB_allocImBuf(x, y, ImBufFlags::FloatData | ImBufFlags::UninitializedPixels);
IMB_colormanagement_assign_float_colorspace(
out.image, IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_SCENE_LINEAR));
CompositorEffectVars *data = static_cast<CompositorEffectVars *>(strip->effectdata);
if (!data || !data->node_group) {
IMB_rectfill(out.image, float4(0, 0, 0, 1));
out.image->color_mode = ImColorMode::RGB;
out.is_opaque_before_transform = true;
}
else {
CompositorCache &com_cache = context->scene->ed->runtime->ensure_compositor_cache();
CompositorEffectContext com_context(com_cache.get_cache_manager(),
*context,
data->node_group,
src1.image,
src2.image,
out.image,
fac,
*strip);
if (com_context.use_gpu()) {
com_context.set_gpu_supported(render_begin_gpu(*context));
}
com_cache.recreate_if_needed(
com_context.use_gpu(), com_context.get_precision(), context->gpu_context);
com_context.evaluate();
com_context.cache_manager().reset();
if (com_context.use_gpu()) {
render_end_gpu(*context);
}
out.translation += com_context.get_result_translation();
out.is_opaque_before_transform = !out.image->can_contain_alpha();
}
return out;
}
static void init_compositor_effect(Strip *strip)
{
CompositorEffectVars *data = MEM_new<CompositorEffectVars>(__func__);
strip->effectdata = data;
}
static void free_compositor_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
CompositorEffectVars *data = static_cast<CompositorEffectVars *>(strip->effectdata);
MEM_delete(data);
strip->effectdata = nullptr;
}
}
static StripEarlyOut early_out_compositor(const Strip *strip, float /*fac*/)
{
/* No inputs: compositor generates the result. */
if (strip->input1 == nullptr) {
return StripEarlyOut::NoInput;
}
/* One or two inputs: do the effect. */
return StripEarlyOut::DoEffect;
}
void compositor_effect_get_handle(EffectHandle &rval)
{
rval.init = init_compositor_effect;
rval.free = free_compositor_effect;
rval.execute = do_compositor_effect;
rval.early_out = early_out_compositor;
}
} // namespace blender::seq

View File

@@ -0,0 +1,136 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_render.hh"
#include "effects.hh"
namespace blender::seq {
struct CrossEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
const float mfac = 1.0f - fac;
const int ifac = int(256.0f * fac);
const int imfac = 256 - ifac;
for (int64_t idx = 0; idx < size; idx++) {
if constexpr (std::is_same_v<T, uchar>) {
dst[0] = (imfac * src1[0] + ifac * src2[0]) >> 8;
dst[1] = (imfac * src1[1] + ifac * src2[1]) >> 8;
dst[2] = (imfac * src1[2] + ifac * src2[2]) >> 8;
dst[3] = (imfac * src1[3] + ifac * src2[3]) >> 8;
}
else {
dst[0] = mfac * src1[0] + fac * src2[0];
dst[1] = mfac * src1[1] + fac * src2[1];
dst[2] = mfac * src1[2] + fac * src2[2];
dst[3] = mfac * src1[3] + fac * src2[3];
}
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_cross_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxCross", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
CrossEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
dst.is_opaque_before_transform = !src1.image->can_contain_alpha() &&
!src2.image->can_contain_alpha();
return dst;
}
/* One could argue that gamma cross should not be hardcoded to 2.0 gamma,
* but instead either do proper input->linear conversion (often sRGB). Or
* maybe not even that, but do interpolation in some perceptual color space
* like OKLAB. But currently it is fixed to just 2.0 gamma. */
static float gammaCorrect(float c)
{
if (UNLIKELY(c < 0)) {
return -(c * c);
}
return c * c;
}
static float invGammaCorrect(float c)
{
return sqrtf_signed(c);
}
struct GammaCrossEffectOp {
template<typename T> void apply(const T *src1, const T *src2, T *dst, int64_t size) const
{
const float fac = this->factor;
const float mfac = 1.0f - fac;
for (int64_t idx = 0; idx < size; idx++) {
float4 col1 = load_premul_pixel(src1);
float4 col2 = load_premul_pixel(src2);
float4 col;
for (int c = 0; c < 4; ++c) {
col[c] = gammaCorrect(mfac * invGammaCorrect(col1[c]) + fac * invGammaCorrect(col2[c]));
}
store_premul_pixel(col, dst);
src1 += 4;
src2 += 4;
dst += 4;
}
}
float factor;
};
static SeqResult do_gammacross_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip * /*strip*/,
float /*timeline_frame*/,
float fac,
const SeqResult &src1,
const SeqResult &src2)
{
PRF_scope_with_name("SeqFxGammaCross", ProfileCategory::Draw);
SeqResult dst = prepare_effect_imbufs(context, src1, src2);
GammaCrossEffectOp op;
op.factor = fac;
apply_effect_op(op, src1.image, src2.image, dst.image);
dst.is_opaque_before_transform = !src1.image->can_contain_alpha() &&
!src2.image->can_contain_alpha();
return dst;
}
void cross_effect_get_handle(EffectHandle &rval)
{
rval.execute = do_cross_effect;
rval.early_out = early_out_fade;
}
void gamma_cross_effect_get_handle(EffectHandle &rval)
{
rval.early_out = early_out_fade;
rval.execute = do_gammacross_effect;
}
} // namespace blender::seq

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_math_base.hh"
#include "BLI_task.hh"
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_render.hh"
#include "effects.hh"
namespace blender::seq {
static void init_gaussian_blur_effect(Strip *strip)
{
GaussianBlurVars *data = MEM_new<GaussianBlurVars>("gaussianblurvars");
strip->effectdata = data;
data->size_x = 9.0f;
data->size_y = 9.0f;
}
static void free_gaussian_blur_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
GaussianBlurVars *data = static_cast<GaussianBlurVars *>(strip->effectdata);
MEM_delete(data);
strip->effectdata = nullptr;
}
}
static StripEarlyOut early_out_gaussian_blur(const Strip *strip, float /*fac*/)
{
GaussianBlurVars *data = static_cast<GaussianBlurVars *>(strip->effectdata);
if (data->size_x == 0.0f && data->size_y == 0) {
return StripEarlyOut::UseInput1;
}
return StripEarlyOut::DoEffect;
}
template<typename T>
static void gaussian_blur_x(const Span<float> gaussian,
int half_size,
int start_line,
int width,
int height,
int /*frame_height*/,
const T *rect,
T *dst)
{
dst += int64_t(start_line) * width * 4;
for (int y = start_line; y < start_line + height; y++) {
for (int x = 0; x < width; x++) {
float4 accum(0.0f);
float accum_weight = 0.0f;
int xmin = math::max(x - half_size, 0);
int xmax = math::min(x + half_size, width - 1);
for (int nx = xmin, index = (xmin - x) + half_size; nx <= xmax; nx++, index++) {
float weight = gaussian[index];
int offset = (y * width + nx) * 4;
accum += float4(rect + offset) * weight;
accum_weight += weight;
}
accum *= (1.0f / accum_weight);
if constexpr (math::is_math_float_type<T>) {
dst[0] = accum[0];
dst[1] = accum[1];
dst[2] = accum[2];
dst[3] = accum[3];
}
else {
dst[0] = accum[0] + 0.5f;
dst[1] = accum[1] + 0.5f;
dst[2] = accum[2] + 0.5f;
dst[3] = accum[3] + 0.5f;
}
dst += 4;
}
}
}
template<typename T>
static void gaussian_blur_y(const Span<float> gaussian,
int half_size,
int start_line,
int width,
int height,
int frame_height,
const T *rect,
T *dst)
{
dst += int64_t(start_line) * width * 4;
for (int y = start_line; y < start_line + height; y++) {
for (int x = 0; x < width; x++) {
float4 accum(0.0f);
float accum_weight = 0.0f;
int ymin = math::max(y - half_size, 0);
int ymax = math::min(y + half_size, frame_height - 1);
for (int ny = ymin, index = (ymin - y) + half_size; ny <= ymax; ny++, index++) {
float weight = gaussian[index];
int offset = (ny * width + x) * 4;
accum += float4(rect + offset) * weight;
accum_weight += weight;
}
accum *= (1.0f / accum_weight);
if constexpr (math::is_math_float_type<T>) {
dst[0] = accum[0];
dst[1] = accum[1];
dst[2] = accum[2];
dst[3] = accum[3];
}
else {
dst[0] = accum[0] + 0.5f;
dst[1] = accum[1] + 0.5f;
dst[2] = accum[2] + 0.5f;
dst[3] = accum[3] + 0.5f;
}
dst += 4;
}
}
}
static SeqResult do_gaussian_blur_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float /*fac*/,
const SeqResult &ibuf1,
const SeqResult & /*ibuf2*/)
{
PRF_scope_with_name("SeqFxBlur", ProfileCategory::Draw);
/* Create blur kernel weights. */
const GaussianBlurVars *data = static_cast<const GaussianBlurVars *>(strip->effectdata);
const float size_scale = seq::get_render_scale_factor(*context);
const float size_x = data->size_x * size_scale;
const float size_y = data->size_y * size_scale;
const int half_size_x = int(size_x + 0.5f);
const int half_size_y = int(size_y + 0.5f);
Array<float> gaussian_x = make_gaussian_blur_kernel(size_x, half_size_x);
Array<float> gaussian_y = make_gaussian_blur_kernel(size_y, half_size_y);
const int width = context->rectx;
const int height = context->recty;
const bool is_float = ibuf1.image->float_data();
/* Horizontal blur: create output, blur ibuf1 into it. */
SeqResult out = prepare_effect_imbufs(context, ibuf1, {});
threading::parallel_for(IndexRange(context->recty), 32, [&](const IndexRange y_range) {
const int y_first = y_range.first();
const int y_size = y_range.size();
if (is_float) {
gaussian_blur_x(gaussian_x,
half_size_x,
y_first,
width,
y_size,
height,
ibuf1.image->float_data(),
out.image->float_data_for_write());
}
else {
gaussian_blur_x(gaussian_x,
half_size_x,
y_first,
width,
y_size,
height,
ibuf1.image->byte_data(),
out.image->byte_data_for_write());
}
});
/* Vertical blur: create output, blur previous output into it. */
SeqResult vin = out;
out = prepare_effect_imbufs(context, vin, {});
threading::parallel_for(IndexRange(context->recty), 32, [&](const IndexRange y_range) {
const int y_first = y_range.first();
const int y_size = y_range.size();
if (is_float) {
gaussian_blur_y(gaussian_y,
half_size_y,
y_first,
width,
y_size,
height,
vin.image->float_data(),
out.image->float_data_for_write());
}
else {
gaussian_blur_y(gaussian_y,
half_size_y,
y_first,
width,
y_size,
height,
vin.image->byte_data(),
out.image->byte_data_for_write());
}
});
/* Free the first output. */
IMB_freeImBuf(vin.image);
return out;
}
void gaussian_blur_effect_get_handle(EffectHandle &rval)
{
rval.init = init_gaussian_blur_effect;
rval.free = free_gaussian_blur_effect;
rval.early_out = early_out_gaussian_blur;
rval.execute = do_gaussian_blur_effect;
}
} // namespace blender::seq

View File

@@ -0,0 +1,246 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_render.hh"
#include "effects.hh"
namespace blender::seq {
static void glow_blur_bitmap(
const float4 *src, float4 *map, int width, int height, float blur, int quality)
{
/* If we're not really blurring, bail out */
if (blur <= 0) {
return;
}
/* If result would be no blurring, early out. */
const int halfWidth = ((quality + 1) * blur);
if (halfWidth == 0) {
return;
}
Array<float4> temp(width * height);
/* Initialize the gaussian filter.
* TODO: use code from #filter_kernel_value. */
Array<float> filter(halfWidth * 2);
const float k = -1.0f / (2.0f * float(M_PI) * blur * blur);
float weight = 0;
for (int ix = 0; ix < halfWidth; ix++) {
weight = exp(k * (ix * ix));
filter[halfWidth - ix] = weight;
filter[halfWidth + ix] = weight;
}
filter[0] = weight;
/* Normalize the array */
float fval = 0;
for (int ix = 0; ix < halfWidth * 2; ix++) {
fval += filter[ix];
}
for (int ix = 0; ix < halfWidth * 2; ix++) {
filter[ix] /= fval;
}
/* Blur the rows: read map, write temp */
threading::parallel_for(IndexRange(height), 32, [&](const IndexRange y_range) {
for (const int y : y_range) {
for (int x = 0; x < width; x++) {
float4 curColor = float4(0.0f);
int xmin = math::max(x - halfWidth, 0);
int xmax = math::min(x + halfWidth, width);
for (int nx = xmin, index = (xmin - x) + halfWidth; nx < xmax; nx++, index++) {
curColor += map[nx + y * width] * filter[index];
}
temp[x + y * width] = curColor;
}
}
});
/* Blur the columns: read temp, write map */
threading::parallel_for(IndexRange(width), 32, [&](const IndexRange x_range) {
const float4 one = float4(1.0f);
for (const int x : x_range) {
for (int y = 0; y < height; y++) {
float4 curColor = float4(0.0f);
int ymin = math::max(y - halfWidth, 0);
int ymax = math::min(y + halfWidth, height);
for (int ny = ymin, index = (ymin - y) + halfWidth; ny < ymax; ny++, index++) {
curColor += temp[x + ny * width] * filter[index];
}
if (src != nullptr) {
curColor = math::min(one, src[x + y * width] + curColor);
}
map[x + y * width] = curColor;
}
}
});
}
static void blur_isolate_highlights(const float4 *in,
float4 *out,
int width,
int height,
float threshold,
float boost,
float clamp)
{
threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) {
const float4 clampv = float4(clamp);
for (const int y : y_range) {
int index = y * width;
for (int x = 0; x < width; x++, index++) {
/* Isolate the intensity */
float intensity = (in[index].x + in[index].y + in[index].z - threshold);
float4 val;
if (intensity > 0) {
val = math::min(clampv, in[index] * (boost * intensity));
}
else {
val = float4(0.0f);
}
out[index] = val;
}
}
});
}
static void init_glow_effect(Strip *strip)
{
GlowVars *data = MEM_new<GlowVars>("glowvars");
strip->effectdata = data;
data->fMini = 0.25f;
data->fClamp = 1.0f;
data->fBoost = 0.5f;
data->dDist = 3.0f;
data->dQuality = 3;
data->bNoComp = 0;
}
static void free_glow_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
GlowVars *data = static_cast<GlowVars *>(strip->effectdata);
MEM_delete(data);
strip->effectdata = nullptr;
}
}
static void do_glow_effect_byte(Strip *strip,
int render_size,
float fac,
int x,
int y,
const uchar *rect1,
const uchar * /*rect2*/,
uchar *out)
{
GlowVars *glow = static_cast<GlowVars *>(strip->effectdata);
Array<float4> inbuf(x * y);
Array<float4> outbuf(x * y);
IMB_colormanagement_transform_byte_to_float(*inbuf.data(), rect1, x, y, 4, "sRGB", "sRGB");
blur_isolate_highlights(
inbuf.data(), outbuf.data(), x, y, glow->fMini * 3.0f, glow->fBoost * fac, glow->fClamp);
glow_blur_bitmap(glow->bNoComp ? nullptr : inbuf.data(),
outbuf.data(),
x,
y,
glow->dDist * (render_size / 100.0f),
glow->dQuality);
threading::parallel_for(IndexRange(y), 64, [&](const IndexRange y_range) {
size_t offset = y_range.first() * x;
IMB_buffer_byte_from_float(
out + offset * 4, *(outbuf.data() + offset), 4, 0.0f, true, x, y_range.size(), x);
});
}
static void do_glow_effect_float(Strip *strip,
int render_size,
float fac,
int x,
int y,
const float *rect1,
const float * /*rect2*/,
float *out)
{
float4 *outbuf = reinterpret_cast<float4 *>(out);
const float4 *inbuf = reinterpret_cast<const float4 *>(rect1);
GlowVars *glow = static_cast<GlowVars *>(strip->effectdata);
blur_isolate_highlights(
inbuf, outbuf, x, y, glow->fMini * 3.0f, glow->fBoost * fac, glow->fClamp);
glow_blur_bitmap(glow->bNoComp ? nullptr : inbuf,
outbuf,
x,
y,
glow->dDist * (render_size / 100.0f),
glow->dQuality);
}
static SeqResult do_glow_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float fac,
const SeqResult &ibuf1,
const SeqResult & /*ibuf2*/)
{
PRF_scope_with_name("SeqFxGlow", ProfileCategory::Draw);
SeqResult out = prepare_effect_imbufs(context, ibuf1, {});
int render_size = 100 * context->rectx / context->scene->r.xsch;
if (out.image->float_data()) {
do_glow_effect_float(strip,
render_size,
fac,
context->rectx,
context->recty,
ibuf1.image->float_data(),
nullptr,
out.image->float_data_for_write());
}
else {
do_glow_effect_byte(strip,
render_size,
fac,
context->rectx,
context->recty,
ibuf1.image->byte_data(),
nullptr,
out.image->byte_data_for_write());
}
return out;
}
void glow_effect_get_handle(EffectHandle &rval)
{
rval.init = init_glow_effect;
rval.free = free_glow_effect;
rval.execute = do_glow_effect;
}
} // namespace blender::seq

View File

@@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "PRF_profile.hh"
#include "SEQ_channels.hh"
#include "SEQ_render.hh"
#include "SEQ_utils.hh"
#include "effects.hh"
#include "render.hh"
namespace blender::seq {
static StripEarlyOut early_out_multicam(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::NoInput;
}
static SeqResult do_multicam(const RenderData *context,
SeqRenderState *state,
Strip *strip,
float timeline_frame,
float /*fac*/,
const SeqResult & /*ibuf1*/,
const SeqResult & /*ibuf2*/)
{
PRF_scope_with_name("SeqFxMultiCam", ProfileCategory::Draw);
if (strip->multicam_source == 0 || strip->multicam_source >= strip->channel) {
return {};
}
Editing *ed = context->scene->ed;
if (!ed || state->strips_in_progress.contains(strip)) {
return {};
}
ListBaseT<Strip> *seqbasep = get_seqbase_by_strip(context->scene, strip);
ListBaseT<SeqTimelineChannel> *channels = get_channels_by_strip(ed, strip);
if (!seqbasep) {
return {};
}
state->strips_in_progress.add(strip);
SeqResult out = seq_render_give_ibuf_seqbase(
context, state, timeline_frame, strip->multicam_source, channels, seqbasep);
state->strips_in_progress.remove(strip);
return out;
}
void multi_camera_effect_get_handle(EffectHandle &rval)
{
rval.early_out = early_out_multicam;
rval.execute = do_multicam;
}
} // namespace blender::seq

View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_task.hh"
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "effects.hh"
namespace blender::seq {
static void init_solid_color(Strip *strip)
{
SolidColorVars *data = MEM_new<SolidColorVars>("solidcolor");
strip->effectdata = data;
data->col[0] = data->col[1] = data->col[2] = 0.5;
data->width = data->height = 1;
}
static void free_solid_color(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
SolidColorVars *data = static_cast<SolidColorVars *>(strip->effectdata);
MEM_delete(data);
strip->effectdata = nullptr;
}
}
static StripEarlyOut early_out_color(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::NoInput;
}
static SeqResult do_solid_color(const RenderData * /*context*/,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float /*fac*/,
const SeqResult & /*ibuf1*/,
const SeqResult & /*ibuf2*/)
{
PRF_scope_with_name("SeqFxColor", ProfileCategory::Draw);
SeqResult out;
const SolidColorVars *cv = static_cast<const SolidColorVars *>(strip->effectdata);
out.image = IMB_allocImBuf(cv->width, cv->height, ImBufFlags::ByteData);
uchar color[4];
rgb_float_to_uchar(color, cv->col);
color[3] = 255;
uchar *byte_data = out.image->byte_data_for_write();
threading::parallel_for(IndexRange(out.image->y), 64, [&](const IndexRange y_range) {
uchar *dst = byte_data + y_range.first() * out.image->x * 4;
uchar *dst_end = dst + y_range.size() * out.image->x * 4;
while (dst < dst_end) {
memcpy(dst, color, sizeof(color));
dst += 4;
}
});
out.image->color_mode = ImColorMode::RGB;
out.is_opaque_before_transform = true;
return out;
}
void solid_color_effect_get_handle(EffectHandle &rval)
{
rval.init = init_solid_color;
rval.free = free_solid_color;
rval.early_out = early_out_color;
rval.execute = do_solid_color;
}
} // namespace blender::seq

View File

@@ -0,0 +1,213 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BKE_fcurve.hh"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "RNA_prototypes.hh"
#include "SEQ_render.hh"
#include "SEQ_time.hh"
#include "effects.hh"
#include "render.hh"
namespace blender::seq {
static void init_speed_effect(Strip *strip)
{
SpeedControlVars *data = MEM_new<SpeedControlVars>("speedcontrolvars");
strip->effectdata = data;
data->speed_control_type = SEQ_SPEED_STRETCH;
data->speed_fader = 1.0f;
data->speed_fader_length = 0.0f;
data->speed_fader_frame_number = 0.0f;
}
static void free_speed_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
SpeedControlVars *v = static_cast<SpeedControlVars *>(strip->effectdata);
if (v->frameMap) {
MEM_delete(v->frameMap);
}
MEM_delete(v);
strip->effectdata = nullptr;
}
}
static void copy_speed_effect(Strip *dst, const Strip *src, const int /*flag*/)
{
SpeedControlVars *v = MEM_dupalloc(static_cast<SpeedControlVars *>(src->effectdata));
v->frameMap = nullptr;
dst->effectdata = v;
}
static StripEarlyOut early_out_speed(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::DoEffect;
}
static FCurve *strip_effect_speed_speed_factor_curve_get(Scene *scene, Strip *strip)
{
return id_data_find_fcurve(&scene->id, strip, RNA_Strip, "speed_factor", 0, nullptr);
}
void strip_effect_speed_rebuild_map(Scene *scene, Strip *strip)
{
const int effect_strip_length = strip->right_handle(scene) - strip->left_handle();
if ((strip->input1 == nullptr) || (effect_strip_length < 1)) {
return; /* Make COVERITY happy and check for (CID 598) input strip. */
}
const FCurve *fcu = strip_effect_speed_speed_factor_curve_get(scene, strip);
if (fcu == nullptr) {
return;
}
SpeedControlVars *v = static_cast<SpeedControlVars *>(strip->effectdata);
if (v->frameMap) {
MEM_delete(v->frameMap);
}
v->frameMap = MEM_new_array_uninitialized<float>(size_t(effect_strip_length), __func__);
v->frameMap[0] = 0.0f;
float target_frame = 0;
for (int frame_index = 1; frame_index < effect_strip_length; frame_index++) {
target_frame += evaluate_fcurve(fcu, strip->left_handle() + frame_index);
const int target_frame_max = strip->input1->length(scene);
CLAMP(target_frame, 0, target_frame_max);
v->frameMap[frame_index] = target_frame;
}
}
static void strip_effect_speed_frame_map_ensure(Scene *scene, Strip *strip)
{
const SpeedControlVars *v = static_cast<SpeedControlVars *>(strip->effectdata);
if (v->frameMap != nullptr) {
return;
}
strip_effect_speed_rebuild_map(scene, strip);
}
float strip_speed_effect_target_frame_get(Scene *scene,
Strip *strip_speed,
float timeline_frame,
int input)
{
if (strip_speed->input1 == nullptr) {
return 0.0f;
}
strip_effect_handle_get(strip_speed); /* Ensure, that data are initialized. */
int frame_index = round_fl_to_int(give_frame_index(scene, strip_speed, timeline_frame));
SpeedControlVars *s = static_cast<SpeedControlVars *>(strip_speed->effectdata);
const Strip *source = strip_speed->input1;
float target_frame = 0.0f;
switch (s->speed_control_type) {
case SEQ_SPEED_STRETCH: {
/* Only right handle controls effect speed! */
const float target_content_length = source->length(scene) - source->startofs;
const float speed_effetct_length = strip_speed->right_handle(scene) -
strip_speed->left_handle();
const float ratio = frame_index / speed_effetct_length;
target_frame = target_content_length * ratio;
break;
}
case SEQ_SPEED_MULTIPLY: {
const FCurve *fcu = strip_effect_speed_speed_factor_curve_get(scene, strip_speed);
if (fcu != nullptr) {
strip_effect_speed_frame_map_ensure(scene, strip_speed);
target_frame = s->frameMap[frame_index];
}
else {
target_frame = frame_index * s->speed_fader;
if (s->speed_fader < 0) {
/* Treat `target_frame` as a negative offset from the last frame of the strip. */
target_frame += source->length(scene);
}
}
break;
}
case SEQ_SPEED_LENGTH:
target_frame = source->length(scene) * (s->speed_fader_length / 100.0f);
break;
case SEQ_SPEED_FRAME_NUMBER:
target_frame = s->speed_fader_frame_number;
break;
}
CLAMP(target_frame, 0, source->length(scene));
target_frame += strip_speed->start;
/* No interpolation. */
if ((s->flags & SEQ_SPEED_USE_INTERPOLATION) == 0) {
return target_frame;
}
/* Interpolation is used, switch between current and next frame based on which input is
* requested. */
return input == 0 ? target_frame : ceil(target_frame);
}
static float speed_effect_interpolation_ratio_get(Scene *scene,
Strip *strip_speed,
float timeline_frame)
{
const float target_frame = strip_speed_effect_target_frame_get(
scene, strip_speed, timeline_frame, 0);
return target_frame - floor(target_frame);
}
static SeqResult do_speed_effect(const RenderData *context,
SeqRenderState *state,
Strip *strip,
float timeline_frame,
float fac,
const SeqResult &ibuf1,
const SeqResult &ibuf2)
{
PRF_scope_with_name("SeqFxSpeed", ProfileCategory::Draw);
SeqResult out;
const SpeedControlVars *s = static_cast<SpeedControlVars *>(strip->effectdata);
EffectHandle cross_effect = effect_handle_get(STRIP_TYPE_CROSS);
if (s->flags & SEQ_SPEED_USE_INTERPOLATION) {
fac = speed_effect_interpolation_ratio_get(context->scene, strip, timeline_frame);
/* Current frame is ibuf1, next frame is ibuf2. */
out = cross_effect.execute(context, state, nullptr, timeline_frame, fac, ibuf1, ibuf2);
return out;
}
/* No interpolation. */
out.image = IMB_dupImBuf(ibuf1.image);
out.is_opaque_before_transform = !ibuf1.image->can_contain_alpha();
return out;
}
void speed_effect_get_handle(EffectHandle &rval)
{
rval.init = init_speed_effect;
rval.free = free_speed_effect;
rval.copy = copy_speed_effect;
rval.execute = do_speed_effect;
rval.early_out = early_out_speed;
}
} // namespace blender::seq

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,253 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include <algorithm>
#include "BLI_math_vector.hh"
#include "BLI_task.hh"
#include "DNA_sequence_types.h"
#include "IMB_imbuf.hh"
#include "PRF_profile.hh"
#include "SEQ_render.hh"
#include "effects.hh"
namespace blender::seq {
struct WipeData {
WipeData(const WipeVars *wipe, int width, int height, float fac)
{
this->type = wipe->wipetype;
this->forward = wipe->forward != 0;
this->size = float2(width, height);
if (this->type == SEQ_WIPE_SINGLE) {
/* Position that the wipe line goes through: moves along
* the image diagonal. The other diagonal when angle is negative. */
this->pos = this->size * (this->forward ? fac : (1.0f - fac));
if (wipe->angle < 0.0f) {
this->pos.x = this->size.x - this->pos.x;
}
}
if (this->type == SEQ_WIPE_DOUBLE) {
/* For double blend, position goes from center of screen
* along the diagonal. The other blend line position will be
* a mirror of it. */
float2 offset = this->size * (this->forward ? (1.0f - fac) : fac) * 0.5f;
if (wipe->angle < 0.0f) {
offset.x = -offset.x;
}
this->pos = this->size * 0.5f + offset;
}
/* Line direction: (cos(a), sin(a)). Perpendicular: (-sin(a), cos(a)).
* Angle is negative to match previous behavior. */
this->normal.x = -sinf(-wipe->angle);
this->normal.y = cosf(-wipe->angle);
/* Blend zone width. */
float blend_width = wipe->edgeWidth * ((width + height) / 2.0f);
if (ELEM(this->type, SEQ_WIPE_DOUBLE, SEQ_WIPE_IRIS)) {
blend_width *= 0.5f;
}
/* For single/double wipes, make sure the blend zone goes to zero at start & end
* of transition. */
if (ELEM(this->type, SEQ_WIPE_SINGLE, SEQ_WIPE_DOUBLE)) {
blend_width = std::min(blend_width, fac * this->size.y);
blend_width = std::min(blend_width, this->size.y - fac * this->size.y);
}
this->blend_width_inv = math::safe_rcp(blend_width);
if (this->type == SEQ_WIPE_IRIS) {
/* Distance to Iris circle at current factor. */
float2 iris = this->size * 0.5f * (this->forward ? (1.0f - fac) : fac);
this->iris_dist = math::length(iris);
}
if (this->type == SEQ_WIPE_CLOCK) {
float angle_cur = 2.0f * float(M_PI) * (this->forward ? (1.0f - fac) : fac);
float angle_width = wipe->edgeWidth * float(M_PI);
float delta_neg = angle_width * (this->forward ? fac : (1.0f - fac));
float delta_pos = angle_width * (this->forward ? (1.0f - fac) : fac);
this->clock_angles.x = std::max(angle_cur - delta_neg, 0.0f);
this->clock_angles.y = std::min(angle_cur + delta_pos, 2.0f * float(M_PI));
this->clock_angle_inv_dif = math::safe_rcp(this->clock_angles.y - this->clock_angles.x);
}
}
float2 size; /* Image size. */
float2 pos; /* Position that wipe line goes through. */
float2 normal; /* Normal vector to single/double wipe line. */
float blend_width_inv = 0.0f;
float iris_dist = 0.0f;
float2 clock_angles; /* Min, max clock angles at current factor. */
float clock_angle_inv_dif = 0.0f;
eEffectWipeType type;
bool forward = false;
};
static float calc_wipe_band(float dist, float inv_width)
{
if (inv_width == 0.0f) {
return dist < 0.0f ? 0.0f : 1.0f;
}
return dist * inv_width + 0.5f;
}
static float calc_wipe_blend(const WipeData *data, int x, int y)
{
float output = 0.0f;
switch (data->type) {
case SEQ_WIPE_SINGLE: {
/* Distance to line: dot(pixel_pos - line_pos, line_normal). */
float dist = math::dot(float2(x, y) - data->pos, data->normal);
output = calc_wipe_band(dist, data->blend_width_inv);
} break;
case SEQ_WIPE_DOUBLE: {
/* Distance to line: dot(pixel_pos - line_pos, line_normal).
* For double wipe, we have two lines to calculate the distance to. */
float2 pos1 = data->pos;
float2 pos2 = data->size - data->pos;
float dist1 = math::dot(float2(x, y) - pos1, -data->normal);
float dist2 = math::dot(float2(x, y) - pos2, data->normal);
float dist = std::min(dist1, dist2);
output = calc_wipe_band(dist, data->blend_width_inv);
} break;
case SEQ_WIPE_CLOCK: {
float2 offset = float2(x, y) - data->size * 0.5f;
if (math::length_squared(offset) < 1.0e-3f) {
output = 0.0f;
}
else {
float angle;
angle = atan2f(offset.y, offset.x);
if (angle < 0.0f) {
angle += 2.0f * float(M_PI);
}
if (angle < data->clock_angles.x) {
output = 1;
}
else if (angle > data->clock_angles.y) {
output = 0;
}
else {
output = (data->clock_angles.y - angle) * data->clock_angle_inv_dif;
}
}
} break;
case SEQ_WIPE_IRIS: {
float dist = math::distance(float2(x, y), data->size * 0.5f);
output = calc_wipe_band(data->iris_dist - dist, data->blend_width_inv);
} break;
}
if (!data->forward) {
output = 1.0f - output;
}
return output;
}
static void init_wipe_effect(Strip *strip)
{
strip->effectdata = MEM_new<WipeVars>("wipevars");
}
static void free_wipe_effect(Strip *strip, const bool /*do_id_user*/)
{
if (strip->effectdata) {
MEM_delete(static_cast<WipeVars *>(strip->effectdata));
strip->effectdata = nullptr;
}
}
template<typename T>
static void do_wipe_effect(
const Strip *strip, float fac, int width, int height, const T *rect1, const T *rect2, T *out)
{
const WipeVars *wipe = static_cast<const WipeVars *>(strip->effectdata);
const WipeData data(wipe, width, height, fac);
threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) {
const T *cp1 = rect1 + y_range.first() * width * 4;
const T *cp2 = rect2 + y_range.first() * width * 4;
T *rt = out + y_range.first() * width * 4;
for (const int y : y_range) {
for (int x = 0; x < width; x++) {
float blend = calc_wipe_blend(&data, x, y);
if (blend <= 0.0f) {
memcpy(rt, cp2, sizeof(T) * 4);
}
else if (blend >= 1.0f) {
memcpy(rt, cp1, sizeof(T) * 4);
}
else {
float4 col1 = load_premul_pixel(cp1);
float4 col2 = load_premul_pixel(cp2);
float4 col = col1 * blend + col2 * (1.0f - blend);
store_premul_pixel(col, rt);
}
rt += 4;
cp1 += 4;
cp2 += 4;
}
}
});
}
static SeqResult do_wipe_effect(const RenderData *context,
SeqRenderState * /*state*/,
Strip *strip,
float /*timeline_frame*/,
float fac,
const SeqResult &ibuf1,
const SeqResult &ibuf2)
{
PRF_scope_with_name("SeqFxWipe", ProfileCategory::Draw);
SeqResult out = prepare_effect_imbufs(context, ibuf1, ibuf2);
if (out.image->float_data()) {
do_wipe_effect(strip,
fac,
context->rectx,
context->recty,
ibuf1.image->float_data(),
ibuf2.image->float_data(),
out.image->float_data_for_write());
}
else {
do_wipe_effect(strip,
fac,
context->rectx,
context->recty,
ibuf1.image->byte_data(),
ibuf2.image->byte_data(),
out.image->byte_data_for_write());
}
out.is_opaque_before_transform = !ibuf1.image->can_contain_alpha() &&
!ibuf2.image->can_contain_alpha();
return out;
}
void wipe_effect_get_handle(EffectHandle &rval)
{
rval.init = init_wipe_effect;
rval.free = free_wipe_effect;
rval.early_out = early_out_fade;
rval.execute = do_wipe_effect;
}
} // namespace blender::seq

View File

@@ -0,0 +1,383 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2003-2024 Blender Authors
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup sequencer
*/
#include "BLI_math_filter.hh"
#include "BKE_fcurve.hh"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_metadata.hh"
#include "PRF_profile.hh"
#include "RNA_prototypes.hh"
#include "SEQ_render.hh"
#include "effects.hh"
#include "render.hh"
namespace blender::seq {
SeqResult prepare_effect_imbufs(const RenderData *context,
const SeqResult &ibuf1,
const SeqResult &ibuf2,
bool uninitialized_pixels)
{
PRF_scope_with_name("SeqFxPrepareImbufs", ProfileCategory::Draw);
SeqResult out;
Scene *scene = context->scene;
int x = context->rectx;
int y = context->recty;
ImBufFlags base_flags = uninitialized_pixels ? ImBufFlags::UninitializedPixels :
ImBufFlags::Zero;
if (!ibuf1.is_valid() && !ibuf2.is_valid()) {
out.image = IMB_allocImBuf(x, y, ImBufFlags::ByteData | base_flags);
}
else if ((ibuf1.is_valid() && ibuf1.image->float_data()) ||
(ibuf2.is_valid() && ibuf2.image->float_data()))
{
/* if any inputs are float, output is float too */
out.image = IMB_allocImBuf(x, y, ImBufFlags::FloatData | base_flags);
}
else {
out.image = IMB_allocImBuf(x, y, ImBufFlags::ByteData | base_flags);
}
if (out.image->float_data()) {
if (ibuf1.is_valid()) {
ensure_ibuf_is_sequencer_space(scene, ibuf1.image, true);
}
if (ibuf2.is_valid()) {
ensure_ibuf_is_sequencer_space(scene, ibuf2.image, true);
}
IMB_colormanagement_assign_float_colorspace(out.image,
scene->sequencer_colorspace_settings.name);
}
else {
if (ibuf1.is_valid() && !ibuf1.image->byte_data()) {
IMB_byte_from_float(ibuf1.image);
}
if (ibuf2.is_valid() && !ibuf2.image->byte_data()) {
IMB_byte_from_float(ibuf2.image);
}
}
/* If effect only affecting a single channel, forward input's metadata to the output. */
if (ibuf1.is_valid() && ibuf1.image == ibuf2.image) {
IMB_metadata_copy(out.image, ibuf1.image);
}
return out;
}
Array<float> make_gaussian_blur_kernel(float rad, int size)
{
int n = 2 * size + 1;
Array<float> gaussian(n);
float sum = 0.0f;
float fac = (rad > 0.0f ? 1.0f / rad : 0.0f);
for (int i = -size; i <= size; i++) {
float val = math::filter_kernel_value(math::FilterKernel::Gauss, float(i) * fac);
sum += val;
gaussian[i + size] = val;
}
float inv_sum = 1.0f / sum;
for (int i = 0; i < n; i++) {
gaussian[i] *= inv_sum;
}
return gaussian;
}
static void init_noop(Strip * /*strip*/) {}
static void copy_effect_default(Strip *dst, const Strip *src, const int /*flag*/)
{
dst->effectdata = MEM_dupalloc_void(src->effectdata);
}
static StripEarlyOut early_out_noop(const Strip * /*strip*/, float /*fac*/)
{
return StripEarlyOut::DoEffect;
}
StripEarlyOut early_out_fade(const Strip * /*strip*/, float fac)
{
if (fac == 0.0f) {
return StripEarlyOut::UseInput1;
}
if (fac == 1.0f) {
return StripEarlyOut::UseInput2;
}
return StripEarlyOut::DoEffect;
}
StripEarlyOut early_out_mul_input2(const Strip * /*strip*/, float fac)
{
if (fac == 0.0f) {
return StripEarlyOut::UseInput1;
}
return StripEarlyOut::DoEffect;
}
StripEarlyOut early_out_mul_input1(const Strip * /*strip*/, float fac)
{
if (fac == 0.0f) {
return StripEarlyOut::UseInput2;
}
return StripEarlyOut::DoEffect;
}
void effect_ensure_initialized(Strip *strip)
{
if (strip->effectdata == nullptr) {
EffectHandle h = strip_effect_handle_get(strip);
if (h.init != nullptr) {
h.init(strip);
}
}
}
void effect_free(Strip *strip)
{
EffectHandle h = strip_effect_handle_get(strip);
if (h.free != nullptr) {
h.free(strip, true);
BLI_assert(strip->effectdata == nullptr);
}
}
EffectHandle effect_handle_get(StripType strip_type)
{
EffectHandle rval;
rval.init = init_noop;
rval.free = nullptr;
rval.early_out = early_out_noop;
rval.execute = nullptr;
rval.copy = copy_effect_default;
switch (strip_type) {
case STRIP_TYPE_CROSS:
cross_effect_get_handle(rval);
break;
case STRIP_TYPE_GAMCROSS:
gamma_cross_effect_get_handle(rval);
break;
case STRIP_TYPE_COMPOSITOR:
compositor_effect_get_handle(rval);
break;
case STRIP_TYPE_ADD:
add_effect_get_handle(rval);
break;
case STRIP_TYPE_SUB:
sub_effect_get_handle(rval);
break;
case STRIP_TYPE_MUL:
mul_effect_get_handle(rval);
break;
case STRIP_TYPE_COLORMIX:
color_mix_effect_get_handle(rval);
break;
case STRIP_TYPE_ALPHAOVER:
alpha_over_effect_get_handle(rval);
break;
case STRIP_TYPE_ALPHAUNDER:
alpha_under_effect_get_handle(rval);
break;
case STRIP_TYPE_WIPE:
wipe_effect_get_handle(rval);
break;
case STRIP_TYPE_GLOW:
glow_effect_get_handle(rval);
break;
case STRIP_TYPE_SPEED:
speed_effect_get_handle(rval);
break;
case STRIP_TYPE_COLOR:
solid_color_effect_get_handle(rval);
break;
case STRIP_TYPE_MULTICAM:
multi_camera_effect_get_handle(rval);
break;
case STRIP_TYPE_ADJUSTMENT:
adjustment_effect_get_handle(rval);
break;
case STRIP_TYPE_GAUSSIAN_BLUR:
gaussian_blur_effect_get_handle(rval);
break;
case STRIP_TYPE_TEXT:
text_effect_get_handle(rval);
break;
default:
break;
}
return rval;
}
static EffectHandle effect_handle_for_blend_mode_get(StripBlendMode blend)
{
EffectHandle rval;
rval.init = init_noop;
rval.free = nullptr;
rval.early_out = early_out_noop;
rval.execute = nullptr;
rval.copy = nullptr;
switch (blend) {
case STRIP_BLEND_CROSS:
cross_effect_get_handle(rval);
break;
case STRIP_BLEND_ADD:
add_effect_get_handle(rval);
break;
case STRIP_BLEND_SUB:
sub_effect_get_handle(rval);
break;
case STRIP_BLEND_ALPHAOVER:
alpha_over_effect_get_handle(rval);
break;
case STRIP_BLEND_ALPHAUNDER:
alpha_under_effect_get_handle(rval);
break;
case STRIP_BLEND_GAMCROSS:
gamma_cross_effect_get_handle(rval);
break;
case STRIP_BLEND_MUL:
mul_effect_get_handle(rval);
break;
case STRIP_BLEND_SCREEN:
case STRIP_BLEND_LIGHTEN:
case STRIP_BLEND_DODGE:
case STRIP_BLEND_DARKEN:
case STRIP_BLEND_COLOR_BURN:
case STRIP_BLEND_LINEAR_BURN:
case STRIP_BLEND_OVERLAY:
case STRIP_BLEND_HARD_LIGHT:
case STRIP_BLEND_SOFT_LIGHT:
case STRIP_BLEND_PIN_LIGHT:
case STRIP_BLEND_LIN_LIGHT:
case STRIP_BLEND_VIVID_LIGHT:
case STRIP_BLEND_HUE:
case STRIP_BLEND_SATURATION:
case STRIP_BLEND_VALUE:
case STRIP_BLEND_BLEND_COLOR:
case STRIP_BLEND_DIFFERENCE:
case STRIP_BLEND_EXCLUSION:
blend_mode_effect_get_handle(rval);
break;
default:
break;
}
return rval;
}
EffectHandle strip_effect_handle_get(Strip *strip)
{
EffectHandle h = {};
if (strip->is_effect()) {
h = effect_handle_get(strip->type);
}
return h;
}
EffectHandle strip_blend_mode_handle_get(Strip *strip)
{
EffectHandle h = {};
if (strip->blend_mode != STRIP_BLEND_REPLACE) {
h = effect_handle_for_blend_mode_get(strip->blend_mode);
}
return h;
}
static float transition_fader_calc(const Scene *scene, const Strip *strip, float timeline_frame)
{
float fac = float(timeline_frame - strip->left_handle());
/* Compositor with no inputs can have strip->len not be updated,
* since most of existing editing code assumes no-input effects never need the length.
* So for the fader, just calculated it here directly. */
if (strip->type == STRIP_TYPE_COMPOSITOR) {
fac /= strip->enddisp - strip->startdisp;
}
else {
fac /= strip->length(scene);
}
fac = math::clamp(fac, 0.0f, 1.0f);
return fac;
}
float effect_fader_calc(Scene *scene, Strip *strip, float timeline_frame)
{
if (strip->flag & SEQ_USE_EFFECT_DEFAULT_FADE) {
if (effect_is_transition(strip->type)) {
return transition_fader_calc(scene, strip, timeline_frame);
}
return 1.0f;
}
const FCurve *fcu = id_data_find_fcurve(
&scene->id, strip, RNA_Strip, "effect_fader", 0, nullptr);
if (fcu) {
return evaluate_fcurve(fcu, timeline_frame);
}
return strip->effect_fader;
}
int effect_type_get_min_num_inputs(StripType type)
{
if (!strip_type_is_effect(type)) {
return 0;
}
/* Zero input effects. Note: compositor is here too, but it supports
* any input count. */
if (ELEM(type,
STRIP_TYPE_ADJUSTMENT,
STRIP_TYPE_MULTICAM,
STRIP_TYPE_COLOR,
STRIP_TYPE_TEXT,
STRIP_TYPE_COMPOSITOR))
{
return 0;
}
/* One input effects. */
if (ELEM(type, STRIP_TYPE_GAUSSIAN_BLUR, STRIP_TYPE_GLOW, STRIP_TYPE_SPEED)) {
return 1;
}
/* Others are two inputs. */
return 2;
}
bool strip_type_is_effect(StripType type)
{
return (type >= STRIP_TYPE_CROSS && type <= STRIP_TYPE_COMPOSITOR) ||
(type >= STRIP_TYPE_WIPE && type <= STRIP_TYPE_ADJUSTMENT) ||
(type >= STRIP_TYPE_GAUSSIAN_BLUR && type <= STRIP_TYPE_COLORMIX);
}
bool effect_is_transition(StripType type)
{
return ELEM(type, STRIP_TYPE_CROSS, STRIP_TYPE_GAMCROSS, STRIP_TYPE_WIPE, STRIP_TYPE_COMPOSITOR);
}
} // namespace blender::seq

View File

@@ -0,0 +1,173 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup sequencer
*/
#include "BLI_array.hh"
#include "BLI_math_color.h"
#include "BLI_math_vector_types.hh"
#include "BLI_task.hh"
#include "IMB_imbuf_types.hh"
#include "SEQ_effects.hh"
#include "render.hh"
namespace blender {
struct ImBuf;
struct Scene;
struct Strip;
namespace seq {
struct SeqRenderState;
struct RenderData;
enum class StripEarlyOut {
NoInput = -1, /* No input needed. */
DoEffect = 0, /* No early out (do the effect). */
UseInput1 = 1, /* Output = input1. */
UseInput2 = 2, /* Output = input2. */
};
struct EffectHandle {
/* #init is only called on first creation, or when changing effect type. */
void (*init)(Strip *strip);
/* duplicate */
void (*copy)(Strip *dst, const Strip *src, int flag);
/* destruct */
void (*free)(Strip *strip, bool do_id_user);
StripEarlyOut (*early_out)(const Strip *strip, float fac);
/* execute the effect */
SeqResult (*execute)(const RenderData *context,
SeqRenderState *state,
Strip *strip,
float timeline_frame,
float fac,
const SeqResult &input1,
const SeqResult &input2);
};
/** Get the effect handle for a given strip.
* If `strip` is not an effect strip, returns empty `EffectHandle`. */
EffectHandle strip_effect_handle_get(Strip *strip);
EffectHandle strip_blend_mode_handle_get(Strip *strip);
/**
* Build frame map when speed in mode #SEQ_SPEED_MULTIPLY is animated.
* This is, because `target_frame` value is integrated over time.
*/
void strip_effect_speed_rebuild_map(Scene *scene, Strip *strip);
/**
* Override timeline_frame when rendering speed effect input.
*/
float strip_speed_effect_target_frame_get(Scene *scene,
Strip *strip_speed,
float timeline_frame,
int input);
SeqResult prepare_effect_imbufs(const RenderData *context,
const SeqResult &ibuf1,
const SeqResult &ibuf2,
bool uninitialized_pixels = true);
Array<float> make_gaussian_blur_kernel(float rad, int size);
inline float4 load_premul_pixel(const uchar *ptr)
{
float4 res;
straight_uchar_to_premul_float(res, ptr);
return res;
}
inline float4 load_premul_pixel(const float *ptr)
{
return float4(ptr);
}
inline void store_premul_pixel(const float4 &pix, uchar *dst)
{
premul_float_to_straight_uchar(dst, pix);
}
inline void store_premul_pixel(const float4 &pix, float *dst)
{
*reinterpret_cast<float4 *>(dst) = pix;
}
StripEarlyOut early_out_mul_input1(const Strip * /*strip*/, float fac);
StripEarlyOut early_out_mul_input2(const Strip * /*strip*/, float fac);
StripEarlyOut early_out_fade(const Strip * /*strip*/, float fac);
EffectHandle effect_handle_get(StripType strip_type);
float effect_fader_calc(Scene *scene, Strip *strip, float timeline_frame);
void add_effect_get_handle(EffectHandle &rval);
void adjustment_effect_get_handle(EffectHandle &rval);
void alpha_over_effect_get_handle(EffectHandle &rval);
void alpha_under_effect_get_handle(EffectHandle &rval);
void blend_mode_effect_get_handle(EffectHandle &rval);
void color_mix_effect_get_handle(EffectHandle &rval);
void compositor_effect_get_handle(EffectHandle &rval);
void cross_effect_get_handle(EffectHandle &rval);
void gamma_cross_effect_get_handle(EffectHandle &rval);
void gaussian_blur_effect_get_handle(EffectHandle &rval);
void glow_effect_get_handle(EffectHandle &rval);
void mul_effect_get_handle(EffectHandle &rval);
void multi_camera_effect_get_handle(EffectHandle &rval);
void solid_color_effect_get_handle(EffectHandle &rval);
void speed_effect_get_handle(EffectHandle &rval);
void sub_effect_get_handle(EffectHandle &rval);
void text_effect_get_handle(EffectHandle &rval);
void transform_effect_get_handle(EffectHandle &rval);
void wipe_effect_get_handle(EffectHandle &rval);
/* Given `OpT` that implements an `apply` function:
*
* template <typename T>
* void apply(const T *src1, const T *src2, T *dst, int64_t size) const;
*
* this function calls the apply() function in parallel
* chunks of the image to process, and with uchar or float types
* All images are expected to have 4 (RGBA) color channels. */
template<typename OpT>
static void apply_effect_op(const OpT &op, const ImBuf *src1, const ImBuf *src2, ImBuf *dst)
{
BLI_assert_msg(src1->channels == 0 || src1->channels == 4,
"Sequencer only supports 4 channel images");
BLI_assert_msg(src2->channels == 0 || src2->channels == 4,
"Sequencer only supports 4 channel images");
BLI_assert_msg(dst->channels == 0 || dst->channels == 4,
"Sequencer only supports 4 channel images");
float *dst_float_data = dst->float_data_for_write();
uchar *dst_byte_data = dst->byte_data_for_write();
threading::parallel_for(IndexRange(size_t(dst->x) * dst->y), 32 * 1024, [&](IndexRange range) {
int64_t offset = range.first() * 4;
if (dst_float_data) {
const float *src1_ptr = src1->float_data() + offset;
const float *src2_ptr = src2->float_data() + offset;
float *dst_ptr = dst_float_data + offset;
op.apply(src1_ptr, src2_ptr, dst_ptr, range.size());
}
else {
const uchar *src1_ptr = src1->byte_data() + offset;
const uchar *src2_ptr = src2->byte_data() + offset;
uchar *dst_ptr = dst_byte_data + offset;
op.apply(src1_ptr, src2_ptr, dst_ptr, range.size());
}
});
}
} // namespace seq
} // namespace blender