我想在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)
这个答案似乎有点缺陷。
可能的情况:
- 每个字符的前进宽度不同
- 无需努力调整基线,因为一行可能包含
几个字形运行
- 由于绝对y位置已埋入行中,因此插入行时不容易移动