1

我尝试在屏幕上显示格式化文本。首先解析非常简单的 HTML 文本(有 b、u、i 之类的标签),然后使用Canvas.TextOut函数以适当的位置和字体呈现每个字符。

我注意到的第一件事是,画布上每个单独字符的渲染都相当慢。整个句子的渲染要快得多。很明显,当画布被迫重新绘制时,当表单在屏幕上移动时。

一种解决方案是用均匀的字体对字符进行聚类并立即渲染它们。但是,当格式丰富时,它不会有太大帮助。此外,我需要字符是离散的实体,可以以任何方式呈现。例如,没有 WinAPI 支持文本对齐 taJustify 或块写入...

另一种方法是在位图上渲染,或者明智地使用ClipRectTCanvas 的属性(我还没有尝试过)。

无论如何,当在 TRichEdit 中显示相同格式的文本时,重绘操作不会造成时间损失。另一个简单的例子是所有主要的浏览器,它们显示大量格式化文本没有问题......他们是否像我一样渲染每个字符,但他们做得更有效???我不知道。

那么你知道一些加速应用程序的方法吗(格式化文本渲染?)。

感谢您的想法...

示例代码:(使TForm尽可能大,用鼠标抓住它并在屏幕下向下拖动它。当你将它向上移动时,你会看到“跳跃”的运动)

procedure TForm1.FormPaint(Sender: TObject);
var i, w, h, j:integer;
    s:string;
    switch:Boolean;
begin
   w:=0;
   h:=0;
   s:='';
   for j:=0 to 5 do
       for i:=65 to 90 do s:=s + Char(i);

   switch:=False; // set true to see the difference

   if switch then
     begin
     for j:=0 to 70 do begin
         for i := 1 to Length(s) do
         begin
         Form1.Canvas.TextOut(50+ w,h +70 , s[i]);
         w:=w +  Form1.Canvas.TextWidth(s[i]);
         end;
         w:=0;
         h:=h+15;
         end;
     end
    else
      begin
      for j:=0 to 70 do begin
       Form1.Canvas.TextOut(50+ w,h +70 , s);
       w:=w +  Form1.Canvas.TextWidth(s);  // not optimalized just for comparison
       w:=0;                               // not optimalized just for comparison
       h:=h+15;
       end;
      end;
end;
4

3 回答 3

4

使用分析器(例如 AQTime)来查找代码实际花费时间的位置。花费最多时间的可能不是它TextOut()自己。您一次通过String一个字符进行索引,将每个字符传递给TextOut()and TextWidth()。这些方法都不接受Char参数作为输入,它们只接受String输入,因此 RTL 花费精力分配和释放内存中的大量临时Strings,具体取决于源String的长度。我见过类似杀死循环性能的东西。

于 2012-09-26T18:33:18.060 回答
2

为避免闪烁,具有最佳性能并仍具有所有高级文本渲染功能(如字距调整),答案是使用临时位图。

在 Windows 中绘制文本非常快,但显示预先计算的位图会快得多。

您可以划分布局以仅呈现文本的显示部分。或者尝试将您的文本拆分为文本“框”(就像伟大的 TeX 引擎所做的那样),使用每个框的宽度缓存。但是 Windows 本身会进行这种缓存,因此只有在您发现真正的瓶颈时才使用这种技术,通过对整个代码进行适当的分析。

不要重新发明轮子。在真实内容上,您会发现文本渲染比想象的要复杂得多,例如,如果您混合使用语言和布局(例如阿拉伯语和英语)。对于这样复杂的工作,您最好依靠 Windows,例如它的UniScribe API 。当我们制作我们的开源 pdf 引擎时,我们尽可能地重复使用它

例如,FireMonkey 遭受重新发明轮子的困扰,并且在呈现复杂的文本内容时失败。所以使用现有的 API 是恕我直言的最佳途径......

于 2012-09-26T19:20:01.993 回答
1

在我的电脑上,渲染到位图然后将其绘制到画布上的速度大约是原来的两倍。好吧,慢版本变得快两倍。快速版本保持不变。

另一种可能有效的优化。您还可以将字符宽度预先计算到一个数组中,这样您就不必经常调用 canvas.TextWidth()。

保持这样的变量

widths:array[char] of byte;

像这样填写:

for c := low(widths) to high(widths) do
  widths[c] := Canvas.TextWidth(char(c));

填充这个 65536 元素数组很慢,所以最好只创建一个 65..90 元素数组,并放弃 unicode 支持。

另一件事.. 调用 Winapi.Windows.TextOut() 比 canvas.TextOut() 快。

你实际上可以赢得很多。

    Winapi.Windows.TextOut(bmp.Canvas.Handle, w, h, @s[i], 1);

您的代码的修改版本:

// set up of off-screen bitmap.. needs to be resized when the form resizes. 
procedure TForm1.FormCreate(Sender: TObject);
begin
  bmp := TBitmap.Create;
  bmp.SetSize(width,height);
end;

这是

procedure TForm36.PaintIt2;
var h,i,j,w: Integer; s: string;
begin
  w := 0;  h := 0;  s := '';

  for j := 0 to 5 do
    for i := 65 to 90 do
      s := s + Char(i);

  bmp.Canvas.Brush.Color := Color;
  bmp.Canvas.FillRect(bmp.Canvas.ClipRect);
  if Checkbox1.Checked then
  begin
    for j := 0 to 70 do
    begin
      for i := 1 to Length(s) do
      begin
        Winapi.Windows.TextOut(bmp.Canvas.Handle, w, h, @s[i], 1);
        w := w + widths[s[i]];
      end;
      w := 0; h := h + 15;
    end;
  end
  else
    for j := 0 to 70 do
    begin
      bmp.Canvas.TextOut(w, h, s);
      w := 0; h := h + 15;
    end;
  canvas.Draw(0,0,bmp);
end;

我用这个程序计时了性能:

procedure TForm1.Button2Click(Sender: TObject);
var i : Integer; const iterations=300;
begin
  with TStopwatch.StartNew do
  begin
    for I := 1 to iterations do
      PaintIt2;
    Caption := IntToStr(Elapsed.Ticks div iterations);
  end;
end;

最后一点:

我已经尝试禁用 cleartype/anti-aliasing,但奇怪的是,这使得渲染速度变慢了两倍!这就是我关闭抗锯齿的方式:

  tagLOGFONT: TLogFont;

  GetObject(
    bmp.Canvas.Font.Handle,
    SizeOf(TLogFont),
    @tagLOGFONT);
  tagLOGFONT.lfQuality  := NONANTIALIASED_QUALITY;
  bmp.Canvas.Font.Handle := CreateFontIndirect(tagLOGFONT);
于 2012-09-26T19:32:27.147 回答