The depsgraph CoW mechanism is a bit of a misnomer. It creates an evaluated copy for data-blocks regardless of whether the copy will actually be written to. The point is to have physical separation between original and evaluated data. This is in contrast to the commonly used performance improvement of keeping a user count and copying data implicitly when it needs to be changed. In Blender code we call this "implicit sharing" instead. Importantly, the dependency graph has no idea about the _actual_ CoW behavior in Blender. Renaming this functionality in the despgraph removes some of the confusion that comes up when talking about this, and will hopefully make the depsgraph less confusing to understand initially too. Wording like "the evaluated copy" (as opposed to the original data-block) has also become common anyway. Pull Request: https://projects.blender.org/blender/blender/pulls/118338
3135 lines
89 KiB
C++
3135 lines
89 KiB
C++
/* 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 bke
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_array.hh"
|
|
#include "BLI_math_rotation.h"
|
|
#include "BLI_math_vector.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_path_util.h"
|
|
#include "BLI_rect.h"
|
|
#include "BLI_string.h"
|
|
#include "BLI_task.hh"
|
|
#include "BLI_threads.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "DNA_packedFile_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_sequence_types.h"
|
|
#include "DNA_space_types.h"
|
|
#include "DNA_vfont_types.h"
|
|
|
|
#include "BKE_fcurve.h"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_main.hh"
|
|
|
|
#include "IMB_colormanagement.hh"
|
|
#include "IMB_imbuf.hh"
|
|
#include "IMB_imbuf_types.hh"
|
|
#include "IMB_interp.hh"
|
|
#include "IMB_metadata.hh"
|
|
|
|
#include "BLI_math_color_blend.h"
|
|
|
|
#include "RNA_prototypes.h"
|
|
|
|
#include "RE_pipeline.h"
|
|
|
|
#include "SEQ_channels.hh"
|
|
#include "SEQ_effects.hh"
|
|
#include "SEQ_proxy.hh"
|
|
#include "SEQ_relations.hh"
|
|
#include "SEQ_render.hh"
|
|
#include "SEQ_time.hh"
|
|
#include "SEQ_utils.hh"
|
|
|
|
#include "BLF_api.hh"
|
|
|
|
#include "effects.hh"
|
|
#include "render.hh"
|
|
|
|
using namespace blender;
|
|
|
|
static SeqEffectHandle get_sequence_effect_impl(int seq_type);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Internal Utilities
|
|
* \{ */
|
|
|
|
static void slice_get_byte_buffers(const SeqRenderData *context,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf *ibuf3,
|
|
const ImBuf *out,
|
|
int start_line,
|
|
uchar **rect1,
|
|
uchar **rect2,
|
|
uchar **rect3,
|
|
uchar **rect_out)
|
|
{
|
|
int offset = 4 * start_line * context->rectx;
|
|
|
|
*rect1 = ibuf1->byte_buffer.data + offset;
|
|
*rect_out = out->byte_buffer.data + offset;
|
|
|
|
if (ibuf2) {
|
|
*rect2 = ibuf2->byte_buffer.data + offset;
|
|
}
|
|
|
|
if (ibuf3) {
|
|
*rect3 = ibuf3->byte_buffer.data + offset;
|
|
}
|
|
}
|
|
|
|
static void slice_get_float_buffers(const SeqRenderData *context,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf *ibuf3,
|
|
const ImBuf *out,
|
|
int start_line,
|
|
float **rect1,
|
|
float **rect2,
|
|
float **rect3,
|
|
float **rect_out)
|
|
{
|
|
int offset = 4 * start_line * context->rectx;
|
|
|
|
*rect1 = ibuf1->float_buffer.data + offset;
|
|
*rect_out = out->float_buffer.data + offset;
|
|
|
|
if (ibuf2) {
|
|
*rect2 = ibuf2->float_buffer.data + offset;
|
|
}
|
|
|
|
if (ibuf3) {
|
|
*rect3 = ibuf3->float_buffer.data + offset;
|
|
}
|
|
}
|
|
|
|
static float4 load_premul_pixel(const uchar *ptr)
|
|
{
|
|
float4 res;
|
|
straight_uchar_to_premul_float(res, ptr);
|
|
return res;
|
|
}
|
|
|
|
static float4 load_premul_pixel(const float *ptr)
|
|
{
|
|
return float4(ptr);
|
|
}
|
|
|
|
static void store_premul_pixel(const float4 &pix, uchar *dst)
|
|
{
|
|
premul_float_to_straight_uchar(dst, pix);
|
|
}
|
|
|
|
static void store_premul_pixel(const float4 &pix, float *dst)
|
|
{
|
|
*reinterpret_cast<float4 *>(dst) = pix;
|
|
}
|
|
|
|
static void store_opaque_black_pixel(uchar *dst)
|
|
{
|
|
dst[0] = 0;
|
|
dst[1] = 0;
|
|
dst[2] = 0;
|
|
dst[3] = 255;
|
|
}
|
|
|
|
static void store_opaque_black_pixel(float *dst)
|
|
{
|
|
dst[0] = 0.0f;
|
|
dst[1] = 0.0f;
|
|
dst[2] = 0.0f;
|
|
dst[3] = 1.0f;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Glow Effect
|
|
* \{ */
|
|
|
|
static ImBuf *prepare_effect_imbufs(const SeqRenderData *context,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3,
|
|
bool uninitialized_pixels = true)
|
|
{
|
|
ImBuf *out;
|
|
Scene *scene = context->scene;
|
|
int x = context->rectx;
|
|
int y = context->recty;
|
|
int base_flags = uninitialized_pixels ? IB_uninitialized_pixels : 0;
|
|
|
|
if (!ibuf1 && !ibuf2 && !ibuf3) {
|
|
/* hmmm, global float option ? */
|
|
out = IMB_allocImBuf(x, y, 32, IB_rect | base_flags);
|
|
}
|
|
else if ((ibuf1 && ibuf1->float_buffer.data) || (ibuf2 && ibuf2->float_buffer.data) ||
|
|
(ibuf3 && ibuf3->float_buffer.data))
|
|
{
|
|
/* if any inputs are rectfloat, output is float too */
|
|
|
|
out = IMB_allocImBuf(x, y, 32, IB_rectfloat | base_flags);
|
|
}
|
|
else {
|
|
out = IMB_allocImBuf(x, y, 32, IB_rect | base_flags);
|
|
}
|
|
|
|
if (out->float_buffer.data) {
|
|
if (ibuf1 && !ibuf1->float_buffer.data) {
|
|
seq_imbuf_to_sequencer_space(scene, ibuf1, true);
|
|
}
|
|
|
|
if (ibuf2 && !ibuf2->float_buffer.data) {
|
|
seq_imbuf_to_sequencer_space(scene, ibuf2, true);
|
|
}
|
|
|
|
if (ibuf3 && !ibuf3->float_buffer.data) {
|
|
seq_imbuf_to_sequencer_space(scene, ibuf3, true);
|
|
}
|
|
|
|
IMB_colormanagement_assign_float_colorspace(out, scene->sequencer_colorspace_settings.name);
|
|
}
|
|
else {
|
|
if (ibuf1 && !ibuf1->byte_buffer.data) {
|
|
IMB_rect_from_float(ibuf1);
|
|
}
|
|
|
|
if (ibuf2 && !ibuf2->byte_buffer.data) {
|
|
IMB_rect_from_float(ibuf2);
|
|
}
|
|
|
|
if (ibuf3 && !ibuf3->byte_buffer.data) {
|
|
IMB_rect_from_float(ibuf3);
|
|
}
|
|
}
|
|
|
|
/* If effect only affecting a single channel, forward input's metadata to the output. */
|
|
if (ibuf1 != nullptr && ibuf1 == ibuf2 && ibuf2 == ibuf3) {
|
|
IMB_metadata_copy(out, ibuf1);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Alpha Over Effect
|
|
* \{ */
|
|
|
|
static void init_alpha_over_or_under(Sequence *seq)
|
|
{
|
|
Sequence *seq1 = seq->seq1;
|
|
Sequence *seq2 = seq->seq2;
|
|
|
|
seq->seq2 = seq1;
|
|
seq->seq1 = seq2;
|
|
}
|
|
|
|
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) */
|
|
template<typename T>
|
|
static void do_alphaover_effect(
|
|
float fac, int width, int height, const T *src1, const T *src2, T *dst)
|
|
{
|
|
if (fac <= 0.0f) {
|
|
memcpy(dst, src2, sizeof(T) * 4 * width * height);
|
|
return;
|
|
}
|
|
|
|
for (int pixel_idx = 0; pixel_idx < width * height; pixel_idx++) {
|
|
if (src1[3] <= 0.0f) {
|
|
/* Alpha of zero. No color addition will happen as the colors are pre-multiplied. */
|
|
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;
|
|
}
|
|
}
|
|
|
|
static void do_alphaover_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_alphaover_effect(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_alphaover_effect(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Alpha Under Effect
|
|
* \{ */
|
|
|
|
/* dst = src1 under src2 (alpha from src2) */
|
|
template<typename T>
|
|
static void do_alphaunder_effect(
|
|
float fac, int width, int height, const T *src1, const T *src2, T *dst)
|
|
{
|
|
if (fac >= 1.0f) {
|
|
memcpy(dst, src1, sizeof(T) * 4 * width * height);
|
|
return;
|
|
}
|
|
|
|
for (int pixel_idx = 0; pixel_idx < width * height; pixel_idx++) {
|
|
if (src2[3] <= 0.0f) {
|
|
memcpy(dst, src1, sizeof(T) * 4);
|
|
}
|
|
else if (alpha_opaque(src2[3]) || fac <= 0.0f) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
static void do_alphaunder_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_alphaunder_effect(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_alphaunder_effect(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Cross Effect
|
|
* \{ */
|
|
|
|
static void do_cross_effect_byte(float fac, int x, int y, uchar *rect1, uchar *rect2, uchar *out)
|
|
{
|
|
uchar *rt1 = rect1;
|
|
uchar *rt2 = rect2;
|
|
uchar *rt = out;
|
|
|
|
int temp_fac = int(256.0f * fac);
|
|
int temp_mfac = 256 - temp_fac;
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
rt[0] = (temp_mfac * rt1[0] + temp_fac * rt2[0]) >> 8;
|
|
rt[1] = (temp_mfac * rt1[1] + temp_fac * rt2[1]) >> 8;
|
|
rt[2] = (temp_mfac * rt1[2] + temp_fac * rt2[2]) >> 8;
|
|
rt[3] = (temp_mfac * rt1[3] + temp_fac * rt2[3]) >> 8;
|
|
|
|
rt1 += 4;
|
|
rt2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_cross_effect_float(float fac, int x, int y, float *rect1, float *rect2, float *out)
|
|
{
|
|
float *rt1 = rect1;
|
|
float *rt2 = rect2;
|
|
float *rt = out;
|
|
|
|
float mfac = 1.0f - fac;
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
rt[0] = mfac * rt1[0] + fac * rt2[0];
|
|
rt[1] = mfac * rt1[1] + fac * rt2[1];
|
|
rt[2] = mfac * rt1[2] + fac * rt2[2];
|
|
rt[3] = mfac * rt1[3] + fac * rt2[3];
|
|
|
|
rt1 += 4;
|
|
rt2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_cross_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_cross_effect_float(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_cross_effect_byte(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Gamma Cross
|
|
* \{ */
|
|
|
|
/* 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);
|
|
}
|
|
|
|
template<typename T>
|
|
static void do_gammacross_effect(
|
|
float fac, int width, int height, const T *src1, const T *src2, T *dst)
|
|
{
|
|
float mfac = 1.0f - fac;
|
|
|
|
for (int y = 0; y < height; y++) {
|
|
for (int x = 0; x < width; x++) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static ImBuf *gammacross_init_execution(const SeqRenderData *context,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3);
|
|
return out;
|
|
}
|
|
|
|
static void do_gammacross_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_gammacross_effect(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_gammacross_effect(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color Add Effect
|
|
* \{ */
|
|
|
|
static void do_add_effect_byte(float fac, int x, int y, uchar *rect1, uchar *rect2, uchar *out)
|
|
{
|
|
uchar *cp1 = rect1;
|
|
uchar *cp2 = rect2;
|
|
uchar *rt = out;
|
|
|
|
int temp_fac = int(256.0f * fac);
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
const int temp_fac2 = temp_fac * int(cp2[3]);
|
|
rt[0] = min_ii(cp1[0] + ((temp_fac2 * cp2[0]) >> 16), 255);
|
|
rt[1] = min_ii(cp1[1] + ((temp_fac2 * cp2[1]) >> 16), 255);
|
|
rt[2] = min_ii(cp1[2] + ((temp_fac2 * cp2[2]) >> 16), 255);
|
|
rt[3] = cp1[3];
|
|
|
|
cp1 += 4;
|
|
cp2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_add_effect_float(float fac, int x, int y, float *rect1, float *rect2, float *out)
|
|
{
|
|
float *rt1 = rect1;
|
|
float *rt2 = rect2;
|
|
float *rt = out;
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
const float temp_fac = (1.0f - (rt1[3] * (1.0f - fac))) * rt2[3];
|
|
rt[0] = rt1[0] + temp_fac * rt2[0];
|
|
rt[1] = rt1[1] + temp_fac * rt2[1];
|
|
rt[2] = rt1[2] + temp_fac * rt2[2];
|
|
rt[3] = rt1[3];
|
|
|
|
rt1 += 4;
|
|
rt2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_add_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_add_effect_float(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_add_effect_byte(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color Subtract Effect
|
|
* \{ */
|
|
|
|
static void do_sub_effect_byte(float fac, int x, int y, uchar *rect1, uchar *rect2, uchar *out)
|
|
{
|
|
uchar *cp1 = rect1;
|
|
uchar *cp2 = rect2;
|
|
uchar *rt = out;
|
|
|
|
int temp_fac = int(256.0f * fac);
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
const int temp_fac2 = temp_fac * int(cp2[3]);
|
|
rt[0] = max_ii(cp1[0] - ((temp_fac2 * cp2[0]) >> 16), 0);
|
|
rt[1] = max_ii(cp1[1] - ((temp_fac2 * cp2[1]) >> 16), 0);
|
|
rt[2] = max_ii(cp1[2] - ((temp_fac2 * cp2[2]) >> 16), 0);
|
|
rt[3] = cp1[3];
|
|
|
|
cp1 += 4;
|
|
cp2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_sub_effect_float(float fac, int x, int y, float *rect1, float *rect2, float *out)
|
|
{
|
|
float *rt1 = rect1;
|
|
float *rt2 = rect2;
|
|
float *rt = out;
|
|
|
|
float mfac = 1.0f - fac;
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
const float temp_fac = (1.0f - (rt1[3] * mfac)) * rt2[3];
|
|
rt[0] = max_ff(rt1[0] - temp_fac * rt2[0], 0.0f);
|
|
rt[1] = max_ff(rt1[1] - temp_fac * rt2[1], 0.0f);
|
|
rt[2] = max_ff(rt1[2] - temp_fac * rt2[2], 0.0f);
|
|
rt[3] = rt1[3];
|
|
|
|
rt1 += 4;
|
|
rt2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_sub_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_sub_effect_float(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_sub_effect_byte(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Drop Effect
|
|
* \{ */
|
|
|
|
/* Must be > 0 or add pre-copy, etc to the function. */
|
|
#define XOFF 8
|
|
#define YOFF 8
|
|
|
|
static void do_drop_effect_byte(float fac, int x, int y, uchar *rect2i, uchar *rect1i, uchar *outi)
|
|
{
|
|
const int xoff = min_ii(XOFF, x);
|
|
const int yoff = min_ii(YOFF, y);
|
|
|
|
int temp_fac = int(70.0f * fac);
|
|
|
|
uchar *rt2 = rect2i + yoff * 4 * x;
|
|
uchar *rt1 = rect1i;
|
|
uchar *out = outi;
|
|
for (int i = 0; i < y - yoff; i++) {
|
|
memcpy(out, rt1, sizeof(*out) * xoff * 4);
|
|
rt1 += xoff * 4;
|
|
out += xoff * 4;
|
|
|
|
for (int j = xoff; j < x; j++) {
|
|
int temp_fac2 = ((temp_fac * rt2[3]) >> 8);
|
|
|
|
*(out++) = std::max(0, *rt1 - temp_fac2);
|
|
rt1++;
|
|
*(out++) = std::max(0, *rt1 - temp_fac2);
|
|
rt1++;
|
|
*(out++) = std::max(0, *rt1 - temp_fac2);
|
|
rt1++;
|
|
*(out++) = std::max(0, *rt1 - temp_fac2);
|
|
rt1++;
|
|
rt2 += 4;
|
|
}
|
|
rt2 += xoff * 4;
|
|
}
|
|
memcpy(out, rt1, sizeof(*out) * yoff * 4 * x);
|
|
}
|
|
|
|
static void do_drop_effect_float(
|
|
float fac, int x, int y, float *rect2i, float *rect1i, float *outi)
|
|
{
|
|
const int xoff = min_ii(XOFF, x);
|
|
const int yoff = min_ii(YOFF, y);
|
|
|
|
float temp_fac = 70.0f * fac;
|
|
|
|
float *rt2 = rect2i + yoff * 4 * x;
|
|
float *rt1 = rect1i;
|
|
float *out = outi;
|
|
for (int i = 0; i < y - yoff; i++) {
|
|
memcpy(out, rt1, sizeof(*out) * xoff * 4);
|
|
rt1 += xoff * 4;
|
|
out += xoff * 4;
|
|
|
|
for (int j = xoff; j < x; j++) {
|
|
float temp_fac2 = temp_fac * rt2[3];
|
|
|
|
*(out++) = std::max(0.0f, *rt1 - temp_fac2);
|
|
rt1++;
|
|
*(out++) = std::max(0.0f, *rt1 - temp_fac2);
|
|
rt1++;
|
|
*(out++) = std::max(0.0f, *rt1 - temp_fac2);
|
|
rt1++;
|
|
*(out++) = std::max(0.0f, *rt1 - temp_fac2);
|
|
rt1++;
|
|
rt2 += 4;
|
|
}
|
|
rt2 += xoff * 4;
|
|
}
|
|
memcpy(out, rt1, sizeof(*out) * yoff * 4 * x);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Multiply Effect
|
|
* \{ */
|
|
|
|
static void do_mul_effect_byte(float fac, int x, int y, uchar *rect1, uchar *rect2, uchar *out)
|
|
{
|
|
uchar *rt1 = rect1;
|
|
uchar *rt2 = rect2;
|
|
uchar *rt = out;
|
|
|
|
int temp_fac = int(256.0f * fac);
|
|
|
|
/* Formula:
|
|
* `fac * (a * b) + (1 - fac) * a => fac * a * (b - 1) + axaux = c * px + py * s;` // + centx
|
|
* `yaux = -s * px + c * py;` // + centy */
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
rt[0] = rt1[0] + ((temp_fac * rt1[0] * (rt2[0] - 255)) >> 16);
|
|
rt[1] = rt1[1] + ((temp_fac * rt1[1] * (rt2[1] - 255)) >> 16);
|
|
rt[2] = rt1[2] + ((temp_fac * rt1[2] * (rt2[2] - 255)) >> 16);
|
|
rt[3] = rt1[3] + ((temp_fac * rt1[3] * (rt2[3] - 255)) >> 16);
|
|
|
|
rt1 += 4;
|
|
rt2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_mul_effect_float(float fac, int x, int y, float *rect1, float *rect2, float *out)
|
|
{
|
|
float *rt1 = rect1;
|
|
float *rt2 = rect2;
|
|
float *rt = out;
|
|
|
|
/* Formula:
|
|
* `fac * (a * b) + (1 - fac) * a => fac * a * (b - 1) + a`. */
|
|
|
|
for (int i = 0; i < y; i++) {
|
|
for (int j = 0; j < x; j++) {
|
|
rt[0] = rt1[0] + fac * rt1[0] * (rt2[0] - 1.0f);
|
|
rt[1] = rt1[1] + fac * rt1[1] * (rt2[1] - 1.0f);
|
|
rt[2] = rt1[2] + fac * rt1[2] * (rt2[2] - 1.0f);
|
|
rt[3] = rt1[3] + fac * rt1[3] * (rt2[3] - 1.0f);
|
|
|
|
rt1 += 4;
|
|
rt2 += 4;
|
|
rt += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_mul_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_mul_effect_float(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_mul_effect_byte(fac, context->rectx, total_lines, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name 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, int width, int height, const T *src1, T *src2, T *dst, Func blend_function)
|
|
{
|
|
for (int y = 0; y < height; y++) {
|
|
for (int x = 0; x < width; x++) {
|
|
T achannel = src2[3];
|
|
src2[3] = T(achannel * fac);
|
|
blend_function(dst, src1, src2);
|
|
src2[3] = achannel;
|
|
dst[3] = src1[3];
|
|
src1 += 4;
|
|
src2 += 4;
|
|
dst += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_blend_effect_float(
|
|
float fac, int x, int y, float *rect1, float *rect2, int btype, float *out)
|
|
{
|
|
switch (btype) {
|
|
case SEQ_TYPE_ADD:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_add_float);
|
|
break;
|
|
case SEQ_TYPE_SUB:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_sub_float);
|
|
break;
|
|
case SEQ_TYPE_MUL:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_mul_float);
|
|
break;
|
|
case SEQ_TYPE_DARKEN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_darken_float);
|
|
break;
|
|
case SEQ_TYPE_COLOR_BURN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_burn_float);
|
|
break;
|
|
case SEQ_TYPE_LINEAR_BURN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_linearburn_float);
|
|
break;
|
|
case SEQ_TYPE_SCREEN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_screen_float);
|
|
break;
|
|
case SEQ_TYPE_LIGHTEN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_lighten_float);
|
|
break;
|
|
case SEQ_TYPE_DODGE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_dodge_float);
|
|
break;
|
|
case SEQ_TYPE_OVERLAY:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_overlay_float);
|
|
break;
|
|
case SEQ_TYPE_SOFT_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_softlight_float);
|
|
break;
|
|
case SEQ_TYPE_HARD_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_hardlight_float);
|
|
break;
|
|
case SEQ_TYPE_PIN_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_pinlight_float);
|
|
break;
|
|
case SEQ_TYPE_LIN_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_linearlight_float);
|
|
break;
|
|
case SEQ_TYPE_VIVID_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_vividlight_float);
|
|
break;
|
|
case SEQ_TYPE_BLEND_COLOR:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_color_float);
|
|
break;
|
|
case SEQ_TYPE_HUE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_hue_float);
|
|
break;
|
|
case SEQ_TYPE_SATURATION:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_saturation_float);
|
|
break;
|
|
case SEQ_TYPE_VALUE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_luminosity_float);
|
|
break;
|
|
case SEQ_TYPE_DIFFERENCE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_difference_float);
|
|
break;
|
|
case SEQ_TYPE_EXCLUSION:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_exclusion_float);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void do_blend_effect_byte(
|
|
float fac, int x, int y, uchar *rect1, uchar *rect2, int btype, uchar *out)
|
|
{
|
|
switch (btype) {
|
|
case SEQ_TYPE_ADD:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_add_byte);
|
|
break;
|
|
case SEQ_TYPE_SUB:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_sub_byte);
|
|
break;
|
|
case SEQ_TYPE_MUL:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_mul_byte);
|
|
break;
|
|
case SEQ_TYPE_DARKEN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_darken_byte);
|
|
break;
|
|
case SEQ_TYPE_COLOR_BURN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_burn_byte);
|
|
break;
|
|
case SEQ_TYPE_LINEAR_BURN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_linearburn_byte);
|
|
break;
|
|
case SEQ_TYPE_SCREEN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_screen_byte);
|
|
break;
|
|
case SEQ_TYPE_LIGHTEN:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_lighten_byte);
|
|
break;
|
|
case SEQ_TYPE_DODGE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_dodge_byte);
|
|
break;
|
|
case SEQ_TYPE_OVERLAY:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_overlay_byte);
|
|
break;
|
|
case SEQ_TYPE_SOFT_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_softlight_byte);
|
|
break;
|
|
case SEQ_TYPE_HARD_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_hardlight_byte);
|
|
break;
|
|
case SEQ_TYPE_PIN_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_pinlight_byte);
|
|
break;
|
|
case SEQ_TYPE_LIN_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_linearlight_byte);
|
|
break;
|
|
case SEQ_TYPE_VIVID_LIGHT:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_vividlight_byte);
|
|
break;
|
|
case SEQ_TYPE_BLEND_COLOR:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_color_byte);
|
|
break;
|
|
case SEQ_TYPE_HUE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_hue_byte);
|
|
break;
|
|
case SEQ_TYPE_SATURATION:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_saturation_byte);
|
|
break;
|
|
case SEQ_TYPE_VALUE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_luminosity_byte);
|
|
break;
|
|
case SEQ_TYPE_DIFFERENCE:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_difference_byte);
|
|
break;
|
|
case SEQ_TYPE_EXCLUSION:
|
|
apply_blend_function(fac, x, y, rect1, rect2, out, blend_color_exclusion_byte);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void do_blend_mode_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
do_blend_effect_float(
|
|
fac, context->rectx, total_lines, rect1, rect2, seq->blend_mode, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
do_blend_effect_byte(
|
|
fac, context->rectx, total_lines, rect1, rect2, seq->blend_mode, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Color Mix Effect
|
|
* \{ */
|
|
|
|
static void init_colormix_effect(Sequence *seq)
|
|
{
|
|
ColorMixVars *data;
|
|
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
seq->effectdata = MEM_callocN(sizeof(ColorMixVars), "colormixvars");
|
|
data = (ColorMixVars *)seq->effectdata;
|
|
data->blend_effect = SEQ_TYPE_OVERLAY;
|
|
data->factor = 1.0f;
|
|
}
|
|
|
|
static void do_colormix_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float /*fac*/,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
float fac;
|
|
|
|
ColorMixVars *data = static_cast<ColorMixVars *>(seq->effectdata);
|
|
fac = data->factor;
|
|
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
do_blend_effect_float(
|
|
fac, context->rectx, total_lines, rect1, rect2, data->blend_effect, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
do_blend_effect_byte(
|
|
fac, context->rectx, total_lines, rect1, rect2, data->blend_effect, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Wipe Effect
|
|
* \{ */
|
|
|
|
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 DO_SINGLE_WIPE:
|
|
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 DO_DOUBLE_WIPE:
|
|
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 DO_CLOCK_WIPE:
|
|
/*
|
|
* 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;
|
|
}
|
|
if (temp3 < 0) {
|
|
temp3 = 0;
|
|
}
|
|
if (temp4 > 2.0f * float(M_PI)) {
|
|
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 DO_IRIS_WIPE:
|
|
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(Sequence *seq)
|
|
{
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
seq->effectdata = MEM_callocN(sizeof(WipeVars), "wipevars");
|
|
}
|
|
|
|
static int num_inputs_wipe()
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
static void free_wipe_effect(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static void copy_wipe_effect(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
template<typename T>
|
|
static void do_wipe_effect(
|
|
const Sequence *seq, float fac, int width, int height, const T *rect1, const T *rect2, T *out)
|
|
{
|
|
using namespace blender;
|
|
const WipeVars *wipe = (const WipeVars *)seq->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 SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3);
|
|
|
|
if (out->float_buffer.data) {
|
|
do_wipe_effect(seq,
|
|
fac,
|
|
context->rectx,
|
|
context->recty,
|
|
ibuf1->float_buffer.data,
|
|
ibuf2->float_buffer.data,
|
|
out->float_buffer.data);
|
|
}
|
|
else {
|
|
do_wipe_effect(seq,
|
|
fac,
|
|
context->rectx,
|
|
context->recty,
|
|
ibuf1->byte_buffer.data,
|
|
ibuf2->byte_buffer.data,
|
|
out->byte_buffer.data);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Transform Effect
|
|
* \{ */
|
|
|
|
static void init_transform_effect(Sequence *seq)
|
|
{
|
|
TransformVars *transform;
|
|
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
seq->effectdata = MEM_callocN(sizeof(TransformVars), "transformvars");
|
|
|
|
transform = (TransformVars *)seq->effectdata;
|
|
|
|
transform->ScalexIni = 1.0f;
|
|
transform->ScaleyIni = 1.0f;
|
|
|
|
transform->xIni = 0.0f;
|
|
transform->yIni = 0.0f;
|
|
|
|
transform->rotIni = 0.0f;
|
|
|
|
transform->interpolation = 1;
|
|
transform->percent = 1;
|
|
transform->uniform_scale = 0;
|
|
}
|
|
|
|
static int num_inputs_transform()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void free_transform_effect(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static void copy_transform_effect(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
static void transform_image(int x,
|
|
int y,
|
|
int start_line,
|
|
int total_lines,
|
|
const ImBuf *ibuf,
|
|
ImBuf *out,
|
|
float scale_x,
|
|
float scale_y,
|
|
float translate_x,
|
|
float translate_y,
|
|
float rotate,
|
|
int interpolation)
|
|
{
|
|
/* Rotate */
|
|
float s = sinf(rotate);
|
|
float c = cosf(rotate);
|
|
|
|
float4 *dst_fl = reinterpret_cast<float4 *>(out->float_buffer.data);
|
|
uchar4 *dst_ch = reinterpret_cast<uchar4 *>(out->byte_buffer.data);
|
|
|
|
size_t offset = size_t(x) * start_line;
|
|
for (int yi = start_line; yi < start_line + total_lines; yi++) {
|
|
for (int xi = 0; xi < x; xi++) {
|
|
/* Translate point. */
|
|
float xt = xi - translate_x;
|
|
float yt = yi - translate_y;
|
|
|
|
/* Rotate point with center ref. */
|
|
float xr = c * xt + s * yt;
|
|
float yr = -s * xt + c * yt;
|
|
|
|
/* Scale point with center ref. */
|
|
xt = xr / scale_x;
|
|
yt = yr / scale_y;
|
|
|
|
/* Undo reference center point. */
|
|
xt += (x / 2.0f);
|
|
yt += (y / 2.0f);
|
|
|
|
/* interpolate */
|
|
switch (interpolation) {
|
|
case 0:
|
|
if (dst_fl) {
|
|
dst_fl[offset] = imbuf::interpolate_nearest_fl(ibuf, xt, yt);
|
|
}
|
|
else {
|
|
dst_ch[offset] = imbuf::interpolate_nearest_byte(ibuf, xt, yt);
|
|
}
|
|
break;
|
|
case 1:
|
|
if (dst_fl) {
|
|
dst_fl[offset] = imbuf::interpolate_bilinear_border_fl(ibuf, xt, yt);
|
|
}
|
|
else {
|
|
dst_ch[offset] = imbuf::interpolate_bilinear_border_byte(ibuf, xt, yt);
|
|
}
|
|
break;
|
|
case 2:
|
|
if (dst_fl) {
|
|
dst_fl[offset] = imbuf::interpolate_cubic_bspline_fl(ibuf, xt, yt);
|
|
}
|
|
else {
|
|
dst_ch[offset] = imbuf::interpolate_cubic_bspline_byte(ibuf, xt, yt);
|
|
}
|
|
break;
|
|
}
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_transform_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float /*fac*/,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf * /*ibuf2*/,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
TransformVars *transform = (TransformVars *)seq->effectdata;
|
|
float scale_x, scale_y, translate_x, translate_y, rotate_radians;
|
|
|
|
/* Scale */
|
|
if (transform->uniform_scale) {
|
|
scale_x = scale_y = transform->ScalexIni;
|
|
}
|
|
else {
|
|
scale_x = transform->ScalexIni;
|
|
scale_y = transform->ScaleyIni;
|
|
}
|
|
|
|
int x = context->rectx;
|
|
int y = context->recty;
|
|
|
|
/* Translate */
|
|
if (!transform->percent) {
|
|
/* Compensate text size for preview render size. */
|
|
double proxy_size_comp = context->scene->r.size / 100.0;
|
|
if (context->preview_render_size != SEQ_RENDER_SIZE_SCENE) {
|
|
proxy_size_comp = SEQ_rendersize_to_scale_factor(context->preview_render_size);
|
|
}
|
|
|
|
translate_x = transform->xIni * proxy_size_comp + (x / 2.0f);
|
|
translate_y = transform->yIni * proxy_size_comp + (y / 2.0f);
|
|
}
|
|
else {
|
|
translate_x = x * (transform->xIni / 100.0f) + (x / 2.0f);
|
|
translate_y = y * (transform->yIni / 100.0f) + (y / 2.0f);
|
|
}
|
|
|
|
/* Rotate */
|
|
rotate_radians = DEG2RADF(transform->rotIni);
|
|
|
|
transform_image(x,
|
|
y,
|
|
start_line,
|
|
total_lines,
|
|
ibuf1,
|
|
out,
|
|
scale_x,
|
|
scale_y,
|
|
translate_x,
|
|
translate_y,
|
|
rotate_radians,
|
|
transform->interpolation);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Glow Effect
|
|
* \{ */
|
|
|
|
static void glow_blur_bitmap(
|
|
const float4 *src, float4 *map, int width, int height, float blur, int quality)
|
|
{
|
|
using namespace blender;
|
|
|
|
/* 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 RE_filter_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 = float(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)
|
|
{
|
|
using namespace blender;
|
|
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(Sequence *seq)
|
|
{
|
|
GlowVars *glow;
|
|
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
seq->effectdata = MEM_callocN(sizeof(GlowVars), "glowvars");
|
|
|
|
glow = (GlowVars *)seq->effectdata;
|
|
glow->fMini = 0.25;
|
|
glow->fClamp = 1.0;
|
|
glow->fBoost = 0.5;
|
|
glow->dDist = 3.0;
|
|
glow->dQuality = 3;
|
|
glow->bNoComp = 0;
|
|
}
|
|
|
|
static int num_inputs_glow()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void free_glow_effect(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static void copy_glow_effect(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
static void do_glow_effect_byte(Sequence *seq,
|
|
int render_size,
|
|
float fac,
|
|
int x,
|
|
int y,
|
|
uchar *rect1,
|
|
uchar * /*rect2*/,
|
|
uchar *out)
|
|
{
|
|
using namespace blender;
|
|
GlowVars *glow = (GlowVars *)seq->effectdata;
|
|
|
|
Array<float4> inbuf(x * y);
|
|
Array<float4> outbuf(x * y);
|
|
|
|
using namespace blender;
|
|
IMB_colormanagement_transform_from_byte_threaded(*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,
|
|
IB_PROFILE_SRGB,
|
|
IB_PROFILE_SRGB,
|
|
true,
|
|
x,
|
|
y_range.size(),
|
|
x,
|
|
x);
|
|
});
|
|
}
|
|
|
|
static void do_glow_effect_float(Sequence *seq,
|
|
int render_size,
|
|
float fac,
|
|
int x,
|
|
int y,
|
|
float *rect1,
|
|
float * /*rect2*/,
|
|
float *out)
|
|
{
|
|
using namespace blender;
|
|
float4 *outbuf = reinterpret_cast<float4 *>(out);
|
|
float4 *inbuf = reinterpret_cast<float4 *>(rect1);
|
|
GlowVars *glow = (GlowVars *)seq->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 ImBuf *do_glow_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3);
|
|
|
|
int render_size = 100 * context->rectx / context->scene->r.xsch;
|
|
|
|
if (out->float_buffer.data) {
|
|
do_glow_effect_float(seq,
|
|
render_size,
|
|
fac,
|
|
context->rectx,
|
|
context->recty,
|
|
ibuf1->float_buffer.data,
|
|
nullptr,
|
|
out->float_buffer.data);
|
|
}
|
|
else {
|
|
do_glow_effect_byte(seq,
|
|
render_size,
|
|
fac,
|
|
context->rectx,
|
|
context->recty,
|
|
ibuf1->byte_buffer.data,
|
|
nullptr,
|
|
out->byte_buffer.data);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Solid Color Effect
|
|
* \{ */
|
|
|
|
static void init_solid_color(Sequence *seq)
|
|
{
|
|
SolidColorVars *cv;
|
|
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
seq->effectdata = MEM_callocN(sizeof(SolidColorVars), "solidcolor");
|
|
|
|
cv = (SolidColorVars *)seq->effectdata;
|
|
cv->col[0] = cv->col[1] = cv->col[2] = 0.5;
|
|
}
|
|
|
|
static int num_inputs_color()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static void free_solid_color(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static void copy_solid_color(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
static StripEarlyOut early_out_color(const Sequence * /*seq*/, float /*fac*/)
|
|
{
|
|
return StripEarlyOut::NoInput;
|
|
}
|
|
|
|
static ImBuf *do_solid_color(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float /*fac*/,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
using namespace blender;
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3);
|
|
|
|
SolidColorVars *cv = (SolidColorVars *)seq->effectdata;
|
|
|
|
threading::parallel_for(IndexRange(out->y), 64, [&](const IndexRange y_range) {
|
|
if (out->byte_buffer.data) {
|
|
/* Byte image. */
|
|
uchar color[4];
|
|
rgb_float_to_uchar(color, cv->col);
|
|
color[3] = 255;
|
|
|
|
uchar *dst = out->byte_buffer.data + y_range.first() * out->x * 4;
|
|
uchar *dst_end = dst + y_range.size() * out->x * 4;
|
|
while (dst < dst_end) {
|
|
memcpy(dst, color, sizeof(color));
|
|
dst += 4;
|
|
}
|
|
}
|
|
else {
|
|
/* Float image. */
|
|
float color[4];
|
|
color[0] = cv->col[0];
|
|
color[1] = cv->col[1];
|
|
color[2] = cv->col[2];
|
|
color[3] = 1.0f;
|
|
|
|
float *dst = out->float_buffer.data + y_range.first() * out->x * 4;
|
|
float *dst_end = dst + y_range.size() * out->x * 4;
|
|
while (dst < dst_end) {
|
|
memcpy(dst, color, sizeof(color));
|
|
dst += 4;
|
|
}
|
|
}
|
|
});
|
|
|
|
out->planes = R_IMF_PLANES_RGB;
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Multi-Camera Effect
|
|
* \{ */
|
|
|
|
/** No effect inputs for multi-camera, we use #give_ibuf_seq. */
|
|
static int num_inputs_multicam()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static StripEarlyOut early_out_multicam(const Sequence * /*seq*/, float /*fac*/)
|
|
{
|
|
return StripEarlyOut::NoInput;
|
|
}
|
|
|
|
static ImBuf *do_multicam(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float timeline_frame,
|
|
float /*fac*/,
|
|
ImBuf * /*ibuf1*/,
|
|
ImBuf * /*ibuf2*/,
|
|
ImBuf * /*ibuf3*/)
|
|
{
|
|
ImBuf *out;
|
|
Editing *ed;
|
|
|
|
if (seq->multicam_source == 0 || seq->multicam_source >= seq->machine) {
|
|
return nullptr;
|
|
}
|
|
|
|
ed = context->scene->ed;
|
|
if (!ed) {
|
|
return nullptr;
|
|
}
|
|
ListBase *seqbasep = SEQ_get_seqbase_by_seq(context->scene, seq);
|
|
ListBase *channels = SEQ_get_channels_by_seq(&ed->seqbase, &ed->channels, seq);
|
|
if (!seqbasep) {
|
|
return nullptr;
|
|
}
|
|
|
|
out = seq_render_give_ibuf_seqbase(
|
|
context, timeline_frame, seq->multicam_source, channels, seqbasep);
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Adjustment Effect
|
|
* \{ */
|
|
|
|
/** No effect inputs for adjustment, we use #give_ibuf_seq. */
|
|
static int num_inputs_adjustment()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static StripEarlyOut early_out_adjustment(const Sequence * /*seq*/, float /*fac*/)
|
|
{
|
|
return StripEarlyOut::NoInput;
|
|
}
|
|
|
|
static ImBuf *do_adjustment_impl(const SeqRenderData *context, Sequence *seq, float timeline_frame)
|
|
{
|
|
Editing *ed;
|
|
ImBuf *i = nullptr;
|
|
|
|
ed = context->scene->ed;
|
|
|
|
ListBase *seqbasep = SEQ_get_seqbase_by_seq(context->scene, seq);
|
|
ListBase *channels = SEQ_get_channels_by_seq(&ed->seqbase, &ed->channels, seq);
|
|
|
|
/* 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,
|
|
SEQ_time_left_handle_frame_get(context->scene, seq),
|
|
SEQ_time_right_handle_frame_get(context->scene, seq) - 1);
|
|
|
|
if (seq->machine > 1) {
|
|
i = seq_render_give_ibuf_seqbase(
|
|
context, timeline_frame, seq->machine - 1, channels, seqbasep);
|
|
}
|
|
|
|
/* Found nothing? so let's work the way up the meta-strip stack, so
|
|
* that it is possible to group a bunch of adjustment strips into
|
|
* a meta-strip and have that work on everything below the meta-strip. */
|
|
|
|
if (!i) {
|
|
Sequence *meta;
|
|
|
|
meta = SEQ_find_metastrip_by_sequence(&ed->seqbase, nullptr, seq);
|
|
|
|
if (meta) {
|
|
i = do_adjustment_impl(context, meta, timeline_frame);
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
static ImBuf *do_adjustment(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float timeline_frame,
|
|
float /*fac*/,
|
|
ImBuf * /*ibuf1*/,
|
|
ImBuf * /*ibuf2*/,
|
|
ImBuf * /*ibuf3*/)
|
|
{
|
|
ImBuf *out;
|
|
Editing *ed;
|
|
|
|
ed = context->scene->ed;
|
|
|
|
if (!ed) {
|
|
return nullptr;
|
|
}
|
|
|
|
out = do_adjustment_impl(context, seq, timeline_frame);
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Speed Effect
|
|
* \{ */
|
|
|
|
static void init_speed_effect(Sequence *seq)
|
|
{
|
|
SpeedControlVars *v;
|
|
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
seq->effectdata = MEM_callocN(sizeof(SpeedControlVars), "speedcontrolvars");
|
|
|
|
v = (SpeedControlVars *)seq->effectdata;
|
|
v->speed_control_type = SEQ_SPEED_STRETCH;
|
|
v->speed_fader = 1.0f;
|
|
v->speed_fader_length = 0.0f;
|
|
v->speed_fader_frame_number = 0.0f;
|
|
}
|
|
|
|
static void load_speed_effect(Sequence *seq)
|
|
{
|
|
SpeedControlVars *v = (SpeedControlVars *)seq->effectdata;
|
|
v->frameMap = nullptr;
|
|
}
|
|
|
|
static int num_inputs_speed()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void free_speed_effect(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
SpeedControlVars *v = (SpeedControlVars *)seq->effectdata;
|
|
if (v->frameMap) {
|
|
MEM_freeN(v->frameMap);
|
|
}
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static void copy_speed_effect(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
SpeedControlVars *v;
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
v = (SpeedControlVars *)dst->effectdata;
|
|
v->frameMap = nullptr;
|
|
}
|
|
|
|
static StripEarlyOut early_out_speed(const Sequence * /*seq*/, float /*fac*/)
|
|
{
|
|
return StripEarlyOut::DoEffect;
|
|
}
|
|
|
|
static FCurve *seq_effect_speed_speed_factor_curve_get(Scene *scene, Sequence *seq)
|
|
{
|
|
return id_data_find_fcurve(&scene->id, seq, &RNA_Sequence, "speed_factor", 0, nullptr);
|
|
}
|
|
|
|
void seq_effect_speed_rebuild_map(Scene *scene, Sequence *seq)
|
|
{
|
|
const int effect_strip_length = SEQ_time_right_handle_frame_get(scene, seq) -
|
|
SEQ_time_left_handle_frame_get(scene, seq);
|
|
|
|
if ((seq->seq1 == nullptr) || (effect_strip_length < 1)) {
|
|
return; /* Make COVERITY happy and check for (CID 598) input strip. */
|
|
}
|
|
|
|
FCurve *fcu = seq_effect_speed_speed_factor_curve_get(scene, seq);
|
|
if (fcu == nullptr) {
|
|
return;
|
|
}
|
|
|
|
SpeedControlVars *v = (SpeedControlVars *)seq->effectdata;
|
|
if (v->frameMap) {
|
|
MEM_freeN(v->frameMap);
|
|
}
|
|
|
|
v->frameMap = static_cast<float *>(MEM_mallocN(sizeof(float) * 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, SEQ_time_left_handle_frame_get(scene, seq) + frame_index);
|
|
const int target_frame_max = SEQ_time_strip_length_get(scene, seq->seq1);
|
|
CLAMP(target_frame, 0, target_frame_max);
|
|
v->frameMap[frame_index] = target_frame;
|
|
}
|
|
}
|
|
|
|
static void seq_effect_speed_frame_map_ensure(Scene *scene, Sequence *seq)
|
|
{
|
|
SpeedControlVars *v = (SpeedControlVars *)seq->effectdata;
|
|
if (v->frameMap != nullptr) {
|
|
return;
|
|
}
|
|
|
|
seq_effect_speed_rebuild_map(scene, seq);
|
|
}
|
|
|
|
float seq_speed_effect_target_frame_get(Scene *scene,
|
|
Sequence *seq_speed,
|
|
float timeline_frame,
|
|
int input)
|
|
{
|
|
if (seq_speed->seq1 == nullptr) {
|
|
return 0.0f;
|
|
}
|
|
|
|
SEQ_effect_handle_get(seq_speed); /* Ensure, that data are initialized. */
|
|
int frame_index = round_fl_to_int(SEQ_give_frame_index(scene, seq_speed, timeline_frame));
|
|
SpeedControlVars *s = (SpeedControlVars *)seq_speed->effectdata;
|
|
const Sequence *source = seq_speed->seq1;
|
|
|
|
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 = SEQ_time_strip_length_get(scene, source) -
|
|
source->startofs;
|
|
const float speed_effetct_length = SEQ_time_right_handle_frame_get(scene, seq_speed) -
|
|
SEQ_time_left_handle_frame_get(scene, seq_speed);
|
|
const float ratio = frame_index / speed_effetct_length;
|
|
target_frame = target_content_length * ratio;
|
|
break;
|
|
}
|
|
case SEQ_SPEED_MULTIPLY: {
|
|
FCurve *fcu = seq_effect_speed_speed_factor_curve_get(scene, seq_speed);
|
|
if (fcu != nullptr) {
|
|
seq_effect_speed_frame_map_ensure(scene, seq_speed);
|
|
target_frame = s->frameMap[frame_index];
|
|
}
|
|
else {
|
|
target_frame = frame_index * s->speed_fader;
|
|
}
|
|
break;
|
|
}
|
|
case SEQ_SPEED_LENGTH:
|
|
target_frame = SEQ_time_strip_length_get(scene, source) * (s->speed_fader_length / 100.0f);
|
|
break;
|
|
case SEQ_SPEED_FRAME_NUMBER:
|
|
target_frame = s->speed_fader_frame_number;
|
|
break;
|
|
}
|
|
|
|
CLAMP(target_frame, 0, SEQ_time_strip_length_get(scene, source));
|
|
target_frame += seq_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,
|
|
Sequence *seq_speed,
|
|
float timeline_frame)
|
|
{
|
|
const float target_frame = seq_speed_effect_target_frame_get(
|
|
scene, seq_speed, timeline_frame, 0);
|
|
return target_frame - floor(target_frame);
|
|
}
|
|
|
|
static ImBuf *do_speed_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float timeline_frame,
|
|
float fac,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
SpeedControlVars *s = (SpeedControlVars *)seq->effectdata;
|
|
SeqEffectHandle cross_effect = get_sequence_effect_impl(SEQ_TYPE_CROSS);
|
|
ImBuf *out;
|
|
|
|
if (s->flags & SEQ_SPEED_USE_INTERPOLATION) {
|
|
fac = speed_effect_interpolation_ratio_get(context->scene, seq, timeline_frame);
|
|
/* Current frame is ibuf1, next frame is ibuf2. */
|
|
out = seq_render_effect_execute_threaded(
|
|
&cross_effect, context, nullptr, timeline_frame, fac, ibuf1, ibuf2, ibuf3);
|
|
return out;
|
|
}
|
|
|
|
/* No interpolation. */
|
|
return IMB_dupImBuf(ibuf1);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Over-Drop Effect
|
|
* \{ */
|
|
|
|
static void do_overdrop_effect(const SeqRenderData *context,
|
|
Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float fac,
|
|
const ImBuf *ibuf1,
|
|
const ImBuf *ibuf2,
|
|
const ImBuf * /*ibuf3*/,
|
|
int start_line,
|
|
int total_lines,
|
|
ImBuf *out)
|
|
{
|
|
int x = context->rectx;
|
|
int y = total_lines;
|
|
|
|
if (out->float_buffer.data) {
|
|
float *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_float_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_drop_effect_float(fac, x, y, rect1, rect2, rect_out);
|
|
do_alphaover_effect(fac, x, y, rect1, rect2, rect_out);
|
|
}
|
|
else {
|
|
uchar *rect1 = nullptr, *rect2 = nullptr, *rect_out = nullptr;
|
|
|
|
slice_get_byte_buffers(
|
|
context, ibuf1, ibuf2, nullptr, out, start_line, &rect1, &rect2, nullptr, &rect_out);
|
|
|
|
do_drop_effect_byte(fac, x, y, rect1, rect2, rect_out);
|
|
do_alphaover_effect(fac, x, y, rect1, rect2, rect_out);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Gaussian Blur
|
|
* \{ */
|
|
|
|
static void init_gaussian_blur_effect(Sequence *seq)
|
|
{
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
seq->effectdata = MEM_callocN(sizeof(WipeVars), "wipevars");
|
|
}
|
|
|
|
static int num_inputs_gaussian_blur()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static void free_gaussian_blur_effect(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static void copy_gaussian_blur_effect(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
static StripEarlyOut early_out_gaussian_blur(const Sequence *seq, float /*fac*/)
|
|
{
|
|
GaussianBlurVars *data = static_cast<GaussianBlurVars *>(seq->effectdata);
|
|
if (data->size_x == 0.0f && data->size_y == 0) {
|
|
return StripEarlyOut::UseInput1;
|
|
}
|
|
return StripEarlyOut::DoEffect;
|
|
}
|
|
|
|
static blender::Array<float> make_gaussian_blur_kernel(float rad, int size)
|
|
{
|
|
int n = 2 * size + 1;
|
|
blender::Array<float> gausstab(n);
|
|
|
|
float sum = 0.0f;
|
|
float fac = (rad > 0.0f ? 1.0f / rad : 0.0f);
|
|
for (int i = -size; i <= size; i++) {
|
|
float val = RE_filter_value(R_FILTER_GAUSS, float(i) * fac);
|
|
sum += val;
|
|
gausstab[i + size] = val;
|
|
}
|
|
|
|
float inv_sum = 1.0f / sum;
|
|
for (int i = 0; i < n; i++) {
|
|
gausstab[i] *= inv_sum;
|
|
}
|
|
|
|
return gausstab;
|
|
}
|
|
|
|
template<typename T>
|
|
static void gaussian_blur_x(const blender::Array<float> &gausstab,
|
|
int half_size,
|
|
int start_line,
|
|
int width,
|
|
int height,
|
|
int /*frame_height*/,
|
|
const T *rect,
|
|
T *dst)
|
|
{
|
|
dst += 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 = blender::math::max(x - half_size, 0);
|
|
int xmax = blender::math::min(x + half_size, width - 1);
|
|
for (int nx = xmin, index = (xmin - x) + half_size; nx <= xmax; nx++, index++) {
|
|
float weight = gausstab[index];
|
|
int offset = (y * width + nx) * 4;
|
|
accum += float4(rect + offset) * weight;
|
|
accum_weight += weight;
|
|
}
|
|
accum *= (1.0f / accum_weight);
|
|
dst[0] = accum[0];
|
|
dst[1] = accum[1];
|
|
dst[2] = accum[2];
|
|
dst[3] = accum[3];
|
|
dst += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
static void gaussian_blur_y(const blender::Array<float> &gausstab,
|
|
int half_size,
|
|
int start_line,
|
|
int width,
|
|
int height,
|
|
int frame_height,
|
|
const T *rect,
|
|
T *dst)
|
|
{
|
|
dst += 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 = blender::math::max(y - half_size, 0);
|
|
int ymax = blender::math::min(y + half_size, frame_height - 1);
|
|
for (int ny = ymin, index = (ymin - y) + half_size; ny <= ymax; ny++, index++) {
|
|
float weight = gausstab[index];
|
|
int offset = (ny * width + x) * 4;
|
|
accum += float4(rect + offset) * weight;
|
|
accum_weight += weight;
|
|
}
|
|
accum *= (1.0f / accum_weight);
|
|
dst[0] = accum[0];
|
|
dst[1] = accum[1];
|
|
dst[2] = accum[2];
|
|
dst[3] = accum[3];
|
|
dst += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
static ImBuf *do_gaussian_blur_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float /*fac*/,
|
|
ImBuf *ibuf1,
|
|
ImBuf * /*ibuf2*/,
|
|
ImBuf * /*ibuf3*/)
|
|
{
|
|
using namespace blender;
|
|
|
|
/* Create blur kernel weights. */
|
|
const GaussianBlurVars *data = static_cast<const GaussianBlurVars *>(seq->effectdata);
|
|
const int half_size_x = int(data->size_x + 0.5f);
|
|
const int half_size_y = int(data->size_y + 0.5f);
|
|
Array<float> gausstab_x = make_gaussian_blur_kernel(data->size_x, half_size_x);
|
|
Array<float> gausstab_y = make_gaussian_blur_kernel(data->size_y, half_size_y);
|
|
|
|
const int width = context->rectx;
|
|
const int height = context->recty;
|
|
const bool is_float = ibuf1->float_buffer.data;
|
|
|
|
/* Horizontal blur: create output, blur ibuf1 into it. */
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, nullptr, nullptr);
|
|
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(gausstab_x,
|
|
half_size_x,
|
|
y_first,
|
|
width,
|
|
y_size,
|
|
height,
|
|
ibuf1->float_buffer.data,
|
|
out->float_buffer.data);
|
|
}
|
|
else {
|
|
gaussian_blur_x(gausstab_x,
|
|
half_size_x,
|
|
y_first,
|
|
width,
|
|
y_size,
|
|
height,
|
|
ibuf1->byte_buffer.data,
|
|
out->byte_buffer.data);
|
|
}
|
|
});
|
|
|
|
/* Vertical blur: create output, blur previous output into it. */
|
|
ibuf1 = out;
|
|
out = prepare_effect_imbufs(context, ibuf1, nullptr, nullptr);
|
|
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(gausstab_y,
|
|
half_size_y,
|
|
y_first,
|
|
width,
|
|
y_size,
|
|
height,
|
|
ibuf1->float_buffer.data,
|
|
out->float_buffer.data);
|
|
}
|
|
else {
|
|
gaussian_blur_y(gausstab_y,
|
|
half_size_y,
|
|
y_first,
|
|
width,
|
|
y_size,
|
|
height,
|
|
ibuf1->byte_buffer.data,
|
|
out->byte_buffer.data);
|
|
}
|
|
});
|
|
|
|
/* Free the first output. */
|
|
IMB_freeImBuf(ibuf1);
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Text Effect
|
|
* \{ */
|
|
|
|
static void init_text_effect(Sequence *seq)
|
|
{
|
|
TextVars *data;
|
|
|
|
if (seq->effectdata) {
|
|
MEM_freeN(seq->effectdata);
|
|
}
|
|
|
|
data = static_cast<TextVars *>(seq->effectdata = MEM_callocN(sizeof(TextVars), "textvars"));
|
|
data->text_font = nullptr;
|
|
data->text_blf_id = -1;
|
|
data->text_size = 60.0f;
|
|
|
|
copy_v4_fl(data->color, 1.0f);
|
|
data->shadow_color[3] = 0.7f;
|
|
data->box_color[0] = 0.2f;
|
|
data->box_color[1] = 0.2f;
|
|
data->box_color[2] = 0.2f;
|
|
data->box_color[3] = 0.7f;
|
|
data->box_margin = 0.01f;
|
|
|
|
STRNCPY(data->text, "Text");
|
|
|
|
data->loc[0] = 0.5f;
|
|
data->loc[1] = 0.5f;
|
|
data->align = SEQ_TEXT_ALIGN_X_CENTER;
|
|
data->align_y = SEQ_TEXT_ALIGN_Y_CENTER;
|
|
data->wrap_width = 1.0f;
|
|
}
|
|
|
|
void SEQ_effect_text_font_unload(TextVars *data, const bool do_id_user)
|
|
{
|
|
if (data == nullptr) {
|
|
return;
|
|
}
|
|
|
|
/* Unlink the VFont */
|
|
if (do_id_user && data->text_font != nullptr) {
|
|
id_us_min(&data->text_font->id);
|
|
data->text_font = nullptr;
|
|
}
|
|
|
|
/* Unload the BLF font. */
|
|
if (data->text_blf_id >= 0) {
|
|
BLF_unload_id(data->text_blf_id);
|
|
}
|
|
}
|
|
|
|
void SEQ_effect_text_font_load(TextVars *data, const bool do_id_user)
|
|
{
|
|
VFont *vfont = data->text_font;
|
|
if (vfont == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (do_id_user) {
|
|
id_us_plus(&vfont->id);
|
|
}
|
|
|
|
if (vfont->packedfile != nullptr) {
|
|
PackedFile *pf = vfont->packedfile;
|
|
/* Create a name that's unique between library data-blocks to avoid loading
|
|
* a font per strip which will load fonts many times.
|
|
*
|
|
* WARNING: this isn't fool proof!
|
|
* The #VFont may be renamed which will cause this to load multiple times,
|
|
* in practice this isn't so likely though. */
|
|
char name[MAX_ID_FULL_NAME];
|
|
BKE_id_full_name_get(name, &vfont->id, 0);
|
|
|
|
data->text_blf_id = BLF_load_mem(name, static_cast<const uchar *>(pf->data), pf->size);
|
|
}
|
|
else {
|
|
char filepath[FILE_MAX];
|
|
STRNCPY(filepath, vfont->filepath);
|
|
if (BLI_thread_is_main()) {
|
|
/* FIXME: This is a band-aid fix. A proper solution has to be worked on by the VSE team.
|
|
*
|
|
* This code can be called from non-main thread, e.g. when copying sequences as part of
|
|
* depsgraph evaluated copy of the evaluated scene. Just skip font loading in that case, BLF
|
|
* code is not thread-safe, and if this happens from threaded context, it almost certainly
|
|
* means that a previous attempt to load the font already failed, e.g. because font file-path
|
|
* is invalid. Proposer fix would likely be to not attempt to reload a failed-to-load font
|
|
* every time. */
|
|
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&vfont->id));
|
|
|
|
data->text_blf_id = BLF_load(filepath);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void free_text_effect(Sequence *seq, const bool do_id_user)
|
|
{
|
|
TextVars *data = static_cast<TextVars *>(seq->effectdata);
|
|
SEQ_effect_text_font_unload(data, do_id_user);
|
|
|
|
if (data) {
|
|
MEM_freeN(data);
|
|
seq->effectdata = nullptr;
|
|
}
|
|
}
|
|
|
|
static void load_text_effect(Sequence *seq)
|
|
{
|
|
TextVars *data = static_cast<TextVars *>(seq->effectdata);
|
|
SEQ_effect_text_font_load(data, false);
|
|
}
|
|
|
|
static void copy_text_effect(Sequence *dst, const Sequence *src, const int flag)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
TextVars *data = static_cast<TextVars *>(dst->effectdata);
|
|
|
|
data->text_blf_id = -1;
|
|
SEQ_effect_text_font_load(data, (flag & LIB_ID_CREATE_NO_USER_REFCOUNT) == 0);
|
|
}
|
|
|
|
static int num_inputs_text()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static StripEarlyOut early_out_text(const Sequence *seq, float /*fac*/)
|
|
{
|
|
TextVars *data = static_cast<TextVars *>(seq->effectdata);
|
|
if (data->text[0] == 0 || data->text_size < 1.0f ||
|
|
((data->color[3] == 0.0f) &&
|
|
(data->shadow_color[3] == 0.0f || (data->flag & SEQ_TEXT_SHADOW) == 0)))
|
|
{
|
|
return StripEarlyOut::UseInput1;
|
|
}
|
|
return StripEarlyOut::NoInput;
|
|
}
|
|
|
|
static ImBuf *do_text_effect(const SeqRenderData *context,
|
|
Sequence *seq,
|
|
float /*timeline_frame*/,
|
|
float /*fac*/,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
/* Note: text rasterization only fills in part of output image,
|
|
* need to clear it. */
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3, false);
|
|
TextVars *data = static_cast<TextVars *>(seq->effectdata);
|
|
int width = out->x;
|
|
int height = out->y;
|
|
ColorManagedDisplay *display;
|
|
const char *display_device;
|
|
int font = blf_mono_font_render;
|
|
int line_height;
|
|
int y_ofs, x, y;
|
|
double proxy_size_comp;
|
|
|
|
if (data->text_blf_id == SEQ_FONT_NOT_LOADED) {
|
|
data->text_blf_id = -1;
|
|
|
|
SEQ_effect_text_font_load(data, false);
|
|
}
|
|
|
|
if (data->text_blf_id >= 0) {
|
|
font = data->text_blf_id;
|
|
}
|
|
|
|
display_device = context->scene->display_settings.display_device;
|
|
display = IMB_colormanagement_display_get_named(display_device);
|
|
|
|
/* Compensate text size for preview render size. */
|
|
proxy_size_comp = context->scene->r.size / 100.0;
|
|
if (context->preview_render_size != SEQ_RENDER_SIZE_SCENE) {
|
|
proxy_size_comp = SEQ_rendersize_to_scale_factor(context->preview_render_size);
|
|
}
|
|
|
|
/* set before return */
|
|
BLF_size(font, proxy_size_comp * data->text_size);
|
|
|
|
const int font_flags = BLF_WORD_WRAP | /* Always allow wrapping. */
|
|
((data->flag & SEQ_TEXT_BOLD) ? BLF_BOLD : 0) |
|
|
((data->flag & SEQ_TEXT_ITALIC) ? BLF_ITALIC : 0);
|
|
BLF_enable(font, font_flags);
|
|
|
|
/* use max width to enable newlines only */
|
|
BLF_wordwrap(font, (data->wrap_width != 0.0f) ? data->wrap_width * width : -1);
|
|
|
|
BLF_buffer(
|
|
font, out->float_buffer.data, out->byte_buffer.data, width, height, out->channels, display);
|
|
|
|
line_height = BLF_height_max(font);
|
|
|
|
y_ofs = -BLF_descender(font);
|
|
|
|
x = (data->loc[0] * width);
|
|
y = (data->loc[1] * height) + y_ofs;
|
|
|
|
/* vars for calculating wordwrap and optional box */
|
|
struct {
|
|
ResultBLF info;
|
|
rcti rect;
|
|
} wrap;
|
|
|
|
BLF_boundbox_ex(font, data->text, sizeof(data->text), &wrap.rect, &wrap.info);
|
|
|
|
if ((data->align == SEQ_TEXT_ALIGN_X_LEFT) && (data->align_y == SEQ_TEXT_ALIGN_Y_TOP)) {
|
|
y -= line_height;
|
|
}
|
|
else {
|
|
if (data->align == SEQ_TEXT_ALIGN_X_RIGHT) {
|
|
x -= BLI_rcti_size_x(&wrap.rect);
|
|
}
|
|
else if (data->align == SEQ_TEXT_ALIGN_X_CENTER) {
|
|
x -= BLI_rcti_size_x(&wrap.rect) / 2;
|
|
}
|
|
|
|
if (data->align_y == SEQ_TEXT_ALIGN_Y_TOP) {
|
|
y -= line_height;
|
|
}
|
|
else if (data->align_y == SEQ_TEXT_ALIGN_Y_BOTTOM) {
|
|
y += (wrap.info.lines - 1) * line_height;
|
|
}
|
|
else if (data->align_y == SEQ_TEXT_ALIGN_Y_CENTER) {
|
|
y += (((wrap.info.lines - 1) / 2) * line_height) - (line_height / 2);
|
|
}
|
|
}
|
|
|
|
if (data->flag & SEQ_TEXT_BOX) {
|
|
if (out->byte_buffer.data) {
|
|
const int margin = data->box_margin * width;
|
|
const int minx = x + wrap.rect.xmin - margin;
|
|
const int maxx = x + wrap.rect.xmax + margin;
|
|
const int miny = y + wrap.rect.ymin - margin;
|
|
const int maxy = y + wrap.rect.ymax + margin;
|
|
IMB_rectfill_area_replace(out, data->box_color, minx, miny, maxx, maxy);
|
|
}
|
|
}
|
|
/* BLF_SHADOW won't work with buffers, instead use cheap shadow trick */
|
|
if (data->flag & SEQ_TEXT_SHADOW) {
|
|
int fontx, fonty;
|
|
fontx = BLF_width_max(font);
|
|
fonty = line_height;
|
|
BLF_position(font, x + max_ii(fontx / 55, 1), y - max_ii(fonty / 30, 1), 0.0f);
|
|
BLF_buffer_col(font, data->shadow_color);
|
|
BLF_draw_buffer(font, data->text, sizeof(data->text));
|
|
}
|
|
|
|
BLF_position(font, x, y, 0.0f);
|
|
BLF_buffer_col(font, data->color);
|
|
BLF_draw_buffer(font, data->text, sizeof(data->text));
|
|
|
|
BLF_buffer(font, nullptr, nullptr, 0, 0, 0, nullptr);
|
|
|
|
BLF_disable(font, font_flags);
|
|
|
|
return out;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Sequence Effect Factory
|
|
* \{ */
|
|
|
|
static void init_noop(Sequence * /*seq*/) {}
|
|
|
|
static void load_noop(Sequence * /*seq*/) {}
|
|
|
|
static void free_noop(Sequence * /*seq*/, const bool /*do_id_user*/) {}
|
|
|
|
static int num_inputs_default()
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
static void copy_effect_default(Sequence *dst, const Sequence *src, const int /*flag*/)
|
|
{
|
|
dst->effectdata = MEM_dupallocN(src->effectdata);
|
|
}
|
|
|
|
static void free_effect_default(Sequence *seq, const bool /*do_id_user*/)
|
|
{
|
|
MEM_SAFE_FREE(seq->effectdata);
|
|
}
|
|
|
|
static StripEarlyOut early_out_noop(const Sequence * /*seq*/, float /*fac*/)
|
|
{
|
|
return StripEarlyOut::DoEffect;
|
|
}
|
|
|
|
static StripEarlyOut early_out_fade(const Sequence * /*seq*/, float fac)
|
|
{
|
|
if (fac == 0.0f) {
|
|
return StripEarlyOut::UseInput1;
|
|
}
|
|
if (fac == 1.0f) {
|
|
return StripEarlyOut::UseInput2;
|
|
}
|
|
return StripEarlyOut::DoEffect;
|
|
}
|
|
|
|
static StripEarlyOut early_out_mul_input2(const Sequence * /*seq*/, float fac)
|
|
{
|
|
if (fac == 0.0f) {
|
|
return StripEarlyOut::UseInput1;
|
|
}
|
|
return StripEarlyOut::DoEffect;
|
|
}
|
|
|
|
static StripEarlyOut early_out_mul_input1(const Sequence * /*seq*/, float fac)
|
|
{
|
|
if (fac == 0.0f) {
|
|
return StripEarlyOut::UseInput2;
|
|
}
|
|
return StripEarlyOut::DoEffect;
|
|
}
|
|
|
|
static void get_default_fac_noop(const Scene * /*scene*/,
|
|
const Sequence * /*seq*/,
|
|
float /*timeline_frame*/,
|
|
float *fac)
|
|
{
|
|
*fac = 1.0f;
|
|
}
|
|
|
|
static void get_default_fac_fade(const Scene *scene,
|
|
const Sequence *seq,
|
|
float timeline_frame,
|
|
float *fac)
|
|
{
|
|
*fac = float(timeline_frame - SEQ_time_left_handle_frame_get(scene, seq));
|
|
*fac /= SEQ_time_strip_length_get(scene, seq);
|
|
*fac = blender::math::clamp(*fac, 0.0f, 1.0f);
|
|
}
|
|
|
|
static ImBuf *init_execution(const SeqRenderData *context,
|
|
ImBuf *ibuf1,
|
|
ImBuf *ibuf2,
|
|
ImBuf *ibuf3)
|
|
{
|
|
ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3);
|
|
|
|
return out;
|
|
}
|
|
|
|
static SeqEffectHandle get_sequence_effect_impl(int seq_type)
|
|
{
|
|
SeqEffectHandle rval;
|
|
int sequence_type = seq_type;
|
|
|
|
rval.multithreaded = false;
|
|
rval.supports_mask = false;
|
|
rval.init = init_noop;
|
|
rval.num_inputs = num_inputs_default;
|
|
rval.load = load_noop;
|
|
rval.free = free_noop;
|
|
rval.early_out = early_out_noop;
|
|
rval.get_default_fac = get_default_fac_noop;
|
|
rval.execute = nullptr;
|
|
rval.init_execution = init_execution;
|
|
rval.execute_slice = nullptr;
|
|
rval.copy = nullptr;
|
|
|
|
switch (sequence_type) {
|
|
case SEQ_TYPE_CROSS:
|
|
rval.multithreaded = true;
|
|
rval.execute_slice = do_cross_effect;
|
|
rval.early_out = early_out_fade;
|
|
rval.get_default_fac = get_default_fac_fade;
|
|
break;
|
|
case SEQ_TYPE_GAMCROSS:
|
|
rval.multithreaded = true;
|
|
rval.early_out = early_out_fade;
|
|
rval.get_default_fac = get_default_fac_fade;
|
|
rval.init_execution = gammacross_init_execution;
|
|
rval.execute_slice = do_gammacross_effect;
|
|
break;
|
|
case SEQ_TYPE_ADD:
|
|
rval.multithreaded = true;
|
|
rval.execute_slice = do_add_effect;
|
|
rval.early_out = early_out_mul_input2;
|
|
break;
|
|
case SEQ_TYPE_SUB:
|
|
rval.multithreaded = true;
|
|
rval.execute_slice = do_sub_effect;
|
|
rval.early_out = early_out_mul_input2;
|
|
break;
|
|
case SEQ_TYPE_MUL:
|
|
rval.multithreaded = true;
|
|
rval.execute_slice = do_mul_effect;
|
|
rval.early_out = early_out_mul_input2;
|
|
break;
|
|
case SEQ_TYPE_SCREEN:
|
|
case SEQ_TYPE_OVERLAY:
|
|
case SEQ_TYPE_COLOR_BURN:
|
|
case SEQ_TYPE_LINEAR_BURN:
|
|
case SEQ_TYPE_DARKEN:
|
|
case SEQ_TYPE_LIGHTEN:
|
|
case SEQ_TYPE_DODGE:
|
|
case SEQ_TYPE_SOFT_LIGHT:
|
|
case SEQ_TYPE_HARD_LIGHT:
|
|
case SEQ_TYPE_PIN_LIGHT:
|
|
case SEQ_TYPE_LIN_LIGHT:
|
|
case SEQ_TYPE_VIVID_LIGHT:
|
|
case SEQ_TYPE_BLEND_COLOR:
|
|
case SEQ_TYPE_HUE:
|
|
case SEQ_TYPE_SATURATION:
|
|
case SEQ_TYPE_VALUE:
|
|
case SEQ_TYPE_DIFFERENCE:
|
|
case SEQ_TYPE_EXCLUSION:
|
|
rval.multithreaded = true;
|
|
rval.execute_slice = do_blend_mode_effect;
|
|
rval.early_out = early_out_mul_input2;
|
|
break;
|
|
case SEQ_TYPE_COLORMIX:
|
|
rval.multithreaded = true;
|
|
rval.init = init_colormix_effect;
|
|
rval.free = free_effect_default;
|
|
rval.copy = copy_effect_default;
|
|
rval.execute_slice = do_colormix_effect;
|
|
rval.early_out = early_out_mul_input2;
|
|
break;
|
|
case SEQ_TYPE_ALPHAOVER:
|
|
rval.multithreaded = true;
|
|
rval.init = init_alpha_over_or_under;
|
|
rval.execute_slice = do_alphaover_effect;
|
|
rval.early_out = early_out_mul_input1;
|
|
break;
|
|
case SEQ_TYPE_OVERDROP:
|
|
rval.multithreaded = true;
|
|
rval.execute_slice = do_overdrop_effect;
|
|
break;
|
|
case SEQ_TYPE_ALPHAUNDER:
|
|
rval.multithreaded = true;
|
|
rval.init = init_alpha_over_or_under;
|
|
rval.execute_slice = do_alphaunder_effect;
|
|
break;
|
|
case SEQ_TYPE_WIPE:
|
|
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;
|
|
break;
|
|
case SEQ_TYPE_GLOW:
|
|
rval.init = init_glow_effect;
|
|
rval.num_inputs = num_inputs_glow;
|
|
rval.free = free_glow_effect;
|
|
rval.copy = copy_glow_effect;
|
|
rval.execute = do_glow_effect;
|
|
break;
|
|
case SEQ_TYPE_TRANSFORM:
|
|
rval.multithreaded = true;
|
|
rval.init = init_transform_effect;
|
|
rval.num_inputs = num_inputs_transform;
|
|
rval.free = free_transform_effect;
|
|
rval.copy = copy_transform_effect;
|
|
rval.execute_slice = do_transform_effect;
|
|
break;
|
|
case SEQ_TYPE_SPEED:
|
|
rval.init = init_speed_effect;
|
|
rval.num_inputs = num_inputs_speed;
|
|
rval.load = load_speed_effect;
|
|
rval.free = free_speed_effect;
|
|
rval.copy = copy_speed_effect;
|
|
rval.execute = do_speed_effect;
|
|
rval.early_out = early_out_speed;
|
|
break;
|
|
case SEQ_TYPE_COLOR:
|
|
rval.init = init_solid_color;
|
|
rval.num_inputs = num_inputs_color;
|
|
rval.early_out = early_out_color;
|
|
rval.free = free_solid_color;
|
|
rval.copy = copy_solid_color;
|
|
rval.execute = do_solid_color;
|
|
break;
|
|
case SEQ_TYPE_MULTICAM:
|
|
rval.num_inputs = num_inputs_multicam;
|
|
rval.early_out = early_out_multicam;
|
|
rval.execute = do_multicam;
|
|
break;
|
|
case SEQ_TYPE_ADJUSTMENT:
|
|
rval.supports_mask = true;
|
|
rval.num_inputs = num_inputs_adjustment;
|
|
rval.early_out = early_out_adjustment;
|
|
rval.execute = do_adjustment;
|
|
break;
|
|
case SEQ_TYPE_GAUSSIAN_BLUR:
|
|
rval.init = init_gaussian_blur_effect;
|
|
rval.num_inputs = num_inputs_gaussian_blur;
|
|
rval.free = free_gaussian_blur_effect;
|
|
rval.copy = copy_gaussian_blur_effect;
|
|
rval.early_out = early_out_gaussian_blur;
|
|
rval.execute = do_gaussian_blur_effect;
|
|
break;
|
|
case SEQ_TYPE_TEXT:
|
|
rval.num_inputs = num_inputs_text;
|
|
rval.init = init_text_effect;
|
|
rval.free = free_text_effect;
|
|
rval.load = load_text_effect;
|
|
rval.copy = copy_text_effect;
|
|
rval.early_out = early_out_text;
|
|
rval.execute = do_text_effect;
|
|
break;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Public Sequencer Effect API
|
|
* \{ */
|
|
|
|
SeqEffectHandle SEQ_effect_handle_get(Sequence *seq)
|
|
{
|
|
SeqEffectHandle rval = {false, false, nullptr};
|
|
|
|
if (seq->type & SEQ_TYPE_EFFECT) {
|
|
rval = get_sequence_effect_impl(seq->type);
|
|
if ((seq->flag & SEQ_EFFECT_NOT_LOADED) != 0) {
|
|
rval.load(seq);
|
|
seq->flag &= ~SEQ_EFFECT_NOT_LOADED;
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
SeqEffectHandle seq_effect_get_sequence_blend(Sequence *seq)
|
|
{
|
|
SeqEffectHandle rval = {false, false, nullptr};
|
|
|
|
if (seq->blend_mode != 0) {
|
|
if ((seq->flag & SEQ_EFFECT_NOT_LOADED) != 0) {
|
|
/* load the effect first */
|
|
rval = get_sequence_effect_impl(seq->type);
|
|
rval.load(seq);
|
|
}
|
|
|
|
rval = get_sequence_effect_impl(seq->blend_mode);
|
|
if ((seq->flag & SEQ_EFFECT_NOT_LOADED) != 0) {
|
|
/* now load the blend and unset unloaded flag */
|
|
rval.load(seq);
|
|
seq->flag &= ~SEQ_EFFECT_NOT_LOADED;
|
|
}
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
int SEQ_effect_get_num_inputs(int seq_type)
|
|
{
|
|
SeqEffectHandle rval = get_sequence_effect_impl(seq_type);
|
|
|
|
int count = rval.num_inputs();
|
|
if (rval.execute || (rval.execute_slice && rval.init_execution)) {
|
|
return count;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** \} */
|