/* GStreamer * Copyright (C) 2023 Seungha Yang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include #include "gstdwritebaseoverlay.h" #include "gstdwriteoverlayobject.h" #include "gstdwrite-effect.h" #include #include GST_DEBUG_CATEGORY_STATIC (dwrite_base_overlay_debug); #define GST_CAT_DEFAULT dwrite_base_overlay_debug /* *INDENT-OFF* */ using namespace Microsoft::WRL; /* *INDENT-ON* */ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw(ANY)") ); static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-raw(ANY)") ); enum { PROP_0, PROP_VISIBLE, PROP_FONT_FAMILY, PROP_FONT_SIZE, PROP_AUTO_RESIZE, PROP_FONT_WEIGHT, PROP_FONT_STYLE, PROP_FONT_STRETCH, PROP_TEXT, PROP_COLOR, PROP_OUTLINE_COLOR, PROP_SHADOW_COLOR, PROP_BACKGROUND_COLOR, PROP_LAYOUT_X, PROP_LAYOUT_Y, PROP_LAYOUT_WIDTH, PROP_LAYOUT_HEIGHT, PROP_TEXT_ALIGNMENT, PROP_PARAGRAPH_ALIGNMENT, PROP_ENABLE_COLOR_FONT, }; /* *INDENT-OFF* */ static std::vector _pspec; /* *INDENT-ON* */ #define DEFAULT_VISIBLE TRUE #define DEFAULT_FONT_FAMILY "MS Reference Sans Serif" #define DEFAULT_FONT_SIZE 24 #define DEFAULT_REFERENCE_FRAME_SIZE 640 #define DEFAULT_AUTO_RESIZE TRUE #define DEFAULT_FONT_WEIGHT DWRITE_FONT_WEIGHT_NORMAL #define DEFAULT_FONT_STYLE DWRITE_FONT_STYLE_NORMAL #define DEFAULT_FONT_STRETCH DWRITE_FONT_STRETCH_NORMAL #define DEFAULT_FOREGROUND_COLOR 0xffffffff #define DEFAULT_OUTLINE_COLOR 0xff000000 #define DEFAULT_SHADOW_COLOR 0x80000000 #define DEFAULT_BACKGROUND_COLOR 0x0 #define DEFAULT_LAYOUT_XY 0.03f #define DEFAULT_LAYOUT_WH 0.92f #define DEFAULT_TEXT_ALIGNMENT DWRITE_TEXT_ALIGNMENT_LEADING #define DEFAULT_PARAGRAPH_ALIGNMENT DWRITE_PARAGRAPH_ALIGNMENT_NEAR #define DEFAULT_COLOR_FONT TRUE /* *INDENT-OFF* */ struct _GstDWriteBaseOverlayPrivate { _GstDWriteBaseOverlayPrivate () { overlay = gst_dwrite_overlay_object_new (); } ~_GstDWriteBaseOverlayPrivate () { gst_clear_object (&overlay); } std::mutex prop_lock; GstDWriteOverlayObject *overlay = nullptr; ComPtr dwrite_factory; ComPtr text_format; ComPtr layout; D2D_POINT_2F layout_origin; D2D_POINT_2F layout_size; std::wstring prev_text; std::wstring cur_text; GstDWriteBlendMode blend_mode = GstDWriteBlendMode::NOT_SUPPORTED; /* properties */ gboolean visible = DEFAULT_VISIBLE; std::string font_family = DEFAULT_FONT_FAMILY; gfloat font_size = DEFAULT_FONT_SIZE; gboolean auto_resize = DEFAULT_AUTO_RESIZE; DWRITE_FONT_WEIGHT font_weight = DEFAULT_FONT_WEIGHT; DWRITE_FONT_STYLE font_style = DEFAULT_FONT_STYLE; DWRITE_FONT_STRETCH font_stretch = DEFAULT_FONT_STRETCH; std::wstring default_text; guint foreground_color = DEFAULT_FOREGROUND_COLOR; guint outline_color = DEFAULT_OUTLINE_COLOR; guint shadow_color = DEFAULT_SHADOW_COLOR; guint background_color = DEFAULT_BACKGROUND_COLOR; gdouble layout_x = DEFAULT_LAYOUT_XY; gdouble layout_y = DEFAULT_LAYOUT_XY; gdouble layout_width = DEFAULT_LAYOUT_WH; gdouble layout_height = DEFAULT_LAYOUT_WH; DWRITE_TEXT_ALIGNMENT text_align = DEFAULT_TEXT_ALIGNMENT; DWRITE_PARAGRAPH_ALIGNMENT paragraph_align = DEFAULT_PARAGRAPH_ALIGNMENT; gboolean color_font = FALSE; }; /* *INDENT-ON* */ static void gst_dwrite_base_overlay_finalize (GObject * object); static void gst_dwrite_base_overlay_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_dwrite_base_overlay_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static void gst_dwrite_base_overlay_set_context (GstElement * elem, GstContext * context); static gboolean gst_dwrite_base_overlay_start (GstBaseTransform * trans); static gboolean gst_dwrite_base_overlay_stop (GstBaseTransform * trans); static gboolean gst_dwrite_base_overlay_query (GstBaseTransform * trans, GstPadDirection direction, GstQuery * query); static gboolean gst_dwrite_base_overlay_set_caps (GstBaseTransform * trans, GstCaps * incaps, GstCaps * outcaps); static gboolean gst_dwrite_base_overlay_decide_allocation (GstBaseTransform * trans, GstQuery * query); static gboolean gst_dwrite_base_overlay_propose_allocation (GstBaseTransform * trans, GstQuery * decide_query, GstQuery * query); static GstCaps *gst_dwrite_base_overlay_transform_caps (GstBaseTransform * trans, GstPadDirection direction, GstCaps * caps, GstCaps * filter); static void gst_dwrite_base_overlay_before_transform (GstBaseTransform * trans, GstBuffer * buf); static GstFlowReturn gst_dwrite_base_overlay_prepare_output_buffer (GstBaseTransform * trans, GstBuffer * inbuf, GstBuffer ** outbuf); static GstFlowReturn gst_dwrite_base_overlay_transform (GstBaseTransform * trans, GstBuffer * inbuf, GstBuffer * outbuf); #define gst_dwrite_base_overlay_parent_class parent_class G_DEFINE_TYPE (GstDWriteBaseOverlay, gst_dwrite_base_overlay, GST_TYPE_BASE_TRANSFORM); static void gst_dwrite_base_overlay_class_init (GstDWriteBaseOverlayClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GstElementClass *element_class = GST_ELEMENT_CLASS (klass); GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass); object_class->finalize = gst_dwrite_base_overlay_finalize; object_class->set_property = gst_dwrite_base_overlay_set_property; object_class->get_property = gst_dwrite_base_overlay_get_property; gst_dwrite_base_overlay_build_param_specs (_pspec); for (guint i = 0; i < (guint) _pspec.size (); i++) g_object_class_install_property (object_class, i + 1, _pspec[i]); gst_element_class_add_static_pad_template (element_class, &sink_template); gst_element_class_add_static_pad_template (element_class, &src_template); element_class->set_context = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_set_context); trans_class->start = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_start); trans_class->stop = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_stop); trans_class->query = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_query); trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_set_caps); trans_class->decide_allocation = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_decide_allocation); trans_class->propose_allocation = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_propose_allocation); trans_class->transform_caps = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_transform_caps); trans_class->before_transform = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_before_transform); trans_class->prepare_output_buffer = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_prepare_output_buffer); trans_class->transform = GST_DEBUG_FUNCPTR (gst_dwrite_base_overlay_transform); GST_DEBUG_CATEGORY_INIT (dwrite_base_overlay_debug, "dwritebaseoverlay", 0, "dwritebaseoverlay"); gst_type_mark_as_plugin_api (GST_TYPE_DWRITE_BASE_OVERLAY, (GstPluginAPIFlags) 0); } static void gst_dwrite_base_overlay_init (GstDWriteBaseOverlay * self) { GstDWriteBaseOverlayPrivate *priv = self->priv; self->priv = priv = new GstDWriteBaseOverlayPrivate (); if (gst_dwrite_is_windows_10_or_greater ()) priv->color_font = DEFAULT_COLOR_FONT; } static void gst_dwrite_base_overlay_finalize (GObject * object) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (object); delete self->priv; G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_dwrite_base_overlay_clear_layout (GstDWriteBaseOverlay * self) { GstDWriteBaseOverlayPrivate *priv = self->priv; priv->layout = nullptr; } static void update_uint (GstDWriteBaseOverlay * self, guint * prev, const GValue * value) { guint val = g_value_get_uint (value); if (val != *prev) { *prev = val; gst_dwrite_base_overlay_clear_layout (self); } } static void update_double (GstDWriteBaseOverlay * self, gdouble * prev, const GValue * value) { gdouble val = g_value_get_double (value); if (val != *prev) { *prev = val; gst_dwrite_base_overlay_clear_layout (self); } } static void update_enum (GstDWriteBaseOverlay * self, gint * prev, const GValue * value) { gint val = g_value_get_enum (value); if (val != *prev) { *prev = val; gst_dwrite_base_overlay_clear_layout (self); } } static void gst_dwrite_base_overlay_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (object); GstDWriteBaseOverlayPrivate *priv = self->priv; std::lock_guard < std::mutex > lk (priv->prop_lock); switch (prop_id) { case PROP_VISIBLE: priv->visible = g_value_get_boolean (value); break; case PROP_FONT_FAMILY:{ const gchar *font_family = g_value_get_string (value); std::string font; if (font_family) font = font_family; else font = DEFAULT_FONT_FAMILY; if (font != priv->font_family) { priv->font_family = font; priv->text_format = nullptr; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_FONT_SIZE: { gfloat font_size = g_value_get_float (value); if (font_size != priv->font_size) { priv->font_size = font_size; priv->text_format = nullptr; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_AUTO_RESIZE: { gboolean auto_resize = g_value_get_uint (value); if (auto_resize != priv->auto_resize) { priv->auto_resize = auto_resize; priv->text_format = nullptr; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_FONT_WEIGHT: { DWRITE_FONT_WEIGHT weight = (DWRITE_FONT_WEIGHT) g_value_get_enum (value); if (weight != priv->font_weight) { priv->font_weight = weight; priv->text_format = nullptr; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_FONT_STYLE: { DWRITE_FONT_STYLE style = (DWRITE_FONT_STYLE) g_value_get_enum (value); if (style != priv->font_style) { priv->font_style = style; priv->text_format = nullptr; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_FONT_STRETCH: { DWRITE_FONT_STRETCH stretch = (DWRITE_FONT_STRETCH) g_value_get_enum (value); if (stretch != priv->font_stretch) { priv->font_stretch = stretch; priv->text_format = nullptr; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_TEXT: { const gchar *new_text = g_value_get_string (value); std::wstring new_string; if (new_text) new_string = gst_dwrite_string_to_wstring (new_text); if (priv->default_text != new_string) { priv->default_text = new_string; gst_dwrite_base_overlay_clear_layout (self); } break; } case PROP_COLOR: update_uint (self, &priv->foreground_color, value); break; case PROP_OUTLINE_COLOR: update_uint (self, &priv->outline_color, value); break; case PROP_SHADOW_COLOR: update_uint (self, &priv->shadow_color, value); break; case PROP_BACKGROUND_COLOR: update_uint (self, &priv->background_color, value); break; case PROP_LAYOUT_X: update_double (self, &priv->layout_x, value); break; case PROP_LAYOUT_Y: update_double (self, &priv->layout_y, value); break; case PROP_LAYOUT_WIDTH: update_double (self, &priv->layout_width, value); break; case PROP_LAYOUT_HEIGHT: update_double (self, &priv->layout_height, value); break; case PROP_TEXT_ALIGNMENT: update_enum (self, (gint *) & priv->text_align, value); break; case PROP_PARAGRAPH_ALIGNMENT: update_enum (self, (gint *) & priv->paragraph_align, value); break; case PROP_ENABLE_COLOR_FONT: priv->color_font = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_dwrite_base_overlay_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (object); GstDWriteBaseOverlayPrivate *priv = self->priv; std::lock_guard < std::mutex > lk (priv->prop_lock); switch (prop_id) { case PROP_VISIBLE: g_value_set_boolean (value, priv->visible); break; case PROP_FONT_FAMILY: g_value_set_string (value, priv->font_family.c_str ()); break; case PROP_FONT_SIZE: g_value_set_float (value, priv->font_size); break; case PROP_AUTO_RESIZE: g_value_set_boolean (value, priv->auto_resize); break; case PROP_FONT_WEIGHT: g_value_set_enum (value, priv->font_weight); break; case PROP_FONT_STYLE: g_value_set_enum (value, priv->font_style); break; case PROP_FONT_STRETCH: g_value_set_enum (value, priv->font_stretch); break; case PROP_TEXT: if (priv->default_text.empty ()) { g_value_set_string (value, ""); } else { std::string str = gst_dwrite_wstring_to_string (priv->default_text); g_value_set_string (value, str.c_str ()); } break; case PROP_COLOR: g_value_set_uint (value, priv->foreground_color); break; case PROP_OUTLINE_COLOR: g_value_set_uint (value, priv->outline_color); break; case PROP_SHADOW_COLOR: g_value_set_uint (value, priv->shadow_color); break; case PROP_BACKGROUND_COLOR: g_value_set_uint (value, priv->background_color); break; case PROP_LAYOUT_X: g_value_set_double (value, priv->layout_x); break; case PROP_LAYOUT_Y: g_value_set_double (value, priv->layout_y); break; case PROP_LAYOUT_WIDTH: g_value_set_double (value, priv->layout_width); break; case PROP_LAYOUT_HEIGHT: g_value_set_double (value, priv->layout_height); break; case PROP_TEXT_ALIGNMENT: g_value_set_enum (value, priv->text_align); break; case PROP_PARAGRAPH_ALIGNMENT: g_value_set_enum (value, priv->paragraph_align); break; case PROP_ENABLE_COLOR_FONT: g_value_set_boolean (value, priv->color_font); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_dwrite_base_overlay_set_context (GstElement * elem, GstContext * context) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (elem); GstDWriteBaseOverlayPrivate *priv = self->priv; gst_dwrite_overlay_object_set_context (priv->overlay, elem, context); GST_ELEMENT_CLASS (parent_class)->set_context (elem, context); } static gboolean gst_dwrite_base_overlay_start (GstBaseTransform * trans) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; HRESULT hr; hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED, __uuidof (IDWriteFactory), (IUnknown **) (&priv->dwrite_factory)); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create dwrite factory"); return FALSE; } gst_video_info_init (&self->info); return gst_dwrite_overlay_object_start (priv->overlay, priv->dwrite_factory.Get ()); } static void gst_dwrite_base_overlay_clear_resource (GstDWriteBaseOverlay * self) { gst_dwrite_base_overlay_clear_layout (self); } static gboolean gst_dwrite_base_overlay_stop (GstBaseTransform * trans) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; gst_dwrite_base_overlay_clear_resource (self); gst_dwrite_overlay_object_stop (priv->overlay); priv->dwrite_factory = nullptr; return TRUE; } static gboolean gst_dwrite_base_overlay_query (GstBaseTransform * trans, GstPadDirection direction, GstQuery * query) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; if (gst_dwrite_overlay_object_handle_query (priv->overlay, GST_ELEMENT (self), query)) { return TRUE; } return GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction, query); } static gboolean gst_dwrite_base_overlay_decide_allocation (GstBaseTransform * trans, GstQuery * query) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; if (!gst_dwrite_overlay_object_decide_allocation (priv->overlay, GST_ELEMENT (self), query)) { return FALSE; } return GST_BASE_TRANSFORM_CLASS (parent_class)->decide_allocation (trans, query); } static gboolean gst_dwrite_base_overlay_propose_allocation (GstBaseTransform * trans, GstQuery * decide_query, GstQuery * query) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; gboolean ret; GST_DEBUG_OBJECT (self, "Propose allocation"); if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans, decide_query, query)) return FALSE; if (!decide_query) { GST_DEBUG_OBJECT (self, "Passthrough"); return TRUE; } ret = gst_pad_peer_query (trans->srcpad, query); if (!ret) return FALSE; return gst_dwrite_overlay_object_propose_allocation (priv->overlay, GST_ELEMENT (self), query); } static GstCaps * gst_dwrite_base_overlay_add_feature (GstCaps * caps) { GstCaps *new_caps = gst_caps_new_empty (); guint caps_size = gst_caps_get_size (caps); for (guint i = 0; i < caps_size; i++) { GstStructure *s = gst_caps_get_structure (caps, i); GstCapsFeatures *f = gst_caps_features_copy (gst_caps_get_features (caps, i)); GstCaps *c = gst_caps_new_full (gst_structure_copy (s), nullptr); if (!gst_caps_features_is_any (f) && !gst_caps_features_contains (f, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) { gst_caps_features_add (f, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION); } gst_caps_set_features (c, 0, f); gst_caps_append (new_caps, c); } return new_caps; } static GstCaps * gst_dwrite_overlay_remove_feature (GstCaps * caps) { GstCaps *new_caps = gst_caps_new_empty (); guint caps_size = gst_caps_get_size (caps); for (guint i = 0; i < caps_size; i++) { GstStructure *s = gst_caps_get_structure (caps, i); GstCapsFeatures *f = gst_caps_features_copy (gst_caps_get_features (caps, i)); GstCaps *c = gst_caps_new_full (gst_structure_copy (s), nullptr); gst_caps_features_remove (f, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION); gst_caps_set_features (c, 0, f); gst_caps_append (new_caps, c); } return new_caps; } static GstCaps * gst_dwrite_base_overlay_transform_caps (GstBaseTransform * trans, GstPadDirection direction, GstCaps * caps, GstCaps * filter) { GstCaps *result, *tmp; GST_DEBUG_OBJECT (trans, "Transforming caps %" GST_PTR_FORMAT " in direction %s", caps, (direction == GST_PAD_SINK) ? "sink" : "src"); if (direction == GST_PAD_SINK) { tmp = gst_dwrite_base_overlay_add_feature (caps); tmp = gst_caps_merge (tmp, gst_caps_ref (caps)); } else { tmp = gst_dwrite_overlay_remove_feature (caps); tmp = gst_caps_merge (gst_caps_ref (caps), tmp); } if (filter) { result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (tmp); } else { result = tmp; } GST_DEBUG_OBJECT (trans, "returning caps: %" GST_PTR_FORMAT, result); return result; } static gboolean gst_dwrite_base_overlay_set_caps (GstBaseTransform * trans, GstCaps * incaps, GstCaps * outcaps) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; gst_dwrite_base_overlay_clear_layout (self); if (!gst_dwrite_overlay_object_set_caps (priv->overlay, GST_ELEMENT (self), incaps, outcaps, &self->info, &priv->blend_mode)) { GST_ERROR_OBJECT (self, "Set caps failed"); return FALSE; } priv->prop_lock.lock (); priv->text_format = nullptr; priv->layout = nullptr; priv->prop_lock.unlock (); if (priv->blend_mode == GstDWriteBlendMode::NOT_SUPPORTED) gst_base_transform_set_passthrough (trans, TRUE); else gst_base_transform_set_passthrough (trans, FALSE); return TRUE; } static void gst_dwrite_base_overlay_before_transform (GstBaseTransform * trans, GstBuffer * buf) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; if (gst_dwrite_overlay_object_update_device (priv->overlay, buf)) gst_base_transform_reconfigure (trans); } static GstFlowReturn gst_dwrite_base_overlay_prepare_output_buffer (GstBaseTransform * trans, GstBuffer * inbuf, GstBuffer ** outbuf) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; GstDWriteBaseOverlayClass *klass = GST_DWRITE_BASE_OVERLAY_GET_CLASS (self); if (priv->blend_mode == GstDWriteBlendMode::NOT_SUPPORTED) { GST_TRACE_OBJECT (self, "Force passthrough"); return GST_BASE_TRANSFORM_CLASS (parent_class)->prepare_output_buffer (trans, inbuf, outbuf); } std::lock_guard < std::mutex > lk (priv->prop_lock); /* Invisible, do passthrough */ if (!priv->visible) { gst_base_transform_set_passthrough (trans, TRUE); *outbuf = inbuf; return GST_FLOW_OK; } priv->cur_text = klass->get_text (self, priv->default_text, inbuf); if (priv->cur_text.empty ()) { priv->prev_text.clear (); gst_dwrite_base_overlay_clear_layout (self); /* Nothing to render, passthrough */ gst_base_transform_set_passthrough (trans, TRUE); *outbuf = inbuf; return GST_FLOW_OK; } gst_base_transform_set_passthrough (trans, FALSE); if (priv->prev_text != priv->cur_text) gst_dwrite_base_overlay_clear_layout (self); priv->prev_text = priv->cur_text; return gst_dwrite_overlay_object_prepare_output (priv->overlay, trans, parent_class, inbuf, outbuf); } static gboolean gst_dwrite_base_overlay_update_text_format (GstDWriteBaseOverlay * self) { GstDWriteBaseOverlayPrivate *priv = self->priv; FLOAT font_size; HRESULT hr; if (priv->text_format) return TRUE; gst_dwrite_base_overlay_clear_layout (self); if (priv->auto_resize) { font_size = (FLOAT) self->info.width * priv->font_size / 640; } else { font_size = priv->font_size; } std::wstring wfont_family = gst_dwrite_string_to_wstring (priv->font_family); hr = priv->dwrite_factory->CreateTextFormat (wfont_family.c_str (), nullptr, priv->font_weight, priv->font_style, priv->font_stretch, font_size, L"en-us", &priv->text_format); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create text format"); return FALSE; } return TRUE; } static inline D2D1_COLOR_F unpack_argb (guint packed) { D2D1_COLOR_F ret; ret.a = ((packed >> 24) & 0xff) / 255.f; ret.r = ((packed >> 16) & 0xff) / 255.f; ret.g = ((packed >> 8) & 0xff) / 255.f; ret.b = ((packed >> 0) & 0xff) / 255.f; return ret; } static gboolean gst_dwrite_base_overlay_create_layout (GstDWriteBaseOverlay * self, const std::wstring & text) { GstDWriteBaseOverlayPrivate *priv = self->priv; D2D1_COLOR_F color; ComPtr < IGstDWriteTextEffect > effect; HRESULT hr; DWRITE_TEXT_RANGE range; priv->layout_origin.x = priv->layout_x * self->info.width; priv->layout_origin.y = priv->layout_y * self->info.height; priv->layout_size.x = priv->layout_width * self->info.width; priv->layout_size.y = priv->layout_height * self->info.height; hr = priv->dwrite_factory->CreateTextLayout (text.c_str (), text.length (), priv->text_format.Get (), priv->layout_size.x, priv->layout_size.y, &priv->layout); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create text layout"); return FALSE; } hr = IGstDWriteTextEffect::CreateInstance (&effect); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't create text effect"); priv->layout = nullptr; return FALSE; } range.startPosition = 0; range.length = G_MAXUINT32; priv->layout->SetTextAlignment (priv->text_align); priv->layout->SetParagraphAlignment (priv->paragraph_align); priv->layout->SetMaxWidth (priv->layout_size.x); priv->layout->SetMaxHeight (priv->layout_size.y); color = unpack_argb (priv->foreground_color); effect->SetBrushColor (GST_DWRITE_BRUSH_FORGROUND, &color); color = unpack_argb (priv->outline_color); effect->SetBrushColor (GST_DWRITE_BRUSH_OUTLINE, &color); color = unpack_argb (priv->shadow_color); effect->SetBrushColor (GST_DWRITE_BRUSH_SHADOW, &color); color = unpack_argb (priv->background_color); effect->SetBrushColor (GST_DWRITE_BRUSH_BACKGROUND, &color); effect->SetEnableColorFont (priv->color_font); hr = priv->layout->SetDrawingEffect (effect.Get (), range); if (FAILED (hr)) { GST_ERROR_OBJECT (self, "Couldn't set drawing effect"); priv->layout = nullptr; return FALSE; } return TRUE; } static GstFlowReturn gst_dwrite_base_overlay_transform (GstBaseTransform * trans, GstBuffer * inbuf, GstBuffer * outbuf) { GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); GstDWriteBaseOverlayPrivate *priv = self->priv; GstDWriteBaseOverlayClass *klass = GST_DWRITE_BASE_OVERLAY_GET_CLASS (self); std::lock_guard < std::mutex > lk (priv->prop_lock); if (!priv->overlay) { GST_ERROR_OBJECT (self, "Overlay object is not configured"); return GST_FLOW_ERROR; } if (!gst_dwrite_base_overlay_update_text_format (self)) return GST_FLOW_ERROR; if (!priv->layout && !gst_dwrite_base_overlay_create_layout (self, priv->cur_text)) { return GST_FLOW_ERROR; } if (!gst_dwrite_overlay_object_draw (priv->overlay, outbuf, priv->layout.Get (), priv->layout_origin.x, priv->layout_origin.y)) { GST_ERROR_OBJECT (self, "Draw failed"); return GST_FLOW_ERROR; } if (klass->after_transform) klass->after_transform (self, outbuf); return GST_FLOW_OK; } void gst_dwrite_base_overlay_build_param_specs (std::vector < GParamSpec * >&pspec) { pspec.push_back (g_param_spec_boolean ("visible", "Visible", "Whether to draw text", DEFAULT_VISIBLE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_string ("font-family", "Font Family", "Font family to use", DEFAULT_FONT_FAMILY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_float ("font-size", "Font Size", "Font size to use", 0.1f, 1638.f, DEFAULT_FONT_SIZE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_boolean ("auto-resize", "Auto Resize", "Calculate font size to be equivalent to \"font-size\" at " "\"reference-frame-size\"", DEFAULT_AUTO_RESIZE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_enum ("font-weight", "Font Weight", "Font Weight", GST_TYPE_DWRITE_FONT_WEIGHT, DEFAULT_FONT_WEIGHT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_enum ("font-style", "Font Style", "Font Style", GST_TYPE_DWRITE_FONT_STYLE, DEFAULT_FONT_STYLE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_enum ("font-stretch", "Font Stretch", "Font Stretch", GST_TYPE_DWRITE_FONT_STRETCH, DEFAULT_FONT_STRETCH, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_string ("text", "Text", "Text to render", "", (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_uint ("foreground-color", "Foreground Color", "Foreground color to use (big-endian ARGB)", 0, G_MAXUINT32, DEFAULT_FOREGROUND_COLOR, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_uint ("outline-color", "Outline Color", "Text outline color to use (big-endian ARGB)", 0, G_MAXUINT32, DEFAULT_OUTLINE_COLOR, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_uint ("shadow-color", "Shadow Color", "Shadow color to use (big-endian ARGB)", 0, G_MAXUINT32, DEFAULT_SHADOW_COLOR, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_uint ("background-color", "Background Color", "Background color to use (big-endian ARGB)", 0, G_MAXUINT32, DEFAULT_BACKGROUND_COLOR, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_double ("layout-x", "Layout X", "Normalized X coordinate of text layout", 0, 1, DEFAULT_LAYOUT_XY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_double ("layout-y", "Layout Y", "Normalized Y coordinate of text layout", 0, 1, DEFAULT_LAYOUT_XY, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_double ("layout-width", "Layout Width", "Normalized width of text layout", 0, 1, DEFAULT_LAYOUT_WH, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_double ("layout-height", "Layout Height", "Normalized height of text layout", 0, 1, DEFAULT_LAYOUT_WH, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_enum ("text-alignment", "Text Alignment", "Text Alignment", GST_TYPE_DWRITE_TEXT_ALIGNMENT, DEFAULT_TEXT_ALIGNMENT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); pspec.push_back (g_param_spec_enum ("paragraph-alignment", "Paragraph alignment", "Paragraph Alignment", GST_TYPE_DWRITE_PARAGRAPH_ALIGNMENT, DEFAULT_PARAGRAPH_ALIGNMENT, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); #ifdef HAVE_DWRITE_COLOR_FONT if (gst_dwrite_is_windows_10_or_greater ()) { pspec.push_back (g_param_spec_boolean ("color-font", "Color Font", "Enable color font, requires Windows 10 or newer", DEFAULT_COLOR_FONT, (GParamFlags) (GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); } #endif }