tornavis/source/blender/sequencer/intern/effects.cc

3136 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.hh"
#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, const 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_border_fl(ibuf, xt, yt);
}
else {
dst_ch[offset] = imbuf::interpolate_nearest_border_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 sequencer 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(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;
}
/** \} */