如何使用SkiaSharp在WPF的WriteableBitmap上绘制文本

发布时间 2023-10-17 15:22:33作者: 非法关键字

引言

在图像处理和图形渲染的世界里,SkiaSharp和WPF都是不可或缺的工具。然而,当需要在WPF的WriteableBitmap上绘制文本或图形时,如何优雅地结合这两个工具呢?在这篇文章中,我们将介绍一个简单的扩展方法,它允许你在WriteableBitmap上使用SkiaSharp进行文本绘制。

基础准备

首先,确保你已经安装了SkiaSharp的NuGet包。

Install-Package SkiaSharp -Version 2.80.3

创建一个扩展方法

我们首先创建一个WriteableBitmapExtensions类来存放我们的扩展方法。

public static class WriteableBitmapExtensions
{
    // 扩展方法将在这里定义
}

添加对齐枚举

在WPF中,我们通常使用TextAlignmentVerticalAlignment来设置对齐方式。然而,在SkiaSharp中,这些需要手动设置。为了解决这个问题,我们可以定义自己的枚举类型。

public enum TextAlignment
{
    Left,
    Center,
    Right
}

public enum VerticalAlignment
{
    Top,
    Center,
    Bottom
}

绘制文本的扩展方法

接下来,我们定义DrawText方法,该方法接受一个WriteableBitmap实例、文本、字体和各种其他参数。

具体的代码:

// File: WriteableBitmapExtensions
// Author: linxmouse@gmail.com
// Creation: 2023/10/17 13:55:49
using System;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows;
using SkiaSharp;

namespace WritableBitmapDemo
{
    public enum TextAlignment
    {
        Left,
        Center,
        Right
    }

    public enum VerticalAlignment
    {
        Top,
        Center,
        Bottom
    }

    public static class WriteableBitmapExtensions
    {
        public static void DrawText(this WriteableBitmap writeableBitmap, string text, Point position, Typeface typeface, double fontSize, Brush brush,
            TextAlignment horizontalAlign = TextAlignment.Left, VerticalAlignment verticalAlign = VerticalAlignment.Top)
        {
            // 锁定 WriteableBitmap 的后备缓冲区
            writeableBitmap.Lock();

            // 获取后备缓冲区的信息
            IntPtr buffer = writeableBitmap.BackBuffer;
            int width = writeableBitmap.PixelWidth;
            int height = writeableBitmap.PixelHeight;
            int stride = writeableBitmap.BackBufferStride;

            // 创建 Skia 的图像信息对象
            SKImageInfo imageInfo = new SKImageInfo(width, height, SKColorType.Bgra8888);

            // 创建 Skia 的绘图表面
            using (SKBitmap bitmap = new SKBitmap(imageInfo))
            {
                bitmap.InstallPixels(imageInfo, buffer, stride);
                using (SKCanvas canvas = new SKCanvas(bitmap))
                {
                    // 绘制文本或其他图形元素
                    using (SKPaint paint = new SKPaint())
                    {
                        paint.TextSize = (float)fontSize;
                        paint.IsAntialias = true;
                        // 使用 WPF 的 Typeface 创建 SkiaSharp 的 SKTypeface
                        if (typeface != null)
                        {
                            var fontFamily = typeface.FontFamily.ToString();
                            var weight = typeface.Weight.ToOpenTypeWeight();
                            var slant = typeface.Style == FontStyles.Normal ? SKFontStyleSlant.Upright : SKFontStyleSlant.Italic;
                            var skFontStyle = new SKFontStyle(weight, 5, slant);  // 这里的5是宽度参数,通常默认值即可
                            using (var skTypeface = SKTypeface.FromFamilyName(fontFamily, skFontStyle))
                            {
                                paint.Typeface = skTypeface;
                            }
                        }
                        // 根据自定义的对齐方式枚举设置Skia的对齐方式
                        switch (horizontalAlign)
                        {
                            case TextAlignment.Left:
                                paint.TextAlign = SKTextAlign.Left;
                                break;
                            case TextAlignment.Center:
                                paint.TextAlign = SKTextAlign.Center;
                                break;
                            case TextAlignment.Right:
                                paint.TextAlign = SKTextAlign.Right;
                                break;
                        }

                        // 从WPF的Brush对象中获取颜色信息
                        var solidColorBrush = brush as SolidColorBrush;
                        if (solidColorBrush != null)
                        {
                            var color = solidColorBrush.Color;
                            paint.Color = new SKColor(color.R, color.G, color.B, color.A);
                        }

                        paint.BlendMode = SKBlendMode.SrcOver;

                        // 计算文本宽度和高度
                        float textWidth = paint.MeasureText(text);
                        float textHeight = paint.FontMetrics.Descent - paint.FontMetrics.Ascent;
                        float textBaseline = -paint.FontMetrics.Ascent;

                        // 根据对齐方式调整x, y坐标
                        float adjustedX = (float)position.X;
                        float adjustedY = (float)position.Y;
                        if (horizontalAlign == TextAlignment.Center) adjustedX -= textWidth / 2;
                        else if (horizontalAlign == TextAlignment.Right) adjustedX -= textWidth; 
                        if (verticalAlign == VerticalAlignment.Center) adjustedY += textHeight / 2 - textBaseline;
                        else if (verticalAlign == VerticalAlignment.Bottom) adjustedY += textHeight - textBaseline;
                        else adjustedY += textBaseline;

                        canvas.DrawText(text, adjustedX, adjustedY, paint);
                    }

                    // 将绘制结果复制回 WriteableBitmap 的后备缓冲区
                    SKPixmap pixmap = bitmap.PeekPixels();
                    pixmap.ReadPixels(imageInfo, buffer, stride, 0, 0);
                }
            }

            // 解锁 WriteableBitmap 的后备缓冲区并进行刷新
            writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
            writeableBitmap.Unlock();
        }
    }
}

使用示例

var writeableBitmap = new WriteableBitmap(200, 200, 96, 96, PixelFormats.Bgra32, null);
var typeface = new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal);
var brush = new SolidColorBrush(Colors.Black);

writeableBitmap.DrawText("Hello, world!", new Point(50, 50), typeface, 12, brush, TextAlignment.Center, VerticalAlignment.Center);