/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved. * SPDX-FileCopyrightText: 2003-2024 Blender Authors * SPDX-FileCopyrightText: 2005-2006 Peter Schlaile * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup bke */ #include #include #include #include "MEM_guardedalloc.h" #include "BLI_array.hh" #include "BLI_listbase.h" #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_anim_types.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_access.hh" #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" #include "strip_time.hh" #include "utils.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(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) { ImBuf *out; Scene *scene = context->scene; int x = context->rectx; int y = context->recty; if (!ibuf1 && !ibuf2 && !ibuf3) { /* hmmm, global float option ? */ out = IMB_allocImBuf(x, y, 32, IB_rect); } 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); } else { out = IMB_allocImBuf(x, y, 32, IB_rect); } 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 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 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 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 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(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 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(out->float_buffer.data); uchar4 *dst_ch = reinterpret_cast(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 temp(width * height); /* Initialize the gaussian filter. @TODO: use code from RE_filter_value */ Array 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 inbuf(x * y); Array 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(out); float4 *inbuf = reinterpret_cast(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(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(seq->effectdata); if (data->size_x == 0.0f && data->size_y == 0) { return StripEarlyOut::UseInput1; } return StripEarlyOut::DoEffect; } static blender::Array make_gaussian_blur_kernel(float rad, int size) { int n = 2 * size + 1; blender::Array 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 static void gaussian_blur_x(const blender::Array &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 static void gaussian_blur_y(const blender::Array &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(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 gausstab_x = make_gaussian_blur_kernel(data->size_x, half_size_x); Array 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(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(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 CoW 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(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(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(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(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) { ImBuf *out = prepare_effect_imbufs(context, ibuf1, ibuf2, ibuf3); TextVars *data = static_cast(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); } 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; } /** \} */