From ba6800ce238f751a438f43adaa1050abf3e64ce3 Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Sun, 28 May 2023 01:59:23 +0900 Subject: [PATCH] dwrite: Add DirectWrite text rendering plugin Adding DirectWrite text rendering elements * dwriteclockoverlay: Equivalent to clockoverlay * dwritetimeoverlay: Equivalent to timeoverlay * dwritetextoverlay: Similar to textoverlay but subtitle is not supported Newly added elements support system memory and d3d11 memory Part-of: --- .indent_cpp_list | 1 + subprojects/gst-plugins-bad/meson_options.txt | 1 + .../sys/dwrite/gstdwrite-effect.cpp | 142 ++ .../sys/dwrite/gstdwrite-effect.h | 65 + .../sys/dwrite/gstdwrite-enums.cpp | 237 ++ .../sys/dwrite/gstdwrite-enums.h | 49 + .../sys/dwrite/gstdwrite-renderer.cpp | 660 +++++ .../sys/dwrite/gstdwrite-renderer.h | 92 + .../sys/dwrite/gstdwrite-utils.cpp | 62 + .../sys/dwrite/gstdwrite-utils.h | 79 + .../sys/dwrite/gstdwritebaseoverlay.cpp | 2245 +++++++++++++++++ .../sys/dwrite/gstdwritebaseoverlay.h | 71 + .../sys/dwrite/gstdwritebitmapmemory.cpp | 300 +++ .../sys/dwrite/gstdwritebitmapmemory.h | 48 + .../sys/dwrite/gstdwritebitmappool.cpp | 195 ++ .../sys/dwrite/gstdwritebitmappool.h | 33 + .../sys/dwrite/gstdwriteclockoverlay.cpp | 193 ++ .../sys/dwrite/gstdwriteclockoverlay.h | 30 + .../sys/dwrite/gstdwritetextoverlay.cpp | 72 + .../sys/dwrite/gstdwritetextoverlay.h | 30 + .../sys/dwrite/gstdwritetimeoverlay.cpp | 438 ++++ .../sys/dwrite/gstdwritetimeoverlay.h | 30 + .../gst-plugins-bad/sys/dwrite/meson.build | 70 + .../gst-plugins-bad/sys/dwrite/plugin.cpp | 55 + subprojects/gst-plugins-bad/sys/meson.build | 1 + 25 files changed, 5199 insertions(+) create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.cpp create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.h create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/meson.build create mode 100644 subprojects/gst-plugins-bad/sys/dwrite/plugin.cpp diff --git a/.indent_cpp_list b/.indent_cpp_list index e82c06c6ad..a2024e3de8 100644 --- a/.indent_cpp_list +++ b/.indent_cpp_list @@ -4,6 +4,7 @@ subprojects/gst-plugins-bad/gst-libs/gst/va subprojects/gst-plugins-bad/gst-libs/gst/winrt subprojects/gst-plugins-bad/sys/amfcodec subprojects/gst-plugins-bad/sys/d3d11 +subprojects/gst-plugins-bad/sys/dwrite subprojects/gst-plugins-bad/sys/mediafoundation subprojects/gst-plugins-bad/sys/nvcodec ^(subprojects/gst-plugins-bad/sys/qsv/)+(\w)+([^/])+(cpp$) diff --git a/subprojects/gst-plugins-bad/meson_options.txt b/subprojects/gst-plugins-bad/meson_options.txt index f76372652a..1cedb2222c 100644 --- a/subprojects/gst-plugins-bad/meson_options.txt +++ b/subprojects/gst-plugins-bad/meson_options.txt @@ -108,6 +108,7 @@ option('directshow', type : 'feature', value : 'auto', description : 'Directshow option('dtls', type : 'feature', value : 'auto', description : 'DTLS encoder and decoder plugin') option('dts', type : 'feature', value : 'auto', description : 'DTS audio decoder plugin (GPL - only built if gpl option is also enabled!)') option('dvb', type : 'feature', value : 'auto', description : 'DVB video bin and source plugin') +option('dwrite', type : 'feature', value : 'auto', description : 'DirectWrite plugin') option('faac', type : 'feature', value : 'auto', description : 'Free AAC audio encoder plugin') option('faad', type : 'feature', value : 'auto', description : 'Free AAC audio decoder plugin (GPL - only built if gpl option is also enabled!)') option('fbdev', type : 'feature', value : 'auto', description : 'Framebuffer video sink plugin') diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.cpp new file mode 100644 index 0000000000..fbc12fd703 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.cpp @@ -0,0 +1,142 @@ +/* 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 "config.h" +#endif + +#include "gstdwrite-effect.h" + +/* *INDENT-OFF* */ +STDMETHODIMP +IGstDWriteTextEffect::CreateInstance (IGstDWriteTextEffect ** effect) +{ + IGstDWriteTextEffect *self = new IGstDWriteTextEffect (); + if (!self) + return E_OUTOFMEMORY; + + *effect = self; + + return S_OK; +} + +STDMETHODIMP_ (BOOL) +IGstDWriteTextEffect::IsEnabledColor (const D2D1_COLOR_F & color) +{ + if (color.r != 0 || color.g != 0 || color.b != 0 || color.a != 0) + return TRUE; + + return FALSE; +} + +STDMETHODIMP_ (ULONG) +IGstDWriteTextEffect::AddRef (void) +{ + return InterlockedIncrement (&ref_count_); +} + +STDMETHODIMP_ (ULONG) +IGstDWriteTextEffect::Release (void) +{ + ULONG ref_count; + + ref_count = InterlockedDecrement (&ref_count_); + + if (ref_count == 0) + delete this; + + return ref_count; +} + +STDMETHODIMP +IGstDWriteTextEffect::QueryInterface (REFIID riid, void ** object) +{ + if (riid == IID_IUnknown) { + *object = static_cast + (static_cast (this)); + } else if (riid == IID_IGstDWriteTextEffect) { + *object = this; + } else { + *object = nullptr; + return E_NOINTERFACE; + } + + AddRef (); + + return S_OK; +} + +STDMETHODIMP +IGstDWriteTextEffect::Clone (IGstDWriteTextEffect ** effect) +{ + IGstDWriteTextEffect *copy = new IGstDWriteTextEffect (); + + if (!copy) + return E_OUTOFMEMORY; + + for (UINT i = 0; i <= GST_DWRITE_BRUSH_SHADOW; i++) + copy->brush_[i] = this->brush_[i]; + + *effect = copy; + + return S_OK; +} + +STDMETHODIMP +IGstDWriteTextEffect::GetBrushColor (GST_DWRITE_BRUSH_TARGET target, + D2D1_COLOR_F * color, BOOL * enabled) +{ + if (color) + *color = brush_[target]; + + if (enabled) { + if (IGstDWriteTextEffect::IsEnabledColor (brush_[target])) + *enabled = TRUE; + else + *enabled = FALSE; + } + + return S_OK; +} + +STDMETHODIMP +IGstDWriteTextEffect::SetBrushColor (GST_DWRITE_BRUSH_TARGET target, + const D2D1_COLOR_F * color) +{ + if (!color) + brush_[target] = D2D1::ColorF (D2D1::ColorF::Black, 0); + else + brush_[target] = *color; + + return S_OK; +} + +IGstDWriteTextEffect::IGstDWriteTextEffect (void) +{ + for (UINT32 i = 0; i < GST_DWRITE_BRUSH_SHADOW; i++) + brush_[i] = D2D1::ColorF (D2D1::ColorF::Black); + + /* Disable custom shadow effects by default */ + brush_[GST_DWRITE_BRUSH_SHADOW] = D2D1::ColorF (D2D1::ColorF::Black, 0); +} + +IGstDWriteTextEffect::~IGstDWriteTextEffect (void) +{ +} +/* *INDENT-ON* */ diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.h new file mode 100644 index 0000000000..c74905c9b9 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-effect.h @@ -0,0 +1,65 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstdwrite-enums.h" + +enum GST_DWRITE_BRUSH_TARGET +{ + GST_DWRITE_BRUSH_TEXT = 0, + GST_DWRITE_BRUSH_TEXT_OUTLINE, + GST_DWRITE_BRUSH_UNDERLINE, + GST_DWRITE_BRUSH_UNDERLINE_OUTLINE, + GST_DWRITE_BRUSH_STRIKETHROUGH, + GST_DWRITE_BRUSH_STRIKETHROUGH_OUTLINE, + GST_DWRITE_BRUSH_SHADOW, +}; + +DEFINE_GUID (IID_IGstDWriteTextEffect, 0x23c579ae, 0x2e18, + 0x11ed, 0xa2, 0x61, 0x02, 0x42, 0xac, 0x12, 0x00, 0x02); +class IGstDWriteTextEffect : public IUnknown +{ +public: + static STDMETHODIMP CreateInstance (IGstDWriteTextEffect ** effect); + static STDMETHODIMP_ (BOOL) IsEnabledColor (const D2D1_COLOR_F & color); + + /* IUnknown */ + STDMETHODIMP_ (ULONG) AddRef (void); + STDMETHODIMP_ (ULONG) Release (void); + STDMETHODIMP QueryInterface (REFIID riid, + void ** object); + + /* effect methods */ + STDMETHODIMP Clone (IGstDWriteTextEffect ** effect); + STDMETHODIMP GetBrushColor (GST_DWRITE_BRUSH_TARGET target, + D2D1_COLOR_F * color, + BOOL * enabled); + STDMETHODIMP SetBrushColor (GST_DWRITE_BRUSH_TARGET target, + const D2D1_COLOR_F * color); + +private: + IGstDWriteTextEffect (void); + virtual ~IGstDWriteTextEffect (void); + +private: + ULONG ref_count_ = 1; + D2D1_COLOR_F brush_[GST_DWRITE_BRUSH_SHADOW + 1]; +}; diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.cpp new file mode 100644 index 0000000000..a18353631b --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.cpp @@ -0,0 +1,237 @@ +/* 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 "config.h" +#endif + +#include "gstdwrite-enums.h" +#include "gstdwrite-utils.h" + +/* XXX: MinGW's header does not define some enum values */ +enum +{ + GST_DWRITE_TEXT_ALIGNMENT_LEADING, + GST_DWRITE_TEXT_ALIGNMENT_TRAILING, + GST_DWRITE_TEXT_ALIGNMENT_CENTER, + GST_DWRITE_TEXT_ALIGNMENT_JUSTIFIED, +}; + +enum +{ + GST_DWRITE_PARAGRAPH_ALIGNMENT_NEAR, + GST_DWRITE_PARAGRAPH_ALIGNMENT_FAR, + GST_DWRITE_PARAGRAPH_ALIGNMENT_CENTER, +}; + +enum +{ + GST_DWRITE_FONT_WEIGHT_THIN = 100, + GST_DWRITE_FONT_WEIGHT_EXTRA_LIGHT = 200, + GST_DWRITE_FONT_WEIGHT_ULTRA_LIGHT = 200, + GST_DWRITE_FONT_WEIGHT_LIGHT = 300, + GST_DWRITE_FONT_WEIGHT_SEMI_LIGHT = 350, + GST_DWRITE_FONT_WEIGHT_NORMAL = 400, + GST_DWRITE_FONT_WEIGHT_REGULAR = 400, + GST_DWRITE_FONT_WEIGHT_MEDIUM = 500, + GST_DWRITE_FONT_WEIGHT_DEMI_BOLD = 600, + GST_DWRITE_FONT_WEIGHT_SEMI_BOLD = 600, + GST_DWRITE_FONT_WEIGHT_BOLD = 700, + GST_DWRITE_FONT_WEIGHT_EXTRA_BOLD = 800, + GST_DWRITE_FONT_WEIGHT_ULTRA_BOLD = 800, + GST_DWRITE_FONT_WEIGHT_BLACK = 900, + GST_DWRITE_FONT_WEIGHT_HEAVY = 900, + GST_DWRITE_FONT_WEIGHT_EXTRA_BLACK = 950, + GST_DWRITE_FONT_WEIGHT_ULTRA_BLACK = 950 +}; + +enum +{ + GST_DWRITE_FONT_STYLE_NORMAL, + GST_DWRITE_FONT_STYLE_OBLIQUE, + GST_DWRITE_FONT_STYLE_ITALIC, +}; + +enum +{ + GST_DWRITE_FONT_STRETCH_UNDEFINED = 0, + GST_DWRITE_FONT_STRETCH_ULTRA_CONDENSED = 1, + GST_DWRITE_FONT_STRETCH_EXTRA_CONDENSED = 2, + GST_DWRITE_FONT_STRETCH_CONDENSED = 3, + GST_DWRITE_FONT_STRETCH_SEMI_CONDENSED = 4, + GST_DWRITE_FONT_STRETCH_NORMAL = 5, + GST_DWRITE_FONT_STRETCH_MEDIUM = 5, + GST_DWRITE_FONT_STRETCH_SEMI_EXPANDED = 6, + GST_DWRITE_FONT_STRETCH_EXPANDED = 7, + GST_DWRITE_FONT_STRETCH_EXTRA_EXPANDED = 8, + GST_DWRITE_FONT_STRETCH_ULTRA_EXPANDED = 9 +}; + +GType +gst_dwrite_text_alignment_get_type (void) +{ + static GType text_align_type = 0; + static const GEnumValue text_align_types[] = { + {GST_DWRITE_TEXT_ALIGNMENT_LEADING, "DWRITE_TEXT_ALIGNMENT_LEADING", + "leading"}, + {GST_DWRITE_TEXT_ALIGNMENT_TRAILING, + "DWRITE_TEXT_ALIGNMENT_TRAILING", "trailing"}, + {GST_DWRITE_TEXT_ALIGNMENT_CENTER, + "DWRITE_TEXT_ALIGNMENT_CENTER", "center"}, + {GST_DWRITE_TEXT_ALIGNMENT_JUSTIFIED, + "DWRITE_TEXT_ALIGNMENT_JUSTIFIED", "justified"}, + {0, nullptr, nullptr}, + }; + + GST_DWRITE_CALL_ONCE_BEGIN { + text_align_type = g_enum_register_static ("GstDWriteTextAlignment", + text_align_types); + } GST_DWRITE_CALL_ONCE_END; + + return text_align_type; +} + +GType +gst_dwrite_paragraph_alignment_get_type (void) +{ + static GType paragraph_align_type = 0; + static const GEnumValue paragraph_align_types[] = { + {GST_DWRITE_PARAGRAPH_ALIGNMENT_NEAR, "DWRITE_PARAGRAPH_ALIGNMENT_NEAR", + "near"}, + {GST_DWRITE_PARAGRAPH_ALIGNMENT_FAR, + "DWRITE_PARAGRAPH_ALIGNMENT_FAR", "far"}, + {GST_DWRITE_PARAGRAPH_ALIGNMENT_CENTER, + "DWRITE_PARAGRAPH_ALIGNMENT_CENTER", "center"}, + {0, nullptr, nullptr}, + }; + + GST_DWRITE_CALL_ONCE_BEGIN { + paragraph_align_type = + g_enum_register_static ("GstDWriteParagraphAlignment", + paragraph_align_types); + } GST_DWRITE_CALL_ONCE_END; + + return paragraph_align_type; +} + +GType +gst_dwrite_font_weight_get_type (void) +{ + static GType font_weight_type = 0; + static const GEnumValue font_weight_types[] = { + {GST_DWRITE_FONT_WEIGHT_THIN, "DWRITE_FONT_WEIGHT_THIN", "thin"}, + {GST_DWRITE_FONT_WEIGHT_EXTRA_LIGHT, "DWRITE_FONT_WEIGHT_EXTRA_LIGHT", + "extra-light"}, + {GST_DWRITE_FONT_WEIGHT_ULTRA_LIGHT, + "DWRITE_FONT_WEIGHT_ULTRA_LIGHT", "ultra-light"}, + {GST_DWRITE_FONT_WEIGHT_LIGHT, "DWRITE_FONT_WEIGHT_LIGHT", "light"}, + {GST_DWRITE_FONT_WEIGHT_SEMI_LIGHT, "DWRITE_FONT_WEIGHT_SEMI_LIGHT", + "semi-light"}, + {GST_DWRITE_FONT_WEIGHT_NORMAL, "DWRITE_FONT_WEIGHT_NORMAL", "normal"}, + {GST_DWRITE_FONT_WEIGHT_REGULAR, "DWRITE_FONT_WEIGHT_REGULAR", + "regular"}, + {GST_DWRITE_FONT_WEIGHT_MEDIUM, "DWRITE_FONT_WEIGHT_MEDIUM", "medium"}, + {GST_DWRITE_FONT_WEIGHT_DEMI_BOLD, "DWRITE_FONT_WEIGHT_DEMI_BOLD", + "demi-bold"}, + {GST_DWRITE_FONT_WEIGHT_SEMI_BOLD, "DWRITE_FONT_WEIGHT_SEMI_BOLD", + "semi-bold"}, + {GST_DWRITE_FONT_WEIGHT_BOLD, "DWRITE_FONT_WEIGHT_BOLD", "bold"}, + {GST_DWRITE_FONT_WEIGHT_EXTRA_BOLD, "DWRITE_FONT_WEIGHT_EXTRA_BOLD", + "extra-bold"}, + {GST_DWRITE_FONT_WEIGHT_ULTRA_BOLD, "DWRITE_FONT_WEIGHT_ULTRA_BOLD", + "ultra-bold"}, + {GST_DWRITE_FONT_WEIGHT_BLACK, "DWRITE_FONT_WEIGHT_BLACK", "black"}, + {GST_DWRITE_FONT_WEIGHT_HEAVY, "DWRITE_FONT_WEIGHT_HEAVY", "heavy"}, + {GST_DWRITE_FONT_WEIGHT_EXTRA_BLACK, "DWRITE_FONT_WEIGHT_EXTRA_BLACK", + "extra-black"}, + {GST_DWRITE_FONT_WEIGHT_ULTRA_BLACK, "DWRITE_FONT_WEIGHT_ULTRA_BLACK", + "ultra-black"}, + {0, nullptr, nullptr}, + }; + + GST_DWRITE_CALL_ONCE_BEGIN { + font_weight_type = g_enum_register_static ("GstDWriteFontWeight", + font_weight_types); + } GST_DWRITE_CALL_ONCE_END; + + return font_weight_type; +} + +GType +gst_dwrite_font_style_get_type (void) +{ + static GType font_style_type = 0; + static const GEnumValue font_style_types[] = { + {GST_DWRITE_FONT_STYLE_NORMAL, "DWRITE_FONT_STYLE_NORMAL", "normal"}, + {GST_DWRITE_FONT_STYLE_OBLIQUE, "DWRITE_FONT_STYLE_OBLIQUE", "oblique"}, + {GST_DWRITE_FONT_STYLE_ITALIC, "DWRITE_FONT_STYLE_ITALIC", "italic"}, + {0, nullptr, nullptr}, + }; + + GST_DWRITE_CALL_ONCE_BEGIN { + font_style_type = g_enum_register_static ("GstDWriteFontStyle", + font_style_types); + } GST_DWRITE_CALL_ONCE_END; + + return font_style_type; +} + +GType +gst_dwrite_font_stretch_get_type (void) +{ + static GType font_stretch_type = 0; + static const GEnumValue font_stretch_types[] = { + {GST_DWRITE_FONT_STRETCH_UNDEFINED, "DWRITE_FONT_STRETCH_UNDEFINED", + "undefined"}, + {GST_DWRITE_FONT_STRETCH_ULTRA_CONDENSED, + "DWRITE_FONT_STRETCH_ULTRA_CONDENSED", + "ultra-condensed"}, + {GST_DWRITE_FONT_STRETCH_EXTRA_CONDENSED, + "DWRITE_FONT_STRETCH_EXTRA_CONDENSED", + "extra-condensed"}, + {GST_DWRITE_FONT_STRETCH_CONDENSED, "DWRITE_FONT_STRETCH_CONDENSED", + "condensed"}, + {GST_DWRITE_FONT_STRETCH_SEMI_CONDENSED, + "DWRITE_FONT_STRETCH_SEMI_CONDENSED", + "semi-condensed"}, + {GST_DWRITE_FONT_STRETCH_NORMAL, "DWRITE_FONT_STRETCH_NORMAL", + "normal"}, + {GST_DWRITE_FONT_STRETCH_MEDIUM, "DWRITE_FONT_STRETCH_MEDIUM", + "medium"}, + {GST_DWRITE_FONT_STRETCH_SEMI_EXPANDED, + "DWRITE_FONT_STRETCH_SEMI_EXPANDED", + "semi-expanded"}, + {GST_DWRITE_FONT_STRETCH_EXPANDED, "DWRITE_FONT_STRETCH_EXPANDED", + "expanded"}, + {GST_DWRITE_FONT_STRETCH_EXTRA_EXPANDED, + "DWRITE_FONT_STRETCH_EXTRA_EXPANDED", + "extra-expanded"}, + {GST_DWRITE_FONT_STRETCH_ULTRA_EXPANDED, + "DWRITE_FONT_STRETCH_ULTRA_EXPANDED", + "ultra-expanded"}, + {0, nullptr, nullptr}, + }; + + GST_DWRITE_CALL_ONCE_BEGIN { + font_stretch_type = g_enum_register_static ("GstDWriteFontStretch", + font_stretch_types); + } GST_DWRITE_CALL_ONCE_END; + + return font_stretch_type; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.h new file mode 100644 index 0000000000..715777e881 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-enums.h @@ -0,0 +1,49 @@ +/* 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. + */ + +#pragma once + +#include +#include +#include +#include + +#ifdef HAVE_DWRITE_COLOR_FONT +#include +#include +#endif + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_TEXT_ALIGNMENT (gst_dwrite_text_alignment_get_type ()) +GType gst_dwrite_text_alignment_get_type (void); + +#define GST_TYPE_DWRITE_PARAGRAPH_ALIGNMENT (gst_dwrite_paragraph_alignment_get_type ()) +GType gst_dwrite_paragraph_alignment_get_type (void); + +#define GST_TYPE_DWRITE_FONT_WEIGHT (gst_dwrite_font_weight_get_type ()) +GType gst_dwrite_font_weight_get_type (void); + +#define GST_TYPE_DWRITE_FONT_STYLE (gst_dwrite_font_style_get_type ()) +GType gst_dwrite_font_style_get_type (void); + +#define GST_TYPE_DWRITE_FONT_STRETCH (gst_dwrite_font_stretch_get_type ()) +GType gst_dwrite_font_stretch_get_type (void); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.cpp new file mode 100644 index 0000000000..8779274d5f --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.cpp @@ -0,0 +1,660 @@ +/* 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 "config.h" +#endif + +#include "gstdwrite-renderer.h" +#include +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_dwrite_debug); +#define GST_CAT_DEFAULT gst_dwrite_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +struct RenderContext +{ + gboolean collect_geometry; + std::vector> backgrounds; + D2D1_RECT_F background_padding = D2D1::RectF (); + ID2D1Factory *factory; + ID2D1RenderTarget *target; + RECT client_rect; + D2D1_SIZE_F scale; + gboolean enable_color_font; +}; +/* *INDENT-ON* */ + +static HRESULT +CombineTwoGeometries (ID2D1Factory * factory, ID2D1Geometry * a, + ID2D1Geometry * b, ID2D1Geometry ** result) +{ + HRESULT hr; + ComPtr < ID2D1GeometrySink > sink; + ComPtr < ID2D1PathGeometry > geometry; + + hr = factory->CreatePathGeometry (&geometry); + if (FAILED (hr)) { + GST_WARNING ("Couldn't create path geometry, 0x%x", (guint) hr); + return hr; + } + + hr = geometry->Open (&sink); + if (FAILED (hr)) { + GST_WARNING ("Couldn't open path geometry, 0x%x", (guint) hr); + return hr; + } + + hr = a->CombineWithGeometry (b, + D2D1_COMBINE_MODE_UNION, nullptr, sink.Get ()); + if (FAILED (hr)) { + GST_WARNING ("Couldn't combine geometry, 0x%x", (guint) hr); + return hr; + } + + hr = sink->Close (); + if (FAILED (hr)) { + GST_WARNING ("Couldn't close sink, 0x%x", (guint) hr); + return hr; + } + + *result = geometry.Detach (); + + return S_OK; +} + +static void +CombineGeometries (ID2D1Factory * factory, + std::vector < ComPtr < ID2D1Geometry >> &geometries, + ID2D1Geometry ** result) +{ + ComPtr < ID2D1Geometry > combined; + HRESULT hr; + + if (geometries.empty ()) + return; + + /* *INDENT-OFF* */ + for (const auto & it: geometries) { + if (!combined) { + combined = it; + } else { + ComPtr tmp; + hr = CombineTwoGeometries (factory, it.Get (), combined.Get (), &tmp); + if (FAILED (hr)) + return; + + combined = tmp; + } + } + /* *INDENT-ON* */ + + if (!combined) + return; + + *result = combined.Detach (); +} + +STDMETHODIMP + IGstDWriteTextRenderer::CreateInstance (IDWriteFactory * factory, + IGstDWriteTextRenderer ** renderer) +{ + IGstDWriteTextRenderer *self = new IGstDWriteTextRenderer (); + + if (!self) + return E_OUTOFMEMORY; + + self->factory_ = factory; + factory->AddRef (); + *renderer = self; + + return S_OK; +} + +/* IUnknown */ +STDMETHODIMP_ (ULONG) + IGstDWriteTextRenderer::AddRef (void) +{ + return InterlockedIncrement (&ref_count_); +} + +STDMETHODIMP_ (ULONG) + IGstDWriteTextRenderer::Release (void) +{ + ULONG ref_count; + + ref_count = InterlockedDecrement (&ref_count_); + + if (ref_count == 0) + delete this; + + return ref_count; +} + +STDMETHODIMP +IGstDWriteTextRenderer::QueryInterface (REFIID riid, void **object) +{ + if (riid == __uuidof (IUnknown)) { + *object = static_cast < IUnknown * > + (static_cast < IGstDWriteTextRenderer * >(this)); + } else if (riid == __uuidof (IDWritePixelSnapping)) { + *object = static_cast < IDWritePixelSnapping * > + (static_cast < IGstDWriteTextRenderer * >(this)); + } else if (riid == IID_IGstDWriteTextRenderer) { + *object = static_cast < IDWriteTextRenderer * > + (static_cast < IGstDWriteTextRenderer * >(this)); + } else { + *object = nullptr; + return E_NOINTERFACE; + } + + AddRef (); + + return S_OK; +} + +/* IDWritePixelSnapping */ +STDMETHODIMP + IGstDWriteTextRenderer::IsPixelSnappingDisabled (void *context, + BOOL * is_disabled) +{ + *is_disabled = FALSE; + return S_OK; +} + +STDMETHODIMP + IGstDWriteTextRenderer::GetCurrentTransform (void *context, + DWRITE_MATRIX * transform) +{ + RenderContext *render_ctx; + + g_assert (context != nullptr); + + render_ctx = (RenderContext *) context; + render_ctx->target->GetTransform (reinterpret_cast < + D2D1_MATRIX_3X2_F * >(transform)); + + return S_OK; +} + +STDMETHODIMP + IGstDWriteTextRenderer::GetPixelsPerDip (void *context, + FLOAT * pixels_per_dip) +{ + *pixels_per_dip = 1.0f; + + return S_OK; +} + +/* IDWriteTextRenderer */ +STDMETHODIMP + IGstDWriteTextRenderer::DrawGlyphRun (void *context, FLOAT origin_x, + FLOAT origin_y, DWRITE_MEASURING_MODE mode, + DWRITE_GLYPH_RUN const *glyph_run, + DWRITE_GLYPH_RUN_DESCRIPTION const *glyph_run_desc, + IUnknown * client_effect) +{ + ComPtr < ID2D1PathGeometry > geometry; + ComPtr < ID2D1GeometrySink > sink; + ComPtr < ID2D1TransformedGeometry > transformed; + ComPtr < ID2D1TransformedGeometry > shadow_transformed; + ComPtr < IGstDWriteTextEffect > effect; + ComPtr < ID2D1SolidColorBrush > brush; + ComPtr < ID2D1SolidColorBrush > outline_brush; + ComPtr < ID2D1SolidColorBrush > shadow_brush; + RenderContext *render_ctx; + ID2D1RenderTarget *target; + ID2D1Factory *factory; + HRESULT hr; + RECT client_rect; + D2D1_COLOR_F fg_color = D2D1::ColorF (D2D1::ColorF::Black); + + g_assert (context != nullptr); + + render_ctx = (RenderContext *) context; + client_rect = render_ctx->client_rect; + target = render_ctx->target; + factory = render_ctx->factory; + + hr = factory->CreatePathGeometry (&geometry); + if (FAILED (hr)) + return hr; + + hr = geometry->Open (&sink); + if (FAILED (hr)) + return hr; + + hr = glyph_run->fontFace->GetGlyphRunOutline (glyph_run->fontEmSize, + glyph_run->glyphIndices, glyph_run->glyphAdvances, + glyph_run->glyphOffsets, glyph_run->glyphCount, glyph_run->isSideways, + glyph_run->bidiLevel % 2, sink.Get ()); + + if (FAILED (hr)) + return hr; + + sink->Close (); + + hr = factory->CreateTransformedGeometry (geometry.Get (), + D2D1::Matrix3x2F::Translation (origin_x, origin_y) * + D2D1::Matrix3x2F::Scale (render_ctx->scale), &transformed); + + if (FAILED (hr)) + return hr; + + /* Create new path geometry from the bound rect. + * Note that rect geometry cannot be used since the combined background + * geometry might not be represented as a single rectangle */ + if (render_ctx->collect_geometry) { + D2D1_RECT_F bounds; + ComPtr < ID2D1RectangleGeometry > rect_geometry; + ComPtr < ID2D1PathGeometry > path_geometry; + ComPtr < ID2D1GeometrySink > path_sink; + + hr = transformed->GetBounds (nullptr, &bounds); + if (FAILED (hr)) + return hr; + + bounds.left += render_ctx->background_padding.left; + bounds.right += render_ctx->background_padding.right; + bounds.top += render_ctx->background_padding.top; + bounds.bottom += render_ctx->background_padding.bottom; + + bounds.left = MAX (bounds.left, (FLOAT) client_rect.left); + bounds.right = MIN (bounds.right, (FLOAT) client_rect.right); + bounds.top = MAX (bounds.top, (FLOAT) client_rect.top); + bounds.bottom = MIN (bounds.bottom, (FLOAT) client_rect.bottom); + + hr = factory->CreateRectangleGeometry (bounds, &rect_geometry); + if (FAILED (hr)) + return hr; + + hr = factory->CreatePathGeometry (&path_geometry); + if (FAILED (hr)) + return hr; + + hr = path_geometry->Open (&path_sink); + if (FAILED (hr)) + return hr; + + hr = rect_geometry->Outline (nullptr, path_sink.Get ()); + if (FAILED (hr)) + return hr; + + path_sink->Close (); + render_ctx->backgrounds.push_back (path_geometry); + + return S_OK; + } + + if (client_effect) + client_effect->QueryInterface (IID_IGstDWriteTextEffect, &effect); + + if (effect) { + D2D1_COLOR_F color; + BOOL enabled; + + effect->GetBrushColor (GST_DWRITE_BRUSH_TEXT, &color, &enabled); + if (enabled) { + target->CreateSolidColorBrush (color, &brush); + fg_color = color; + } + + effect->GetBrushColor (GST_DWRITE_BRUSH_TEXT_OUTLINE, &color, &enabled); + if (enabled) + target->CreateSolidColorBrush (color, &outline_brush); + + effect->GetBrushColor (GST_DWRITE_BRUSH_SHADOW, &color, &enabled); + if (enabled) + target->CreateSolidColorBrush (color, &shadow_brush); + } else { + target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), &brush); + outline_brush = brush; + } + +#ifdef HAVE_DWRITE_COLOR_FONT + if (render_ctx->enable_color_font) { + const DWRITE_GLYPH_IMAGE_FORMATS supported_formats = + DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE | + DWRITE_GLYPH_IMAGE_FORMATS_CFF | + DWRITE_GLYPH_IMAGE_FORMATS_COLR | + DWRITE_GLYPH_IMAGE_FORMATS_SVG | + DWRITE_GLYPH_IMAGE_FORMATS_PNG | + DWRITE_GLYPH_IMAGE_FORMATS_JPEG | + DWRITE_GLYPH_IMAGE_FORMATS_TIFF | + DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8; + + ComPtr < IDWriteColorGlyphRunEnumerator1 > glyph_run_enum; + ComPtr < IDWriteFactory4 > factory4; + ComPtr < ID2D1DeviceContext4 > ctx4; + hr = factory_->QueryInterface (IID_PPV_ARGS (&factory4)); + if (SUCCEEDED (hr)) + hr = target->QueryInterface (IID_PPV_ARGS (&ctx4)); + + if (SUCCEEDED (hr)) { + hr = factory4->TranslateColorGlyphRun (D2D1::Point2 (origin_x, origin_y), + glyph_run, glyph_run_desc, supported_formats, + DWRITE_MEASURING_MODE_NATURAL, nullptr, 0, &glyph_run_enum); + if (hr != DWRITE_E_NOCOLOR && SUCCEEDED (hr)) { + ComPtr < ID2D1SolidColorBrush > tmp_brush; + BOOL has_run = FALSE; + do { + DWRITE_COLOR_GLYPH_RUN1 const *color_run; + hr = glyph_run_enum->MoveNext (&has_run); + + if (FAILED (hr)) + return hr; + + if (!has_run) + return S_OK; + + hr = glyph_run_enum->GetCurrentRun (&color_run); + if (FAILED (hr)) + return hr; + + const auto cur_origin = D2D1::Point2F (color_run->baselineOriginX, + color_run->baselineOriginY); + switch (color_run->glyphImageFormat) { + case DWRITE_GLYPH_IMAGE_FORMATS_PNG: + case DWRITE_GLYPH_IMAGE_FORMATS_JPEG: + case DWRITE_GLYPH_IMAGE_FORMATS_TIFF: + case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8: + ctx4->DrawColorBitmapGlyphRun (color_run->glyphImageFormat, + cur_origin, &color_run->glyphRun, + DWRITE_MEASURING_MODE_NATURAL); + break; + case DWRITE_GLYPH_IMAGE_FORMATS_SVG: + { + ComPtr < ID2D1SolidColorBrush > svg_brush; + if (brush) + svg_brush = brush; + else if (outline_brush) + svg_brush = outline_brush; + + ctx4->DrawSvgGlyphRun (cur_origin, &color_run->glyphRun, + svg_brush.Get ()); + break; + } + case DWRITE_GLYPH_IMAGE_FORMATS_TRUETYPE: + case DWRITE_GLYPH_IMAGE_FORMATS_CFF: + case DWRITE_GLYPH_IMAGE_FORMATS_COLR: + default: + if (!tmp_brush) { + hr = target->CreateSolidColorBrush (fg_color, &tmp_brush); + if (FAILED (hr)) + return hr; + } + + if (color_run->paletteIndex == 0xffff) + tmp_brush->SetColor (fg_color); + else + tmp_brush->SetColor (color_run->runColor); + + target->DrawGlyphRun (cur_origin, &color_run->glyphRun, + tmp_brush.Get ()); + break; + } + + } while (has_run && SUCCEEDED (hr)); + + return S_OK; + } + } + } +#endif /* HAVE_DWRITE_COLOR_FONT */ + + if (shadow_brush) { + /* TODO: do we want to make this shadow configurable ? */ + hr = factory->CreateTransformedGeometry (geometry.Get (), + D2D1::Matrix3x2F::Translation (origin_x + 1.5, origin_y + 1.5) * + D2D1::Matrix3x2F::Scale (render_ctx->scale), &shadow_transformed); + + if (FAILED (hr)) + return hr; + } + + if (shadow_brush) + target->FillGeometry (shadow_transformed.Get (), shadow_brush.Get ()); + + if (outline_brush) + target->DrawGeometry (transformed.Get (), outline_brush.Get ()); + + if (brush) + target->FillGeometry (transformed.Get (), brush.Get ()); + + return S_OK; +} + +STDMETHODIMP + IGstDWriteTextRenderer::DrawUnderline (void *context, FLOAT origin_x, + FLOAT origin_y, DWRITE_UNDERLINE const *underline, IUnknown * client_effect) +{ + ComPtr < ID2D1RectangleGeometry > geometry; + ComPtr < ID2D1TransformedGeometry > transformed; + ComPtr < IGstDWriteTextEffect > effect; + ComPtr < ID2D1SolidColorBrush > brush; + ComPtr < ID2D1SolidColorBrush > outline_brush; + RenderContext *render_ctx; + ID2D1RenderTarget *target; + ID2D1Factory *factory; + HRESULT hr; + + g_assert (context != nullptr); + + render_ctx = (RenderContext *) context; + if (render_ctx->collect_geometry) + return S_OK; + + target = render_ctx->target; + factory = render_ctx->factory; + + hr = factory->CreateRectangleGeometry (D2D1::RectF (0, underline->offset, + underline->width, underline->offset + underline->thickness), + &geometry); + if (FAILED (hr)) { + GST_WARNING ("Couldn't create geometry, 0x%x", (guint) hr); + return hr; + } + + hr = factory->CreateTransformedGeometry (geometry.Get (), + D2D1::Matrix3x2F::Translation (origin_x, origin_y) * + D2D1::Matrix3x2F::Scale (render_ctx->scale), &transformed); + if (FAILED (hr)) { + GST_WARNING ("Couldn't create transformed geometry, 0x%x", (guint) hr); + return hr; + } + + if (client_effect) + client_effect->QueryInterface (IID_IGstDWriteTextEffect, &effect); + + if (effect) { + D2D1_COLOR_F color; + BOOL enabled; + + effect->GetBrushColor (GST_DWRITE_BRUSH_UNDERLINE, &color, &enabled); + if (enabled) + target->CreateSolidColorBrush (color, &brush); + + effect->GetBrushColor (GST_DWRITE_BRUSH_UNDERLINE_OUTLINE, &color, + &enabled); + if (enabled) + target->CreateSolidColorBrush (color, &outline_brush); + } else { + target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), &brush); + outline_brush = brush; + } + + if (outline_brush) + target->DrawGeometry (transformed.Get (), outline_brush.Get ()); + + if (brush) + target->FillGeometry (transformed.Get (), brush.Get ()); + + return S_OK; +} + +STDMETHODIMP + IGstDWriteTextRenderer::DrawStrikethrough (void *context, FLOAT origin_x, + FLOAT origin_y, DWRITE_STRIKETHROUGH const *strikethrough, + IUnknown * client_effect) +{ + ComPtr < ID2D1RectangleGeometry > geometry; + ComPtr < ID2D1TransformedGeometry > transformed; + ComPtr < IGstDWriteTextEffect > effect; + ComPtr < ID2D1SolidColorBrush > brush; + ComPtr < ID2D1SolidColorBrush > outline_brush; + RenderContext *render_ctx; + ID2D1RenderTarget *target; + ID2D1Factory *factory; + HRESULT hr; + + g_assert (context != nullptr); + + render_ctx = (RenderContext *) context; + if (render_ctx->collect_geometry) + return S_OK; + + target = render_ctx->target; + factory = render_ctx->factory; + + hr = factory->CreateRectangleGeometry (D2D1::RectF (0, + strikethrough->offset, strikethrough->width, + strikethrough->offset + strikethrough->thickness), &geometry); + + if (FAILED (hr)) { + GST_WARNING ("Couldn't create geometry, 0x%x", (guint) hr); + return hr; + } + + hr = factory->CreateTransformedGeometry (geometry.Get (), + D2D1::Matrix3x2F::Translation (origin_x, origin_y) * + D2D1::Matrix3x2F::Scale (render_ctx->scale), &transformed); + + if (FAILED (hr)) { + GST_WARNING ("Couldn't create transformed geometry, 0x%x", (guint) hr); + return hr; + } + + if (client_effect) + client_effect->QueryInterface (IID_IGstDWriteTextEffect, &effect); + + if (effect) { + D2D1_COLOR_F color; + BOOL enabled; + + effect->GetBrushColor (GST_DWRITE_BRUSH_STRIKETHROUGH, &color, &enabled); + if (enabled) + target->CreateSolidColorBrush (color, &brush); + + effect->GetBrushColor (GST_DWRITE_BRUSH_STRIKETHROUGH_OUTLINE, &color, + &enabled); + if (enabled) + target->CreateSolidColorBrush (color, &outline_brush); + } else { + target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), &brush); + outline_brush = brush; + } + + if (outline_brush) + target->DrawGeometry (transformed.Get (), outline_brush.Get ()); + + if (brush) + target->FillGeometry (transformed.Get (), brush.Get ()); + + return S_OK; +} + +STDMETHODIMP + IGstDWriteTextRenderer::DrawInlineObject (void *context, FLOAT origin_x, + FLOAT origin_y, IDWriteInlineObject * inline_object, BOOL is_sideways, + BOOL is_right_to_left, IUnknown * client_effect) +{ + GST_WARNING ("Not implemented"); + return E_NOTIMPL; +} + +IGstDWriteTextRenderer::IGstDWriteTextRenderer (void) +{ +} + +IGstDWriteTextRenderer::~IGstDWriteTextRenderer (void) +{ + factory_->Release (); +} + +STDMETHODIMP + IGstDWriteTextRenderer::Draw (const D2D1_POINT_2F & origin, + const D2D1_SIZE_F & scale, const RECT & client_rect, + const D2D1_COLOR_F & background_color, + const D2D1_RECT_F & background_padding, + gboolean enable_color_font, + IDWriteTextLayout * layout, ID2D1RenderTarget * target) +{ + HRESULT hr; + RenderContext context; + ComPtr < ID2D1Factory > d2d_factory; + + g_return_val_if_fail (layout != nullptr, E_INVALIDARG); + g_return_val_if_fail (target != nullptr, E_INVALIDARG); + + target->GetFactory (&d2d_factory); + context.client_rect = client_rect; + context.target = target; + context.factory = d2d_factory.Get (); + context.scale = scale; + context.enable_color_font = enable_color_font; + + if (IGstDWriteTextEffect::IsEnabledColor (background_color)) { + ComPtr < ID2D1Geometry > geometry; + + context.collect_geometry = TRUE; + context.background_padding = background_padding; + + hr = layout->Draw (&context, this, origin.x, origin.y); + if (FAILED (hr)) { + GST_WARNING ("Couldn't draw layout for collecting geometry, 0x%x", + (guint) hr); + return hr; + } + + CombineGeometries (d2d_factory.Get (), context.backgrounds, &geometry); + if (geometry) { + ComPtr < ID2D1SolidColorBrush > brush; + hr = target->CreateSolidColorBrush (background_color, &brush); + if (FAILED (hr)) { + GST_WARNING ("Couldn't create solid brush, 0x%x", (guint) hr); + return hr; + } + + target->FillGeometry (geometry.Get (), brush.Get ()); + } + } + + context.collect_geometry = FALSE; + + hr = layout->Draw (&context, this, origin.x, origin.y); + + if (FAILED (hr)) { + GST_WARNING ("Draw failed with 0x%x", (guint) hr); + } + + return hr; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.h new file mode 100644 index 0000000000..caaa58e38c --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.h @@ -0,0 +1,92 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstdwrite-enums.h" +#include "gstdwrite-effect.h" + +DEFINE_GUID (IID_IGstDWriteTextRenderer, 0xb969dc25, 0xf7d2, + 0x4cf8, 0x92, 0x0f, 0x5a, 0x27, 0xd1, 0x6d, 0x03, 0x6a); + +class IGstDWriteTextRenderer : public IDWriteTextRenderer +{ +public: + static STDMETHODIMP CreateInstance (IDWriteFactory * factory, + IGstDWriteTextRenderer ** renderer); + + /* IUnknown */ + STDMETHODIMP_ (ULONG) AddRef (void); + STDMETHODIMP_ (ULONG) Release (void); + STDMETHODIMP QueryInterface (REFIID riid, + void ** object); + + /* IDWritePixelSnapping */ + STDMETHODIMP IsPixelSnappingDisabled (void * context, + BOOL * is_disabled); + STDMETHODIMP GetCurrentTransform (void * context, + DWRITE_MATRIX * transform); + STDMETHODIMP GetPixelsPerDip (void * context, + FLOAT * pixels_per_dip); + + /* IDWriteTextRenderer */ + STDMETHODIMP DrawGlyphRun (void * context, + FLOAT origin_x, + FLOAT origin_y, + DWRITE_MEASURING_MODE mode, + DWRITE_GLYPH_RUN const *glyph_run, + DWRITE_GLYPH_RUN_DESCRIPTION const *glyph_run_desc, + IUnknown * client_effect); + STDMETHODIMP DrawUnderline (void * context, + FLOAT origin_x, + FLOAT origin_y, + DWRITE_UNDERLINE const * underline, + IUnknown * client_effect); + STDMETHODIMP DrawStrikethrough (void * context, + FLOAT origin_x, + FLOAT origin_y, + DWRITE_STRIKETHROUGH const * strikethrough, + IUnknown * client_effect); + STDMETHODIMP DrawInlineObject (void * context, + FLOAT origin_x, + FLOAT origin_y, + IDWriteInlineObject * inline_object, + BOOL is_sideways, + BOOL is_right_to_left, + IUnknown * client_effect); + + /* Entry point for drawing text */ + STDMETHODIMP Draw (const D2D1_POINT_2F & origin, + const D2D1_SIZE_F & scale, + const RECT & client_rect, + const D2D1_COLOR_F & background_color, + const D2D1_RECT_F & background_padding, + gboolean enable_color_font, + IDWriteTextLayout * layout, + ID2D1RenderTarget * target); + +private: + IGstDWriteTextRenderer (void); + virtual ~IGstDWriteTextRenderer (void); + +private: + IDWriteFactory *factory_; + ULONG ref_count_ = 1; +}; diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.cpp new file mode 100644 index 0000000000..8e90cd5152 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.cpp @@ -0,0 +1,62 @@ +/* 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 "config.h" +#endif + +#include +#include "gstdwrite-utils.h" + +gboolean +gst_dwrite_is_windows_10_or_greater (void) +{ + static gboolean ret = FALSE; + + GST_DWRITE_CALL_ONCE_BEGIN { +#if (!GST_D3D11_WINAPI_ONLY_APP) + OSVERSIONINFOEXW osverinfo; + typedef NTSTATUS (WINAPI fRtlGetVersion) (PRTL_OSVERSIONINFOEXW); + fRtlGetVersion *RtlGetVersion = NULL; + HMODULE hmodule = NULL; + + memset (&osverinfo, 0, sizeof (OSVERSIONINFOEXW)); + osverinfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEXW); + + hmodule = LoadLibraryW (L"ntdll.dll"); + if (!hmodule) + return; + + RtlGetVersion = + (fRtlGetVersion *) GetProcAddress (hmodule, "RtlGetVersion"); + if (RtlGetVersion) { + RtlGetVersion (&osverinfo); + if (osverinfo.dwMajorVersion > 10 || osverinfo.dwMajorVersion == 10) + ret = TRUE; + } + + if (hmodule) + FreeLibrary (hmodule); +#else + ret = TRUE; +#endif + } GST_DWRITE_CALL_ONCE_END; + + return ret; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.h new file mode 100644 index 0000000000..7637f83220 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-utils.h @@ -0,0 +1,79 @@ +/* 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. + */ + +#pragma once + +#include + +#define GST_DWRITE_TEXT_META_NAME "GstDWriteTextMeta" + +#define GST_DWRITE_CAPS \ + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY "," \ + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, \ + GST_D3D11_ALL_FORMATS) "; " \ + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY, \ + GST_D3D11_ALL_FORMATS) "; " \ + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY "," \ + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, GST_VIDEO_FORMATS_ALL) ";" \ + GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL) + +G_BEGIN_DECLS + +gboolean gst_dwrite_is_windows_10_or_greater (void); + +G_END_DECLS + +#ifdef __cplusplus +#include +#include + +static inline std::wstring +gst_dwrite_string_to_wstring (const std::string & str) +{ + auto tmp = g_utf8_to_utf16 (str.c_str (), -1, nullptr, nullptr, nullptr); + if (!tmp) + return std::wstring (); + + std::wstring ret = (wchar_t *) tmp; + g_free (tmp); + + return ret; +} + +static inline std::string +gst_dwrite_wstring_to_string (const std::wstring & str) +{ + auto tmp = g_utf16_to_utf8 ((const gunichar2 *) str.c_str (), + -1, nullptr, nullptr, nullptr); + if (!tmp) + return std::string (); + + std::string ret = tmp; + g_free (tmp); + + return ret; +} + +#define GST_DWRITE_CALL_ONCE_BEGIN \ + static std::once_flag __once_flag; \ + std::call_once (__once_flag, [&]() + +#define GST_DWRITE_CALL_ONCE_END ) + +#endif /* __cplusplus */ diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.cpp new file mode 100644 index 0000000000..217e372a6c --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.cpp @@ -0,0 +1,2245 @@ +/* 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 "gstdwritebitmappool.h" +#include "gstdwrite-renderer.h" +#include "gstdwrite-effect.h" +#include +#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 (GST_DWRITE_CAPS) + ); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_DWRITE_CAPS) + ); + +enum +{ + PROP_0, + PROP_VISIBLE, + PROP_FONT_FAMILY, + PROP_FONT_SIZE, + PROP_REFERENCE_FRAME_SIZE, + PROP_AUTO_RESIZE, + PROP_FONT_WEIGHT, + PROP_FONT_STYLE, + PROP_FONT_STRETCH, + PROP_TEXT, + PROP_COLOR, + PROP_OUTLINE_COLOR, + PROP_UNDERLINE_COLOR, + PROP_UNDERLINE_OUTLINE_COLOR, + PROP_STRIKETHROUGH_COLOR, + PROP_STRIKETHROUGH_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, +}; + +enum class GstDWriteBaseOverlayBlendMode +{ + UNKNOWN, + + /* attach meta with d3d11 texture buffer. */ + ATTACH_TEXTURE, + + /* attach meta with bitmap buffer */ + ATTACH_BITMAP, + + /* software blending */ + SW_BLEND, + + /* 1) renders text on BGRA + * 2) blends */ + BLEND, + + /* 1) convert texture to BGRA + * 2) render text on another BGRA texture + * 3) blends two textures + * 3) converts back to original format */ + CONVERT, + + /* 1) converts texture to RGBA64_LE + * 2) renders text on BGRA texture + * 3) blends two textures + * 3) converts back original format */ + CONVERT_64, +}; + +#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_COLOR 0xffffffff +#define DEFAULT_OUTLINE_COLOR 0xff000000 +#define DEFAULT_UNDERLINE_COLOR 0x0 +#define DEFAULT_UNDERLINE_OUTLINE_COLOR 0x0 +#define DEFAULT_STRIKETHROUGH_COLOR 0x0 +#define DEFAULT_STRIKETHROUGH_OUTLINE_COLOR 0x0 +#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 +{ + GstPad *video_pad = nullptr; + GstPad *text_pad = nullptr; + GstPad *src_pad = nullptr; + + GstD3D11Device *device = nullptr; + + GstVideoInfo bgra_info; + GstSegment text_segment; + + std::mutex prop_lock; + std::condition_variable cond; + std::recursive_mutex context_lock; + + ComPtr dwrite_factory; + ComPtr font_collection; + ComPtr text_format; + ComPtr layout; + + ComPtr d2d_factory; + + ComPtr renderer; + + GstD3D11Converter *pre_conv = nullptr; + GstD3D11Converter *blend_conv = nullptr; + GstD3D11Converter *post_conv = nullptr; + + GstDWriteBaseOverlayBlendMode blend_mode = + GstDWriteBaseOverlayBlendMode::UNKNOWN; + gboolean attach_meta = FALSE; + gboolean is_d3d11 = FALSE; + + GstBufferPool *bitmap_pool = nullptr; + GstBufferPool *text_pool = nullptr; + GstBufferPool *blend_pool = nullptr; + GstBuffer *text_buf = nullptr; + GstVideoOverlayRectangle *overlay_rect = nullptr; + D2D_POINT_2F layout_origin; + D2D_POINT_2F layout_size; + D2D1_RECT_F background_padding; + + std::wstring prev_text; + std::wstring cur_text; + + /* properties */ + gboolean visible = DEFAULT_VISIBLE; + std::string font_family = DEFAULT_FONT_FAMILY; + gfloat font_size = DEFAULT_FONT_SIZE; + guint reference_frame_size = DEFAULT_REFERENCE_FRAME_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 text_color = DEFAULT_COLOR; + guint outline_color = DEFAULT_OUTLINE_COLOR; + guint underline_color = DEFAULT_UNDERLINE_COLOR; + guint underline_outline_color = DEFAULT_UNDERLINE_OUTLINE_COLOR; + guint strikethrough_color = DEFAULT_STRIKETHROUGH_COLOR; + guint strikethrough_outline_color = DEFAULT_STRIKETHROUGH_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; + + g_object_class_install_property (object_class, PROP_VISIBLE, + g_param_spec_boolean ("visible", "Visible", + "Whether to draw text", DEFAULT_VISIBLE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_FONT_FAMILY, + g_param_spec_string ("font-family", "Font Family", + "Font family to use", DEFAULT_FONT_FAMILY, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_FONT_SIZE, + 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))); + g_object_class_install_property (object_class, PROP_REFERENCE_FRAME_SIZE, + g_param_spec_uint ("reference-frame-size", "Reference Frame Size", + "Reference Frame size used for \"auto-resize\"", 16, 16384, + DEFAULT_REFERENCE_FRAME_SIZE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_AUTO_RESIZE, + 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))); + g_object_class_install_property (object_class, PROP_FONT_WEIGHT, + 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))); + g_object_class_install_property (object_class, PROP_FONT_STYLE, + 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))); + g_object_class_install_property (object_class, PROP_FONT_STRETCH, + 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))); + g_object_class_install_property (object_class, PROP_TEXT, + g_param_spec_string ("text", "Text", + "Text to render", "", + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_COLOR, + g_param_spec_uint ("color", "Color", + "Text color to use (big-endian ARGB)", 0, G_MAXUINT32, DEFAULT_COLOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_OUTLINE_COLOR, + 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))); + g_object_class_install_property (object_class, PROP_UNDERLINE_COLOR, + g_param_spec_uint ("underline-color", "Underline Color", + "Underline color to use (big-endian ARGB)", 0, G_MAXUINT32, + DEFAULT_UNDERLINE_COLOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_UNDERLINE_OUTLINE_COLOR, + g_param_spec_uint ("underline-outline-color", "Underline Outline Color", + "Outline of underline color to use (big-endian ARGB)", 0, G_MAXUINT32, + DEFAULT_UNDERLINE_OUTLINE_COLOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_STRIKETHROUGH_COLOR, + g_param_spec_uint ("strikethrough-color", "Strikethrough Color", + "Strikethrough color to use (big-endian ARGB)", 0, G_MAXUINT32, + DEFAULT_STRIKETHROUGH_COLOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, + PROP_STRIKETHROUGH_OUTLINE_COLOR, + g_param_spec_uint ("strikethrough-outline-color", + "Strikethrough Outline Color", + "Outline of strikethrough color to use (big-endian ARGB)", + 0, G_MAXUINT32, DEFAULT_STRIKETHROUGH_OUTLINE_COLOR, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_SHADOW_COLOR, + 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))); + g_object_class_install_property (object_class, PROP_BACKGROUND_COLOR, + 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))); + g_object_class_install_property (object_class, PROP_LAYOUT_X, + 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))); + g_object_class_install_property (object_class, PROP_LAYOUT_Y, + 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))); + g_object_class_install_property (object_class, PROP_LAYOUT_WIDTH, + 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))); + g_object_class_install_property (object_class, PROP_LAYOUT_HEIGHT, + 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))); + g_object_class_install_property (object_class, PROP_TEXT_ALIGNMENT, + 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))); + g_object_class_install_property (object_class, PROP_PARAGRAPH_ALIGNMENT, + 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 ()) { + g_object_class_install_property (object_class, PROP_VISIBLE, + 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 + + 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; + gst_clear_buffer (&priv->text_buf); + g_clear_pointer (&priv->overlay_rect, gst_video_overlay_rectangle_unref); +} + +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_REFERENCE_FRAME_SIZE: + { + guint size = g_value_get_uint (value); + if (size != priv->reference_frame_size) { + priv->reference_frame_size = 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->text_color, value); + break; + case PROP_OUTLINE_COLOR: + update_uint (self, &priv->outline_color, value); + break; + case PROP_UNDERLINE_COLOR: + update_uint (self, &priv->underline_color, value); + break; + case PROP_UNDERLINE_OUTLINE_COLOR: + update_uint (self, &priv->underline_outline_color, value); + break; + case PROP_STRIKETHROUGH_COLOR: + update_uint (self, &priv->strikethrough_color, value); + break; + case PROP_STRIKETHROUGH_OUTLINE_COLOR: + update_uint (self, &priv->strikethrough_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; + 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_REFERENCE_FRAME_SIZE: + g_value_set_uint (value, priv->reference_frame_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->text_color); + break; + case PROP_OUTLINE_COLOR: + g_value_set_uint (value, priv->outline_color); + break; + case PROP_UNDERLINE_COLOR: + g_value_set_uint (value, priv->underline_color); + break; + case PROP_UNDERLINE_OUTLINE_COLOR: + g_value_set_uint (value, priv->underline_outline_color); + break; + case PROP_STRIKETHROUGH_COLOR: + g_value_set_uint (value, priv->strikethrough_color); + break; + case PROP_STRIKETHROUGH_OUTLINE_COLOR: + g_value_set_uint (value, priv->strikethrough_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; + 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; + + priv->context_lock.lock (); + gst_d3d11_handle_set_context (elem, context, -1, &priv->device); + priv->context_lock.unlock (); + + 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"); + goto error; + } + + hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED, + IID_PPV_ARGS (&priv->d2d_factory)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create d2d factory"); + goto error; + } + + hr = IGstDWriteTextRenderer::CreateInstance (priv->dwrite_factory.Get (), + &priv->renderer); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create renderer"); + goto error; + } + + gst_video_info_init (&self->info); + priv->blend_mode = GstDWriteBaseOverlayBlendMode::UNKNOWN; + + return TRUE; + +error: + priv->renderer = nullptr; + priv->dwrite_factory = nullptr; + priv->d2d_factory = nullptr; + + return FALSE; +} + +static void +gst_dwrite_base_overlay_clear_resource (GstDWriteBaseOverlay * self) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + + gst_dwrite_base_overlay_clear_layout (self); + + if (priv->text_pool) { + gst_buffer_pool_set_active (priv->text_pool, FALSE); + gst_clear_object (&priv->text_pool); + } + + if (priv->blend_pool) { + gst_buffer_pool_set_active (priv->blend_pool, FALSE); + gst_clear_object (&priv->blend_pool); + } + + if (priv->bitmap_pool) { + gst_buffer_pool_set_active (priv->bitmap_pool, FALSE); + gst_clear_object (&priv->bitmap_pool); + } + + gst_clear_object (&priv->pre_conv); + gst_clear_object (&priv->blend_conv); + gst_clear_object (&priv->post_conv); +} + +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); + + priv->renderer = nullptr; + priv->dwrite_factory = nullptr; + priv->d2d_factory = nullptr; + gst_clear_object (&priv->device); + + 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; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + { + std::lock_guard < std::recursive_mutex > lk (priv->context_lock); + if (gst_d3d11_handle_context_query (GST_ELEMENT (self), + query, priv->device)) { + return TRUE; + } + break; + } + default: + break; + } + + 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; + guint min, max, size; + gboolean update_pool; + gboolean is_d3d11 = FALSE; + GstCaps *caps = nullptr; + GstVideoInfo info; + GstBufferPool *pool = nullptr; + GstCapsFeatures *features; + GstStructure *config; + + GST_DEBUG_OBJECT (self, "Decide allocation"); + + gst_query_parse_allocation (query, &caps, nullptr); + if (!caps) { + GST_WARNING_OBJECT (self, "Query without caps"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_ERROR_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps); + return FALSE; + } + + features = gst_caps_get_features (caps, 0); + if (features && gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) { + is_d3d11 = TRUE; + } + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + update_pool = TRUE; + } else { + min = max = 0; + size = info.size; + } + + if (pool && is_d3d11) { + std::lock_guard < std::recursive_mutex > lk (priv->context_lock); + if (!gst_d3d11_ensure_element_data (GST_ELEMENT_CAST (self), -1, + &priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create deice"); + return FALSE; + } + + if (!GST_IS_D3D11_BUFFER_POOL (pool)) { + gst_clear_object (&pool); + } else { + GstD3D11BufferPool *dpool = GST_D3D11_BUFFER_POOL (pool); + if (dpool->device != priv->device) + gst_clear_object (&pool); + } + } + + if (!pool) { + if (is_d3d11) + pool = gst_d3d11_buffer_pool_new (priv->device); + else + pool = gst_video_buffer_pool_new (); + } + + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_set_params (config, caps, size, min, max); + + if (is_d3d11) { + GstD3D11AllocationParams *params; + guint bind_flags = 0; + GstD3D11Format d3d11_format; + + gst_d3d11_device_get_format (priv->device, GST_VIDEO_INFO_FORMAT (&info), + &d3d11_format); + if ((d3d11_format.format_support[0] & + D3D11_FORMAT_SUPPORT_SHADER_SAMPLE) != 0) { + bind_flags |= D3D11_BIND_SHADER_RESOURCE; + } + + if ((d3d11_format.format_support[0] & + D3D11_FORMAT_SUPPORT_RENDER_TARGET) != 0) { + bind_flags |= D3D11_BIND_RENDER_TARGET; + } + + params = gst_buffer_pool_config_get_d3d11_allocation_params (config); + if (!params) { + params = gst_d3d11_allocation_params_new (priv->device, &info, + GST_D3D11_ALLOCATION_FLAG_DEFAULT, bind_flags, 0); + } else { + for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (&info); i++) + params->desc[i].BindFlags |= bind_flags; + } + + gst_buffer_pool_config_set_d3d11_allocation_params (config, params); + gst_d3d11_allocation_params_free (params); + } + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set config"); + gst_object_unref (pool); + return FALSE; + } + + /* Get updated size in case of d3d11 */ + if (is_d3d11) { + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, + nullptr); + gst_structure_free (config); + } + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + gst_object_unref (pool); + + 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; + GstCaps *caps = nullptr; + GstVideoInfo info; + GstCapsFeatures *features; + gboolean is_d3d11 = FALSE; + gboolean has_meta = FALSE; + + GST_DEBUG_OBJECT (self, "Propose allocation"); + + if (!GST_BASE_TRANSFORM_CLASS (parent_class)->propose_allocation (trans, + decide_query, query)) + return FALSE; + + if (!decide_query) + return TRUE; + + gst_query_parse_allocation (query, &caps, nullptr); + + if (!caps) { + GST_WARNING_OBJECT (self, "Query without caps"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_ERROR_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, caps); + return FALSE; + } + + features = gst_caps_get_features (caps, 0); + if (features) { + if (gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) { + is_d3d11 = TRUE; + } + + if (gst_caps_features_contains (features, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) { + has_meta = TRUE; + } + } + + if (gst_query_get_n_allocation_pools (query) == 0) { + GstBufferPool *pool; + GstStructure *config; + guint size; + + if (is_d3d11) { + std::lock_guard < std::recursive_mutex > lk (priv->context_lock); + if (!gst_d3d11_ensure_element_data (GST_ELEMENT_CAST (self), -1, + &priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create deice"); + return FALSE; + } + + pool = gst_d3d11_buffer_pool_new (priv->device); + } else { + pool = gst_video_buffer_pool_new (); + } + + GST_DEBUG_OBJECT (self, "Creating new pool with caps %" GST_PTR_FORMAT, + caps); + + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + if (!is_d3d11) { + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + } + + size = info.size; + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + + if (is_d3d11) { + GstD3D11AllocationParams *params; + guint bind_flags = 0; + GstD3D11Format d3d11_format; + + gst_d3d11_device_get_format (priv->device, GST_VIDEO_INFO_FORMAT (&info), + &d3d11_format); + if ((d3d11_format.format_support[0] & + D3D11_FORMAT_SUPPORT_SHADER_SAMPLE) != 0) { + bind_flags |= D3D11_BIND_SHADER_RESOURCE; + } + + if ((d3d11_format.format_support[0] & + D3D11_FORMAT_SUPPORT_RENDER_TARGET) != 0) { + bind_flags |= D3D11_BIND_RENDER_TARGET; + } + + params = gst_buffer_pool_config_get_d3d11_allocation_params (config); + if (!params) { + params = gst_d3d11_allocation_params_new (priv->device, &info, + GST_D3D11_ALLOCATION_FLAG_DEFAULT, bind_flags, 0); + } else { + for (guint i = 0; i < GST_VIDEO_INFO_N_PLANES (&info); i++) + params->desc[i].BindFlags |= bind_flags; + } + + gst_buffer_pool_config_set_d3d11_allocation_params (config, params); + gst_d3d11_allocation_params_free (params); + } + + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set config"); + gst_object_unref (pool); + return FALSE; + } + + /* Get updated size in case of d3d11 */ + if (is_d3d11) { + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, + nullptr); + gst_structure_free (config); + } + + gst_query_add_allocation_pool (query, pool, size, 0, 0); + gst_object_unref (pool); + } + + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr); + + if (has_meta && !gst_query_find_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, nullptr)) { + gst_query_add_allocation_meta (query, + GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, nullptr); + } + + return TRUE; +} + +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_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 +is_subsampled_yuv (const GstVideoInfo * info) +{ + if (!GST_VIDEO_INFO_IS_YUV (info)) + return FALSE; + + for (guint i = 0; i < GST_VIDEO_MAX_COMPONENTS; i++) { + if (info->finfo->w_sub[i] != 0 || info->finfo->h_sub[i] != 0) + return TRUE; + } + + return FALSE; +} + +static GstD3D11Converter * +gst_dwrite_base_overlay_create_converter (GstDWriteBaseOverlay * self, + const GstVideoInfo * in_info, const GstVideoInfo * out_info) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstStructure *config; + D3D11_FILTER filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + + if (is_subsampled_yuv (in_info) || is_subsampled_yuv (out_info)) + filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; + + config = gst_structure_new ("convert-config", + GST_D3D11_CONVERTER_OPT_BACKEND, GST_TYPE_D3D11_CONVERTER_BACKEND, + GST_D3D11_CONVERTER_BACKEND_SHADER, + GST_D3D11_CONVERTER_OPT_SAMPLER_FILTER, + GST_TYPE_D3D11_CONVERTER_SAMPLER_FILTER, filter, nullptr); + + return gst_d3d11_converter_new (priv->device, in_info, out_info, config); +} + +static GstBufferPool * +gst_dwrite_base_overlay_create_d3d11_pool (GstDWriteBaseOverlay * self, + const GstVideoInfo * info) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstCaps *caps = nullptr; + GstStructure *config; + GstD3D11AllocationParams *params; + GstBufferPool *pool = nullptr; + + caps = gst_video_info_to_caps (info); + if (!caps) { + GST_ERROR_OBJECT (self, "Couldn't create caps"); + return nullptr; + } + + pool = gst_d3d11_buffer_pool_new (priv->device); + config = gst_buffer_pool_get_config (pool); + + params = gst_d3d11_allocation_params_new (priv->device, info, + GST_D3D11_ALLOCATION_FLAG_DEFAULT, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, 0); + gst_buffer_pool_config_set_d3d11_allocation_params (config, params); + gst_d3d11_allocation_params_free (params); + gst_buffer_pool_config_set_params (config, caps, info->size, 0, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set pool config"); + goto error; + } + + if (!gst_buffer_pool_set_active (pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't activate pool"); + goto error; + } + + return pool; + +error: + gst_clear_caps (&caps); + gst_clear_object (&pool); + + return nullptr; +} + +static gboolean +gst_dwrite_base_overlay_setup_bitmap_pool (GstDWriteBaseOverlay * self, + const GstVideoInfo * info) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstStructure *config; + GstCaps *caps; + + if (priv->bitmap_pool) { + gst_buffer_pool_set_active (priv->bitmap_pool, FALSE); + gst_clear_object (&priv->bitmap_pool); + } + + caps = gst_video_info_to_caps (info); + + priv->bitmap_pool = gst_dwrite_bitmap_pool_new (); + config = gst_buffer_pool_get_config (priv->bitmap_pool); + gst_buffer_pool_config_set_params (config, caps, info->size, 0, 0); + gst_caps_unref (caps); + + if (!gst_buffer_pool_set_config (priv->bitmap_pool, config)) { + GST_ERROR_OBJECT (self, "Couldn't set config"); + gst_clear_object (&priv->bitmap_pool); + return FALSE; + } + + if (!gst_buffer_pool_set_active (priv->bitmap_pool, TRUE)) { + GST_ERROR_OBJECT (self, "Couldn't set active"); + gst_clear_object (&priv->bitmap_pool); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_dwrite_base_overlay_prepare_resource (GstDWriteBaseOverlay * self) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + + gst_dwrite_base_overlay_clear_resource (self); + + switch (priv->blend_mode) { + case GstDWriteBaseOverlayBlendMode::ATTACH_TEXTURE: + priv->text_pool = gst_dwrite_base_overlay_create_d3d11_pool (self, + &priv->bgra_info); + if (!priv->text_pool) + goto error; + break; + case GstDWriteBaseOverlayBlendMode::ATTACH_BITMAP: + case GstDWriteBaseOverlayBlendMode::SW_BLEND: + if (!gst_dwrite_base_overlay_setup_bitmap_pool (self, &priv->bgra_info)) + goto error; + break; + case GstDWriteBaseOverlayBlendMode::BLEND: + priv->text_pool = gst_dwrite_base_overlay_create_d3d11_pool (self, + &priv->bgra_info); + if (!priv->text_pool) + goto error; + + priv->blend_conv = gst_dwrite_base_overlay_create_converter (self, + &priv->bgra_info, &self->info); + if (!priv->blend_conv) { + GST_ERROR_OBJECT (self, "Couldn't create blend converter"); + goto error; + } + break; + case GstDWriteBaseOverlayBlendMode::CONVERT: + priv->text_pool = gst_dwrite_base_overlay_create_d3d11_pool (self, + &priv->bgra_info); + if (!priv->text_pool) + goto error; + + priv->pre_conv = gst_dwrite_base_overlay_create_converter (self, + &self->info, &priv->bgra_info); + if (!priv->pre_conv) + goto error; + + priv->blend_conv = gst_dwrite_base_overlay_create_converter (self, + &priv->bgra_info, &priv->bgra_info); + if (!priv->blend_conv) { + GST_ERROR_OBJECT (self, "Couldn't create blend converter"); + goto error; + } + + priv->post_conv = gst_dwrite_base_overlay_create_converter (self, + &priv->bgra_info, &self->info); + if (!priv->post_conv) + goto error; + break; + case GstDWriteBaseOverlayBlendMode::CONVERT_64: + { + GstVideoInfo blend_info; + + gst_video_info_set_format (&blend_info, GST_VIDEO_FORMAT_RGBA64_LE, + self->info.width, self->info.height); + + priv->blend_pool = gst_dwrite_base_overlay_create_d3d11_pool (self, + &blend_info); + if (!priv->blend_pool) + goto error; + + priv->text_pool = gst_dwrite_base_overlay_create_d3d11_pool (self, + &priv->bgra_info); + if (!priv->text_pool) + goto error; + + priv->pre_conv = gst_dwrite_base_overlay_create_converter (self, + &self->info, &blend_info); + if (!priv->pre_conv) + goto error; + + priv->blend_conv = gst_dwrite_base_overlay_create_converter (self, + &priv->bgra_info, &blend_info); + if (!priv->blend_conv) + goto error; + + priv->post_conv = gst_dwrite_base_overlay_create_converter (self, + &blend_info, &self->info); + if (!priv->post_conv) + goto error; + + break; + } + default: + g_assert_not_reached (); + return FALSE; + } + + if (priv->blend_conv) { + D3D11_BLEND_DESC desc = { 0, }; + ComPtr < ID3D11BlendState > blend; + ID3D11Device *device = gst_d3d11_device_get_device_handle (priv->device); + HRESULT hr; + + desc.AlphaToCoverageEnable = FALSE; + desc.IndependentBlendEnable = FALSE; + desc.RenderTarget[0].BlendEnable = TRUE; + desc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + desc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + desc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + desc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + desc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + hr = device->CreateBlendState (&desc, &blend); + if (!gst_d3d11_result (hr, priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create blend state"); + goto error; + } + + g_object_set (priv->blend_conv, "blend-state", blend.Get (), + "src-alpha-mode", GST_D3D11_CONVERTER_ALPHA_MODE_PREMULTIPLIED, + nullptr); + } + + return TRUE; + +error: + gst_dwrite_base_overlay_clear_resource (self); + return FALSE; +} + +static const gchar * +blend_mode_to_string (GstDWriteBaseOverlayBlendMode mode) +{ + switch (mode) { + case GstDWriteBaseOverlayBlendMode::ATTACH_TEXTURE: + return "attach-texture"; + case GstDWriteBaseOverlayBlendMode::ATTACH_BITMAP: + return "attach-bitmap"; + case GstDWriteBaseOverlayBlendMode::SW_BLEND: + return "software-blend"; + case GstDWriteBaseOverlayBlendMode::BLEND: + return "blend"; + case GstDWriteBaseOverlayBlendMode::CONVERT: + return "convert"; + case GstDWriteBaseOverlayBlendMode::CONVERT_64: + return "convert-64"; + default: + return "unknown"; + } +} + +static void +gst_dwrite_base_overlay_decide_blend_mode (GstDWriteBaseOverlay * self) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + + priv->blend_mode = GstDWriteBaseOverlayBlendMode::UNKNOWN; + + if (priv->attach_meta) { + if (priv->is_d3d11) + priv->blend_mode = GstDWriteBaseOverlayBlendMode::ATTACH_TEXTURE; + else + priv->blend_mode = GstDWriteBaseOverlayBlendMode::ATTACH_BITMAP; + return; + } + + if (!priv->is_d3d11) { + priv->blend_mode = GstDWriteBaseOverlayBlendMode::SW_BLEND; + return; + } + + /* Decide best blend mode to use based on format */ + switch (GST_VIDEO_INFO_FORMAT (&self->info)) { + case GST_VIDEO_FORMAT_BGRA: + case GST_VIDEO_FORMAT_RGBA: + case GST_VIDEO_FORMAT_BGRx: + case GST_VIDEO_FORMAT_RGBx: + case GST_VIDEO_FORMAT_VUYA: + case GST_VIDEO_FORMAT_RGBA64_LE: + case GST_VIDEO_FORMAT_RGB10A2_LE: + /* Alpha aware formats */ + priv->blend_mode = GstDWriteBaseOverlayBlendMode::BLEND; + break; + case GST_VIDEO_FORMAT_NV12: + case GST_VIDEO_FORMAT_NV21: + case GST_VIDEO_FORMAT_I420: + case GST_VIDEO_FORMAT_YV12: + case GST_VIDEO_FORMAT_Y42B: + case GST_VIDEO_FORMAT_Y444: + case GST_VIDEO_FORMAT_GRAY8: + case GST_VIDEO_FORMAT_AYUV: + case GST_VIDEO_FORMAT_RGBP: + case GST_VIDEO_FORMAT_BGRP: + case GST_VIDEO_FORMAT_GBR: + case GST_VIDEO_FORMAT_GBRA: + /* 8bits formats */ + priv->blend_mode = GstDWriteBaseOverlayBlendMode::CONVERT; + break; + case GST_VIDEO_FORMAT_P010_10LE: + case GST_VIDEO_FORMAT_P012_LE: + case GST_VIDEO_FORMAT_P016_LE: + case GST_VIDEO_FORMAT_I420_10LE: + case GST_VIDEO_FORMAT_I420_12LE: + case GST_VIDEO_FORMAT_I422_10LE: + case GST_VIDEO_FORMAT_I422_12LE: + case GST_VIDEO_FORMAT_Y444_10LE: + case GST_VIDEO_FORMAT_Y444_12LE: + case GST_VIDEO_FORMAT_Y444_16LE: + case GST_VIDEO_FORMAT_GRAY16_LE: + case GST_VIDEO_FORMAT_AYUV64: + case GST_VIDEO_FORMAT_GBR_10LE: + case GST_VIDEO_FORMAT_GBR_12LE: + case GST_VIDEO_FORMAT_GBRA_10LE: + case GST_VIDEO_FORMAT_GBRA_12LE: + /* high bitdept formats */ + priv->blend_mode = GstDWriteBaseOverlayBlendMode::CONVERT_64; + break; + default: + /* d3d11 blending is not supported, fallback to software blending */ + priv->blend_mode = GstDWriteBaseOverlayBlendMode::SW_BLEND; + break; + } +} + +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; + GstCapsFeatures *features; + + GST_DEBUG_OBJECT (self, "Set caps, in caps %" GST_PTR_FORMAT + ", out caps %" GST_PTR_FORMAT, incaps, outcaps); + + gst_dwrite_base_overlay_clear_resource (self); + priv->blend_mode = GstDWriteBaseOverlayBlendMode::UNKNOWN; + priv->is_d3d11 = FALSE; + priv->attach_meta = FALSE; + + if (!gst_video_info_from_caps (&self->info, incaps)) { + GST_WARNING_OBJECT (self, "Invalid caps %" GST_PTR_FORMAT, incaps); + return FALSE; + } + + gst_video_info_set_format (&priv->bgra_info, GST_VIDEO_FORMAT_BGRA, + self->info.width, self->info.height); + + features = gst_caps_get_features (incaps, 0); + + if (features && gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) { + priv->is_d3d11 = TRUE; + + std::lock_guard < std::recursive_mutex > lk (priv->context_lock); + if (!gst_d3d11_ensure_element_data (GST_ELEMENT_CAST (self), -1, + &priv->device)) { + GST_ERROR_OBJECT (self, "Couldn't create deice"); + return FALSE; + } + } + + features = gst_caps_get_features (outcaps, 0); + if (features && gst_caps_features_contains (features, + GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) { + priv->attach_meta = TRUE; + GST_DEBUG_OBJECT (self, "Downstream support overlay meta"); + } + + priv->prop_lock.lock (); + priv->text_format = nullptr; + priv->layout = nullptr; + 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; + priv->prop_lock.unlock (); + + gst_dwrite_base_overlay_decide_blend_mode (self); + + GST_DEBUG_OBJECT (self, "Decided blend mode \"%s\"", + blend_mode_to_string (priv->blend_mode)); + + if (!gst_dwrite_base_overlay_prepare_resource (self)) { + GST_ERROR_OBJECT (self, "Couldn't prepare resource"); + return 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 (!priv->is_d3d11) + return; + + GstMemory *mem = gst_buffer_peek_memory (buf, 0); + if (!gst_is_d3d11_memory (mem)) + return; + + GstD3D11Memory *dmem = GST_D3D11_MEMORY_CAST (mem); + + std::lock_guard < std::recursive_mutex > lk (priv->context_lock); + if (dmem->device == priv->device) + return; + + GST_DEBUG_OBJECT (self, "Updating device"); + gst_object_unref (priv->device); + priv->device = (GstD3D11Device *) gst_object_ref (dmem->device); + gst_dwrite_base_overlay_clear_resource (self); + + gst_base_transform_reconfigure (trans); + gst_dwrite_base_overlay_prepare_resource (self); +} + +static gboolean +gst_dwrite_base_overlay_upload_system (GstDWriteBaseOverlay * self, + GstBuffer * dst, GstBuffer * src, const GstVideoInfo * info) +{ + GstVideoFrame in_frame, out_frame; + gboolean ret; + + GST_LOG_OBJECT (self, "system copy"); + + if (!gst_video_frame_map (&in_frame, (GstVideoInfo *) info, src, + (GstMapFlags) (GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF))) { + GST_ERROR_OBJECT (self, "Couldn't map input frame"); + return FALSE; + } + + if (!gst_video_frame_map (&out_frame, (GstVideoInfo *) info, dst, + (GstMapFlags) (GST_MAP_WRITE | GST_VIDEO_FRAME_MAP_FLAG_NO_REF))) { + gst_video_frame_unmap (&in_frame); + GST_ERROR_OBJECT (self, "Couldn't map output frame"); + } + + ret = gst_video_frame_copy (&out_frame, &in_frame); + + gst_video_frame_unmap (&in_frame); + gst_video_frame_unmap (&out_frame); + + return ret; +} + +static gboolean +gst_dwrite_base_overlay_upload_d3d11 (GstDWriteBaseOverlay * self, + GstBuffer * dst, GstBuffer * src) +{ + GST_LOG_OBJECT (self, "d3d11 copy"); + + for (guint i = 0; i < gst_buffer_n_memory (dst); i++) { + GstMemory *dst_mem, *src_mem; + GstD3D11Memory *dst_dmem, *src_dmem; + GstMapInfo dst_info; + GstMapInfo src_info; + ID3D11Resource *dst_texture, *src_texture; + ID3D11DeviceContext *device_context; + GstD3D11Device *device; + D3D11_BOX src_box = { 0, }; + D3D11_TEXTURE2D_DESC dst_desc, src_desc; + guint dst_subidx, src_subidx; + + dst_mem = gst_buffer_peek_memory (dst, i); + src_mem = gst_buffer_peek_memory (src, i); + + dst_dmem = (GstD3D11Memory *) dst_mem; + src_dmem = (GstD3D11Memory *) src_mem; + + device = dst_dmem->device; + + gst_d3d11_memory_get_texture_desc (dst_dmem, &dst_desc); + gst_d3d11_memory_get_texture_desc (src_dmem, &src_desc); + + device_context = gst_d3d11_device_get_device_context_handle (device); + + if (!gst_memory_map (dst_mem, &dst_info, + (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11))) { + GST_ERROR_OBJECT (self, "Cannot map dst d3d11 memory"); + return FALSE; + } + + if (!gst_memory_map (src_mem, &src_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D11))) { + GST_ERROR_OBJECT (self, "Cannot map src d3d11 memory"); + gst_memory_unmap (dst_mem, &dst_info); + return FALSE; + } + + dst_texture = (ID3D11Resource *) dst_info.data; + src_texture = (ID3D11Resource *) src_info.data; + + /* src/dst texture size might be different if padding was used. + * select smaller size */ + src_box.left = 0; + src_box.top = 0; + src_box.front = 0; + src_box.back = 1; + src_box.right = MIN (src_desc.Width, dst_desc.Width); + src_box.bottom = MIN (src_desc.Height, dst_desc.Height); + + dst_subidx = gst_d3d11_memory_get_subresource_index (dst_dmem); + src_subidx = gst_d3d11_memory_get_subresource_index (src_dmem); + + GstD3D11DeviceLockGuard lk (device); + device_context->CopySubresourceRegion (dst_texture, dst_subidx, 0, 0, 0, + src_texture, src_subidx, &src_box); + + gst_memory_unmap (src_mem, &src_info); + gst_memory_unmap (dst_mem, &dst_info); + } + + return TRUE; +} + +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); + GstMemory *mem = gst_buffer_peek_memory (inbuf, 0); + GstFlowReturn ret; + gboolean is_d3d11 = FALSE; + gboolean upload_ret; + + 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; + + /* attaching meta or software blending can be in-place processing */ + if (priv->attach_meta || !priv->is_d3d11 || + priv->blend_mode == GstDWriteBaseOverlayBlendMode::SW_BLEND) { + goto inplace; + } + + if (gst_is_d3d11_memory (mem)) { + D3D11_TEXTURE2D_DESC desc; + GstD3D11Memory *dmem; + const guint bind_flags = (D3D11_BIND_RENDER_TARGET | + D3D11_BIND_SHADER_RESOURCE); + + is_d3d11 = TRUE; + + dmem = GST_D3D11_MEMORY_CAST (mem); + gst_d3d11_memory_get_texture_desc (dmem, &desc); + + /* Cannot write on decoder resource */ + if ((desc.BindFlags & D3D11_BIND_DECODER) == 0 && + (desc.BindFlags & bind_flags) == bind_flags) { + goto inplace; + } + } + + /* Needs to allocate new buffer */ + ret = GST_BASE_TRANSFORM_CLASS (parent_class)->prepare_output_buffer (trans, + inbuf, outbuf); + if (ret != GST_FLOW_OK) + return ret; + + if (is_d3d11) + upload_ret = gst_dwrite_base_overlay_upload_d3d11 (self, *outbuf, inbuf); + else + upload_ret = gst_dwrite_base_overlay_upload_system (self, *outbuf, inbuf, + &self->info); + + if (!upload_ret) { + gst_clear_buffer (outbuf); + return GST_FLOW_ERROR; + } + + return GST_FLOW_OK; + +inplace: + if (gst_buffer_is_writable (inbuf)) + *outbuf = inbuf; + else + *outbuf = gst_buffer_copy (inbuf); + + return GST_FLOW_OK; +} + +static gboolean +gst_dwrite_base_overlay_get_d2d_target (GstDWriteBaseOverlay * self, + ID3D11Texture2D * texture, ID2D1RenderTarget ** target) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + ComPtr < IDXGISurface > surface; + HRESULT hr; + static const D2D1_RENDER_TARGET_PROPERTIES props = { + D2D1_RENDER_TARGET_TYPE_DEFAULT, DXGI_FORMAT_UNKNOWN, + D2D1_ALPHA_MODE_PREMULTIPLIED, 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, + D2D1_FEATURE_LEVEL_DEFAULT + }; + + hr = texture->QueryInterface (IID_PPV_ARGS (&surface)); + if (!gst_d3d11_result (hr, priv->device)) + return FALSE; + + hr = priv->d2d_factory->CreateDxgiSurfaceRenderTarget (surface.Get (), props, + target); + if (!gst_d3d11_result (hr, priv->device)) + return FALSE; + + return TRUE; +} + +static void +gst_dwrite_base_overlay_attach (GstDWriteBaseOverlay * self, GstBuffer * buffer) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstVideoOverlayCompositionMeta *meta; + + meta = gst_buffer_get_video_overlay_composition_meta (buffer); + if (meta) { + if (meta->overlay) { + meta->overlay = + gst_video_overlay_composition_make_writable (meta->overlay); + gst_video_overlay_composition_add_rectangle (meta->overlay, + priv->overlay_rect); + } else { + meta->overlay = gst_video_overlay_composition_new (priv->overlay_rect); + } + } else { + GstVideoOverlayComposition *comp = + gst_video_overlay_composition_new (priv->overlay_rect); + meta = gst_buffer_add_video_overlay_composition_meta (buffer, comp); + gst_video_overlay_composition_unref (comp); + } +} + +static gboolean +gst_dwrite_base_overlay_mode_sw_blend (GstDWriteBaseOverlay * self, + GstBuffer * buffer) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstVideoFrame dst_frame, src_frame; + gboolean ret; + + if (!gst_video_frame_map (&dst_frame, &self->info, buffer, + (GstMapFlags) (GST_MAP_WRITE | GST_VIDEO_FRAME_MAP_FLAG_NO_REF))) { + GST_ERROR_OBJECT (self, "Couldn't map input buffer"); + return FALSE; + } + + if (!gst_video_frame_map (&src_frame, &priv->bgra_info, priv->text_buf, + (GstMapFlags) (GST_MAP_READ | GST_VIDEO_FRAME_MAP_FLAG_NO_REF))) { + gst_video_frame_unmap (&dst_frame); + GST_ERROR_OBJECT (self, "Couldn't map text buffer"); + return FALSE; + } + + src_frame.info.flags = (GstVideoFlags) + (src_frame.info.flags | GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA); + ret = gst_video_blend (&dst_frame, &src_frame, 0, 0, 1.0); + gst_video_frame_unmap (&src_frame); + gst_video_frame_unmap (&dst_frame); + + return ret; +} + +static gboolean +gst_d3d11_bast_text_overlay_mode_blend (GstDWriteBaseOverlay * self, + GstBuffer * buffer) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + + if (!gst_d3d11_converter_convert_buffer (priv->blend_conv, + priv->text_buf, buffer)) { + GST_ERROR_OBJECT (self, "Couldn't blend texture"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_d3d11_bast_text_overlay_mode_convert (GstDWriteBaseOverlay * self, + GstBuffer * buffer) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstBuffer *pre_buf = nullptr; + + gst_buffer_pool_acquire_buffer (priv->text_pool, &pre_buf, nullptr); + if (!pre_buf) { + GST_ERROR_OBJECT (self, "Couldn't acquire preconv buffer"); + return FALSE; + } + + gst_d3d11_device_lock (priv->device); + if (!gst_d3d11_converter_convert_buffer_unlocked (priv->pre_conv, + buffer, pre_buf)) { + GST_ERROR_OBJECT (self, "pre-convert failed"); + gst_d3d11_device_unlock (priv->device); + goto error; + } + + if (!gst_d3d11_converter_convert_buffer_unlocked (priv->blend_conv, + priv->text_buf, pre_buf)) { + GST_ERROR_OBJECT (self, "blend-convert failed"); + gst_d3d11_device_unlock (priv->device); + goto error; + } + + if (!gst_d3d11_converter_convert_buffer_unlocked (priv->post_conv, + pre_buf, buffer)) { + GST_ERROR_OBJECT (self, "post-convert failed"); + gst_d3d11_device_unlock (priv->device); + goto error; + } + + gst_d3d11_device_unlock (priv->device); + gst_buffer_unref (pre_buf); + + return TRUE; + +error: + gst_clear_buffer (&pre_buf); + return FALSE; +} + +static gboolean +gst_d3d11_bast_text_overlay_mode_convert_64 (GstDWriteBaseOverlay * self, + GstBuffer * buffer) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstBuffer *pre_buf = nullptr; + + gst_buffer_pool_acquire_buffer (priv->blend_pool, &pre_buf, nullptr); + if (!pre_buf) { + GST_ERROR_OBJECT (self, "Couldn't acquire pre-convert buffer"); + return FALSE; + } + + gst_d3d11_device_lock (priv->device); + if (!gst_d3d11_converter_convert_buffer_unlocked (priv->pre_conv, + buffer, pre_buf)) { + GST_ERROR_OBJECT (self, "pre-convert failed"); + gst_d3d11_device_unlock (priv->device); + goto error; + } + + if (!gst_d3d11_converter_convert_buffer_unlocked (priv->blend_conv, + priv->text_buf, pre_buf)) { + GST_ERROR_OBJECT (self, "Couldn't blend texture"); + gst_d3d11_device_unlock (priv->device); + goto error; + } + + if (!gst_d3d11_converter_convert_buffer_unlocked (priv->post_conv, + pre_buf, buffer)) { + GST_ERROR_OBJECT (self, "post-convert failed"); + gst_d3d11_device_unlock (priv->device); + goto error; + } + + gst_d3d11_device_unlock (priv->device); + gst_buffer_unref (pre_buf); + + return TRUE; + +error: + gst_clear_buffer (&pre_buf); + + return FALSE; +} + +static gboolean +gst_dwrite_base_overlay_update_text_format (GstDWriteBaseOverlay * self) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + FLOAT font_size; + FLOAT background_padding; + 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 / + priv->reference_frame_size; + } else { + font_size = priv->font_size; + } + + background_padding = (font_size / priv->font_size) * 5.0f; + priv->background_padding.left = priv->background_padding.top = + -background_padding; + priv->background_padding.right = priv->background_padding.bottom = + background_padding; + + 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; + + hr = priv->dwrite_factory->CreateTextLayout (text.c_str (), text.length (), + priv->text_format.Get (), self->info.width, self->info.height, + &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->text_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_TEXT, &color); + + color = unpack_argb (priv->outline_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_TEXT_OUTLINE, &color); + + color = unpack_argb (priv->underline_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_UNDERLINE, &color); + + color = unpack_argb (priv->underline_outline_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_UNDERLINE_OUTLINE, &color); + + if (priv->underline_color || priv->underline_outline_color) { + priv->layout->SetUnderline (TRUE, range); + } else { + priv->layout->SetUnderline (FALSE, range); + } + + color = unpack_argb (priv->strikethrough_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_STRIKETHROUGH, &color); + + color = unpack_argb (priv->strikethrough_outline_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_STRIKETHROUGH_OUTLINE, &color); + + if (priv->strikethrough_color || priv->strikethrough_outline_color) { + priv->layout->SetStrikethrough (TRUE, range); + } else { + priv->layout->SetStrikethrough (FALSE, range); + } + + color = unpack_argb (priv->shadow_color); + effect->SetBrushColor (GST_DWRITE_BRUSH_SHADOW, &color); + + 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 gboolean +gst_dwrite_base_overlay_render_text (GstDWriteBaseOverlay * self) +{ + GstDWriteBaseOverlayPrivate *priv = self->priv; + GstBuffer *text_buf = nullptr; + gboolean bitmap_render = FALSE; + ComPtr < ID2D1RenderTarget > target; + GstMemory *mem; + HRESULT hr; + GstMapInfo info; + D2D1_COLOR_F bg_color; + D2D1_RECT_F bg_padding = D2D1::RectF (); + + if (priv->text_buf) + return TRUE; + + if (priv->blend_mode == GstDWriteBaseOverlayBlendMode::ATTACH_BITMAP || + priv->blend_mode == GstDWriteBaseOverlayBlendMode::SW_BLEND) { + gst_buffer_pool_acquire_buffer (priv->bitmap_pool, &text_buf, nullptr); + bitmap_render = TRUE; + } else { + gst_buffer_pool_acquire_buffer (priv->text_pool, &text_buf, nullptr); + } + + if (!text_buf) { + GST_ERROR_OBJECT (self, "Couldn't get text buffer"); + return FALSE; + } + + mem = gst_buffer_peek_memory (text_buf, 0); + if (bitmap_render) { + static const D2D1_RENDER_TARGET_PROPERTIES props = { + D2D1_RENDER_TARGET_TYPE_DEFAULT, DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_PREMULTIPLIED, 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, + D2D1_FEATURE_LEVEL_DEFAULT + }; + + GstDWriteBitmapMemory *bmem = (GstDWriteBitmapMemory *) mem; + hr = priv->d2d_factory->CreateWicBitmapRenderTarget (bmem->bitmap, &props, + &target); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't create render target, hr: 0x%x", + (guint) hr); + goto error; + } + } else { + ID3D11Texture2D *texture; + + if (!gst_memory_map (mem, &info, + (GstMapFlags) (GST_MAP_WRITE | GST_MAP_D3D11))) { + GST_ERROR_OBJECT (self, "Could not map buffer"); + goto error; + } + + texture = (ID3D11Texture2D *) info.data; + if (!gst_dwrite_base_overlay_get_d2d_target (self, texture, &target)) { + gst_memory_unmap (mem, &info); + goto error; + } + + gst_d3d11_device_lock (priv->device); + } + + bg_color = unpack_argb (priv->background_color); + if (priv->background_color) + bg_padding = priv->background_padding; + + target->BeginDraw (); + target->Clear (D2D1::ColorF (D2D1::ColorF::Black, 0.0)); + priv->renderer->Draw (priv->layout_origin, D2D1::SizeF (1.0, 1.0), + D2D1::Rect (0, 0, self->info.width, self->info.height), + bg_color, bg_padding, priv->color_font, priv->layout.Get (), + target.Get ()); + target->EndDraw (); + + if (!bitmap_render) { + gst_d3d11_device_unlock (priv->device); + gst_memory_unmap (mem, &info); + } + + priv->text_buf = text_buf; + priv->overlay_rect = gst_video_overlay_rectangle_new_raw (text_buf, 0, 0, + self->info.width, self->info.height, + GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA); + + return TRUE; + +error: + gst_clear_buffer (&text_buf); + return FALSE; +} + +static GstFlowReturn +gst_dwrite_base_overlay_transform (GstBaseTransform * trans, GstBuffer * inbuf, + GstBuffer * outbuf) +{ + GstDWriteBaseOverlay *self = GST_DWRITE_BASE_OVERLAY (trans); + GstDWriteBaseOverlayPrivate *priv = self->priv; + gboolean ret = FALSE; + std::lock_guard < std::mutex > lk (priv->prop_lock); + + 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_base_overlay_render_text (self)) + return GST_FLOW_ERROR; + + GST_LOG_OBJECT (self, + "Blending mode \"%s\"", blend_mode_to_string (priv->blend_mode)); + + switch (priv->blend_mode) { + case GstDWriteBaseOverlayBlendMode::ATTACH_TEXTURE: + case GstDWriteBaseOverlayBlendMode::ATTACH_BITMAP: + gst_dwrite_base_overlay_attach (self, outbuf); + ret = TRUE; + break; + case GstDWriteBaseOverlayBlendMode::SW_BLEND: + ret = gst_dwrite_base_overlay_mode_sw_blend (self, outbuf); + break; + case GstDWriteBaseOverlayBlendMode::BLEND: + ret = gst_d3d11_bast_text_overlay_mode_blend (self, outbuf); + break; + case GstDWriteBaseOverlayBlendMode::CONVERT: + ret = gst_d3d11_bast_text_overlay_mode_convert (self, outbuf); + break; + case GstDWriteBaseOverlayBlendMode::CONVERT_64: + ret = gst_d3d11_bast_text_overlay_mode_convert_64 (self, outbuf); + break; + case GstDWriteBaseOverlayBlendMode::UNKNOWN: + GST_ERROR_OBJECT (self, "Conversion mode was not configured"); + ret = FALSE; + } + + if (!ret) + return GST_FLOW_ERROR; + + return GST_FLOW_OK; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.h new file mode 100644 index 0000000000..93f9b99a29 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebaseoverlay.h @@ -0,0 +1,71 @@ +/* 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. + */ + +#pragma once + +#include +#include +#include +#include +#include "gstdwrite-utils.h" +#include "gstdwrite-enums.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_BASE_OVERLAY (gst_dwrite_base_overlay_get_type()) +#define GST_DWRITE_BASE_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_DWRITE_BASE_OVERLAY,GstDWriteBaseOverlay)) +#define GST_DWRITE_BASE_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_DWRITE_BASE_OVERLAY,GstDWriteBaseOverlayClass)) +#define GST_DWRITE_BASE_OVERLAY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_DWRITE_BASE_OVERLAY,GstDWriteBaseOverlayClass)) +#define GST_IS_DWRITE_BASE_OVERLAY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_DWRITE_BASE_OVERLAY)) +#define GST_IS_DWRITE_BASE_OVERLAY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_DWRITE_BASE_OVERLAY)) + +typedef struct _GstDWriteBaseOverlay GstDWriteBaseOverlay; +typedef struct _GstDWriteBaseOverlayClass GstDWriteBaseOverlayClass; +typedef struct _GstDWriteBaseOverlayPrivate GstDWriteBaseOverlayPrivate; + +typedef std::wstring WString; + +struct _GstDWriteBaseOverlay +{ + GstBaseTransform parent; + + GstVideoInfo info; + + GstDWriteBaseOverlayPrivate *priv; +}; + +struct _GstDWriteBaseOverlayClass +{ + GstBaseTransformClass parent_class; + + gboolean (*sink_event) (GstDWriteBaseOverlay * overlay, + GstEvent * event); + + gboolean (*start) (GstDWriteBaseOverlay * overlay); + + WString (*get_text) (GstDWriteBaseOverlay * overlay, + const std::wstring & default_text, + GstBuffer * buffer); +}; + +GType gst_dwrite_base_overlay_get_type (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstDWriteBaseOverlay, gst_object_unref) + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.cpp new file mode 100644 index 0000000000..8419fc32c8 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.cpp @@ -0,0 +1,300 @@ +/* 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 "config.h" +#endif + +#include "gstdwritebitmapmemory.h" +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_STATIC (gst_dwrite_bitmap_allocator_debug); +#define GST_CAT_DEFAULT gst_dwrite_bitmap_allocator_debug + +#define GST_DWRITE_BITMAP_MEMORY_NAME "DWriteBitmapMemory" + +struct GstDWriteBitmapAllocatorPrivate +{ + GstDWriteBitmapAllocatorPrivate () + { + com_thread = std::thread (&GstDWriteBitmapAllocatorPrivate::ComThreadFunc, + this); + std::unique_lock < std::mutex > lk (init_lock); + while (!running) + init_cond.wait (lk); + } + + ~GstDWriteBitmapAllocatorPrivate () + { + thread_lock.lock (); + terminate = true; + thread_cond.notify_one (); + thread_lock.unlock (); + + com_thread.join (); + } + + void ComThreadFunc () + { + HRESULT hr; + CoInitializeEx (nullptr, COINIT_MULTITHREADED); + + hr = CoCreateInstance (CLSID_WICImagingFactory, nullptr, + CLSCTX_INPROC_SERVER, IID_PPV_ARGS (&factory)); + if (FAILED (hr)) + GST_ERROR ("Couldn't create image factory"); + + init_lock.lock (); + running = true; + init_cond.notify_one (); + init_lock.unlock (); + + std::unique_lock < std::mutex > lk (thread_lock); + while (!terminate) + thread_cond.wait (lk); + + if (factory) + factory->Release (); + + CoUninitialize (); + } + + IWICImagingFactory *factory = nullptr; + + std::mutex init_lock; + std::condition_variable init_cond; + + std::mutex thread_lock; + std::condition_variable thread_cond; + + bool running = false; + bool terminate = false; + + std::thread com_thread; +}; + +struct _GstDWriteBitmapAllocator +{ + GstAllocator parent; + + GstDWriteBitmapAllocatorPrivate *priv; +}; + +static void gst_dwrite_bitmap_allocator_finalize (GObject * object); + +static GstMemory *gst_dwrite_bitmap_allocator_dummy_alloc (GstAllocator * alloc, + gsize size, GstAllocationParams * params); +static void gst_dwrite_bitmap_allocator_free (GstAllocator * alloc, + GstMemory * mem); + +static gpointer gst_dwrite_bitmap_allocator_map (GstMemory * mem, + GstMapInfo * info, gsize maxsize); +static void gst_dwrite_bitmap_allocator_unmap (GstMemory * mem, + GstMapInfo * info); +static GstMemory *gst_dwrite_bitmap_allocator_share (GstMemory * mem, + gssize offset, gssize size); + +#define gst_dwrite_bitmap_allocator_parent_class parent_class +G_DEFINE_TYPE (GstDWriteBitmapAllocator, gst_dwrite_bitmap_allocator, + GST_TYPE_ALLOCATOR); + +static void +gst_dwrite_bitmap_allocator_class_init (GstDWriteBitmapAllocatorClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstAllocatorClass *alloc_class = GST_ALLOCATOR_CLASS (klass); + + object_class->finalize = gst_dwrite_bitmap_allocator_finalize; + + alloc_class->alloc = gst_dwrite_bitmap_allocator_dummy_alloc; + alloc_class->free = gst_dwrite_bitmap_allocator_free; + + GST_DEBUG_CATEGORY_INIT (gst_dwrite_bitmap_allocator_debug, + "dwritebitmapallocator", 0, "dwritebitmapallocator"); +} + +static void +gst_dwrite_bitmap_allocator_init (GstDWriteBitmapAllocator * self) +{ + GstAllocator *alloc = GST_ALLOCATOR_CAST (self); + + alloc->mem_type = GST_DWRITE_BITMAP_MEMORY_NAME; + alloc->mem_map_full = gst_dwrite_bitmap_allocator_map; + alloc->mem_unmap_full = gst_dwrite_bitmap_allocator_unmap; + alloc->mem_share = gst_dwrite_bitmap_allocator_share; + + GST_OBJECT_FLAG_SET (alloc, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); + + self->priv = new GstDWriteBitmapAllocatorPrivate (); +} + +static void +gst_dwrite_bitmap_allocator_finalize (GObject * object) +{ + GstDWriteBitmapAllocator *self = GST_DWRITE_BITMAP_ALLOCATOR (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstMemory * +gst_dwrite_bitmap_allocator_dummy_alloc (GstAllocator * alloc, gsize size, + GstAllocationParams * params) +{ + g_assert_not_reached (); + return nullptr; +} + +static void +gst_dwrite_bitmap_allocator_free (GstAllocator * alloc, GstMemory * mem) +{ + GstDWriteBitmapMemory *dmem = (GstDWriteBitmapMemory *) mem; + + if (dmem->bitmap) + dmem->bitmap->Release (); + + g_free (dmem); +} + +static GstMemory * +gst_dwrite_bitmap_allocator_share (GstMemory * mem, gssize offset, gssize size) +{ + return nullptr; +} + +static gpointer +gst_dwrite_bitmap_allocator_map (GstMemory * mem, GstMapInfo * info, + gsize maxsize) +{ + GstDWriteBitmapMemory *dmem = (GstDWriteBitmapMemory *) mem; + ComPtr < IWICBitmapLock > bitmap_lock; + HRESULT hr; + WICRect rect = { 0, 0, dmem->info.width, dmem->info.height }; + DWORD map_flags = 0; + BYTE *ptr = nullptr; + UINT size; + + info->user_data[0] = nullptr; + + if ((info->flags & GST_MAP_READ) == GST_MAP_READ) + map_flags |= (gint) WICBitmapLockRead; + + if ((info->flags & GST_MAP_WRITE) == GST_MAP_WRITE) + map_flags |= (gint) WICBitmapLockWrite; + + + hr = dmem->bitmap->Lock (&rect, map_flags, &bitmap_lock); + if (FAILED (hr)) { + GST_ERROR_OBJECT (mem->allocator, + "Couldn't map bitmap, hr: 0x%x", (guint) hr); + return nullptr; + } + + hr = bitmap_lock->GetDataPointer (&size, &ptr); + if (FAILED (hr)) { + GST_ERROR_OBJECT (mem->allocator, "Couldn't get data pointer, hr: 0x%x", + (guint) hr); + return nullptr; + } + + info->user_data[0] = (gpointer) bitmap_lock.Detach (); + + return (gpointer) ptr; +} + +static void +gst_dwrite_bitmap_allocator_unmap (GstMemory * mem, GstMapInfo * info) +{ + IWICBitmapLock *bitmap_lock; + + bitmap_lock = (IWICBitmapLock *) info->user_data[0]; + if (!bitmap_lock) { + GST_WARNING_OBJECT (mem->allocator, "No attached bitmap lock"); + return; + } + + bitmap_lock->Release (); + info->user_data[0] = nullptr; +} + +GstDWriteBitmapAllocator * +gst_dwrite_bitmap_allocator_new (void) +{ + GstDWriteBitmapAllocator *self = (GstDWriteBitmapAllocator *) + g_object_new (GST_TYPE_DWRITE_BITMAP_ALLOCATOR, nullptr); + + gst_object_ref_sink (self); + + if (!self->priv->factory) { + gst_object_unref (self); + return nullptr; + } + + return self; +} + +GstMemory * +gst_dwrite_bitmap_allocator_alloc (GstDWriteBitmapAllocator * alloc, + guint width, guint height) +{ + HRESULT hr; + IWICImagingFactory *factory = alloc->priv->factory; + ComPtr < IWICBitmap > bitmap; + ComPtr < IWICBitmapLock > bitmap_lock; + WICRect rect = { 0, 0, (INT) width, (INT) height }; + guint stride = width * 4; + GstDWriteBitmapMemory *mem; + gsize size; + + hr = factory->CreateBitmap (width, height, GUID_WICPixelFormat32bppPBGRA, + WICBitmapCacheOnDemand, &bitmap); + if (FAILED (hr)) { + GST_ERROR_OBJECT (alloc, "Couldn't create bitmap, hr: 0x%x", (guint) hr); + return nullptr; + } + + hr = bitmap->Lock (&rect, WICBitmapLockRead, &bitmap_lock); + if (FAILED (hr)) { + GST_ERROR_OBJECT (alloc, "Couldn't lock bitmap, hr: 0x%x", (guint) hr); + return nullptr; + } + + bitmap_lock = nullptr; + size = stride * height; + + mem = g_new0 (GstDWriteBitmapMemory, 1); + gst_memory_init (GST_MEMORY_CAST (mem), (GstMemoryFlags) 0, + GST_ALLOCATOR_CAST (alloc), nullptr, size, 0, 0, size); + + gst_video_info_set_format (&mem->info, GST_VIDEO_FORMAT_BGRA, width, height); + mem->info.size = size; + mem->info.stride[0] = stride; + + mem->bitmap = bitmap.Detach (); + + return GST_MEMORY_CAST (mem); +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.h new file mode 100644 index 0000000000..fb12e512d2 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmapmemory.h @@ -0,0 +1,48 @@ +/* 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_BITMAP_ALLOCATOR (gst_dwrite_bitmap_allocator_get_type()) +G_DECLARE_FINAL_TYPE (GstDWriteBitmapAllocator, + gst_dwrite_bitmap_allocator, GST, DWRITE_BITMAP_ALLOCATOR, GstAllocator); + +typedef struct _GstDWriteBitmapMemory GstDWriteBitmapMemory; + +struct _GstDWriteBitmapMemory +{ + GstMemory mem; + + GstVideoInfo info; + IWICBitmap *bitmap; +}; + +GstDWriteBitmapAllocator * gst_dwrite_bitmap_allocator_new (void); + +GstMemory * gst_dwrite_bitmap_allocator_alloc (GstDWriteBitmapAllocator * alloc, + guint width, + guint height); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.cpp new file mode 100644 index 0000000000..2de888a9d6 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.cpp @@ -0,0 +1,195 @@ +/* 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 "config.h" +#endif + +#include "gstdwritebitmappool.h" + +GST_DEBUG_CATEGORY_STATIC (dwrite_bitmap_pool_debug); +#define GST_CAT_DEFAULT dwrite_bitmap_pool_debug + +struct _GstDWriteBitmapPool +{ + GstBufferPool parent; + + GstDWriteBitmapAllocator *alloc; + GstVideoInfo info; +}; + +#define gst_dwrite_bitmap_pool_parent_class parent_class +G_DEFINE_TYPE (GstDWriteBitmapPool, + gst_dwrite_bitmap_pool, GST_TYPE_BUFFER_POOL); + +static void gst_dwrite_bitmap_pool_finalize (GObject * object); +static const gchar **gst_dwrite_bitmap_pool_get_options (GstBufferPool * pool); +static gboolean gst_dwrite_bitmap_pool_set_config (GstBufferPool * pool, + GstStructure * config); +static GstFlowReturn gst_dwrite_bitmap_pool_alloc_buffer (GstBufferPool * + pool, GstBuffer ** buffer, GstBufferPoolAcquireParams * params); + +static void +gst_dwrite_bitmap_pool_class_init (GstDWriteBitmapPoolClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstBufferPoolClass *pool_class = GST_BUFFER_POOL_CLASS (klass); + + object_class->finalize = gst_dwrite_bitmap_pool_finalize; + + pool_class->get_options = + GST_DEBUG_FUNCPTR (gst_dwrite_bitmap_pool_get_options); + pool_class->set_config = + GST_DEBUG_FUNCPTR (gst_dwrite_bitmap_pool_set_config); + pool_class->alloc_buffer = + GST_DEBUG_FUNCPTR (gst_dwrite_bitmap_pool_alloc_buffer); + + GST_DEBUG_CATEGORY_INIT (dwrite_bitmap_pool_debug, + "dwritebitmappool", 0, "dwritebitmappool"); +} + +static void +gst_dwrite_bitmap_pool_init (GstDWriteBitmapPool * self) +{ +} + +static void +gst_dwrite_bitmap_pool_finalize (GObject * object) +{ + GstDWriteBitmapPool *self = GST_DWRITE_BITMAP_POOL (object); + + gst_clear_object (&self->alloc); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static const gchar ** +gst_dwrite_bitmap_pool_get_options (GstBufferPool * pool) +{ + static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, + nullptr + }; + + return options; +} + +static gboolean +gst_dwrite_bitmap_pool_set_config (GstBufferPool * pool, GstStructure * config) +{ + GstDWriteBitmapPool *self = GST_DWRITE_BITMAP_POOL (pool); + GstCaps *caps; + guint size, min_buffers, max_buffers; + GstDWriteBitmapMemory *dmem; + GstMemory *mem; + + if (!gst_buffer_pool_config_get_params (config, + &caps, &size, &min_buffers, &max_buffers)) { + GST_WARNING_OBJECT (self, "Invalid config"); + return FALSE; + } + + if (!caps) { + GST_WARNING_OBJECT (self, "No caps"); + return FALSE; + } + + if (!gst_video_info_from_caps (&self->info, caps)) { + GST_WARNING_OBJECT (self, "Invalid caps"); + return FALSE; + } + + if (GST_VIDEO_INFO_FORMAT (&self->info) != GST_VIDEO_FORMAT_BGRA) { + GST_WARNING_OBJECT (self, "Unsupported format"); + return FALSE; + } + + if (!self->alloc) + self->alloc = gst_dwrite_bitmap_allocator_new (); + + if (!self->alloc) { + GST_WARNING_OBJECT (self, "Couldn't create allocator"); + return FALSE; + } + + mem = gst_dwrite_bitmap_allocator_alloc (self->alloc, + self->info.width, self->info.height); + if (!mem) { + GST_WARNING_OBJECT (self, "Couldn't allocate memory"); + return FALSE; + } + + dmem = (GstDWriteBitmapMemory *) mem; + size = dmem->info.size; + gst_memory_unref (mem); + + gst_buffer_pool_config_set_params (config, caps, size, min_buffers, + max_buffers); + + return GST_BUFFER_POOL_CLASS (parent_class)->set_config (pool, config); +} + +static GstFlowReturn +gst_dwrite_bitmap_pool_alloc_buffer (GstBufferPool * pool, + GstBuffer ** buffer, GstBufferPoolAcquireParams * params) +{ + GstDWriteBitmapPool *self = GST_DWRITE_BITMAP_POOL (pool); + GstBuffer *buf; + GstMemory *mem; + GstDWriteBitmapMemory *dmem; + + *buffer = nullptr; + + if (!self->alloc) { + GST_ERROR_OBJECT (self, "Allocator was not configured"); + return GST_FLOW_ERROR; + } + + mem = gst_dwrite_bitmap_allocator_alloc (self->alloc, + self->info.width, self->info.height); + if (!mem) { + GST_ERROR_OBJECT (self, "Couldn't allocate memory"); + return GST_FLOW_ERROR; + } + + buf = gst_buffer_new (); + gst_buffer_append_memory (buf, mem); + + dmem = (GstDWriteBitmapMemory *) mem; + + gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (&dmem->info), GST_VIDEO_INFO_WIDTH (&dmem->info), + GST_VIDEO_INFO_HEIGHT (&dmem->info), + GST_VIDEO_INFO_N_PLANES (&dmem->info), dmem->info.offset, + dmem->info.stride); + + *buffer = buf; + + return GST_FLOW_OK; +} + +GstBufferPool * +gst_dwrite_bitmap_pool_new (void) +{ + GstBufferPool *pool = (GstBufferPool *) + g_object_new (GST_TYPE_DWRITE_BITMAP_POOL, nullptr); + + gst_object_ref_sink (pool); + + return pool; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.h new file mode 100644 index 0000000000..9b3b2c75fa --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritebitmappool.h @@ -0,0 +1,33 @@ +/* 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. + */ + +#pragma once + +#include +#include "gstdwritebitmapmemory.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_BITMAP_POOL (gst_dwrite_bitmap_pool_get_type()) +G_DECLARE_FINAL_TYPE (GstDWriteBitmapPool, + gst_dwrite_bitmap_pool, GST, DWRITE_BITMAP_POOL, GstBufferPool); + +GstBufferPool * gst_dwrite_bitmap_pool_new (void); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.cpp new file mode 100644 index 0000000000..706ed6b9a2 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.cpp @@ -0,0 +1,193 @@ +/* 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 "config.h" +#endif + +#include "gstdwriteclockoverlay.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (dwrite_clock_overlay_debug); +#define GST_CAT_DEFAULT dwrite_clock_overlay_debug + +enum +{ + PROP_0, + PROP_TIME_FORMAT, +}; + +#define DEFAULT_TIME_FORMAT "%H:%M:%S" + +struct GstDWriteClockOverlayPrivate +{ + std::mutex lock; + std::string format; + WString wformat; +}; + +struct _GstDWriteClockOverlay +{ + GstDWriteBaseOverlay parent; + + GstDWriteClockOverlayPrivate *priv; +}; + +static void gst_dwrite_clock_overlay_finalize (GObject * object); +static void gst_dwrite_clock_overlay_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_dwrite_clock_overlay_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static WString +gst_dwrite_clock_overlay_get_text (GstDWriteBaseOverlay * overlay, + const WString & default_text, GstBuffer * buffer); + +#define gst_dwrite_clock_overlay_parent_class parent_class +G_DEFINE_TYPE (GstDWriteClockOverlay, gst_dwrite_clock_overlay, + GST_TYPE_DWRITE_BASE_OVERLAY); + +static void +gst_dwrite_clock_overlay_class_init (GstDWriteClockOverlayClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstDWriteBaseOverlayClass *overlay_class = + GST_DWRITE_BASE_OVERLAY_CLASS (klass); + + object_class->finalize = gst_dwrite_clock_overlay_finalize; + object_class->set_property = gst_dwrite_clock_overlay_set_property; + object_class->get_property = gst_dwrite_clock_overlay_get_property; + + g_object_class_install_property (object_class, PROP_TIME_FORMAT, + g_param_spec_string ("time-format", "Date/Time Format", + "Format to use for time and date value, as in strftime.", + DEFAULT_TIME_FORMAT, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + gst_element_class_set_static_metadata (element_class, + "DirectWrite Clock Overlay", "Filter/Editor/Video", + "Overlays the current clock time on a video stream", + "Seungha Yang "); + + overlay_class->get_text = + GST_DEBUG_FUNCPTR (gst_dwrite_clock_overlay_get_text); + + GST_DEBUG_CATEGORY_INIT (dwrite_clock_overlay_debug, + "dwriteclockoverlay", 0, "dwriteclockoverlay"); +} + +static void +gst_dwrite_clock_overlay_init (GstDWriteClockOverlay * self) +{ + g_object_set (self, "text-alignment", DWRITE_TEXT_ALIGNMENT_LEADING, + "paragraph-alignment", DWRITE_PARAGRAPH_ALIGNMENT_NEAR, nullptr); + + self->priv = new GstDWriteClockOverlayPrivate (); + self->priv->format = DEFAULT_TIME_FORMAT; + self->priv->wformat = gst_dwrite_string_to_wstring (DEFAULT_TIME_FORMAT); +} + +static void +gst_dwrite_clock_overlay_finalize (GObject * object) +{ + GstDWriteClockOverlay *self = GST_DWRITE_CLOCK_OVERLAY (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_dwrite_clock_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstDWriteClockOverlay *self = GST_DWRITE_CLOCK_OVERLAY (object); + GstDWriteClockOverlayPrivate *priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_TIME_FORMAT: + { + const gchar *format = g_value_get_string (value); + if (format) + priv->format = format; + else + priv->format = DEFAULT_TIME_FORMAT; + + priv->wformat = gst_dwrite_string_to_wstring (priv->format); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_dwrite_clock_overlay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstDWriteClockOverlay *self = GST_DWRITE_CLOCK_OVERLAY (object); + GstDWriteClockOverlayPrivate *priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_TIME_FORMAT: + g_value_set_string (value, priv->format.c_str ()); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static WString +gst_dwrite_clock_overlay_render_time (GstDWriteClockOverlay * self) +{ + GstDWriteClockOverlayPrivate *priv = self->priv; + struct tm *t; + time_t now; + wchar_t text[256]; + + now = time (nullptr); + t = localtime (&now); + + if (!t) + return WString (L"--:--:--"); + + if (wcsftime (text, G_N_ELEMENTS (text), priv->wformat.c_str (), t) == 0) + return WString (L"--:--:--"); + + return WString (text); +} + +static WString +gst_dwrite_clock_overlay_get_text (GstDWriteBaseOverlay * overlay, + const WString & default_text, GstBuffer * buffer) +{ + GstDWriteClockOverlay *self = GST_DWRITE_CLOCK_OVERLAY (overlay); + WString time_str = gst_dwrite_clock_overlay_render_time (self); + + if (default_text.empty ()) + return time_str; + + return default_text + WString (L" ") + time_str; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.h new file mode 100644 index 0000000000..a9a911d583 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwriteclockoverlay.h @@ -0,0 +1,30 @@ +/* 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. + */ + +#pragma once + +#include "gstdwritebaseoverlay.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_CLOCK_OVERLAY (gst_dwrite_clock_overlay_get_type()) +G_DECLARE_FINAL_TYPE (GstDWriteClockOverlay, + gst_dwrite_clock_overlay, GST, DWRITE_CLOCK_OVERLAY, GstDWriteBaseOverlay); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.cpp new file mode 100644 index 0000000000..482409d0f1 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.cpp @@ -0,0 +1,72 @@ +/* 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 "config.h" +#endif + +#include "gstdwritetextoverlay.h" + +GST_DEBUG_CATEGORY_STATIC (dwrite_text_overlay_debug); +#define GST_CAT_DEFAULT dwrite_text_overlay_debug + +struct _GstDWriteTextOverlay +{ + GstDWriteBaseOverlay parent; +}; + +static WString gst_dwrite_text_overlay_get_text (GstDWriteBaseOverlay * overlay, + const WString & default_text, GstBuffer * buffer); + +#define gst_dwrite_text_overlay_parent_class parent_class +G_DEFINE_TYPE (GstDWriteTextOverlay, gst_dwrite_text_overlay, + GST_TYPE_DWRITE_BASE_OVERLAY); + +static void +gst_dwrite_text_overlay_class_init (GstDWriteTextOverlayClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstDWriteBaseOverlayClass *overlay_class = + GST_DWRITE_BASE_OVERLAY_CLASS (klass); + + gst_element_class_set_static_metadata (element_class, + "DirectWrite Text Overlay", "Filter/Editor/Video", + "Adds text strings on top of a video buffer", + "Seungha Yang "); + + overlay_class->get_text = + GST_DEBUG_FUNCPTR (gst_dwrite_text_overlay_get_text); + + GST_DEBUG_CATEGORY_INIT (dwrite_text_overlay_debug, + "dwritetextoverlay", 0, "dwritetextoverlay"); +} + +static void +gst_dwrite_text_overlay_init (GstDWriteTextOverlay * self) +{ + g_object_set (self, "text-alignment", DWRITE_TEXT_ALIGNMENT_CENTER, + "paragraph-alignment", DWRITE_PARAGRAPH_ALIGNMENT_FAR, nullptr); +} + +static WString +gst_dwrite_text_overlay_get_text (GstDWriteBaseOverlay * overlay, + const WString & default_text, GstBuffer * buffer) +{ + return default_text; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.h new file mode 100644 index 0000000000..ce6836660c --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetextoverlay.h @@ -0,0 +1,30 @@ +/* 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. + */ + +#pragma once + +#include "gstdwritebaseoverlay.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_TEXT_OVERLAY (gst_dwrite_text_overlay_get_type()) +G_DECLARE_FINAL_TYPE (GstDWriteTextOverlay, + gst_dwrite_text_overlay, GST, DWRITE_TEXT_OVERLAY, GstDWriteBaseOverlay); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.cpp b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.cpp new file mode 100644 index 0000000000..46fd7ceefa --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.cpp @@ -0,0 +1,438 @@ +/* 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 "config.h" +#endif + +#include "gstdwritetimeoverlay.h" +#include +#include + +GST_DEBUG_CATEGORY_STATIC (dwrite_time_overlay_debug); +#define GST_CAT_DEFAULT dwrite_time_overlay_debug + +typedef enum +{ + GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_TIME, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_STREAM_TIME, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_RUNNING_TIME, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_TIME_CODE, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_ELAPSED_RUNNING_TIME, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_REFERENCE_TIMESTAMP, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_COUNT, + GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_OFFSET, +} GstDWriteTimeOverlayTimeLine; + +#define GST_TYPE_DWrite_TIME_OVERLAY_TIME_LINE (gst_dwrite_time_overlay_time_line_type ()) +static GType +gst_dwrite_time_overlay_time_line_type (void) +{ + static GType type; + static const GEnumValue modes[] = { + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_TIME, + "buffer-time", "buffer-time"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_STREAM_TIME, + "stream-time", "stream-time"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_RUNNING_TIME, + "running-time", "running-time"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_TIME_CODE, "time-code", "time-code"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_ELAPSED_RUNNING_TIME, + "elapsed-running-time", "elapsed-running-time"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_REFERENCE_TIMESTAMP, + "reference-timestamp", "reference-timestamp"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_COUNT, + "buffer-count", "buffer-count"}, + {GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_OFFSET, + "buffer-offset", "buffer-offset"}, + {0, nullptr, nullptr}, + }; + + GST_DWRITE_CALL_ONCE_BEGIN { + type = g_enum_register_static ("GstDWriteTimeOverlayTimeLine", modes); + } GST_DWRITE_CALL_ONCE_END; + + return type; +} + +enum +{ + PROP_0, + PROP_TIME_LINE, + PROP_SHOW_TIMES_AS_DATES, + PROP_DATETIME_EPOCH, + PROP_DATETIME_FORMAT, + PROP_REFERENCE_TIMESTAMP_CAPS, +}; + +#define DEFAULT_TIME_LINE GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_TIME +#define DEFAULT_SHOW_TIMES_AS_DATES FALSE +#define DEFAULT_DATETIME_FORMAT "%F %T" /* YYYY-MM-DD hh:mm:ss */ + +static GstStaticCaps ntp_reference_timestamp_caps = +GST_STATIC_CAPS ("timestamp/x-ntp"); + +struct GstDWriteTimeOverlayPrivate +{ + GstDWriteTimeOverlayPrivate () + { + datetime_epoch = g_date_time_new_utc (1900, 1, 1, 0, 0, 0); + reference_timestamp_caps = + gst_static_caps_get (&ntp_reference_timestamp_caps); + } + + ~GstDWriteTimeOverlayPrivate () + { + if (datetime_epoch) + g_date_time_unref (datetime_epoch); + gst_clear_caps (&reference_timestamp_caps); + } + + std::mutex lock; + GstDWriteTimeOverlayTimeLine time_line = DEFAULT_TIME_LINE; + + gboolean show_times_as_dates = DEFAULT_SHOW_TIMES_AS_DATES; + guint64 buffer_count = 0; + std::string datetime_format = DEFAULT_DATETIME_FORMAT; + GDateTime *datetime_epoch; + GstCaps *reference_timestamp_caps; + GstClockTime first_running_time = GST_CLOCK_TIME_NONE; +}; + +struct _GstDWriteTimeOverlay +{ + GstDWriteBaseOverlay parent; + + GstDWriteTimeOverlayPrivate *priv; +}; + +static void gst_dwrite_time_overlay_finalize (GObject * object); +static void gst_dwrite_time_overlay_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_dwrite_time_overlay_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static gboolean gst_dwrite_time_overlay_sink_event (GstBaseTransform * trans, + GstEvent * event); +static gboolean gst_dwrite_time_overlay_start (GstDWriteBaseOverlay * overlay); +static WString gst_dwrite_time_overlay_get_text (GstDWriteBaseOverlay * overlay, + const WString & default_text, GstBuffer * buffer); + +#define gst_dwrite_time_overlay_parent_class parent_class +G_DEFINE_TYPE (GstDWriteTimeOverlay, gst_dwrite_time_overlay, + GST_TYPE_DWRITE_BASE_OVERLAY); + +static void +gst_dwrite_time_overlay_class_init (GstDWriteTimeOverlayClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass); + GstDWriteBaseOverlayClass *overlay_class = + GST_DWRITE_BASE_OVERLAY_CLASS (klass); + + object_class->finalize = gst_dwrite_time_overlay_finalize; + object_class->set_property = gst_dwrite_time_overlay_set_property; + object_class->get_property = gst_dwrite_time_overlay_get_property; + + g_object_class_install_property (object_class, PROP_TIME_LINE, + g_param_spec_enum ("time-mode", "Time Mode", "What time to show", + GST_TYPE_DWrite_TIME_OVERLAY_TIME_LINE, DEFAULT_TIME_LINE, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_DATETIME_EPOCH, + g_param_spec_boxed ("datetime-epoch", "Datetime Epoch", + "When showing times as dates, the initial date from which time " + "is counted, if not specified prime epoch is used (1900-01-01)", + G_TYPE_DATE_TIME, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_DATETIME_FORMAT, + g_param_spec_string ("datetime-format", "Datetime Format", + "When showing times as dates, the format to render date and time in", + DEFAULT_DATETIME_FORMAT, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_SHOW_TIMES_AS_DATES, + g_param_spec_boolean ("show-times-as-dates", "Show times as dates", + "Whether to display times, counted from datetime-epoch, as dates", + DEFAULT_SHOW_TIMES_AS_DATES, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + g_object_class_install_property (object_class, PROP_REFERENCE_TIMESTAMP_CAPS, + g_param_spec_boxed ("reference-timestamp-caps", + "Reference Timestamp Caps", + "Caps to use for the reference timestamp time mode", + GST_TYPE_CAPS, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + gst_element_class_set_static_metadata (element_class, + "DirectWrite Time Overlay", "Filter/Editor/Video", + "Overlays buffer time stamps on a video stream", + "Seungha Yang "); + + trans_class->sink_event = + GST_DEBUG_FUNCPTR (gst_dwrite_time_overlay_sink_event); + + overlay_class->start = GST_DEBUG_FUNCPTR (gst_dwrite_time_overlay_start); + overlay_class->get_text = + GST_DEBUG_FUNCPTR (gst_dwrite_time_overlay_get_text); + + GST_DEBUG_CATEGORY_INIT (dwrite_time_overlay_debug, + "d3d11timeoverlay", 0, "d3d11timeoverlay"); + + gst_type_mark_as_plugin_api (GST_TYPE_DWrite_TIME_OVERLAY_TIME_LINE, + (GstPluginAPIFlags) 0); +} + +static void +gst_dwrite_time_overlay_init (GstDWriteTimeOverlay * self) +{ + g_object_set (self, "text-alignment", DWRITE_TEXT_ALIGNMENT_LEADING, + "paragraph-alignment", DWRITE_PARAGRAPH_ALIGNMENT_NEAR, nullptr); + + self->priv = new GstDWriteTimeOverlayPrivate (); +} + +static void +gst_dwrite_time_overlay_finalize (GObject * object) +{ + GstDWriteTimeOverlay *self = GST_DWRITE_TIME_OVERLAY (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_dwrite_time_overlay_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstDWriteTimeOverlay *self = GST_DWRITE_TIME_OVERLAY (object); + GstDWriteTimeOverlayPrivate *priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_TIME_LINE: + priv->time_line = (GstDWriteTimeOverlayTimeLine) g_value_get_enum (value); + break; + case PROP_SHOW_TIMES_AS_DATES: + priv->show_times_as_dates = g_value_get_boolean (value); + break; + case PROP_DATETIME_EPOCH: + g_date_time_unref (priv->datetime_epoch); + priv->datetime_epoch = (GDateTime *) g_value_dup_boxed (value); + break; + case PROP_DATETIME_FORMAT: + { + const gchar *format = g_value_get_string (value); + if (format) + priv->datetime_format = format; + else + priv->datetime_format = DEFAULT_DATETIME_FORMAT; + break; + } + case PROP_REFERENCE_TIMESTAMP_CAPS: + gst_clear_caps (&priv->reference_timestamp_caps); + priv->reference_timestamp_caps = (GstCaps *) g_value_dup_boxed (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_dwrite_time_overlay_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstDWriteTimeOverlay *self = GST_DWRITE_TIME_OVERLAY (object); + GstDWriteTimeOverlayPrivate *priv = self->priv; + std::lock_guard < std::mutex > lk (priv->lock); + + switch (prop_id) { + case PROP_TIME_LINE: + g_value_set_enum (value, priv->time_line); + break; + case PROP_SHOW_TIMES_AS_DATES: + g_value_set_boolean (value, priv->show_times_as_dates); + break; + case PROP_DATETIME_EPOCH: + g_value_set_boxed (value, priv->datetime_epoch); + break; + case PROP_DATETIME_FORMAT: + g_value_set_string (value, priv->datetime_format.c_str ()); + break; + case PROP_REFERENCE_TIMESTAMP_CAPS: + g_value_set_boxed (value, priv->reference_timestamp_caps); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_dwrite_time_overlay_start (GstDWriteBaseOverlay * overlay) +{ + GstDWriteTimeOverlay *self = GST_DWRITE_TIME_OVERLAY (overlay); + GstDWriteTimeOverlayPrivate *priv = self->priv; + + priv->first_running_time = GST_CLOCK_TIME_NONE; + priv->buffer_count = 0; + + return TRUE; +} + +static gboolean +gst_dwrite_time_overlay_sink_event (GstBaseTransform * trans, GstEvent * event) +{ + GstDWriteTimeOverlay *self = GST_DWRITE_TIME_OVERLAY (trans); + GstDWriteTimeOverlayPrivate *priv = self->priv; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_STOP: + priv->first_running_time = GST_CLOCK_TIME_NONE; + break; + default: + break; + } + + return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event); +} + +static WString +gst_dwrite_time_overlay_render_time (GstDWriteTimeOverlay * self, + GstClockTime time) +{ + wchar_t text[256]; + HRESULT hr; + guint h, m, s, ms; + + if (!GST_CLOCK_TIME_IS_VALID (time)) + return WString (); + + h = (guint) (time / (GST_SECOND * 60 * 60)); + m = (guint) ((time / (GST_SECOND * 60)) % 60); + s = (guint) ((time / GST_SECOND) % 60); + ms = (guint) ((time % GST_SECOND) / (1000 * 1000)); + + hr = StringCbPrintfW (text, sizeof (text), L"%u:%02u:%02u.%03u", h, m, s, ms); + + if (FAILED (hr)) + return WString (); + + return WString (text); +} + +static WString +gst_dwrite_time_overlay_get_text (GstDWriteBaseOverlay * overlay, + const WString & default_text, GstBuffer * buffer) +{ + GstDWriteTimeOverlay *self = GST_DWRITE_TIME_OVERLAY (overlay); + GstDWriteTimeOverlayPrivate *priv = self->priv; + WString time_str; + WString ret; + std::lock_guard < std::mutex > lk (priv->lock); + gboolean show_buffer_count = FALSE; + + if (priv->time_line == GST_DWRITE_TIME_OVERLAY_TIME_LINE_TIME_CODE) { + GstVideoTimeCodeMeta *tc_meta = + gst_buffer_get_video_time_code_meta (buffer); + if (!tc_meta) { + GST_DEBUG_OBJECT (self, "buffer without valid timecode"); + time_str = L"00:00:00:00"; + } else { + gchar *str = gst_video_time_code_to_string (&tc_meta->tc); + GST_DEBUG_OBJECT (self, "buffer with timecode %s", str); + time_str = gst_dwrite_string_to_wstring (str); + g_free (str); + } + } else { + GstBaseTransform *trans = GST_BASE_TRANSFORM (overlay); + GstClockTime ts, ts_buffer; + GstSegment *seg = &trans->segment; + + ts = ts_buffer = GST_BUFFER_TIMESTAMP (buffer); + if (GST_CLOCK_TIME_IS_VALID (ts)) { + switch (priv->time_line) { + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_STREAM_TIME: + ts = gst_segment_to_stream_time (seg, GST_FORMAT_TIME, ts_buffer); + break; + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_RUNNING_TIME: + ts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, ts_buffer); + break; + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_ELAPSED_RUNNING_TIME: + ts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, ts_buffer); + if (!GST_CLOCK_TIME_IS_VALID (priv->first_running_time)) + priv->first_running_time = ts; + ts -= priv->first_running_time; + break; + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_REFERENCE_TIMESTAMP: + { + GstReferenceTimestampMeta *meta; + if (priv->reference_timestamp_caps) { + meta = gst_buffer_get_reference_timestamp_meta (buffer, + priv->reference_timestamp_caps); + if (meta) + ts = meta->timestamp; + else + ts = 0; + } else { + ts = 0; + } + break; + } + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_COUNT: + show_buffer_count = TRUE; + priv->buffer_count++; + break; + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_OFFSET: + show_buffer_count = TRUE; + ts = gst_segment_to_running_time (seg, GST_FORMAT_TIME, ts_buffer); + priv->buffer_count = gst_util_uint64_scale (ts, overlay->info.fps_n, + overlay->info.fps_d * GST_SECOND); + break; + case GST_DWRITE_TIME_OVERLAY_TIME_LINE_BUFFER_TIME: + default: + ts = ts_buffer; + break; + } + + if (show_buffer_count) { + time_str = std::to_wstring (priv->buffer_count); + } else if (priv->show_times_as_dates) { + GDateTime *datetime; + gchar *str; + + datetime = + g_date_time_add_seconds (priv->datetime_epoch, + ((gdouble) ts) / GST_SECOND); + + str = g_date_time_format (datetime, priv->datetime_format.c_str ()); + time_str = gst_dwrite_string_to_wstring (str); + + g_free (str); + g_date_time_unref (datetime); + } else { + time_str = gst_dwrite_time_overlay_render_time (self, ts); + } + } + } + + if (default_text.empty ()) + return time_str; + + return default_text + WString (L" ") + time_str; +} diff --git a/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.h b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.h new file mode 100644 index 0000000000..3cccc7d8f7 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/gstdwritetimeoverlay.h @@ -0,0 +1,30 @@ +/* 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. + */ + +#pragma once + +#include "gstdwritebaseoverlay.h" + +G_BEGIN_DECLS + +#define GST_TYPE_DWRITE_TIME_OVERLAY (gst_dwrite_time_overlay_get_type()) +G_DECLARE_FINAL_TYPE (GstDWriteTimeOverlay, + gst_dwrite_time_overlay, GST, DWRITE_TIME_OVERLAY, GstDWriteBaseOverlay); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/dwrite/meson.build b/subprojects/gst-plugins-bad/sys/dwrite/meson.build new file mode 100644 index 0000000000..b0f60862b1 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/meson.build @@ -0,0 +1,70 @@ +dwrite_sources = [ + 'gstdwrite-effect.cpp', + 'gstdwrite-enums.cpp', + 'gstdwrite-renderer.cpp', + 'gstdwrite-utils.cpp', + 'gstdwritebaseoverlay.cpp', + 'gstdwritebitmapmemory.cpp', + 'gstdwritebitmappool.cpp', + 'gstdwriteclockoverlay.cpp', + 'gstdwritetextoverlay.cpp', + 'gstdwritetimeoverlay.cpp', + 'plugin.cpp', +] + +extra_args = ['-DGST_USE_UNSTABLE_API'] + +dwrite_option = get_option('dwrite') +if host_system != 'windows' or dwrite_option.disabled() + subdir_done() +endif + +if not gstd3d11_dep.found() + if dwrite_option.enabled() + error('The dwrite was enabled explicitly, but required dependencies were not found.') + endif + subdir_done() +endif + +d2d_dep = cc.find_library('d2d1', required: dwrite_option) +dwrite_lib = cc.find_library('dwrite', required : dwrite_option) +windowscodecs_lib = cc.find_library('windowscodecs', required : dwrite_option) +have_d2d_h = cc.has_header('d2d1_1.h') +have_dwrite_h = cc.has_header('dwrite.h') +have_wincodec_h = cc.has_header('wincodec.h') + +if not have_d2d_h or not have_dwrite_h or not have_wincodec_h + if dwrite_option.enabled() + error('The dwrite was enabled explicitly, but required dependencies were not found.') + endif + subdir_done () +endif + +if cc.has_header('d2d1_3.h') and cc.has_header('dwrite_3.h') + # DWRITE_GLYPH_IMAGE_FORMATS enum requires NTDDI_WIN10_RS1 + extra_args += ['-DWINVER=0x0A00', + '-D_WIN32_WINNT=0x0A00', + '-DNTDDI_VERSION=0x0A000002', + '-DHAVE_DWRITE_COLOR_FONT'] +endif + +# MinGW 32bits compiler seems to be complaining about redundant-decls +# when ComPtr is in use. Let's just disable the warning +if cc.get_id() != 'msvc' + extra_mingw_args = cc.get_supported_arguments([ + '-Wno-redundant-decls', + ]) + + extra_args += extra_mingw_args +endif + +gstdwrite = library('gstdwrite', + dwrite_sources, + c_args : gst_plugins_bad_args + extra_args, + cpp_args: gst_plugins_bad_args + extra_args, + include_directories : [configinc], + dependencies : [gstbase_dep, gstvideo_dep, gstd3d11_dep, d2d_dep, dwrite_lib], + install : true, + install_dir : plugins_install_dir, +) +plugins += [gstdwrite] diff --git a/subprojects/gst-plugins-bad/sys/dwrite/plugin.cpp b/subprojects/gst-plugins-bad/sys/dwrite/plugin.cpp new file mode 100644 index 0000000000..efabcb9b75 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/dwrite/plugin.cpp @@ -0,0 +1,55 @@ +/* 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. + */ + +/** + * plugin-dwrite: + * + * Since: 1.24 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstdwriteclockoverlay.h" +#include "gstdwritetextoverlay.h" +#include "gstdwritetimeoverlay.h" + +GST_DEBUG_CATEGORY (gst_dwrite_debug); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_dwrite_debug, "dwrite", 0, "dwrite"); + + gst_element_register (plugin, "dwriteclockoverlay", GST_RANK_NONE, + GST_TYPE_DWRITE_CLOCK_OVERLAY); + gst_element_register (plugin, "dwritetextoverlay", GST_RANK_NONE, + GST_TYPE_DWRITE_TEXT_OVERLAY); + gst_element_register (plugin, "dwritetimeoverlay", GST_RANK_NONE, + GST_TYPE_DWRITE_TIME_OVERLAY); + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + dwrite, + "dwrite", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/subprojects/gst-plugins-bad/sys/meson.build b/subprojects/gst-plugins-bad/sys/meson.build index 2ad1b22878..f554617957 100644 --- a/subprojects/gst-plugins-bad/sys/meson.build +++ b/subprojects/gst-plugins-bad/sys/meson.build @@ -9,6 +9,7 @@ subdir('decklink') subdir('directsound') subdir('directshow') subdir('dvb') +subdir('dwrite') subdir('fbdev') subdir('ipcpipeline') subdir('kms')