gstreamer/subprojects/gst-plugins-bad/sys/dwrite/gstdwrite-renderer.cpp

650 lines
18 KiB
C++

/* GStreamer
* Copyright (C) 2023 Seungha Yang <seungha@centricular.com>
*
* 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 <wrl.h>
#include <vector>
GST_DEBUG_CATEGORY_EXTERN (gst_dwrite_debug);
#define GST_CAT_DEFAULT gst_dwrite_debug
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
enum class RenderPath
{
BACKGROUND,
TEXT,
};
struct RenderContext
{
RenderPath render_path;
ID2D1Factory *factory;
ID2D1RenderTarget *target;
RECT client_rect;
std::vector<DWRITE_LINE_METRICS> line_metrics;
UINT line_index = 0;
UINT char_index = 0;
ComPtr<ID2D1Geometry> bg_rect;
D2D1_COLOR_F bg_color = D2D1::ColorF (D2D1::ColorF::Black, 0.0);
};
/* *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;
}
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);
BOOL enable_color_font = FALSE;
g_assert (context != nullptr);
render_ctx = (RenderContext *) context;
client_rect = render_ctx->client_rect;
target = render_ctx->target;
factory = render_ctx->factory;
if (client_effect)
client_effect->QueryInterface (IID_IGstDWriteTextEffect, &effect);
if (render_ctx->render_path == RenderPath::BACKGROUND) {
D2D1_COLOR_F color;
BOOL enabled;
DWRITE_FONT_METRICS font_metrics;
FLOAT run_width = 0;
FLOAT adjust, ascent, descent;
D2D1_RECT_F bg_rect;
ComPtr < ID2D1SolidColorBrush > bg_brush;
DWRITE_LINE_METRICS line_metrics =
render_ctx->line_metrics.at (render_ctx->line_index);
if (effect &&
render_ctx->char_index + line_metrics.trailingWhitespaceLength <
line_metrics.length) {
effect->GetBrushColor (GST_DWRITE_BRUSH_BACKGROUND, &color, &enabled);
if (enabled) {
ComPtr < ID2D1RectangleGeometry > rect_geometry;
ComPtr < ID2D1PathGeometry > path_geometry;
ComPtr < ID2D1GeometrySink > path_sink;
for (UINT32 i = 0; i < glyph_run->glyphCount; i++)
run_width += glyph_run->glyphAdvances[i];
glyph_run->fontFace->GetMetrics (&font_metrics);
adjust = glyph_run->fontEmSize / font_metrics.designUnitsPerEm;
ascent = adjust * font_metrics.ascent;
descent = adjust * font_metrics.descent;
bg_rect =
D2D1::RectF (origin_x, origin_y - ascent, origin_x + run_width,
origin_y + descent);
hr = factory->CreateRectangleGeometry (bg_rect, &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 ();
if (render_ctx->bg_rect) {
if (render_ctx->bg_color.r == color.r &&
render_ctx->bg_color.g == color.g &&
render_ctx->bg_color.b == color.b &&
render_ctx->bg_color.a == color.a) {
ComPtr < ID2D1Geometry > combined;
hr = CombineTwoGeometries (factory,
render_ctx->bg_rect.Get (), path_geometry.Get (), &combined);
if (FAILED (hr))
return hr;
render_ctx->bg_rect = combined;
} else {
target->CreateSolidColorBrush (render_ctx->bg_color, &bg_brush);
target->FillGeometry (render_ctx->bg_rect.Get (), bg_brush.Get ());
render_ctx->bg_rect = nullptr;
}
}
if (!render_ctx->bg_rect) {
render_ctx->bg_rect = path_geometry;
render_ctx->bg_color = color;
}
}
}
render_ctx->char_index += glyph_run_desc->stringLength;
if (render_ctx->char_index >= line_metrics.length) {
render_ctx->line_index++;
render_ctx->char_index = 0;
}
return S_OK;
}
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), &transformed);
if (FAILED (hr))
return hr;
if (effect) {
D2D1_COLOR_F color;
BOOL enabled;
effect->GetBrushColor (GST_DWRITE_BRUSH_FORGROUND, &color, &enabled);
if (enabled) {
target->CreateSolidColorBrush (color, &brush);
fg_color = color;
}
effect->GetBrushColor (GST_DWRITE_BRUSH_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);
effect->GetEnableColorFont (&enable_color_font);
} else {
target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), &brush);
outline_brush = brush;
}
#ifdef HAVE_DWRITE_COLOR_FONT
if (enable_color_font) {
const DWRITE_GLYPH_IMAGE_FORMATS supported_formats =
(DWRITE_GLYPH_IMAGE_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) {
FLOAT adjust = glyph_run->fontEmSize * 0.06;
hr = factory->CreateTransformedGeometry (geometry.Get (),
D2D1::Matrix3x2F::Translation (origin_x + adjust, origin_y + adjust),
&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;
RenderContext *render_ctx;
ID2D1RenderTarget *target;
ID2D1Factory *factory;
HRESULT hr;
g_assert (context != nullptr);
render_ctx = (RenderContext *) context;
if (render_ctx->render_path == RenderPath::BACKGROUND)
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), &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);
} else {
target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), &brush);
}
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;
RenderContext *render_ctx;
ID2D1RenderTarget *target;
ID2D1Factory *factory;
HRESULT hr;
g_assert (context != nullptr);
render_ctx = (RenderContext *) context;
if (render_ctx->render_path == RenderPath::BACKGROUND)
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), &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);
} else {
target->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black), &brush);
}
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 RECT & client_rect, IDWriteTextLayout * layout,
ID2D1RenderTarget * target)
{
HRESULT hr;
RenderContext context;
ComPtr < ID2D1Factory > d2d_factory;
UINT32 num_lines = 0;
g_return_val_if_fail (layout != nullptr, E_INVALIDARG);
g_return_val_if_fail (target != nullptr, E_INVALIDARG);
hr = layout->GetLineMetrics (nullptr, 0, &num_lines);
if (hr != E_NOT_SUFFICIENT_BUFFER)
return hr;
context.line_metrics.resize (num_lines);
hr = layout->GetLineMetrics (&context.line_metrics[0],
context.line_metrics.size (), &num_lines);
if (FAILED (hr))
return hr;
target->GetFactory (&d2d_factory);
context.render_path = RenderPath::BACKGROUND;
context.client_rect = client_rect;
context.target = target;
context.factory = d2d_factory.Get ();
hr = layout->Draw (&context, this, origin.x, origin.y);
if (FAILED (hr)) {
GST_WARNING ("Background Draw failed with 0x%x", (guint) hr);
return hr;
}
if (context.bg_rect) {
ComPtr < ID2D1SolidColorBrush > bg_brush;
target->CreateSolidColorBrush (context.bg_color, &bg_brush);
target->FillGeometry (context.bg_rect.Get (), bg_brush.Get ());
context.bg_rect = nullptr;
}
context.render_path = RenderPath::TEXT;
hr = layout->Draw (&context, this, origin.x, origin.y);
if (FAILED (hr)) {
GST_WARNING ("Draw failed with 0x%x", (guint) hr);
}
return hr;
}