随着 Alpha 的降低,玻璃背景上的绘图文本变得模糊

Posted

技术标签:

【中文标题】随着 Alpha 的降低,玻璃背景上的绘图文本变得模糊【英文标题】:Drawing text on glass background becomes blurred as Alpha is lowered 【发布时间】:2012-07-03 16:25:04 【问题描述】:

我正在对 TIceTabSet(Chrome 选项卡)组件进行多项更新。其中一项更改是增加透明度。除了文字,一切都很好。随着背景的 alpha 通道越来越低,文本变得越来越模糊。这是截图。

这是绘制标签的代码。其中大部分是原始的 TIceTabSet 代码。我只是添加了一些更改以使选项卡透明。对于示例屏幕截图,代码也进行了一些修改。底部的 DrawText 命令是将文本绘制到画布的位置。

procedure TIceTabSet.InnerDraw(Canvas: TCanvas; TabRect: TRect; Item: TIceTab);
var
  graphics : TGPGraphics;
  Pen: TGPPen;
  Brush: TGPSolidBrush;
  path, linePath: TGPGraphicsPath;
  linGrBrush: TGPLinearGradientBrush;
  font: TGPFont;
  solidBrush: TGPSolidBrush;
  rectF: TGPRectF;
  stringFormat: TGPStringFormat;
  DC: HDC;
  marginRight: integer;
  iconY, iconX: integer;
  textStart: Extended;
  startColor, EndColor, textColor, borderColor: cardinal;
  borderWidth: Integer;
  TabProperties: TIceTabProperties;
  Alpha: Byte;
