【问题标题】:Can I generate an anti-aliased font size larger than 149?我可以生成大于 149 的抗锯齿字体大小吗?
【发布时间】:2014-10-11 16:36:35
【问题描述】:

我注意到,在 Delphi XE6(以及其他生成在 Windows 上运行的应用程序并使用本机 GDI 字体呈现的工具/语言中)Win32 TextOut API 似乎无法平滑任何大于 149 的字体,即,字体大小>149。这是一个屏幕截图,显示了两个 SpeedButton,两者的 Font.Quality 设置为 fqClearType,左侧的 Font.Size 设置为 149,右侧的 Font.Size 设置为 150。这是一个点差。高度值分别为 -199 和 -200。这只是为了演示一个 Delphi 组件和表单,也可以在 TPaintBox 中演示,使用 Canvas.Font 和调用 Win32 API DrawText,或使用创建窗口的纯 Win32 API 应用程序,并使用DrawText 绘制到设备上下文。

GDI 的局限性在这里很明显;请注意,ClearType 在 size=149 时看起来很平庸(水平抗锯齿但没有垂直),并且 ClearType 在 150 时完全关闭:

我的问题是,有没有什么办法可以绕过 Win32 API GDI 中的这一限制,使用 Windows 7 及更高版本上可用的一些原始 Win32 函数来绘制文本并始终消除锯齿?我在这里假设逻辑字体处理在 VCL 中正确完成,因为当我在 Delphi 中尝试此操作时,我看到 C# 应用程序(使用 WinForms,它在 GDI 上运行)中出现相同的限制。

我想使用 Clear Type 或经典的 Anti-Aliasing 将字体大小大于 149 的抗锯齿字符绘制到 GDI 画布上。我该怎么做?

请注意,我已经将 Font.Quality 明确设置为 AntiAliased 和 ClearType 模式,并且 Win32 GDI api 调用会忽略这些大约特定大小的逻辑字体属性,显然是设计使然。某些应用程序(例如 Microsoft Word)显然具有绘制 155 磅或更大字体的字体渲染功能,并且在这种情况下仍然具有抗锯齿功能。

更新:我回答了我自己的问题,展示了 DirectWrite+GDI 互操作是多么容易。在 windows 7 和 windows 8 及更高版本上,DirectWrite 实际上提供了水平和垂直抗锯齿,我相信这是 MS Word 2013 等应用程序正在使用的高质量屏幕字体渲染模式。我相信有人可以通过显示 GDI+ 示例轻松回答我的问题,这也符合我的上述要求(因为 GDI+ 包含在 Windows 7 和 8 中)。

【问题讨论】:

  • 没有发生的原因大概是因为在大字体下抗锯齿有点无意义。
  • 我相信这就是想法,尽管实际的限制是任意的,并且编码在我们没有源代码的 GDI 中。就我而言,我想使用 TTF 字体来绘制图标,在高 DPI 显示器上,我有时会遇到要求字体大小大于 149 的图标分辨率。也许在 150+ dpi 显示器上,这只是我有点太挑剔了。如果有办法告诉 Windows 以某种方式扩展此限制,我想知道。
  • 一个解决方案可能是不使用 GDI。 GDI+ 或 Direct2D DirectWrite。一篇 directWrite 博文:blogs.embarcadero.com/pawelglowacki/2009/12/14/38872
  • 我觉得你运气不好
  • 同意。在这种情况下,编写 MS Word 等应用程序并且想要更好的字体渲染的人不要使用 GDI。 MS Word 使用 DirectWrite。在这里可以找到 DirectWrite 的 Delphi 示例:cc.embarcadero.com/item/27491

标签: delphi winapi fonts gdi


【解决方案1】:

我发现与 GDI 互操作比 GDI+ 更好的一种工作方法是使用 DirectWrite,但这仅适用于 Windows 7 和 8,并且我在此处提供的示例代码具有简单的 GDI 回退模式(普通GDI,无抗锯齿),涵盖 XP 和 Vista,至少提供优雅的降级;它仍然使用 GDI 在 Win7 之前的操作系统上绘制文本。

原来的演示应用在这里,但它使用的是我更改为 TWinControl 的 TForm,它没有 GDI 回退,只是一个例外。

