[vlc-commits] [Git][videolan/vlc][master] 2 commits: text_style: add transparent (knockout) blending mode.
Felix Paul Kühne (@fkuehne)
gitlab at videolan.org
Sat Apr 18 21:23:54 UTC 2026
Felix Paul Kühne pushed to branch master at VideoLAN / VLC
Commits:
82037708 by sarah-kamall at 2026-04-18T22:06:19+02:00
text_style: add transparent (knockout) blending mode.
This patch adds a configuration option for a new blending mode.
Changes:
- Adding an Enum with the two possible blending modes: the default and the transparent (knockout) mode.
- Set the blending mode to the default
Fixes: #29195 Signed-off-by:Sarah Soliman sarah.kamal.soliman at gmail.com
- - - - -
95fb4927 by sarah-kamall at 2026-04-18T22:06:19+02:00
freetype: add transparent (knockout) blending mode.
This patch implements knockout blending logic for Freetype renderer.
In knockout mode, outline alpha is handled independently to allow
outline-only rendering when font alpha is set to 0. This enables
use cases where text is transparent but the outline remains visible.
Changes:
- Add GetDrawingFunctions() helper to select drawing functions
- Add freetype-blending-mode configuration option
- Add STYLE_BLENDING_TRANSPARENT mode support for YUVA/RGBA/ARGB
- Fix outline alpha handling for knockout blending mode
Fixes: #29195
Signed-off-by:Sarah Soliman sarah.kamal.soliman at gmail.com
- - - - -
6 changed files:
- include/vlc_text_style.h
- modules/text_renderer/freetype/blend/blend.h
- modules/text_renderer/freetype/blend/rgb.h
- modules/text_renderer/freetype/blend/yuv.h
- modules/text_renderer/freetype/freetype.c
- src/misc/text_style.c
Changes:
=====================================
include/vlc_text_style.h
=====================================
@@ -72,6 +72,13 @@ typedef struct
STYLE_WRAP_CHAR, /**< Breaks at character level only */
STYLE_WRAP_NONE, /**< No line breaks (except explicit ones) */
} e_wrapinfo;
+
+ /*blending style*/
+ enum
+ {
+ STYLE_BLENDING_DEFAULT = 0, /**< Overlay blending style>**/
+ STYLE_BLENDING_TRANSPARENT, /**< Knockout blending style>**/
+ } e_blending_mode;
} text_style_t;
#define STYLE_ALPHA_OPAQUE 0xFF
@@ -90,6 +97,7 @@ typedef struct
#define STYLE_HAS_BACKGROUND_COLOR (1 << 7)
#define STYLE_HAS_BACKGROUND_ALPHA (1 << 8)
#define STYLE_HAS_WRAP_INFO (1 << 9)
+#define STYLE_HAS_BLENDING_MODE (1 << 10)
/* Style flags for \ref text_style_t */
#define STYLE_BOLD (1 << 0)
=====================================
modules/text_renderer/freetype/blend/blend.h
=====================================
@@ -63,6 +63,26 @@ static inline void Blend##name##Pixel( uint8_t **dst, int a, int x, int y, int z
}\
}
+#define DECL_PIXEL_BLENDER_KNOCKOUT( name, APOS, XPOS, YPOS, ZPOS, APLANE, XPLANE, YPLANE, ZPLANE ) \
+static inline void Blend##name##PixelKnockout( uint8_t **dst, int a, int x, int y, int z, int glyph_a )\
+{\
+ if( glyph_a == 0 )\
+ return;\
+\
+ int i_ao = dst[APLANE][APOS];\
+ /* Knockout Mode: Interpolate between Source and Dest based on glyph shape */ \
+ /* Result = (Dest * (255 - Glyph) + Src * Glyph) / 255 */ \
+ int i_an = (i_ao * (255 - glyph_a) + a * glyph_a) / 255; \
+ dst[APLANE][APOS] = i_an; \
+\
+ if (i_an > 0)\
+ {\
+ dst[XPLANE][XPOS] = ( dst[XPLANE][XPOS] * (255 - glyph_a) + x * glyph_a ) / 255;\
+ dst[YPLANE][YPOS] = ( dst[YPLANE][YPOS] * (255 - glyph_a) + y * glyph_a ) / 255;\
+ dst[ZPLANE][ZPOS] = ( dst[ZPLANE][ZPOS] * (255 - glyph_a) + z * glyph_a ) / 255;\
+ }\
+}
+
static void BlendAXYZLine( picture_t *p_picture,
int i_picture_x, int i_picture_y,
int i_a, int i_x, int i_y, int i_z,
=====================================
modules/text_renderer/freetype/blend/rgb.h
=====================================
@@ -57,53 +57,39 @@ static void Fill##name##Picture( picture_t *p_picture,\
DECL_RGB_FILLER( RGBA, 3, 0, 1, 2 );
DECL_PIXEL_BLENDER(RGBA, 3, 0, 1, 2, 0, 0, 0, 0);
+DECL_PIXEL_BLENDER_KNOCKOUT(RGBA, 3, 0, 1, 2, 0, 0, 0, 0);
DECL_RGB_FILLER( ARGB, 0, 1, 2, 3 );
DECL_PIXEL_BLENDER(ARGB, 0, 1, 2, 3, 0, 0, 0, 0);
+DECL_PIXEL_BLENDER_KNOCKOUT(ARGB, 0, 1, 2, 3, 0, 0, 0, 0);
#undef DECL_RGB_FILLER
-
-static inline void BlendGlyphToRGB( picture_t *p_picture,
- int i_picture_x, int i_picture_y,
- int i_a, int i_x, int i_y, int i_z,
- FT_BitmapGlyph p_glyph,
- void (*BlendPixel)( uint8_t **, int, int, int, int, int ) )
-{
- const uint8_t *srcrow = p_glyph->bitmap.buffer;
- int i_pitch_src = p_glyph->bitmap.pitch;
- int i_pitch_dst = p_picture->p[0].i_pitch;
- uint8_t *dstrow = &p_picture->p[0].p_pixels[i_picture_y * i_pitch_dst + 4 * i_picture_x];
-
- for( unsigned int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
- {
- const uint8_t *src = srcrow;
- uint8_t *dst = dstrow;
- for( unsigned int dx = 0; dx < p_glyph->bitmap.width; dx++ )
- {
- BlendPixel( &dst, i_a, i_x, i_y, i_z, *src++ );
- dst += 4;
- }
- srcrow += i_pitch_src;
- dstrow += i_pitch_dst;
- }
-}
-
-static void BlendGlyphToRGBA( picture_t *p_picture,
- int i_picture_x, int i_picture_y,
- int i_a, int i_x, int i_y, int i_z,
- FT_BitmapGlyph p_glyph )
-{
- BlendGlyphToRGB( p_picture, i_picture_x, i_picture_y,
- i_a, i_x, i_y, i_z, p_glyph,
- BlendRGBAPixel );
+#define DECL_BLEND_GLYPH_TO_RGB(name, BlendFunc) \
+static void BlendGlyphTo##name( picture_t *p_picture, \
+ int i_picture_x, int i_picture_y, \
+ int i_a, int i_x, int i_y, int i_z, \
+ FT_BitmapGlyph p_glyph ) \
+{\
+ const uint8_t *srcrow = p_glyph->bitmap.buffer;\
+ int i_pitch_src = p_glyph->bitmap.pitch;\
+ int i_pitch_dst = p_picture->p[0].i_pitch;\
+ uint8_t *dstrow = &p_picture->p[0].p_pixels[i_picture_y * i_pitch_dst + 4 * i_picture_x];\
+\
+ for( unsigned int dy = 0; dy < p_glyph->bitmap.rows; dy++ )\
+ {\
+ const uint8_t *src = srcrow;\
+ uint8_t *dst = dstrow;\
+ for( unsigned int dx = 0; dx < p_glyph->bitmap.width; dx++ )\
+ {\
+ BlendFunc( &dst, i_a, i_x, i_y, i_z, *src++ );\
+ dst += 4;\
+ }\
+ srcrow += i_pitch_src;\
+ dstrow += i_pitch_dst;\
+ }\
}
-static void BlendGlyphToARGB( picture_t *p_picture,
- int i_picture_x, int i_picture_y,
- int i_a, int i_x, int i_y, int i_z,
- FT_BitmapGlyph p_glyph )
-{
- BlendGlyphToRGB( p_picture, i_picture_x, i_picture_y,
- i_a, i_x, i_y, i_z, p_glyph,
- BlendARGBPixel );
-}
+DECL_BLEND_GLYPH_TO_RGB(RGBA, BlendRGBAPixel);
+DECL_BLEND_GLYPH_TO_RGB(ARGB, BlendARGBPixel);
+DECL_BLEND_GLYPH_TO_RGB(RGBAKnockout, BlendRGBAPixelKnockout);
+DECL_BLEND_GLYPH_TO_RGB(ARGBKnockout, BlendARGBPixelKnockout);
\ No newline at end of file
=====================================
modules/text_renderer/freetype/blend/yuv.h
=====================================
@@ -60,44 +60,48 @@ static void FillYUVAPicture( picture_t *p_picture,
}
DECL_PIXEL_BLENDER(YUVA, 0, 0, 0, 0, A_PLANE, Y_PLANE, U_PLANE, V_PLANE);
+DECL_PIXEL_BLENDER_KNOCKOUT(YUVA, 0, 0, 0, 0, A_PLANE, Y_PLANE, U_PLANE, V_PLANE);
-static void BlendGlyphToYUVA( picture_t *p_picture,
- int i_picture_x, int i_picture_y,
- int i_a, int i_x, int i_y, int i_z,
- FT_BitmapGlyph p_glyph )
-{
- const uint8_t *srcrow = p_glyph->bitmap.buffer;
- int i_pitch_src = p_glyph->bitmap.pitch;
-
- uint8_t *dstrows[4];
- dstrows[0] = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch +
- i_picture_x * p_picture->p[0].i_pixel_pitch];
- dstrows[1] = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch +
- i_picture_x * p_picture->p[1].i_pixel_pitch];
- dstrows[2] = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch +
- i_picture_x * p_picture->p[2].i_pixel_pitch];
- dstrows[3] = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch +
- i_picture_x * p_picture->p[3].i_pixel_pitch];
-
- for( unsigned int dy = 0; dy < p_glyph->bitmap.rows; dy++ )
- {
- const uint8_t *src = srcrow;
-
- uint8_t *dst[4];
- memcpy(dst, dstrows, 4 * sizeof(dst[0]));
- for( unsigned int dx = 0; dx < p_glyph->bitmap.width; dx++ )
- {
- BlendYUVAPixel( dst, i_a, i_x, i_y, i_z, *src++ );
- dst[0] += p_picture->p[0].i_pixel_pitch;
- dst[1] += p_picture->p[1].i_pixel_pitch;
- dst[2] += p_picture->p[2].i_pixel_pitch;
- dst[3] += p_picture->p[3].i_pixel_pitch;
- }
-
- srcrow += i_pitch_src;
- dstrows[0] += p_picture->p[0].i_pitch;
- dstrows[1] += p_picture->p[1].i_pitch;
- dstrows[2] += p_picture->p[2].i_pitch;
- dstrows[3] += p_picture->p[3].i_pitch;
- }
+#define DECL_BLEND_GLYPH_TO_YUVA(name, BlendFunc) \
+static void BlendGlyphTo##name( picture_t *p_picture, \
+ int i_picture_x, int i_picture_y, \
+ int i_a, int i_x, int i_y, int i_z, \
+ FT_BitmapGlyph p_glyph ) \
+{\
+ const uint8_t *srcrow = p_glyph->bitmap.buffer;\
+ int i_pitch_src = p_glyph->bitmap.pitch;\
+\
+ uint8_t *dstrows[4];\
+ dstrows[0] = &p_picture->p[0].p_pixels[i_picture_y * p_picture->p[0].i_pitch + \
+ i_picture_x * p_picture->p[0].i_pixel_pitch];\
+ dstrows[1] = &p_picture->p[1].p_pixels[i_picture_y * p_picture->p[1].i_pitch +\
+ i_picture_x * p_picture->p[1].i_pixel_pitch];\
+ dstrows[2] = &p_picture->p[2].p_pixels[i_picture_y * p_picture->p[2].i_pitch +\
+ i_picture_x * p_picture->p[2].i_pixel_pitch];\
+ dstrows[3] = &p_picture->p[3].p_pixels[i_picture_y * p_picture->p[3].i_pitch +\
+ i_picture_x * p_picture->p[3].i_pixel_pitch];\
+\
+ for( unsigned int dy = 0; dy < p_glyph->bitmap.rows; dy++ )\
+ {\
+ const uint8_t *src = srcrow;\
+\
+ uint8_t *dst[4];\
+ memcpy(dst, dstrows, 4 * sizeof(dst[0]));\
+ for( unsigned int dx = 0; dx < p_glyph->bitmap.width; dx++ )\
+ {\
+ BlendFunc( dst, i_a, i_x, i_y, i_z, *src++ );\
+ dst[0] += p_picture->p[0].i_pixel_pitch;\
+ dst[1] += p_picture->p[1].i_pixel_pitch;\
+ dst[2] += p_picture->p[2].i_pixel_pitch;\
+ dst[3] += p_picture->p[3].i_pixel_pitch;\
+ }\
+\
+ srcrow += i_pitch_src;\
+ dstrows[0] += p_picture->p[0].i_pitch;\
+ dstrows[1] += p_picture->p[1].i_pitch;\
+ dstrows[2] += p_picture->p[2].i_pitch;\
+ dstrows[3] += p_picture->p[3].i_pitch;\
+ }\
}
+DECL_BLEND_GLYPH_TO_YUVA(YUVA, BlendYUVAPixel)
+DECL_BLEND_GLYPH_TO_YUVA(YUVAKnockout, BlendYUVAPixelKnockout)
=====================================
modules/text_renderer/freetype/freetype.c
=====================================
@@ -95,6 +95,8 @@ static void Destroy( filter_t * );
#define YUVP_TEXT N_("Use YUVP renderer")
#define YUVP_LONGTEXT N_("This renders the font using \"paletized YUV\". " \
"This option is only needed if you want to encode into DVB subtitles" )
+#define BLENDING_MODE_TEXT N_("Blending Mode")
+#define BLENDING_MODE_LONG_TEXT N_("Blending mode for the font, can be transparent or overlay")
static const int pi_color_values[] = {
0x00000000, 0x00808080, 0x00C0C0C0, 0x00FFFFFF, 0x00800000,
@@ -120,6 +122,14 @@ static const int pi_text_direction[] = {
static const char *const ppsz_text_direction[] = {
N_("Left to right"), N_("Right to left"), N_("Auto"),
};
+
+static const int pi_blending_mode[] = {
+ 0, 1
+};
+static const char *const ppsz_blending_mode[] = {
+ N_("Overlay"), N_("Transparent"),
+};
+
#endif
vlc_module_begin ()
@@ -190,6 +200,10 @@ vlc_module_begin ()
add_bool( "freetype-yuvp", false, YUVP_TEXT,
YUVP_LONGTEXT )
+ add_integer_with_range( "freetype-blending-mode", 0, 0, 1, BLENDING_MODE_TEXT,
+ BLENDING_MODE_LONG_TEXT )
+ change_integer_list( pi_blending_mode, ppsz_blending_mode )
+ change_safe()
#ifdef HAVE_FRIBIDI
add_integer_with_range( "freetype-text-direction", 0, 0, 2, TEXT_DIRECTION_TEXT,
@@ -565,7 +579,12 @@ static void RenderCharAXYZ( filter_t *p_filter,
i_color = ch->p_style->i_shadow_color;
break;
case 1:
- i_a = i_a * ch->p_style->i_outline_alpha / 255;
+ /* In knockout mode, outline alpha is independent to allow
+ * outline-only rendering when font alpha is 0 */
+ if( ch->p_style->e_blending_mode == STYLE_BLENDING_DEFAULT )
+ i_a = i_a * ch->p_style->i_outline_alpha / 255;
+ else
+ i_a = ch->p_style->i_outline_alpha;
i_color = ch->p_style->i_outline_color;
break;
default:
@@ -586,7 +605,7 @@ static void RenderCharAXYZ( filter_t *p_filter,
}
/* Don't render if invisible or not wanted */
- if( i_a == STYLE_ALPHA_TRANSPARENT ||
+ if(
(g == 0 && 0 == (ch->p_style->i_style_flags & STYLE_SHADOW) ) ||
(g == 1 && 0 == (ch->p_style->i_style_flags & STYLE_OUTLINE) )
)
@@ -734,6 +753,8 @@ static void FillDefaultStyles( filter_t *p_filter )
p_sys->p_default_style->i_shadow_alpha = var_InheritInteger( p_filter, "freetype-shadow-opacity" );
p_sys->p_default_style->i_shadow_color = var_InheritInteger( p_filter, "freetype-shadow-color" );
+ p_sys->p_default_style->e_blending_mode = var_InheritInteger( p_filter, "freetype-blending-mode" );
+ p_sys->p_default_style->i_features |= STYLE_HAS_BLENDING_MODE;
p_sys->p_default_style->i_font_size = 0;
p_sys->p_default_style->i_style_flags |= STYLE_SHADOW;
@@ -946,6 +967,63 @@ static size_t SegmentsToTextAndStyles( filter_t *p_filter, const text_segment_t
return i_nb_char;
}
+/**
+ * Get the appropriate drawing functions based on chroma codec and blending mode
+ */
+static const ft_drawing_functions *GetDrawingFunctions( vlc_fourcc_t chroma,
+ int i_blending_mode )
+{
+ if( chroma == VLC_CODEC_YUVA )
+ {
+ static const ft_drawing_functions DRAW_YUVA =
+ { .extract = YUVFromXRGB,
+ .fill = FillYUVAPicture,
+ .blend = BlendGlyphToYUVA };
+ static const ft_drawing_functions DRAW_YUVA_KNOCKOUT =
+ { .extract = YUVFromXRGB,
+ .fill = FillYUVAPicture,
+ .blend = BlendGlyphToYUVAKnockout };
+
+ if( i_blending_mode == STYLE_BLENDING_TRANSPARENT )
+ return &DRAW_YUVA_KNOCKOUT;
+ return &DRAW_YUVA;
+ }
+ else if( chroma == VLC_CODEC_RGBA
+ || chroma == VLC_CODEC_BGRA )
+ {
+ static const ft_drawing_functions DRAW_RGBA =
+ { .extract = RGBFromXRGB,
+ .fill = FillRGBAPicture,
+ .blend = BlendGlyphToRGBA };
+ static const ft_drawing_functions DRAW_RGBA_KNOCKOUT =
+ { .extract = RGBFromXRGB,
+ .fill = FillRGBAPicture,
+ .blend = BlendGlyphToRGBAKnockout };
+
+ if( i_blending_mode == STYLE_BLENDING_TRANSPARENT )
+ return &DRAW_RGBA_KNOCKOUT;
+ return &DRAW_RGBA;
+ }
+ else if( chroma == VLC_CODEC_ARGB
+ || chroma == VLC_CODEC_ABGR )
+ {
+ static const ft_drawing_functions DRAW_ARGB =
+ { .extract = RGBFromXRGB,
+ .fill = FillARGBPicture,
+ .blend = BlendGlyphToARGB };
+ static const ft_drawing_functions DRAW_ARGB_KNOCKOUT =
+ { .extract = RGBFromXRGB,
+ .fill = FillARGBPicture,
+ .blend = BlendGlyphToARGBKnockout };
+
+ if( i_blending_mode == STYLE_BLENDING_TRANSPARENT )
+ return &DRAW_ARGB_KNOCKOUT;
+ return &DRAW_ARGB;
+ }
+
+ return NULL;
+}
+
/**
* This function renders a text subpicture region into another one.
* It also calculates the size needed for this string, and renders the
@@ -1103,6 +1181,7 @@ static subpicture_region_t *Render( filter_t *p_filter,
fmt.i_height =
fmt.i_visible_height = renderbbox.yMax - renderbbox.yMin;
fmt.i_sar_num = fmt.i_sar_den = 1;
+ int i_blending_mode = p_sys->p_default_style->e_blending_mode;
for( const vlc_fourcc_t *p_chroma = p_chroma_list; *p_chroma != 0; p_chroma++ )
{
@@ -1125,34 +1204,8 @@ static subpicture_region_t *Render( filter_t *p_filter,
&renderbbox, &bbox );
else
{
- const ft_drawing_functions *func;
- if( *p_chroma == VLC_CODEC_YUVA )
- {
- static const ft_drawing_functions DRAW_YUVA =
- { .extract = YUVFromXRGB,
- .fill = FillYUVAPicture,
- .blend = BlendGlyphToYUVA };
- func = &DRAW_YUVA;
- }
- else if( *p_chroma == VLC_CODEC_RGBA
- || *p_chroma == VLC_CODEC_BGRA )
- {
- static const ft_drawing_functions DRAW_RGBA =
- { .extract = RGBFromXRGB,
- .fill = FillRGBAPicture,
- .blend = BlendGlyphToRGBA };
- func = &DRAW_RGBA;
- }
- else if( *p_chroma == VLC_CODEC_ARGB
- || *p_chroma == VLC_CODEC_ABGR)
- {
- static const ft_drawing_functions DRAW_ARGB =
- { .extract = RGBFromXRGB,
- .fill = FillARGBPicture,
- .blend = BlendGlyphToARGB };
- func = &DRAW_ARGB;
- }
- else
+ const ft_drawing_functions *func = GetDrawingFunctions( *p_chroma, i_blending_mode );
+ if( func == NULL )
{
subpicture_region_Delete(region);
region = NULL;
@@ -1258,7 +1311,7 @@ static int Create( filter_t *p_filter )
/*
* The following variables should not be cached, as they might be changed on-the-fly:
* freetype-rel-fontsize, freetype-background-opacity, freetype-background-color,
- * freetype-outline-thickness, freetype-color
+ * freetype-outline-thickness, freetype-color, freetype-blending-style
*
*/
=====================================
src/misc/text_style.c
=====================================
@@ -238,6 +238,8 @@ text_style_t *text_style_Create( int i_defaults )
p_style->i_shadow_width = 0;
p_style->i_spacing = -1;
p_style->e_wrapinfo = STYLE_WRAP_DEFAULT;
+ p_style->e_blending_mode = STYLE_BLENDING_DEFAULT;
+
return p_style;
}
@@ -292,6 +294,7 @@ void text_style_Merge( text_style_t *p_dst, const text_style_t *p_src, bool b_ov
MERGE(i_background_color, STYLE_HAS_BACKGROUND_COLOR);
MERGE(i_background_alpha, STYLE_HAS_BACKGROUND_ALPHA);
MERGE(e_wrapinfo, STYLE_HAS_WRAP_INFO);
+ MERGE(e_blending_mode, STYLE_HAS_BLENDING_MODE);
p_dst->i_features |= p_src->i_features;
p_dst->i_style_flags |= p_src->i_style_flags;
}
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/9acb1b02f60f13dee27c3af876d4a2b5b0020fc0...95fb4927885bd29d431017ea69b6d43a63e1c360
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/9acb1b02f60f13dee27c3af876d4a2b5b0020fc0...95fb4927885bd29d431017ea69b6d43a63e1c360
You're receiving this email because of your account on code.videolan.org.
More information about the vlc-commits
mailing list