begin
  DC := Canvas.Handle;

  TabProperties := GetTabProperties(Item);

  Alpha := Item.Index * 50;

  startColor := MakeGDIPColor(TabProperties.TabStyle.StartColor, Alpha);// TabProperties.TabStyle.Alpha);
  endColor := MakeGDIPColor(TabProperties.TabStyle.StopColor, Alpha); //TabProperties.TabStyle.Alpha);
  textColor := MakeGDIPColor(TabProperties.Font.Color, 255); //TabProperties.TabStyle.Alpha);
  borderColor := MakeGDIPColor(TabProperties.BorderColor, TabProperties.TabStyle.Alpha);
  borderWidth := TabProperties.BorderWidth;

  graphics := TGPGraphics.Create(DC);
  Brush := TGPSolidBrush.Create(borderColor);
  Pen:= TGPPen.Create(borderColor);
  Font := GetGDIPFont(Canvas, FTabActive.Font); //TabProperties.Font);
  try
    graphics.SetSmoothingMode(SmoothingModeHighQuality);

    pen.SetWidth(borderWidth);

    path := TGPGraphicsPath.Create();
    try
      path.AddBezier(TabRect.Left, TabRect.Bottom, TabRect.Left + FTabShape.LeftEdgeWidth / 2, TabRect.Bottom, TabRect.Left + FTabShape.LeftEdgeWidth / 2, TabRect.Top, TabRect.Left + FTabShape.LeftEdgeWidth, TabRect.Top);
      path.AddLine(TabRect.Left + FTabShape.LeftEdgeWidth, TabRect.Top, TabRect.Right - FTabShape.RightEdgeWidth, TabRect.Top);
      path.AddBezier(TabRect.Right - FTabShape.RightEdgeWidth, TabRect.Top, TabRect.Right - FTabShape.RightEdgeWidth / 2, TabRect.Top, TabRect.Right - FTabShape.RightEdgeWidth / 2, TabRect.Bottom, TabRect.Right, TabRect.Bottom);

      linePath := TGPGraphicsPath.Create;
      try
        linePath.AddPath(path, false);
        path.AddLine(TabRect.Right, TabRect.Bottom, TabRect.Left, TabRect.Bottom);

        linGrBrush := TGPLinearGradientBrush.Create(
          MakePoint(0, TabRect.Top),
          MakePoint(0, TabRect.Bottom),
          startColor,
          endColor);
        try
          graphics.DrawPath(pen, linePath);

          graphics.FillPath(linGrBrush, path);
        finally
          linGrBrush.Free;
        end;
      finally
        linePath.Free;
      end;
    finally
      path.Free;
    end;

    marginRight := 0;

    if TabDisplaysCloseButton(Item) then
    begin
      if (HighLightTabClose = Item) and
         (FTabCloseButton.ShowCircle) then
      begin
        pen.SetWidth(1);

        pen.SetColor(MakeGDIPColor(FTabCloseButton.CrossColorHotTrack, 255));
        brush.SetColor(MakeGDIPColor(FTabCloseButton.CircleColorHotTrack, 255));

        graphics.FillEllipse(brush, TabRect.Right - FTabShape.RightEdgeWidth - 7 - 2,
                                    TabRect.Top + ((TabRect.Bottom - TabRect.Top - 7) div 2) - 3,
                                    (TabRect.Right - FTabShape.RightEdgeWidth) - (TabRect.Right - FTabShape.RightEdgeWidth - 7) + 6,
                                    (TabRect.Top + ((TabRect.Bottom - TabRect.Top + 7) div 2)) - (TabRect.Top + ((TabRect.Bottom - TabRect.Top - 7) div 2)) + 6);

        graphics.DrawLine(pen, TabRect.Right - FTabShape.RightEdgeWidth - 5, TabRect.Top + ((TabRect.Bottom - TabRect.Top - 5) div 2),
                               TabRect.Right - FTabShape.RightEdgeWidth, TabRect.Top + ((TabRect.Bottom - TabRect.Top + 5) div 2));

        graphics.DrawLine(pen, TabRect.Right - FTabShape.RightEdgeWidth, TabRect.Top + ((TabRect.Bottom - TabRect.Top - 5) div 2),
                               TabRect.Right - FTabShape.RightEdgeWidth - 5, TabRect.Top + ((TabRect.Bottom - TabRect.Top + 5) div 2));
      end
      else
      begin
        pen.SetWidth(2);

        if HighlightTabClose = Item then
          pen.SetColor(MakeGDIPColor(FTabCloseButton.CrossColorHotTrack, 255))
        else
          pen.SetColor(MakeGDIPColor(FTabCloseButton.CrossColorNormal, 255));

        graphics.DrawLine(pen, TabRect.Right - FTabShape.RightEdgeWidth - 7, TabRect.Top + ((TabRect.Bottom - TabRect.Top - 7) div 2),
                               TabRect.Right - FTabShape.RightEdgeWidth, TabRect.Top + ((TabRect.Bottom - TabRect.Top + 7) div 2));

        graphics.DrawLine(pen, TabRect.Right - FTabShape.RightEdgeWidth, TabRect.Top + ((TabRect.Bottom - TabRect.Top - 7) div 2),
                               TabRect.Right - FTabShape.RightEdgeWidth - 7, TabRect.Top + ((TabRect.Bottom - TabRect.Top + 7) div 2));
      end;

      marginRight := 10;
    end;

    solidBrush:= TGPSolidBrush.Create(MakeGDIPColor(textColor, 255));

    stringFormat:= TGPStringFormat.Create;
    stringFormat.SetAlignment(StringAlignmentNear);
    stringFormat.SetLineAlignment(StringAlignmentCenter);
    stringFormat.SetTrimming(StringTrimmingEllipsisCharacter);
    stringFormat.SetFormatFlags(StringFormatFlagsNoWrap);

    SelectClipRgn(Canvas.Handle, 0);
    textStart := TabRect.Left + FTabShape.LeftEdgeWidth;
    iconX := 0;
    iconY := 0;

    if Assigned(Images) and (Item.ImageIndex <> -1) then
    begin
      iconY := TabRect.Top + ((TabRect.Bottom - TabRect.Top - Images.Height) div 2);
      iconX := Round(textStart);
      textStart := textStart + Images.Width + 4;
    end;

    rectF := MakeRect(textStart, TabRect.Top, TabRect.Right - textStart - FTabShape.RightEdgeWidth - marginRight,
      TabRect.Bottom - TabRect.Top);

    // ****** Text is drawn here *******
    if rectF.Width > 10 then
      graphics.DrawString(format('Alpha: %d', [Alpha]), -1, font, rectF, stringFormat, solidBrush);
    // *********************************

  finally
    font.Free;
    solidBrush.Free;
    Pen.Free;
    graphics.Free;
  end;

  if Assigned(Images) and
    (Item.ImageIndex <> -1) then
    Images.Draw(Canvas, iconX, iconY, Item.ImageIndex, true);
