From 140ff12eae01402cf5007a3d68e75170bb39bfb5 Mon Sep 17 00:00:00 2001 From: kitt Date: Sun, 17 Nov 2024 12:07:16 +0100 Subject: [PATCH] VSE: Add Box Roundness option to text strips The background box for VSE text strips can have rounded corners now. Actual rounded shape is a superellipse with 2.1 exponent; this is very close to a circle section but feels a bit nicer with more continuity between the flat part and the rounded part of the box. At very large rounding radius this is not very fast; optimization for that case will come in a separate commit. Pull Request: https://projects.blender.org/blender/blender/pulls/129665 --- scripts/startup/bl_ui/space_sequencer.py | 5 + source/blender/makesdna/DNA_sequence_types.h | 3 +- .../blender/makesrna/intern/rna_sequencer.cc | 6 + source/blender/sequencer/intern/effects.cc | 115 ++++++++++++++++-- 4 files changed, 116 insertions(+), 13 deletions(-) diff --git a/scripts/startup/bl_ui/space_sequencer.py b/scripts/startup/bl_ui/space_sequencer.py index cc58b021423..97404c1e72a 100644 --- a/scripts/startup/bl_ui/space_sequencer.py +++ b/scripts/startup/bl_ui/space_sequencer.py @@ -1725,6 +1725,11 @@ class SEQUENCER_PT_effect_text_style(SequencerButtonsPanel, Panel): sub.prop(strip, "box_margin") sub.active = strip.use_box and (not strip.mute) + row = layout.row(align=True, heading="Box Roundness") + sub = row.row(align=True) + sub.prop(strip, "box_roundness") + sub.active = strip.use_box and (not strip.mute) + class SEQUENCER_PT_source(SequencerButtonsPanel, Panel): bl_label = "Source" diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index 45c0a3b7fd6..5b82dc6ee7a 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -446,6 +446,7 @@ typedef struct TextVars { float loc[2]; float wrap_width; float box_margin; + float box_roundness; float shadow_angle; float shadow_offset; float shadow_blur; @@ -454,7 +455,7 @@ typedef struct TextVars { char align; char align_y DNA_DEPRECATED /* Only used for versioning. */; char anchor_x, anchor_y; - char _pad[3]; + char _pad[7]; } TextVars; /** #TextVars.flag */ diff --git a/source/blender/makesrna/intern/rna_sequencer.cc b/source/blender/makesrna/intern/rna_sequencer.cc index 1731eb19bee..97cac45b4f8 100644 --- a/source/blender/makesrna/intern/rna_sequencer.cc +++ b/source/blender/makesrna/intern/rna_sequencer.cc @@ -3437,6 +3437,12 @@ static void rna_def_text(StructRNA *srna) RNA_def_property_float_default(prop, 0.01f); RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_raw_update"); + prop = RNA_def_property(srna, "box_roundness", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_sdna(prop, nullptr, "box_roundness"); + RNA_def_property_ui_text(prop, "Box Roundness", "Box corner radius as a factor of box height"); + RNA_def_property_range(prop, 0, 1.0); + RNA_def_property_update(prop, NC_SCENE | ND_SEQUENCER, "rna_Sequence_invalidate_raw_update"); + prop = RNA_def_property(srna, "alignment_x", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "align"); RNA_def_property_enum_items(prop, text_alignment_x_items); diff --git a/source/blender/sequencer/intern/effects.cc b/source/blender/sequencer/intern/effects.cc index 374428051f6..72f1a00e96e 100644 --- a/source/blender/sequencer/intern/effects.cc +++ b/source/blender/sequencer/intern/effects.cc @@ -2528,6 +2528,7 @@ static void init_text_effect(Sequence *seq) data->box_color[2] = 0.2f; data->box_color[3] = 0.7f; data->box_margin = 0.01f; + data->box_roundness = 0.0f; data->outline_color[3] = 0.7f; data->outline_width = 0.05f; @@ -3037,10 +3038,42 @@ static rcti draw_text_outline(const SeqRenderData *context, return outline_rect; } +static inline void fill_ellipse_alpha_under(const ImBuf *ibuf, + const float col[4], + int x1, + int y1, + int x2, + int y2, + float origin_x, + float origin_y, + float radius) +{ + float curve_pow = 2.1f; + float4 color; + float4 premul_color; + for (int y = y1; y < y2; y++) { + uchar *dst = ibuf->byte_buffer.data + (size_t(ibuf->x) * y + x1) * 4; + for (int x = x1; x < x2; x++) { + color = col; + + float r = powf(powf(abs(x - origin_x), curve_pow) + powf(abs(y - origin_y), curve_pow), + 1.0f / curve_pow); + color.w = math::clamp(radius - r, 0.0f, color.w); + + straight_to_premul_v4_v4(premul_color, color); + float4 pix = load_premul_pixel(dst); + float fac = 1.0f - pix.w; + float4 dst_fl = fac * premul_color + pix; + store_premul_pixel(dst_fl, dst); + dst += 4; + } + } +} + /* Similar to #IMB_rectfill_area but blends the given color under the * existing image. Also only works on byte buffers. */ static void fill_rect_alpha_under( - const ImBuf *ibuf, const float col[4], int x1, int y1, int x2, int y2) + const ImBuf *ibuf, const float col[4], int x1, int y1, int x2, int y2, float corner_radius) { const int width = ibuf->x; const int height = ibuf->y; @@ -3058,17 +3091,74 @@ static void fill_rect_alpha_under( return; } - float4 premul_col = col; - straight_to_premul_v4(premul_col); + corner_radius = math::clamp(corner_radius, 0.0f, math::min(x2 - x1, y2 - y1) / 2.0f); - for (int y = y1; y < y2; y++) { - uchar *dst = ibuf->byte_buffer.data + (size_t(width) * y + x1) * 4; - for (int x = x1; x < x2; x++) { - float4 pix = load_premul_pixel(dst); - float fac = 1.0f - pix.w; - float4 dst_fl = fac * premul_col + pix; - store_premul_pixel(dst_fl, dst); - dst += 4; + if (corner_radius > 0.0f) { + int cr = (int)corner_radius; + /* bottom left */ + fill_ellipse_alpha_under(ibuf, + col, + x1, + y1, + x1 + cr, + y1 + cr, + x1 + corner_radius - 1, + y1 + corner_radius - 1, + corner_radius); + + /* top left */ + fill_ellipse_alpha_under(ibuf, + col, + x1, + y2 - cr, + x1 + cr, + y2, + x1 + corner_radius - 1, + y2 - corner_radius, + corner_radius); + + /* top right */ + fill_ellipse_alpha_under(ibuf, + col, + x2 - cr, + y2 - cr, + x2, + y2, + x2 - corner_radius, + y2 - corner_radius, + corner_radius); + + /* bottom right */ + fill_ellipse_alpha_under(ibuf, + col, + x2 - cr, + y1, + x2, + y1 + cr, + x2 - corner_radius, + y1 + corner_radius - 1, + corner_radius); + + /* fill in areas between corners */ + /* bottom */ + fill_rect_alpha_under(ibuf, col, x1 + cr, y1, x2 - cr, y1 + cr, 0.0f); + /* middle */ + fill_rect_alpha_under(ibuf, col, x1, y1 + cr, x2, y2 - cr, 0.0f); + /* top */ + fill_rect_alpha_under(ibuf, col, x1 + cr, y2, x2 - cr, y2 - cr, 0.0f); + } + else { + float4 premul_col; + straight_to_premul_v4_v4(premul_col, col); + for (int y = y1; y < y2; y++) { + uchar *dst = ibuf->byte_buffer.data + (size_t(width) * y + x1) * 4; + for (int x = x1; x < x2; x++) { + float4 pix = load_premul_pixel(dst); + float fac = 1.0f - pix.w; + float4 dst_fl = fac * premul_col + pix; + store_premul_pixel(dst_fl, dst); + dst += 4; + } } } } @@ -3331,7 +3421,8 @@ static ImBuf *do_text_effect(const SeqRenderData *context, const int maxx = runtime.text_boundbox.xmax + margin; const int miny = runtime.text_boundbox.ymin - margin; const int maxy = runtime.text_boundbox.ymax + margin; - fill_rect_alpha_under(out, data->box_color, minx, miny, maxx, maxy); + float corner_radius = data->box_roundness * (maxy - miny) / 2.0f; + fill_rect_alpha_under(out, data->box_color, minx, miny, maxx, maxy, corner_radius); } }