Blender V5.0
vse_effect_text.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <cmath>
10#include <mutex>
11
12#include "BKE_lib_id.hh"
13#include "BKE_library.hh"
14#include "BKE_main.hh"
15
16#include "BLI_map.hh"
17#include "BLI_math_base.hh"
18#include "BLI_math_rotation.h"
19#include "BLI_math_vector.h"
20#include "BLI_math_vector.hh"
21#include "BLI_path_utils.hh"
22#include "BLI_rect.h"
23#include "BLI_string.h"
24#include "BLI_string_utf8.h"
25#include "BLI_task.hh"
26#include "BLI_vector.hh"
27
28#include "BLF_api.hh"
29
31#include "DNA_scene_types.h"
32#include "DNA_sequence_types.h"
33#include "DNA_space_types.h"
34#include "DNA_vfont_types.h"
35
37#include "IMB_imbuf_types.hh"
38
39#include "SEQ_effects.hh"
40#include "SEQ_proxy.hh"
41#include "SEQ_render.hh"
42#include "SEQ_utils.hh"
43
44#include "effects.hh"
45
46namespace blender::seq {
47
48/* -------------------------------------------------------------------- */
49/* Sequencer font access.
50 *
51 * Text strips can access and use fonts from a background thread
52 * (when depsgraph evaluation copies the scene, or when prefetch renders
53 * frames with text strips in a background thread).
54 *
55 * To not interfere with what might be happening on the main thread, all
56 * fonts used by the sequencer are made unique via #BLF_load_unique
57 * #BLF_load_mem_unique, and there's a mutex to guard against
58 * sequencer itself possibly using the fonts from several threads.
59 */
60
61struct SeqFontMap {
62 /* File path -> font ID mapping for file-based fonts. */
64 /* Datablock name -> font ID mapping for memory (datablock) fonts. */
66
67 /* Font access mutex. Recursive since it is locked from
68 * text strip rendering, which can call into loading from within. */
69 std::recursive_mutex mutex;
70};
71
73
75{
76 for (const auto &item : g_font_map.path_to_file_font_id.items()) {
77 BLF_unload_id(item.value);
78 }
79 g_font_map.path_to_file_font_id.clear();
80 for (const auto &item : g_font_map.name_to_mem_font_id.items()) {
81 BLF_unload_id(item.value);
82 }
83 g_font_map.name_to_mem_font_id.clear();
84}
85
86static int strip_load_font_file(const std::string &path)
87{
88 std::lock_guard lock(g_font_map.mutex);
89 int fontid = g_font_map.path_to_file_font_id.add_or_modify(
90 path,
91 [&](int *fontid) {
92 /* New path: load font. */
93 *fontid = BLF_load_unique(path.c_str());
94 return *fontid;
95 },
96 [&](int *fontid) {
97 /* Path already in cache: add reference to already loaded font,
98 * or load a new one in case that
99 * font id was unloaded behind our backs. */
100 if (*fontid >= 0) {
101 if (BLF_is_loaded_id(*fontid)) {
102 BLF_addref_id(*fontid);
103 }
104 else {
105 *fontid = BLF_load_unique(path.c_str());
106 }
107 }
108 return *fontid;
109 });
110 return fontid;
111}
112
113static int strip_load_font_mem(const std::string &name, const uchar *data, int data_size)
114{
115 std::lock_guard lock(g_font_map.mutex);
116 int fontid = g_font_map.name_to_mem_font_id.add_or_modify(
117 name,
118 [&](int *fontid) {
119 /* New name: load font. */
120 *fontid = BLF_load_mem_unique(name.c_str(), data, data_size);
121 return *fontid;
122 },
123 [&](int *fontid) {
124 /* Name already in cache: add reference to already loaded font,
125 * or (if we're on the main thread) load a new one in case that
126 * font id was unloaded behind our backs. */
127 if (*fontid >= 0) {
128 if (BLF_is_loaded_id(*fontid)) {
129 BLF_addref_id(*fontid);
130 }
131 else {
132 *fontid = BLF_load_mem_unique(name.c_str(), data, data_size);
133 }
134 }
135 return *fontid;
136 });
137 return fontid;
138}
139
140static void strip_unload_font(int fontid)
141{
142 std::lock_guard lock(g_font_map.mutex);
143 bool unloaded = BLF_unload_id(fontid);
144 /* If that was the last usage of the font and it got unloaded: remove
145 * it from our maps. */
146 if (unloaded) {
147 g_font_map.path_to_file_font_id.remove_if([&](auto item) { return item.value == fontid; });
148 g_font_map.name_to_mem_font_id.remove_if([&](auto item) { return item.value == fontid; });
149 }
150}
151
152/* -------------------------------------------------------------------- */
155
157{
158 /* `data->text[0] == 0` is ignored on purpose in order to make it possible to edit. */
159
160 TextVars *data = static_cast<TextVars *>(strip->effectdata);
161 if (data->text_size < 1.0f ||
162 ((data->color[3] == 0.0f) &&
163 (data->shadow_color[3] == 0.0f || (data->flag & SEQ_TEXT_SHADOW) == 0) &&
164 (data->outline_color[3] == 0.0f || data->outline_width <= 0.0f ||
165 (data->flag & SEQ_TEXT_OUTLINE) == 0)))
166 {
167 return false;
168 }
169 return true;
170}
171
172static void init_text_effect(Strip *strip)
173{
174 if (strip->effectdata) {
175 MEM_freeN(strip->effectdata);
176 }
177
178 TextVars *data = MEM_callocN<TextVars>("textvars");
179 strip->effectdata = data;
180
181 data->text_font = nullptr;
182 data->text_blf_id = -1;
183 data->text_size = 60.0f;
184
185 copy_v4_fl(data->color, 1.0f);
186 data->shadow_color[3] = 0.7f;
187 data->shadow_angle = DEG2RADF(65.0f);
188 data->shadow_offset = 0.04f;
189 data->shadow_blur = 0.0f;
190 data->box_color[0] = 0.2f;
191 data->box_color[1] = 0.2f;
192 data->box_color[2] = 0.2f;
193 data->box_color[3] = 0.7f;
194 data->box_margin = 0.01f;
195 data->box_roundness = 0.0f;
196 data->outline_color[3] = 0.7f;
197 data->outline_width = 0.05f;
198
199 data->text_ptr = BLI_strdup("Text");
200 data->text_len_bytes = strlen(data->text_ptr);
201
202 data->loc[0] = 0.5f;
203 data->loc[1] = 0.5f;
204 data->anchor_x = SEQ_TEXT_ALIGN_X_CENTER;
205 data->anchor_y = SEQ_TEXT_ALIGN_Y_CENTER;
207 data->wrap_width = 1.0f;
208}
209
210void effect_text_font_unload(TextVars *data, const bool do_id_user)
211{
212 if (data == nullptr) {
213 return;
214 }
215
216 /* Unlink the VFont */
217 if (do_id_user && data->text_font != nullptr) {
218 id_us_min(&data->text_font->id);
219 data->text_font = nullptr;
220 }
221
222 /* Unload the font. */
223 if (data->text_blf_id >= 0) {
224 strip_unload_font(data->text_blf_id);
225 data->text_blf_id = -1;
226 }
227}
228
229void effect_text_font_load(TextVars *data, const bool do_id_user)
230{
231 VFont *vfont = data->text_font;
232 if (vfont == nullptr) {
233 return;
234 }
235
236 if (do_id_user) {
237 id_us_plus(&vfont->id);
238 }
239
240 if (vfont->packedfile != nullptr) {
241 PackedFile *pf = vfont->packedfile;
242 /* Create a name that's unique between library data-blocks to avoid loading
243 * a font per strip which will load fonts many times.
244 *
245 * WARNING: this isn't fool proof!
246 * The #VFont may be renamed which will cause this to load multiple times,
247 * in practice this isn't so likely though. */
249 BKE_id_full_name_get(name, &vfont->id, 0);
250
251 data->text_blf_id = strip_load_font_mem(name, static_cast<const uchar *>(pf->data), pf->size);
252 }
253 else {
254 char filepath[FILE_MAX];
255 STRNCPY(filepath, vfont->filepath);
256
257 BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&vfont->id));
258 data->text_blf_id = strip_load_font_file(filepath);
259 }
260}
261
262static void free_text_effect(Strip *strip, const bool do_id_user)
263{
264 TextVars *data = static_cast<TextVars *>(strip->effectdata);
265 effect_text_font_unload(data, do_id_user);
266
267 if (data) {
268 MEM_SAFE_FREE(data->text_ptr);
269 MEM_delete(data->runtime);
271 strip->effectdata = nullptr;
272 }
273}
274
275static void load_text_effect(Strip *strip)
276{
277 TextVars *data = static_cast<TextVars *>(strip->effectdata);
279}
280
281static void copy_text_effect(Strip *dst, const Strip *src, const int flag)
282{
284 TextVars *data = static_cast<TextVars *>(dst->effectdata);
285 data->text_ptr = BLI_strdup_null(data->text_ptr);
286
287 data->runtime = nullptr;
288 data->text_blf_id = -1;
290}
291
292static int num_inputs_text()
293{
294 return 0;
295}
296
297static StripEarlyOut early_out_text(const Strip *strip, float /*fac*/)
298{
299 if (!effects_can_render_text(strip)) {
301 }
303}
304
305/* Simplified version of gaussian blur specifically for text shadow blurring:
306 * - Data is only the alpha channel,
307 * - Skips blur outside of shadow rectangle. */
308static void text_gaussian_blur_x(const Span<float> gaussian,
309 int half_size,
310 int start_line,
311 int width,
312 int height,
313 const uchar *rect,
314 uchar *dst,
315 const rcti &shadow_rect)
316{
317 dst += int64_t(start_line) * width;
318 for (int y = start_line; y < start_line + height; y++) {
319 for (int x = 0; x < width; x++) {
320 float accum(0.0f);
321 if (x >= shadow_rect.xmin && x <= shadow_rect.xmax) {
322 float accum_weight = 0.0f;
323 int xmin = math::max(x - half_size, shadow_rect.xmin);
324 int xmax = math::min(x + half_size, shadow_rect.xmax);
325 for (int nx = xmin, index = (xmin - x) + half_size; nx <= xmax; nx++, index++) {
326 float weight = gaussian[index];
327 int offset = y * width + nx;
328 accum += rect[offset] * weight;
329 accum_weight += weight;
330 }
331 accum *= (1.0f / accum_weight);
332 }
333
334 *dst = accum;
335 dst++;
336 }
337 }
338}
339
340static void text_gaussian_blur_y(const Span<float> gaussian,
341 int half_size,
342 int start_line,
343 int width,
344 int height,
345 const uchar *rect,
346 uchar *dst,
347 const rcti &shadow_rect)
348{
349 dst += int64_t(start_line) * width;
350 for (int y = start_line; y < start_line + height; y++) {
351 for (int x = 0; x < width; x++) {
352 float accum(0.0f);
353 if (x >= shadow_rect.xmin && x <= shadow_rect.xmax) {
354 float accum_weight = 0.0f;
355 int ymin = math::max(y - half_size, shadow_rect.ymin);
356 int ymax = math::min(y + half_size, shadow_rect.ymax);
357 for (int ny = ymin, index = (ymin - y) + half_size; ny <= ymax; ny++, index++) {
358 float weight = gaussian[index];
359 int offset = ny * width + x;
360 accum += rect[offset] * weight;
361 accum_weight += weight;
362 }
363 accum *= (1.0f / accum_weight);
364 }
365 *dst = accum;
366 dst++;
367 }
368 }
369}
370
371static void clamp_rect(int width, int height, rcti &r_rect)
372{
373 r_rect.xmin = math::clamp(r_rect.xmin, 0, width - 1);
374 r_rect.xmax = math::clamp(r_rect.xmax, 0, width - 1);
375 r_rect.ymin = math::clamp(r_rect.ymin, 0, height - 1);
376 r_rect.ymax = math::clamp(r_rect.ymax, 0, height - 1);
377}
378
379static void initialize_shadow_alpha(int width,
380 int height,
381 int2 offset,
382 const rcti &shadow_rect,
383 const uchar *input,
384 Array<uchar> &r_shadow_mask)
385{
386 const IndexRange shadow_y_range(shadow_rect.ymin, shadow_rect.ymax - shadow_rect.ymin + 1);
387 threading::parallel_for(shadow_y_range, 8, [&](const IndexRange y_range) {
388 for (const int64_t y : y_range) {
389 const int64_t src_y = math::clamp<int64_t>(y + offset.y, 0, height - 1);
390 for (int x = shadow_rect.xmin; x <= shadow_rect.xmax; x++) {
391 int src_x = math::clamp(x - offset.x, 0, width - 1);
392 size_t src_offset = width * src_y + src_x;
393 size_t dst_offset = width * y + x;
394 r_shadow_mask[dst_offset] = input[src_offset * 4 + 3];
395 }
396 }
397 });
398}
399
400static void composite_shadow(int width,
401 const rcti &shadow_rect,
402 const float4 &shadow_color,
403 const Array<uchar> &shadow_mask,
404 uchar *output)
405{
406 const IndexRange shadow_y_range(shadow_rect.ymin, shadow_rect.ymax - shadow_rect.ymin + 1);
407 threading::parallel_for(shadow_y_range, 8, [&](const IndexRange y_range) {
408 for (const int64_t y : y_range) {
409 size_t offset = y * width + shadow_rect.xmin;
410 uchar *dst = output + offset * 4;
411 for (int x = shadow_rect.xmin; x <= shadow_rect.xmax; x++, offset++, dst += 4) {
412 uchar a = shadow_mask[offset];
413 if (a == 0) {
414 /* Fully transparent, leave output pixel as is. */
415 continue;
416 }
417 float4 col1 = load_premul_pixel(dst);
418 float4 col2 = shadow_color * (a * (1.0f / 255.0f));
419 /* Blend under the output. */
420 float fac = 1.0f - col1.w;
421 float4 col = col1 + fac * col2;
423 }
424 }
425 });
426}
427
429 const RenderData *context, const TextVars *data, int line_height, const rcti &rect, ImBuf *out)
430{
431 const int width = context->rectx;
432 const int height = context->recty;
433 /* Blur value of 1.0 applies blur kernel that is half of text line height. */
434 const float blur_amount = line_height * 0.5f * data->shadow_blur;
435 bool do_blur = blur_amount >= 1.0f;
436
437 Array<uchar> shadow_mask(size_t(width) * height, 0);
438
439 const int2 offset = int2(cosf(data->shadow_angle) * line_height * data->shadow_offset,
440 sinf(data->shadow_angle) * line_height * data->shadow_offset);
441
442 rcti shadow_rect = rect;
443 BLI_rcti_translate(&shadow_rect, offset.x, -offset.y);
444 BLI_rcti_pad(&shadow_rect, 1, 1);
445 clamp_rect(width, height, shadow_rect);
446
447 /* Initialize shadow by copying existing text/outline alpha. */
448 initialize_shadow_alpha(width, height, offset, shadow_rect, out->byte_buffer.data, shadow_mask);
449
450 if (do_blur) {
451 /* Create blur kernel weights. */
452 const int half_size = int(blur_amount + 0.5f);
453 Array<float> gaussian = make_gaussian_blur_kernel(blur_amount, half_size);
454
455 BLI_rcti_pad(&shadow_rect, half_size + 1, half_size + 1);
456 clamp_rect(width, height, shadow_rect);
457
458 /* Horizontal blur: blur shadow_mask into blur_buffer. */
459 Array<uchar> blur_buffer(size_t(width) * height, NoInitialization());
460 IndexRange blur_y_range(shadow_rect.ymin, shadow_rect.ymax - shadow_rect.ymin + 1);
461 threading::parallel_for(blur_y_range, 8, [&](const IndexRange y_range) {
462 const int y_first = y_range.first();
463 const int y_size = y_range.size();
464 text_gaussian_blur_x(gaussian,
465 half_size,
466 y_first,
467 width,
468 y_size,
469 shadow_mask.data(),
470 blur_buffer.data(),
471 shadow_rect);
472 });
473
474 /* Vertical blur: blur blur_buffer into shadow_mask. */
475 threading::parallel_for(blur_y_range, 8, [&](const IndexRange y_range) {
476 const int y_first = y_range.first();
477 const int y_size = y_range.size();
478 text_gaussian_blur_y(gaussian,
479 half_size,
480 y_first,
481 width,
482 y_size,
483 blur_buffer.data(),
484 shadow_mask.data(),
485 shadow_rect);
486 });
487 }
488
489 /* Composite shadow under regular output. */
490 float4 color = data->shadow_color;
491 color.x *= color.w;
492 color.y *= color.w;
493 color.z *= color.w;
494 composite_shadow(width, shadow_rect, color, shadow_mask, out->byte_buffer.data);
495}
496
497/* Text outline calculation is done by Jump Flooding Algorithm (JFA).
498 * This is similar to inpaint/jump_flooding in Compositor, also to
499 * "The Quest for Very Wide Outlines", Ben Golus 2020
500 * https://bgolus.medium.com/the-quest-for-very-wide-outlines-ba82ed442cd9 */
501
502constexpr uint16_t JFA_INVALID = 0xFFFF;
503
504struct JFACoord {
505 uint16_t x;
506 uint16_t y;
507};
508
511 int2 size,
512 IndexRange x_range,
513 IndexRange y_range,
514 int step_size)
515{
516 threading::parallel_for(y_range, 8, [&](const IndexRange sub_y_range) {
517 for (const int64_t y : sub_y_range) {
518 size_t index = y * size.x;
519 for (const int64_t x : x_range) {
520 float2 coord = float2(x, y);
521
522 /* For each pixel, sample 9 pixels at +/- step size pattern,
523 * and output coordinate of closest to the boundary. */
524 JFACoord closest_texel{JFA_INVALID, JFA_INVALID};
525 float minimum_squared_distance = std::numeric_limits<float>::max();
526 for (int dy = -step_size; dy <= step_size; dy += step_size) {
527 int yy = y + dy;
528 if (yy < 0 || yy >= size.y) {
529 continue;
530 }
531 for (int dx = -step_size; dx <= step_size; dx += step_size) {
532 int xx = x + dx;
533 if (xx < 0 || xx >= size.x) {
534 continue;
535 }
536 JFACoord val = input[size_t(yy) * size.x + xx];
537 if (val.x == JFA_INVALID) {
538 continue;
539 }
540
541 float squared_distance = math::distance_squared(float2(val.x, val.y), coord);
542 if (squared_distance < minimum_squared_distance) {
543 minimum_squared_distance = squared_distance;
544 closest_texel = val;
545 }
546 }
547 }
548
549 output[index + x] = closest_texel;
550 }
551 }
552 });
553}
554
555static void text_draw(const char *text_ptr, const TextVarsRuntime *runtime, float color[4])
556{
557 const bool use_fallback = BLF_is_builtin(runtime->font);
558 if (!use_fallback) {
560 }
561
562 for (const LineInfo &line : runtime->lines) {
563 for (const CharInfo &character : line.characters) {
564 BLF_position(runtime->font, character.position.x, character.position.y, 0.0f);
565 BLF_buffer_col(runtime->font, color);
566 BLF_draw_buffer(runtime->font, text_ptr + character.offset, character.byte_length);
567 }
568 }
569
570 if (!use_fallback) {
572 }
573}
574
575static rcti draw_text_outline(const RenderData *context,
576 const TextVars *data,
577 const TextVarsRuntime *runtime,
578 ImBuf *out)
579{
580 /* Outline width of 1.0 maps to half of text line height. */
581 const int outline_width = int(runtime->line_height * 0.5f * data->outline_width);
582 if (outline_width < 1 || data->outline_color[3] <= 0.0f ||
583 ((data->flag & SEQ_TEXT_OUTLINE) == 0))
584 {
585 return runtime->text_boundbox;
586 }
587
588 const int2 size = int2(context->rectx, context->recty);
589
590 /* Draw white text into temporary buffer. */
591 const size_t pixel_count = size_t(size.x) * size.y;
592 Array<uchar4> tmp_buf(pixel_count, uchar4(0));
593 BLF_buffer(runtime->font,
594 nullptr,
595 (uchar *)tmp_buf.data(),
596 size.x,
597 size.y,
598 out->byte_buffer.colorspace);
599
600 text_draw(data->text_ptr, runtime, float4(1.0f));
601
602 rcti outline_rect = runtime->text_boundbox;
603 BLI_rcti_pad(&outline_rect, outline_width + 1, outline_width + 1);
604 outline_rect.xmin = clamp_i(outline_rect.xmin, 0, size.x - 1);
605 outline_rect.xmax = clamp_i(outline_rect.xmax, 0, size.x - 1);
606 outline_rect.ymin = clamp_i(outline_rect.ymin, 0, size.y - 1);
607 outline_rect.ymax = clamp_i(outline_rect.ymax, 0, size.y - 1);
608 const IndexRange rect_x_range(outline_rect.xmin, outline_rect.xmax - outline_rect.xmin + 1);
609 const IndexRange rect_y_range(outline_rect.ymin, outline_rect.ymax - outline_rect.ymin + 1);
610
611 /* Initialize JFA: invalid values for empty regions, pixel coordinates
612 * for opaque regions. */
613 Array<JFACoord> boundary(pixel_count, NoInitialization());
614 threading::parallel_for(IndexRange(size.y), 16, [&](const IndexRange y_range) {
615 for (const int y : y_range) {
616 size_t index = size_t(y) * size.x;
617 for (int x = 0; x < size.x; x++, index++) {
618 bool is_opaque = tmp_buf[index].w >= 128;
619 JFACoord coord;
620 coord.x = is_opaque ? x : JFA_INVALID;
621 coord.y = is_opaque ? y : JFA_INVALID;
622 boundary[index] = coord;
623 }
624 }
625 });
626
627 /* Do jump flooding calculations. */
628 JFACoord invalid_coord{JFA_INVALID, JFA_INVALID};
629 Array<JFACoord> initial_flooded_result(pixel_count, invalid_coord);
630 jump_flooding_pass(boundary, initial_flooded_result, size, rect_x_range, rect_y_range, 1);
631
632 Array<JFACoord> *result_to_flood = &initial_flooded_result;
633 Array<JFACoord> intermediate_result(pixel_count, invalid_coord);
634 Array<JFACoord> *result_after_flooding = &intermediate_result;
635
636 int step_size = power_of_2_max_i(outline_width) / 2;
637
638 while (step_size != 0) {
640 *result_to_flood, *result_after_flooding, size, rect_x_range, rect_y_range, step_size);
641 std::swap(result_to_flood, result_after_flooding);
642 step_size /= 2;
643 }
644
645 /* Premultiplied outline color. */
646 float4 color = data->outline_color;
647 color.x *= color.w;
648 color.y *= color.w;
649 color.z *= color.w;
650
651 const float text_color_alpha = data->color[3];
652
653 /* We have distances to the closest opaque parts of the image now. Composite the
654 * outline into the output image. */
655
656 threading::parallel_for(rect_y_range, 8, [&](const IndexRange y_range) {
657 for (const int y : y_range) {
658 size_t index = size_t(y) * size.x + rect_x_range.start();
659 uchar *dst = out->byte_buffer.data + index * 4;
660 for (int x = rect_x_range.start(); x < rect_x_range.one_after_last(); x++, index++, dst += 4)
661 {
662 JFACoord closest_texel = (*result_to_flood)[index];
663 if (closest_texel.x == JFA_INVALID) {
664 /* Outside of outline, leave output pixel as is. */
665 continue;
666 }
667
668 /* Fade out / anti-alias the outline over one pixel towards outline distance. */
669 float distance = math::distance(float2(x, y), float2(closest_texel.x, closest_texel.y));
670 float alpha = math::clamp(outline_width - distance + 1.0f, 0.0f, 1.0f);
671
672 /* Do not put outline inside the text shape:
673 * - When overall text color is fully opaque, we want to make
674 * outline fully transparent only where text is fully opaque.
675 * This ensures that combined anti-aliased pixels at text boundary
676 * are properly fully opaque.
677 * - However when text color is fully transparent, we want to
678 * Use opposite alpha of text, to anti-alias the inner edge of
679 * the outline.
680 * In between those two, interpolate the alpha modulation factor. */
681 float text_alpha = tmp_buf[index].w * (1.0f / 255.0f);
682 float mul_opaque_text = text_alpha >= 1.0f ? 0.0f : 1.0f;
683 float mul_transparent_text = 1.0f - text_alpha;
684 float mul = math::interpolate(mul_transparent_text, mul_opaque_text, text_color_alpha);
685 alpha *= mul;
686
687 float4 col1 = color;
688 col1 *= alpha;
689
690 /* Blend over the output. */
691 float mfac = 1.0f - col1.w;
692 float4 col2 = load_premul_pixel(dst);
693 float4 col = col1 + mfac * col2;
695 }
696 }
697 });
699 runtime->font, nullptr, out->byte_buffer.data, size.x, size.y, out->byte_buffer.colorspace);
700
701 return outline_rect;
702}
703
704/* Similar to #IMB_rectfill_area but blends the given color under the
705 * existing image. Also can do rounded corners. Only works on byte buffers. */
707 const ImBuf *ibuf, const float col[4], int x1, int y1, int x2, int y2, float corner_radius)
708{
709 const int width = ibuf->x;
710 const int height = ibuf->y;
711 x1 = math::clamp(x1, 0, width);
712 x2 = math::clamp(x2, 0, width);
713 y1 = math::clamp(y1, 0, height);
714 y2 = math::clamp(y2, 0, height);
715 if (x1 > x2) {
716 std::swap(x1, x2);
717 }
718 if (y1 > y2) {
719 std::swap(y1, y2);
720 }
721 if (x1 == x2 || y1 == y2) {
722 return;
723 }
724
725 corner_radius = math::clamp(corner_radius, 0.0f, math::min(x2 - x1, y2 - y1) / 2.0f);
726
727 float4 premul_col_base;
728 straight_to_premul_v4_v4(premul_col_base, col);
729
730 threading::parallel_for(IndexRange::from_begin_end(y1, y2), 16, [&](const IndexRange y_range) {
731 for (const int y : y_range) {
732 uchar *dst = ibuf->byte_buffer.data + (size_t(width) * y + x1) * 4;
733 float origin_x = 0.0f, origin_y = 0.0f;
734 for (int x = x1; x < x2; x++) {
735 float4 pix = load_premul_pixel(dst);
736 float fac = 1.0f - pix.w;
737
738 float4 premul_col = premul_col_base;
739 bool is_corner = false;
740 if (x < x1 + corner_radius && y < y1 + corner_radius) {
741 is_corner = true;
742 origin_x = x1 + corner_radius - 1;
743 origin_y = y1 + corner_radius - 1;
744 }
745 else if (x >= x2 - corner_radius && y < y1 + corner_radius) {
746 is_corner = true;
747 origin_x = x2 - corner_radius;
748 origin_y = y1 + corner_radius - 1;
749 }
750 else if (x < x1 + corner_radius && y >= y2 - corner_radius) {
751 is_corner = true;
752 origin_x = x1 + corner_radius - 1;
753 origin_y = y2 - corner_radius;
754 }
755 else if (x >= x2 - corner_radius && y >= y2 - corner_radius) {
756 is_corner = true;
757 origin_x = x2 - corner_radius;
758 origin_y = y2 - corner_radius;
759 }
760 if (is_corner) {
761 /* If we are inside rounded corner, evaluate a superellipse and
762 * modulate color with that. Superellipse instead of just a circle
763 * since the curvature between flat and rounded area looks a bit
764 * nicer. */
765 constexpr float curve_pow = 2.1f;
766 float r = powf(powf(abs(x - origin_x), curve_pow) + powf(abs(y - origin_y), curve_pow),
767 1.0f / curve_pow);
768 float alpha = math::clamp(corner_radius - r, 0.0f, 1.0f);
769 premul_col *= alpha;
770 }
771
772 float4 dst_fl = fac * premul_col + pix;
773 store_premul_pixel(dst_fl, dst);
774 dst += 4;
775 }
776 }
777 });
778}
779
780static int text_effect_line_size_get(const RenderData *context, const Strip *strip)
781{
782 TextVars *data = static_cast<TextVars *>(strip->effectdata);
783
784 /* Used to calculate boundbox. Render scale compensation is not needed there. */
785 if (context == nullptr) {
786 return data->text_size;
787 }
788
789 /* Compensate for preview render size. */
790 const float size_scale = seq::get_render_scale_factor(*context);
791 return size_scale * data->text_size;
792}
793
794int text_effect_font_init(const RenderData *context, const Strip *strip, FontFlags font_flags)
795{
796 TextVars *data = static_cast<TextVars *>(strip->effectdata);
797 int font = blf_mono_font_render;
798
799 /* In case font got unloaded behind our backs: mark it as needing a load. */
800 if (data->text_blf_id >= 0 && !BLF_is_loaded_id(data->text_blf_id)) {
801 data->text_blf_id = STRIP_FONT_NOT_LOADED;
802 }
803
804 if (data->text_blf_id == STRIP_FONT_NOT_LOADED) {
805 data->text_blf_id = -1;
806
808 }
809
810 if (data->text_blf_id >= 0) {
811 font = data->text_blf_id;
812 }
813
814 BLF_size(font, text_effect_line_size_get(context, strip));
815 BLF_enable(font, font_flags);
816 return font;
817}
818
820{
821 Vector<CharInfo> characters;
822 const int len_max = data->text_len_bytes;
823 int byte_offset = 0;
824 int char_index = 0;
825
826 const bool use_fallback = BLF_is_builtin(font);
827 if (!use_fallback) {
829 }
830
831 while (byte_offset <= len_max) {
832 const char *str = data->text_ptr + byte_offset;
833 const int char_length = BLI_str_utf8_size_safe(str);
834
835 CharInfo char_info;
836 char_info.index = char_index;
837 char_info.offset = byte_offset;
838 char_info.byte_length = char_length;
839 char_info.advance_x = BLF_glyph_advance(font, str);
840 characters.append(char_info);
841
842 byte_offset += char_length;
843 char_index++;
844 }
845
846 if (!use_fallback) {
848 }
849
850 return characters;
851}
852
853static int wrap_width_get(const TextVars *data, const int2 image_size)
854{
855 if (data->wrap_width == 0.0f) {
856 return std::numeric_limits<int>::max();
857 }
858 return data->wrap_width * image_size.x;
859}
860
861/* Lines must contain CharInfo for newlines and \0, as UI must know where they begin. */
863 TextVarsRuntime *runtime,
864 const int2 image_size,
865 Vector<CharInfo> &characters)
866{
867 const int wrap_width = wrap_width_get(data, image_size);
868
869 float2 char_position{0.0f, 0.0f};
870 CharInfo *last_space = nullptr;
871
872 /* First pass: Find characters where line has to be broken. */
873 for (CharInfo &character : characters) {
874 char ch = data->text_ptr[character.offset];
875 if (ch == ' ') {
876 character.position = char_position;
877 last_space = &character;
878 }
879 if (ch == '\n') {
880 char_position.x = 0;
881 last_space = nullptr;
882 }
883 if (ch != '\0' && char_position.x > wrap_width && last_space != nullptr) {
884 last_space->do_wrap = true;
885 char_position -= last_space->position + last_space->advance_x;
886 }
887 char_position.x += character.advance_x;
888 }
889
890 /* Second pass: Fill lines with characters. */
891 char_position = {0.0f, 0.0f};
892 runtime->lines.append(LineInfo());
893 for (CharInfo &character : characters) {
894 character.position = char_position;
895 runtime->lines.last().characters.append(character);
896 runtime->lines.last().width = char_position.x;
897
898 char_position.x += character.advance_x;
899
900 if (character.do_wrap || data->text_ptr[character.offset] == '\n') {
901 runtime->lines.append(LineInfo());
902 char_position.x = 0;
903 char_position.y -= runtime->line_height;
904 }
905 }
906
907 /* Third pass: Ensure, that lines have correct width.
908 * Note, that with italic fonts it is not possible to rely on `advance_x` value only. The actual
909 * last character position (\0 or \n) is not changed, because cursor would be drawn at slightly
910 * incorrect position. */
911 for (LineInfo &line : runtime->lines) {
912 if (line.characters.size() <= 1) {
913 continue;
914 }
915
916 CharInfo last_visible_char = line.characters[line.characters.size() - 2];
917 const char *buf = &data->text_ptr[last_visible_char.offset];
918 int glyph_width = math::ceil(BLF_width(runtime->font, buf, last_visible_char.byte_length));
919 line.width = last_visible_char.position.x + glyph_width;
920 }
921}
922
923static int text_box_width_get(const Vector<LineInfo> &lines)
924{
925 int width_max = 0;
926
927 for (const LineInfo &line : lines) {
928 width_max = std::max(width_max, line.width);
929 }
930 return width_max;
931}
932
934 float line_width,
935 int width_max)
936{
937 const float line_offset = (width_max - line_width);
938
939 if (data->align == SEQ_TEXT_ALIGN_X_RIGHT) {
940 return {line_offset, 0.0f};
941 }
942 if (data->align == SEQ_TEXT_ALIGN_X_CENTER) {
943 return {line_offset / 2.0f, 0.0f};
944 }
945
946 return {0.0f, 0.0f};
947}
948
949static float2 anchor_offset_get(const TextVars *data, int width_max, int text_height)
950{
951 float2 anchor_offset;
952
953 switch (data->anchor_x) {
955 anchor_offset.x = 0;
956 break;
958 anchor_offset.x = -width_max / 2.0f;
959 break;
961 anchor_offset.x = -width_max;
962 break;
963 }
964 switch (data->anchor_y) {
966 anchor_offset.y = 0;
967 break;
969 anchor_offset.y = text_height / 2.0f;
970 break;
972 anchor_offset.y = text_height;
973 break;
974 }
975
976 return anchor_offset;
977}
978
979static void calc_boundbox(const TextVars *data, TextVarsRuntime *runtime, const int2 image_size)
980{
981 /* `BLF_bounds_max()` is used, because some fonts have glyphs overlapping with lines above. */
982 rctf glyph_bounds_max;
983 BLF_bounds_max(runtime->font, &glyph_bounds_max);
984 const int text_height = (runtime->lines.size() - 1) * runtime->line_height +
985 math::ceil(BLI_rctf_size_y(&glyph_bounds_max));
986
987 int width_max = text_box_width_get(runtime->lines);
988
989 /* Add width to empty text, so there is something to draw or select. */
990 if (width_max == 0) {
991 width_max = text_height * 2;
992 }
993
994 const float2 image_center{data->loc[0] * image_size.x, data->loc[1] * image_size.y};
995 const float2 anchor = anchor_offset_get(data, width_max, text_height);
996
997 runtime->text_boundbox.xmin = anchor.x + image_center.x;
998 runtime->text_boundbox.xmax = anchor.x + image_center.x + width_max;
999 runtime->text_boundbox.ymin = anchor.y + image_center.y - text_height;
1000 runtime->text_boundbox.ymax = runtime->text_boundbox.ymin + text_height;
1001}
1002
1004 TextVarsRuntime *runtime,
1005 const int2 image_size)
1006{
1007 const int width_max = text_box_width_get(runtime->lines);
1008 const int text_height = runtime->lines.size() * runtime->line_height;
1009
1010 const float2 image_center{data->loc[0] * image_size.x, data->loc[1] * image_size.y};
1011 const float2 line_height_offset{0.0f,
1012 float(-runtime->line_height - BLF_descender(runtime->font))};
1013 const float2 anchor = anchor_offset_get(data, width_max, text_height);
1014
1015 for (LineInfo &line : runtime->lines) {
1016 const float2 alignment_x = horizontal_alignment_offset_get(data, line.width, width_max);
1017 const float2 alignment = math::round(image_center + line_height_offset + alignment_x + anchor);
1018
1019 for (CharInfo &character : line.characters) {
1020 character.position += alignment;
1021 }
1022 }
1023}
1024
1025TextVarsRuntime *text_effect_calc_runtime(const Strip *strip, int font, const int2 image_size)
1026{
1027 TextVars *data = static_cast<TextVars *>(strip->effectdata);
1028 TextVarsRuntime *runtime = MEM_new<TextVarsRuntime>(__func__);
1029
1030 runtime->font = font;
1031 runtime->line_height = BLF_height_max(font);
1032 runtime->font_descender = BLF_descender(font);
1033 runtime->character_count = BLI_strlen_utf8(data->text_ptr);
1034
1035 Vector<CharInfo> characters_temp = build_character_info(data, font);
1036 apply_word_wrapping(data, runtime, image_size, characters_temp);
1037 apply_text_alignment(data, runtime, image_size);
1038 calc_boundbox(data, runtime, image_size);
1039 return runtime;
1040}
1041
1042static ImBuf *do_text_effect(const RenderData *context,
1043 SeqRenderState * /*state*/,
1044 Strip *strip,
1045 float /*timeline_frame*/,
1046 float /*fac*/,
1047 ImBuf * /*ibuf1*/,
1048 ImBuf * /*ibuf2*/)
1049{
1050 /* NOTE: text rasterization only fills in part of output image,
1051 * need to clear it. */
1052 ImBuf *out = prepare_effect_imbufs(context, nullptr, nullptr, false);
1053 TextVars *data = static_cast<TextVars *>(strip->effectdata);
1054
1055 const FontFlags font_flags = ((data->flag & SEQ_TEXT_BOLD) ? BLF_BOLD : BLF_NONE) |
1056 ((data->flag & SEQ_TEXT_ITALIC) ? BLF_ITALIC : BLF_NONE);
1057
1058 /* Guard against parallel accesses to the fonts map. */
1059 std::lock_guard lock(g_font_map.mutex);
1060
1061 const int font = text_effect_font_init(context, strip, font_flags);
1062
1063 if (data->runtime != nullptr) {
1064 MEM_delete(data->runtime);
1065 }
1066
1067 TextVarsRuntime *runtime = text_effect_calc_runtime(strip, font, {out->x, out->y});
1068 data->runtime = runtime;
1069
1070 rcti outline_rect = draw_text_outline(context, data, runtime, out);
1071 BLF_buffer(font, nullptr, out->byte_buffer.data, out->x, out->y, out->byte_buffer.colorspace);
1072 text_draw(data->text_ptr, runtime, data->color);
1073 BLF_buffer(font, nullptr, nullptr, 0, 0, nullptr);
1074 BLF_disable(font, font_flags);
1075
1076 /* Draw shadow. */
1077 if (data->flag & SEQ_TEXT_SHADOW) {
1078 draw_text_shadow(context, data, runtime->line_height, outline_rect, out);
1079 }
1080
1081 /* Draw box under text. */
1082 if (data->flag & SEQ_TEXT_BOX) {
1083 if (out->byte_buffer.data) {
1084 const int margin = data->box_margin * out->x;
1085 const int minx = runtime->text_boundbox.xmin - margin;
1086 const int maxx = runtime->text_boundbox.xmax + margin;
1087 const int miny = runtime->text_boundbox.ymin - margin;
1088 const int maxy = runtime->text_boundbox.ymax + margin;
1089 float corner_radius = data->box_roundness * (maxy - miny) / 2.0f;
1090 fill_rect_alpha_under(out, data->box_color, minx, miny, maxx, maxy, corner_radius);
1091 }
1092 }
1093
1094 return out;
1095}
1096
1098{
1100 rval.init = init_text_effect;
1101 rval.free = free_text_effect;
1102 rval.load = load_text_effect;
1103 rval.copy = copy_text_effect;
1105 rval.execute = do_text_effect;
1106}
1107
1109
1110} // namespace blender::seq
void BKE_id_full_name_get(char name[MAX_ID_FULL_NAME], const ID *id, char separator_char)
Definition lib_id.cc:2432
void id_us_plus(ID *id)
Definition lib_id.cc:358
@ LIB_ID_CREATE_NO_USER_REFCOUNT
void id_us_min(ID *id)
Definition lib_id.cc:366
#define MAX_ID_FULL_NAME
int BLF_load_mem_unique(const char *name, const unsigned char *mem, int mem_size) ATTR_NONNULL(1
void BLF_size(int fontid, float size)
Definition blf.cc:443
int BLF_descender(int fontid) ATTR_WARN_UNUSED_RESULT
Definition blf.cc:872
void BLF_enable(int fontid, FontFlags flag)
Definition blf.cc:320
void BLF_addref_id(int fontid)
Definition blf.cc:311
void BLF_buffer(int fontid, float *fbuf, unsigned char *cbuf, int w, int h, const ColorSpace *colorspace)
Definition blf.cc:956
bool BLF_bounds_max(int fontid, rctf *r_bounds) ATTR_NONNULL(2)
Definition blf.cc:894
void BLF_draw_buffer(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_NONNULL(2)
Definition blf.cc:1045
bool BLF_is_loaded_id(int fontid) ATTR_WARN_UNUSED_RESULT
Definition blf.cc:170
int blf_mono_font_render
Definition blf.cc:49
int BLF_glyph_advance(int fontid, const char *str)
Definition blf.cc:826
void BLF_buffer_col(int fontid, const float srgb_color[4]) ATTR_NONNULL(2)
Definition blf.cc:1015
int BLF_load_unique(const char *filepath) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition blf.cc:188
float BLF_width(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(2)
Definition blf.cc:802
void BLF_disable(int fontid, FontFlags flag)
Definition blf.cc:329
bool BLF_unload_id(int fontid)
Definition blf.cc:280
bool BLF_is_builtin(int fontid)
Definition blf.cc:338
int BLF_height_max(int fontid) ATTR_WARN_UNUSED_RESULT
Definition blf.cc:850
void BLF_position(int fontid, float x, float y, float z)
Definition blf.cc:388
FontFlags
Definition BLF_enums.hh:31
@ BLF_ITALIC
Definition BLF_enums.hh:46
@ BLF_NONE
Definition BLF_enums.hh:32
@ BLF_BOLD
Definition BLF_enums.hh:45
@ BLF_NO_FALLBACK
Definition BLF_enums.hh:65
MINLINE int power_of_2_max_i(int n)
MINLINE int clamp_i(int value, int min, int max)
MINLINE void straight_to_premul_v4_v4(float premul[4], const float straight[4])
#define DEG2RADF(_deg)
MINLINE void copy_v4_fl(float r[4], float f)
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1
#define FILE_MAX
void BLI_rcti_pad(struct rcti *rect, int pad_x, int pad_y)
Definition rct.cc:629
void BLI_rcti_translate(struct rcti *rect, int x, int y)
Definition rct.cc:566
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char * BLI_strdup_null(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_MALLOC
Definition string.cc:46
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
int BLI_str_utf8_size_safe(const char *p) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
size_t BLI_strlen_utf8(const char *strc) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
unsigned char uchar
#define ID_BLEND_PATH_FROM_GLOBAL(_id)
Definition DNA_ID.h:688
@ SEQ_TEXT_ALIGN_X_RIGHT
@ SEQ_TEXT_ALIGN_X_CENTER
@ SEQ_TEXT_ALIGN_X_LEFT
@ SEQ_TEXT_ITALIC
@ SEQ_TEXT_SHADOW
@ SEQ_TEXT_BOLD
@ SEQ_TEXT_OUTLINE
@ SEQ_TEXT_BOX
@ SEQ_TEXT_ALIGN_Y_BOTTOM
@ SEQ_TEXT_ALIGN_Y_TOP
@ SEQ_TEXT_ALIGN_Y_CENTER
#define STRIP_FONT_NOT_LOADED
#define MEM_SAFE_FREE(v)
volatile int lock
BMesh const char void * data
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static void mul(btAlignedObjectArray< T > &items, const Q &value)
const T * data() const
Definition BLI_array.hh:312
constexpr int64_t first() const
constexpr int64_t size() const
static constexpr IndexRange from_begin_end(const int64_t begin, const int64_t end)
void append(const T &value)
nullptr float
#define powf(x, y)
#define str(s)
#define pf(_x, _i)
Prefetch 64.
Definition gim_memory.h:48
uint col
#define input
#define out
#define output
#define abs
float distance(VecOp< float, D >, VecOp< float, D >) RET
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void * MEM_dupallocN(const void *vmemh)
Definition mallocn.cc:143
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
T clamp(const T &a, const T &min, const T &max)
T distance(const T &a, const T &b)
T min(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
T ceil(const T &a)
T distance_squared(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
T max(const T &a, const T &b)
T round(const T &a)
int text_effect_font_init(const RenderData *context, const Strip *strip, FontFlags font_flags)
static float2 horizontal_alignment_offset_get(const TextVars *data, float line_width, int width_max)
static int wrap_width_get(const TextVars *data, const int2 image_size)
static int text_effect_line_size_get(const RenderData *context, const Strip *strip)
static ImBuf * do_text_effect(const RenderData *context, SeqRenderState *, Strip *strip, float, float, ImBuf *, ImBuf *)
TextVarsRuntime * text_effect_calc_runtime(const Strip *strip, int font, const int2 image_size)
static void calc_boundbox(const TextVars *data, TextVarsRuntime *runtime, const int2 image_size)
static void initialize_shadow_alpha(int width, int height, int2 offset, const rcti &shadow_rect, const uchar *input, Array< uchar > &r_shadow_mask)
static void text_draw(const char *text_ptr, const TextVarsRuntime *runtime, float color[4])
void text_effect_get_handle(EffectHandle &rval)
float get_render_scale_factor(eSpaceSeq_Proxy_RenderSize render_size, short scene_render_scale)
Definition render.cc:2118
Array< float > make_gaussian_blur_kernel(float rad, int size)
Definition effects.cc:80
void store_premul_pixel(const blender::float4 &pix, uchar *dst)
Definition effects.hh:60
ImBuf * prepare_effect_imbufs(const RenderData *context, ImBuf *ibuf1, ImBuf *ibuf2, bool uninitialized_pixels)
Definition effects.cc:28
static Vector< CharInfo > build_character_info(const TextVars *data, int font)
static float2 anchor_offset_get(const TextVars *data, int width_max, int text_height)
blender::float4 load_premul_pixel(const uchar *ptr)
Definition effects.hh:48
static SeqFontMap g_font_map
static void init_text_effect(Strip *strip)
static StripEarlyOut early_out_text(const Strip *strip, float)
void effect_text_font_load(TextVars *data, const bool do_id_user)
bool effects_can_render_text(const Strip *strip)
static void jump_flooding_pass(Span< JFACoord > input, MutableSpan< JFACoord > output, int2 size, IndexRange x_range, IndexRange y_range, int step_size)
static void apply_word_wrapping(const TextVars *data, TextVarsRuntime *runtime, const int2 image_size, Vector< CharInfo > &characters)
static void composite_shadow(int width, const rcti &shadow_rect, const float4 &shadow_color, const Array< uchar > &shadow_mask, uchar *output)
static void load_text_effect(Strip *strip)
static void apply_text_alignment(const TextVars *data, TextVarsRuntime *runtime, const int2 image_size)
static int num_inputs_text()
static rcti draw_text_outline(const RenderData *context, const TextVars *data, const TextVarsRuntime *runtime, ImBuf *out)
static void text_gaussian_blur_y(const Span< float > gaussian, int half_size, int start_line, int width, int height, const uchar *rect, uchar *dst, const rcti &shadow_rect)
static int text_box_width_get(const Vector< LineInfo > &lines)
void effect_text_font_unload(TextVars *data, const bool do_id_user)
static void draw_text_shadow(const RenderData *context, const TextVars *data, int line_height, const rcti &rect, ImBuf *out)
static void text_gaussian_blur_x(const Span< float > gaussian, int half_size, int start_line, int width, int height, const uchar *rect, uchar *dst, const rcti &shadow_rect)
static void free_text_effect(Strip *strip, const bool do_id_user)
static int strip_load_font_mem(const std::string &name, const uchar *data, int data_size)
static void copy_text_effect(Strip *dst, const Strip *src, const int flag)
static void strip_unload_font(int fontid)
static int strip_load_font_file(const std::string &path)
static void fill_rect_alpha_under(const ImBuf *ibuf, const float col[4], int x1, int y1, int x2, int y2, float corner_radius)
constexpr uint16_t JFA_INVALID
static void clamp_rect(int width, int height, rcti &r_rect)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
blender::VecBase< uint8_t, 4 > uchar4
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
const char * name
#define sinf
#define cosf
ImBufByteBuffer byte_buffer
void * effectdata
char filepath[1024]
struct PackedFile * packedfile
void(* copy)(Strip *dst, const Strip *src, int flag)
ImBuf *(* execute)(const RenderData *context, SeqRenderState *state, Strip *strip, float timeline_frame, float fac, ImBuf *ibuf1, ImBuf *ibuf2)
void(* free)(Strip *strip, bool do_id_user)
StripEarlyOut(* early_out)(const Strip *strip, float fac)
void(* load)(Strip *seqconst)
void(* init)(Strip *strip)
Vector< CharInfo > characters
Map< std::string, int > path_to_file_font_id
std::recursive_mutex mutex
Map< std::string, int > name_to_mem_font_id
float y
Definition sky_math.h:225
float z
Definition sky_math.h:225
float x
Definition sky_math.h:225
float w
Definition sky_math.h:225
int ymin
int ymax
int xmin
int xmax
uint8_t flag
Definition wm_window.cc:145