end;

您可以下载完整源代码here。请注意,这是一项正在进行的工作。源文件完成后将提交给原作者。

更新 1

按照 TLama 的建议更改代码肯定会有所帮助,但并不能完全解决问题。下面是文本现在的样子:

...谷歌浏览器的外观如下:

更新 2

这是 TextRenderingHintSingleBitPerPixelGridFit 的外观。

我已经尝试了所有选项,TextRenderingHintAntiAlias 给出了最好的结果。

【问题讨论】:

只是对其余代码的一些提示(我知道您的工作仍在进行中),但您似乎在绘图过程中混合了字体属性(我想您知道这一点)。另外不要忘记实现TIceTabProperties.FFont.OnChange 事件处理程序,其中只有Invalidate FIceTabSet 以反映Font 属性更改。并尽量保持组中方法和属性的格式和顺序,我的意思是设置器、消息处理程序和属性最好在明显分开的组中查看。 @TLama:感谢您的评论。我目前正在努力让一切正常运行,然后我将整理现有代码(已有两年多的历史,需要相当多的关注)。 你试过TextRenderingHintSingleBitPerPixelGridFit模式吗? @TLama:请查看我的更新以获得答案。 【参考方案1】:

正如 Ian Boyd 在他关于 How to draw ClearType text on Aero glass ? 的好帖子中所建议的那样,当您在一块玻璃上渲染文本时,您应该应用抗锯齿。因此,要解决您的问题,请尝试以这种方式修改您的代码:

if rectF.Width > 10 then
begin
  if (GetParentForm.GlassFrame.Enabled) and (GetParentForm.GlassFrame.SheetOfGlass) then
    graphics.SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
  graphics.DrawString(Item.DisplayCaption, -1, font, rectF, stringFormat, solidBrush);
end;

要模拟您的问题,只需在 Aero 玻璃片上渲染文本就足够了,就像以下代码以 3 种不同方式执行的那样:

uses
  GDIPAPI, GDIPOBJ;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Font.Color := clWhite;
  GlassFrame.SheetOfGlass := True;
  GlassFrame.Enabled := True;
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  S: WideString;
  GPFont: TGPFont;
  GPGraphics: TGPGraphics;
  GPSolidBrush: TGPSolidBrush;
  GPGraphicsPath: TGPGraphicsPath;
begin
  S := 'This is a sample text rendered on the sheet of Aero glass!';
  GPFont := TGPFont.Create(Canvas.Handle, Font.Handle);
  GPSolidBrush := TGPSolidBrush.Create(MakeColor(GetRValue(Font.Color),
    GetGValue(Font.Color), GetBValue(Font.Color)));
  GPGraphicsPath := TGPGraphicsPath.Create;
  GPGraphicsPath.AddString(S, Length(S), TGPFontFamily.Create(Font.Name),
    GPFont.GetStyle, GPFont.GetSize, MakePoint(20.0, 60.0), nil);
  try
    GPGraphics := TGPGraphics.Create(Canvas.Handle);
    try
      GPGraphics.SetSmoothingMode(SmoothingModeAntiAlias);
      GPGraphics.FillPath(GPSolidBrush, GPGraphicsPath);
      GPGraphics.DrawString(S, Length(S), GPFont, MakePoint(20.0, 20.0),
        nil, GPSolidBrush);
      GPGraphics.SetTextRenderingHint(
        TextRenderingHintSingleBitPerPixelGridFit);
      GPGraphics.DrawString(S, Length(S), GPFont, MakePoint(20.0, 40.0),
        nil, GPSolidBrush);
    finally
      GPGraphics.Free;
    end;
  finally
    GPFont.Free;
    GPSolidBrush.Free;
    GPGraphicsPath.Free;
  end;
end;