http://cc.embarcadero.com/item/27491

编写上述演示的 Pawel Glowacki 的讨论/博客文章在这里:

http://blogs.embarcadero.com/pawelglowacki/2009/12/14/38872

此处显示了一个代码 sn-p,其中包含来自 Pawel 演示的修改后的 D2DUtils.pas,并添加了 GDI 回退功能(而不是因异常而崩溃)。

uses
  Windows,
  Messages,
  SysUtils,
  Variants,
  Classes,
  Graphics,
  Controls,
  Forms,
  Dialogs,
  Winapi.D2D1,
  Vcl.Direct2D;


type
   TCanvasD2D = class(TWinControl) // a base class, using TWinControl instead of TForm.
   private
      FInitFlag: Boolean;
      FGDIMode: Boolean; { Fallback }
      FD2DCanvas: TDirect2DCanvas; { Used When D2D is available and GDIMode=False }
      FGDICanvas: TCanvas; { Fallback canvas, used when FGDIMode=True }
      procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
   protected
      procedure Resize; override;

      procedure DoPaint(AHDC: HDC); virtual;

      procedure CreateD2DResources; virtual;

      procedure PaintD2D; virtual;
      procedure PaintGDI; virtual;

      function RenderTarget: ID2D1RenderTarget; // convenience function used during D2D Paints.

      procedure PaintWindow(DC: HDC); override;

   public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;

      procedure Init;
      property D2DCanvas: TDirect2DCanvas read FD2DCanvas;
      property GDICanvas: TCanvas read FGDICanvas;

      property GDIMode: Boolean read FGDIMode write FGDIMode;
      { Set to true to force GDI fallback, will automatically set true if D2D is not available, also }
   end;


TCanvasD2DSample = class(TCanvasD2D) // subclass of TCanvasD2D that is a primitive "TLabel"
    private
    FFontBrush: ID2D1SolidColorBrush;// Brush generated from current value of FFontColor
    FBackgroundColor:TColor; // clWhite
    FFontColor:TColor; //clBlack;
    FTextFormat: IDWriteTextFormat;
    FFontName: string;
    FFontSize: Integer;  { Units?}
    FDisplayText: String;
    FLocale: String;


    procedure SetFontName(const Value: String);
    procedure SetFontSize(const Value: Integer);
    procedure SetDisplayText(const Value: String);

  protected
    procedure PaintD2D; override;
    procedure PaintGDI; override;
    procedure CreateD2DResources; override;

    function FontSizeToDip(FontSize:Integer ):Double;

  public
    constructor Create(AOwner: TComponent); override;

    property TextFormat:IDWriteTextFormat read FTextFormat;
    property FontSize:Integer read FFontSize write SetFontSize;
    property FontName:String read FFontName write SetFontName;
    property DisplayText: String read FDisplayText write SetDisplayText;

    property BackgroundColor:TColor read FBackgroundColor write FBackgroundColor;
    property FontColor:TColor read FFontColor write FFontColor; //clBlack;


    property Locale: String read FLocale write FLocale; // string like 'en-us'

  end;

implementation

constructor TCanvasD2D.Create(AOwner: TComponent);
begin
   inherited;

end;

destructor TCanvasD2D.Destroy;
begin
   FD2DCanvas.Free;
   FD2DCanvas := nil;
   FGDICanvas.Free;
   FGDICanvas := nil;

   inherited;
end;

procedure TCanvasD2D.Init;
begin
   if not FInitFlag then
   begin
      FInitFlag := True;

      if (not FGDIMode) and (TDirect2DCanvas.Supported) then
      begin
         if Assigned(FD2DCanvas) then
            FD2DCanvas.Free;
         FD2DCanvas := TDirect2DCanvas.Create(Handle);
         CreateD2DResources;
      end
      else
      begin
         FGDIMode := True;
         if Assigned(FGDICanvas) then
            FGDICanvas.Free;
         FGDICanvas := TCanvas.Create;
         FGDICanvas.Handle := GetDC(Self.Handle);
      end;
   end;
end;

procedure TCanvasD2D.CreateD2DResources;
begin
   // create Direct2D resources in descendant class
end;

function TCanvasD2D.RenderTarget: ID2D1RenderTarget;
begin
   Result := D2DCanvas.RenderTarget;
