When effect of adjustment layer strip is moved below the adjustment layer, this causes infinite loop in strip rendering. Same happens when you use multicam strip and set source channel to one of its effects. This is fixed by passing `SeqRenderState` to the effects. If any strip renders "seqbase" pointer of strip is stored in set in the render state struct. If the pointer exists in this set, function returns without rendering anything. In other words, The strip must never render itself. Pull Request: https://projects.blender.org/blender/blender/pulls/146624
400 lines
9.2 KiB
C++
400 lines
9.2 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup sequencer
|
|
*/
|
|
|
|
#include <algorithm>
|
|
|
|
#include "BLI_task.hh"
|
|
|
|
#include "DNA_sequence_types.h"
|
|
|
|
#include "IMB_imbuf.hh"
|
|
|
|
#include "SEQ_render.hh"
|
|
|
|
#include "effects.hh"
|
|
|
|
namespace blender::seq {
|
|
|
|
struct WipeZone {
|
|
float angle;
|
|
int flip;
|
|
int xo, yo;
|
|
int width;
|
|
float pythangle;
|
|
float clockWidth;
|
|
int type;
|
|
bool forward;
|
|
};
|
|
|
|
static WipeZone precalc_wipe_zone(const WipeVars *wipe, int xo, int yo)
|
|
{
|
|
WipeZone zone;
|
|
zone.flip = (wipe->angle < 0.0f);
|
|
zone.angle = tanf(fabsf(wipe->angle));
|
|
zone.xo = xo;
|
|
zone.yo = yo;
|
|
zone.width = int(wipe->edgeWidth * ((xo + yo) / 2.0f));
|
|
zone.pythangle = 1.0f / sqrtf(zone.angle * zone.angle + 1.0f);
|
|
zone.clockWidth = wipe->edgeWidth * float(M_PI);
|
|
zone.type = wipe->wipetype;
|
|
zone.forward = wipe->forward != 0;
|
|
return zone;
|
|
}
|
|
|
|
/**
|
|
* This function calculates the blur band for the wipe effects.
|
|
*/
|
|
static float in_band(float width, float dist, int side, int dir)
|
|
{
|
|
float alpha;
|
|
|
|
if (width == 0) {
|
|
return float(side);
|
|
}
|
|
|
|
if (width < dist) {
|
|
return float(side);
|
|
}
|
|
|
|
if (side == 1) {
|
|
alpha = (dist + 0.5f * width) / (width);
|
|
}
|
|
else {
|
|
alpha = (0.5f * width - dist) / (width);
|
|
}
|
|
|
|
if (dir == 0) {
|
|
alpha = 1 - alpha;
|
|
}
|
|
|
|
return alpha;
|
|
}
|
|
|
|
static float check_zone(const WipeZone *wipezone, int x, int y, float fac)
|
|
{
|
|
float posx, posy, hyp, hyp2, angle, hwidth, b1, b2, b3, pointdist;
|
|
float temp1, temp2, temp3, temp4; /* some placeholder variables */
|
|
int xo = wipezone->xo;
|
|
int yo = wipezone->yo;
|
|
float halfx = xo * 0.5f;
|
|
float halfy = yo * 0.5f;
|
|
float widthf, output = 0;
|
|
int width;
|
|
|
|
if (wipezone->flip) {
|
|
x = xo - x;
|
|
}
|
|
angle = wipezone->angle;
|
|
|
|
if (wipezone->forward) {
|
|
posx = fac * xo;
|
|
posy = fac * yo;
|
|
}
|
|
else {
|
|
posx = xo - fac * xo;
|
|
posy = yo - fac * yo;
|
|
}
|
|
|
|
switch (wipezone->type) {
|
|
case SEQ_WIPE_SINGLE:
|
|
width = min_ii(wipezone->width, fac * yo);
|
|
width = min_ii(width, yo - fac * yo);
|
|
|
|
if (angle == 0.0f) {
|
|
b1 = posy;
|
|
b2 = y;
|
|
hyp = fabsf(y - posy);
|
|
}
|
|
else {
|
|
b1 = posy - (-angle) * posx;
|
|
b2 = y - (-angle) * x;
|
|
hyp = fabsf(angle * x + y + (-posy - angle * posx)) * wipezone->pythangle;
|
|
}
|
|
|
|
if (angle < 0) {
|
|
temp1 = b1;
|
|
b1 = b2;
|
|
b2 = temp1;
|
|
}
|
|
|
|
if (wipezone->forward) {
|
|
if (b1 < b2) {
|
|
output = in_band(width, hyp, 1, 1);
|
|
}
|
|
else {
|
|
output = in_band(width, hyp, 0, 1);
|
|
}
|
|
}
|
|
else {
|
|
if (b1 < b2) {
|
|
output = in_band(width, hyp, 0, 1);
|
|
}
|
|
else {
|
|
output = in_band(width, hyp, 1, 1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SEQ_WIPE_DOUBLE:
|
|
if (!wipezone->forward) {
|
|
fac = 1.0f - fac; /* Go the other direction */
|
|
}
|
|
|
|
width = wipezone->width; /* calculate the blur width */
|
|
hwidth = width * 0.5f;
|
|
if (angle == 0) {
|
|
b1 = posy * 0.5f;
|
|
b3 = yo - posy * 0.5f;
|
|
b2 = y;
|
|
|
|
hyp = fabsf(y - posy * 0.5f);
|
|
hyp2 = fabsf(y - (yo - posy * 0.5f));
|
|
}
|
|
else {
|
|
b1 = posy * 0.5f - (-angle) * posx * 0.5f;
|
|
b3 = (yo - posy * 0.5f) - (-angle) * (xo - posx * 0.5f);
|
|
b2 = y - (-angle) * x;
|
|
|
|
hyp = fabsf(angle * x + y + (-posy * 0.5f - angle * posx * 0.5f)) * wipezone->pythangle;
|
|
hyp2 = fabsf(angle * x + y + (-(yo - posy * 0.5f) - angle * (xo - posx * 0.5f))) *
|
|
wipezone->pythangle;
|
|
}
|
|
|
|
hwidth = min_ff(hwidth, fabsf(b3 - b1) / 2.0f);
|
|
|
|
if (b2 < b1 && b2 < b3) {
|
|
output = in_band(hwidth, hyp, 0, 1);
|
|
}
|
|
else if (b2 > b1 && b2 > b3) {
|
|
output = in_band(hwidth, hyp2, 0, 1);
|
|
}
|
|
else {
|
|
if (hyp < hwidth && hyp2 > hwidth) {
|
|
output = in_band(hwidth, hyp, 1, 1);
|
|
}
|
|
else if (hyp > hwidth && hyp2 < hwidth) {
|
|
output = in_band(hwidth, hyp2, 1, 1);
|
|
}
|
|
else {
|
|
output = in_band(hwidth, hyp2, 1, 1) * in_band(hwidth, hyp, 1, 1);
|
|
}
|
|
}
|
|
if (!wipezone->forward) {
|
|
output = 1 - output;
|
|
}
|
|
break;
|
|
case SEQ_WIPE_CLOCK:
|
|
/*
|
|
* temp1: angle of effect center in rads
|
|
* temp2: angle of line through (halfx, halfy) and (x, y) in rads
|
|
* temp3: angle of low side of blur
|
|
* temp4: angle of high side of blur
|
|
*/
|
|
output = 1.0f - fac;
|
|
widthf = wipezone->clockWidth;
|
|
temp1 = 2.0f * float(M_PI) * fac;
|
|
|
|
if (wipezone->forward) {
|
|
temp1 = 2.0f * float(M_PI) - temp1;
|
|
}
|
|
|
|
x = x - halfx;
|
|
y = y - halfy;
|
|
|
|
temp2 = atan2f(y, x);
|
|
if (temp2 < 0.0f) {
|
|
temp2 += 2.0f * float(M_PI);
|
|
}
|
|
|
|
if (wipezone->forward) {
|
|
temp3 = temp1 - widthf * fac;
|
|
temp4 = temp1 + widthf * (1 - fac);
|
|
}
|
|
else {
|
|
temp3 = temp1 - widthf * (1 - fac);
|
|
temp4 = temp1 + widthf * fac;
|
|
}
|
|
temp3 = std::max<float>(temp3, 0);
|
|
temp4 = std::min(temp4, 2.0f * float(M_PI));
|
|
|
|
if (temp2 < temp3) {
|
|
output = 0;
|
|
}
|
|
else if (temp2 > temp4) {
|
|
output = 1;
|
|
}
|
|
else {
|
|
output = (temp2 - temp3) / (temp4 - temp3);
|
|
}
|
|
if (x == 0 && y == 0) {
|
|
output = 1;
|
|
}
|
|
if (output != output) {
|
|
output = 1;
|
|
}
|
|
if (wipezone->forward) {
|
|
output = 1 - output;
|
|
}
|
|
break;
|
|
case SEQ_WIPE_IRIS:
|
|
if (xo > yo) {
|
|
yo = xo;
|
|
}
|
|
else {
|
|
xo = yo;
|
|
}
|
|
|
|
if (!wipezone->forward) {
|
|
fac = 1 - fac;
|
|
}
|
|
|
|
width = wipezone->width;
|
|
hwidth = width * 0.5f;
|
|
|
|
temp1 = (halfx - (halfx)*fac);
|
|
pointdist = hypotf(temp1, temp1);
|
|
|
|
temp2 = hypotf(halfx - x, halfy - y);
|
|
if (temp2 > pointdist) {
|
|
output = in_band(hwidth, fabsf(temp2 - pointdist), 0, 1);
|
|
}
|
|
else {
|
|
output = in_band(hwidth, fabsf(temp2 - pointdist), 1, 1);
|
|
}
|
|
|
|
if (!wipezone->forward) {
|
|
output = 1 - output;
|
|
}
|
|
|
|
break;
|
|
}
|
|
if (output < 0) {
|
|
output = 0;
|
|
}
|
|
else if (output > 1) {
|
|
output = 1;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
static void init_wipe_effect(Strip *strip)
|
|
{
|
|
if (strip->effectdata) {
|
|
MEM_freeN(strip->effectdata);
|
|
}
|
|
|
|
strip->effectdata = MEM_callocN<WipeVars>("wipevars");
|
|
}
|
|
|
|
static int num_inputs_wipe()
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
static void free_wipe_effect(Strip *strip, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(strip->effectdata);
|
|
}
|
|
|
|
static void copy_wipe_effect(Strip *dst, const Strip *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
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)
|
|
{
|
|
using namespace blender;
|
|
const WipeVars *wipe = (const WipeVars *)strip->effectdata;
|
|
const WipeZone wipezone = precalc_wipe_zone(wipe, width, height);
|
|
|
|
threading::parallel_for(IndexRange(height), 64, [&](const IndexRange y_range) {
|
|
const T *cp1 = rect1 ? rect1 + y_range.first() * width * 4 : nullptr;
|
|
const T *cp2 = rect2 ? rect2 + y_range.first() * width * 4 : nullptr;
|
|
T *rt = out + y_range.first() * width * 4;
|
|
for (const int y : y_range) {
|
|
for (int x = 0; x < width; x++) {
|
|
float check = check_zone(&wipezone, x, y, fac);
|
|
if (check) {
|
|
if (cp1) {
|
|
float4 col1 = load_premul_pixel(cp1);
|
|
float4 col2 = load_premul_pixel(cp2);
|
|
float4 col = col1 * check + col2 * (1.0f - check);
|
|
store_premul_pixel(col, rt);
|
|
}
|
|
else {
|
|
store_opaque_black_pixel(rt);
|
|
}
|
|
}
|
|
else {
|
|
if (cp2) {
|
|
memcpy(rt, cp2, sizeof(T) * 4);
|
|
}
|
|
else {
|
|
store_opaque_black_pixel(rt);
|
|
}
|
|
}
|
|
|
|
rt += 4;
|
|
if (cp1 != nullptr) {
|
|
cp1 += 4;
|
|
}
|
|
if (cp2 != nullptr) {
|
|
cp2 += 4;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
static ImBuf *do_wipe_effect(const RenderData *context,
|
|
SeqRenderState * /*state*/,
|
|
Strip *strip,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2)
|
|
{
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2);
|
|
|
|
if (out->float_buffer.data) {
|
|
do_wipe_effect(strip,
|
|
fac,
|
|
context->rectx,
|
|
context->recty,
|
|
ibuf1->float_buffer.data,
|
|
ibuf2->float_buffer.data,
|
|
out->float_buffer.data);
|
|
}
|
|
else {
|
|
do_wipe_effect(strip,
|
|
fac,
|
|
context->rectx,
|
|
context->recty,
|
|
ibuf1->byte_buffer.data,
|
|
ibuf2->byte_buffer.data,
|
|
out->byte_buffer.data);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
void wipe_effect_get_handle(EffectHandle &rval)
|
|
{
|
|
rval.init = init_wipe_effect;
|
|
rval.num_inputs = num_inputs_wipe;
|
|
rval.free = free_wipe_effect;
|
|
rval.copy = copy_wipe_effect;
|
|
rval.early_out = early_out_fade;
|
|
rval.get_default_fac = get_default_fac_fade;
|
|
rval.execute = do_wipe_effect;
|
|
}
|
|
|
|
} // namespace blender::seq
|