如何以60 fps的速度快速绘制WPF中的文本或字形?

时间:2015-10-20 07:43:30

标签: c# wpf

我想在WPF中创建一个用户控件,它可以使用单空格字体绘制n x m个字符矩阵。 控件将尽可能快地接受字符串[](目标为60 fps)并在屏幕上绘制。

我需要与mplayer ascii playback类似的效果。

所有字符都使用相同的单空格字体绘制,但根据某些规则可能有不同的颜色和背景(类似于VS中的语法高亮)。

我已经在C#WinForms中实现了解决方案而没有任何问题,并且达到了60 FPS,但是当我想学习如何在WPF中执行此操作时,我发现了几篇描述WPF性能和冲突信息问题的文章和帖子。

那么在这种情况下实现最高性能的最佳方法是什么?

我尝试过的一种天真的方法是:

 /// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    Random rand = new Random();

    public MainWindow()
    {
        InitializeComponent();

        DispatcherTimer timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(1);
        timer.Tick += timer_Tick;
        timer.Start();
    }

    string GenerateRandomString(int length)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++)
        {
            sb.Append(rand.Next(10));
        }
        return sb.ToString();
    }
    void timer_Tick(object sender, EventArgs e)
    {
        myTextBlock.Inlines.Clear();

        for (int i = 0; i < 30; i++)
        {
            var run = new Run();
            run.Text = GenerateRandomString(800);
            run.Foreground = new SolidColorBrush(Color.FromArgb((byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256)));
            run.Background = new SolidColorBrush(Color.FromArgb((byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256),(byte)rand.Next(256)));
            myTextBlock.Inlines.Add(run);
        }

    }
}

问题是:你能在WPF中做得更好吗?

P.S。 是的,我可以直接使用DirectX,但这个问题是关于WPF而不是DX。

2 个答案:

答案 0 :(得分:6)

Probably the fastest way to draw text in WPF is to use GlyphRun. Here is a sample code: MainWindow.xaml <Window x:Class="WpfApplication.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300"> <Image> <Image.Source> <DrawingImage x:Name="drawingImage"/> </Image.Source> </Image> </Window> MainWindow.xaml.cs using System; using System.Collections.Generic; using System.Diagnostics; using System.Windows; using System.Windows.Media; using System.Windows.Threading; namespace WpfApplication { public partial class MainWindow : Window { Random rand = new Random(); Stopwatch stopwatch; long frameCounter = 0; GlyphTypeface glyphTypeface; double renderingEmSize, advanceWidth, advanceHeight; Point baselineOrigin; public MainWindow() { InitializeComponent(); new Typeface("Consolas").TryGetGlyphTypeface(out this.glyphTypeface); this.renderingEmSize = 10; this.advanceWidth = this.glyphTypeface.AdvanceWidths[0] * this.renderingEmSize; this.advanceHeight = this.glyphTypeface.Height * this.renderingEmSize; this.baselineOrigin = new Point(0, this.glyphTypeface.Baseline * this.renderingEmSize); CompositionTarget.Rendering += CompositionTarget_Rendering; DispatcherTimer timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(1000); timer.Tick += timer_Tick; timer.Start(); } void CompositionTarget_Rendering(object sender, EventArgs e) { if (this.stopwatch == null) this.stopwatch = Stopwatch.StartNew(); ++this.frameCounter; this.drawingImage.Drawing = this.Render(); } string GenerateRandomString(int length) { var chars = new char[length]; for (int i = 0; i < chars.Length; ++i) chars[i] = (char)rand.Next('A', 'Z' + 1); return new string(chars); } void timer_Tick(object sender, EventArgs e) { var seconds = this.stopwatch.Elapsed.TotalSeconds; Trace.WriteLine((long)(this.frameCounter / seconds)); if (seconds > 10) { this.stopwatch.Restart(); this.frameCounter = 0; } } private Drawing Render() { var lines = new string[30]; for (int i = 0; i < lines.Length; ++i) lines[i] = GenerateRandomString(100); var drawing = new DrawingGroup(); using (var drawingContext = drawing.Open()) { // TODO: draw rectangles which represent background. // TODO: group of glyphs which has the same color should be drawn together. // Following code draws all glyphs in Red color. var glyphRun = ConvertTextLinesToGlyphRun(this.glyphTypeface, this.renderingEmSize, this.advanceWidth, this.advanceHeight, this.baselineOrigin, lines); drawingContext.DrawGlyphRun(Brushes.Red, glyphRun); } return drawing; } static GlyphRun ConvertTextLinesToGlyphRun(GlyphTypeface glyphTypeface, double renderingEmSize, double advanceWidth, double advanceHeight, Point baselineOrigin, string[] lines) { var glyphIndices = new List<ushort>(); var advanceWidths = new List<double>(); var glyphOffsets = new List<Point>(); var y = baselineOrigin.Y; for (int i = 0; i < lines.Length; ++i) { var line = lines[i]; var x = baselineOrigin.X; for (int j = 0; j < line.Length; ++j) { var glyphIndex = glyphTypeface.CharacterToGlyphMap[line[j]]; glyphIndices.Add(glyphIndex); advanceWidths.Add(0); glyphOffsets.Add(new Point(x, y)); x += advanceWidth; } y += advanceHeight; } return new GlyphRun( glyphTypeface, 0, false, renderingEmSize, glyphIndices, baselineOrigin, advanceWidths, glyphOffsets, null, null, null, null, null); } } }

答案 1 :(得分:0)

这个答案似乎有点缺陷。 可能的情况:

  1. 每个字符的前进宽度不同
  2. 无需努力调整基线,因为一行可能包含 几个字形运行
  3. 由于绝对y位置已埋入行中,因此插入行时不容易移动