end;

procedure TCanvasD2D.Resize;
var
   HwndTarget: ID2D1HwndRenderTarget;
   ASize: TD2D1SizeU;
begin
   inherited;

   if Assigned(D2DCanvas) then
      if Supports(RenderTarget, ID2D1HwndRenderTarget, HwndTarget) then
      begin
         ASize := D2D1SizeU(ClientWidth, ClientHeight);
         HwndTarget.Resize(ASize);
      end;

   Invalidate;
end;

procedure TCanvasD2D.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
   if (not FGDIMode) then
      // avoid flicker as described here:
      // http://chrisbensen.blogspot.com/2009/09/touch-demo-part-i.html
      Message.Result := 1
   else
      inherited;
end;

procedure TCanvasD2D.DoPaint(AHDC: HDC);
begin
   Init;
   if FGDIMode then
   begin
      FGDICanvas.Handle := AHDC;
      PaintGDI;
   end
   else
   begin
      D2DCanvas.BeginDraw;
      try
         PaintD2D;
      finally
         D2DCanvas.EndDraw;
      end;
   end;
end;

procedure TCanvasD2D.PaintD2D;
begin
   // implement painting code in descendant class
end;

procedure TCanvasD2D.PaintGDI;
begin
   // implement in descendant.
end;

procedure TCanvasD2D.PaintWindow(DC: HDC);
begin
   DoPaint(DC);
   inherited;

end;




{ Custom Control Subclass }
procedure TCanvasD2DSample.CreateD2DResources;

begin
  inherited;

  D2DCanvas.RenderTarget.CreateSolidColorBrush(
    D2D1ColorF(FFontColor, 1),
    nil,
    FFontBrush
    );

  DWriteFactory.CreateTextFormat(
    PWideChar(FontName),
    nil,
    DWRITE_FONT_WEIGHT_REGULAR,
    DWRITE_FONT_STYLE_NORMAL,
    DWRITE_FONT_STRETCH_NORMAL,
    FontSizeToDip( FontSize),
    PWideChar(FLocale),
    FTextFormat
   );

   FTextFormat.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
   FTextFormat.SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
end;



function TCanvasD2DSample.FontSizeToDip(FontSize: Integer): Double;
begin
   result := FontSize * (96.0 / 72.0); { TODO: 96.0 should not be hard coded? }
end;

procedure TCanvasD2DSample.PaintD2D;
var
  aRect: TD2D1RectF;
//  ASize:D2D_SIZE_F;
begin

  // fill with white color the whole window
  RenderTarget.Clear(D2D1ColorF(FBackgroundColor));



  RenderTarget.DrawText(
    PWideChar(FDisplayText),
    Length(FDisplayText),
    FTextFormat,
    D2D1RectF(0, 0, ClientWidth, ClientHeight),
    FFontBrush
    );

  //  RenderTarget.GetSize(ASize);
end;

procedure TCanvasD2DSample.PaintGDI;
begin
  { FALLBACK PAINT MODE}
  GDICanvas.Lock;
  GDICanvas.Font.Name := FFontName;
  GDICanvas.Font.Size := FFontSize;
  GDICanvas.Font.Color := FFontColor;
  GDICanvas.Brush.Style := bsSolid;
  GDICanvas.Brush.Color := FBackgroundColor;
  GDICanvas.Rectangle(Self.ClientRect);


  GDICanvas.TextOut(0,0, FDisplayText);
GDICanvas.Unlock;
end;

procedure TCanvasD2DSample.SetDisplayText(const Value: String);
begin
   if Value<>FDisplayText then
   begin
    FDisplayText := Value;
    Invalidate;
   end;
end;

procedure TCanvasD2DSample.SetFontName(const Value: String);
begin
  FFontName := Value;
end;

procedure TCanvasD2DSample.SetFontSize(const Value: Integer);
begin
  FFontSize := Value;
end;

【讨论】:

  • 走这条路的任何人;请注意dwrite.h 的 Delphi XE6 RTL 包装器包含许多导致 DirectWrite 接口无法运行的翻译错误,除非您修复(修改)XE6 附带的 Direct2D 包装器源代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-26
相关资源
最近更新 更多