结果如下图:

    第一个文本由 DrawString 函数呈现,但未启用文本抗锯齿功能 第二个由DrawString 函数渲染,启用文本抗锯齿,配置为TextRenderingHintSingleBitPerPixelGridFit 模式 第三个由路径填充渲染,灵感来自this article,平滑模式设置为SmoothingModeAntiAlias 样式

【讨论】:

截图中的文字看起来不错。问题似乎在于,当在本身绘制在航空玻璃上的透明背景上绘制文本时,它开始看起来不正确。例如你的第二行对我来说是最好的。当我在透明背景上使用 TextRenderingHintSingleBitPerPixelGridFit 时,无论我将字体更改为哪种颜色,它总是以白色绘制。请参阅更新 2 中的图像。 我已经接受了这个答案,尽管当不直接在玻璃上绘图时它不能 100% 工作。如果我找出问题,我会在这里发布解决方案。【参考方案2】:

作为替代方案,您可以尝试使用主题 api(Vista 及更高版本)进行绘图。使用各种阴影/边框/发光设置,可能会想出可读的文本。玻璃片上的一些试验:

  

代码(XE2):

procedure TForm1.FormPaint(Sender: TObject);
var
  R: TRect;
  ThemeData: HTHEME;
  Opts: TDTTOpts;
begin
  R := Rect(10, 10, 150, 30);
  vcl.themes.DrawGlassText(Canvas.Handle, 'DrawGlassText Sample', R, 0, 3,
      clBlack, TStyleManager.SystemStyle.GetElementDetails(ttsLabel));


  OffsetRect(R, 160, 0);
  ThemeData := OpenThemeData(Handle, 'textstyle');

  Opts.dwSize := SizeOf(Opts);
  Opts.crText := ColorToRGB(clBlack);
  Opts.crShadow := $D0D0B0;
  Opts.iTextShadowType := TST_SINGLE;
  Opts.ptShadowOffset := Point(1, 1);
  Opts.fApplyOverlay := True;
  Opts.iGlowSize := 3;
  Opts.dwFlags := DTT_TEXTCOLOR or DTT_SHADOWTYPE or DTT_SHADOWCOLOR
      or DTT_SHADOWOFFSET or DTT_GLOWSIZE;
  DrawThemeTextEx(ThemeData, Canvas.Handle, TEXT_LABEL, TS_NORMAL,
      'DrawThemeTextEx Sample', -1, 0, @R, Opts);

  OffsetRect(R, 180, 0);
  Opts.crText := ColorToRGB(clBlack);
  Opts.iGlowSize := 4;
  Opts.fApplyOverlay := True;
  Opts.dwFlags := DTT_TEXTCOLOR or DTT_GLOWSIZE;
  DrawThemeTextEx(ThemeData, Canvas.Handle, TEXT_BODYTITLE, 0,
      'Another Sample', -1, 0, @R, Opts);

  CloseThemeData(ThemeData);
end;

【讨论】:

+1,我认为 OP 想要呈现不发光的文本,但即使使用 DrawThemeTextEx 函数也应该可以实现。 @TLama - 这是可能的,但是当您将发光大小设置为 0 时,通过某些设置,文本就会消失。我同意 OP 似乎想在没有发光的情况下渲染文本,但据我所知,如果没有发光,很难在各种背景下获得可读的文本。 不幸的是,在选项卡上绘制发光效果时会显得格格不入。好主意,谢谢。 我认为您需要选择选项卡的平均颜色(包括透明度)而不是发光,然后在文本的所有边上 DE-TRANSPARENT-IFY 1 个像素的区域(可能通过重复在所有四个方向上以 +1 和 -1 偏移量绘制文本),然后在中心位置绘制文本。简而言之,自己部分地击败透明度。

以上是关于随着 Alpha 的降低,玻璃背景上的绘图文本变得模糊的主要内容,如果未能解决你的问题,请参考以下文章

在缓冲图像上绘图时,RGB 颜色会随着包含 alpha 的变化而变化

Python PIL 降低 alpha 级别,但不更改透明背景

工具栏上的文本背景问题

JavaFX 滚动表更新性能随着时间的推移而降低

使文本字段背景透明,但占位符文本不透明

MATLAB | 一行代码让你的绘图背景变得逼格满满