Graphics.Clear(Color.Transparent) 在选项卡控件内绘制模糊文本

Graphics.Clear(Color.Transparent) draw blurry text inside tab control

我在选项卡中设置了用户控件。用户控件内的文本模糊。请在下面找到用户控件绘制事件的代码

protected override void OnPaint(PaintEventArgs e)
{
     //Avoid redrawing the control unnecessarily
     if(redraw)
     {
         using (Graphics g = Graphics.FromImage(buffer))
         {
                g.Clear(Color.Transparent);
                draw(g, ClientRectangle);
         }
     }
     e.Graphics.DrawImage(buffer, Point.Empty);
}

如果我将控件放在 VS 工具箱中可用的 Tab 控件中,我会注意到以下情况。

1) 用户控件中绘制的文本模糊

2) Paint事件触发了3次。由于条件重绘,绘制逻辑将执行一次,现有缓冲区将在第 2 次和第 3 次绘制

3) 如果我使用 g.Clear(Color.White),文本看起来不错

g.Clear 行应该清除位图(缓冲区)并用指定的颜色填充它。它还会清除图形对象的状态。如果我使用透明以外的任何颜色,文本不会模糊。

g.Clear(Color.White) 和 g.Clear(Color.Transparent) 之间有什么显着区别吗?

注意:Color.White只是一个例子。任何命名的颜色都可以解决这个问题。也仅在使用 Tab 控件时出现

编辑: 下面提供了模糊和正确的图像

在哪个宇宙中 "clear" 具有 t运行sparent 颜色有意义?

Color.Transparent 是...t运行sparent。定义为没有颜色。如果用 t运行sparent 颜色填充,则什么也没有填充。空虚。 T运行透明。

如果您用 t运行sparent 颜色填充上面有文字的内容,它不会有任何效果,文字会保留在那里。然后,如果你在它上面绘制相同的文本,你会看到模糊(至少,假设文本是抗锯齿的;如果不是,什么都不会改变)。

除 Color.Transparent 以外的任何颜色都可以清除该区域,因为它们是 不透明 颜色。这意味着它们与 t运行sparent 相反。你看不透它们,所以它们实际上掩盖了它们背后的东西。


你也太努力优化代码了。当调用 Paint 事件处理程序时,项目 需要被绘制 。操作系统已经负责为您优化它。所以 if (redraw) 检查既不是必需的也不是一个好主意。只需填充您的背景颜色,然后在其上绘制图像或文本即可。


更新: 我被指向 here 据称重现该问题的代码。和 Moonlight Sheng 一样,我无法用该代码重现所描述的问题。

这并不奇怪。在链接代码中,对 g.Clear 的调用是完全多余的,原因有二。首先,正如我已经解释过的,使用 Color.Transparent 清除本质上是一个空操作——它什么都不做。其次,紧随其后的一行用纯白色画笔填充了整个图像矩形。这用于 "clearing" 绘图表面的目的,使其全白。您可以使用 g.Clear(Color.White) 完成同样的事情。然后在这个白色背景上绘制文本,最后将图像绘制到控件上。这非常有效,因为从来没有任何 "transparent" 区域。每当在控件顶部绘制图像时,它都会覆盖(因此擦除)之前存在的所有内容。绝对没有模糊。因此,这与 Graphics.ClearColor.Transparent.

的使用无关

我想既然您正在覆盖 OnLayout,您打算将此控件与其 Anchor 或 Dock 属性集一起使用。所以我把Dock属性设置为Fill in my test project,然后重新运行应用。同样,我无法重现该问题。但是,我确实注意到,在快速调整容器窗体大小时,我会产生 "tearing" 效果或其他视觉伪像。例如,当我把表格做得很小时,我看到多次绘制文本:

这是由快速调整窗体大小引起的竞争条件。客户端矩形的尺寸变化太快,窗体新暴露的区域没有被擦除。让我看看我是否可以解释实际发生的事情。

  • 当窗体(和控件)大小更改为 X 时,您销毁旧的缓冲区图像并创建大小为 X[=73 的新缓冲区图像=].然后调用 DrawString 将文本绘制到其中。图像太窄,无法将整个字符串放在一行中,因此它将“100”换行到第二行。然后将图像绘制到表单上,一切看起来都很好。
  • 然后窗体(和控件)大小变为 Y。因此,您再次销毁旧缓冲区图像并创建大小为 Y 的新缓冲区图像。这一次,宽度增加了,所以在绘制文本时,所有内容都适合一行。然后将图像绘制到表单上。
  • 哦,等等。尺码 Y 比尺码 X 宽,但也更短(矮)。因此,当您将这个新缓冲区绘制到窗体时,不会导致之前的区域被擦除。这就是为什么您看到第二行的旧“100”仍然可见。

这很难解释,我想更难理解,除非你已经知道绘画是如何工作的。

重要的是有一个简单的解决方案:将窗体的 ResizeRedraw 属性 设置为 true,确保每次调整窗体大小时都引发 Paint 事件。在控件的 OnLayout 事件处理程序方法中强制重绘也可以工作 (this.Invalidate()),如下所示:

public partial class MyControl : UserControl
{
   Image buffer;
   public MyControl()
   {
      InitializeComponent();
   }

   protected override void OnLayout(LayoutEventArgs e)
   {
      if (buffer != null)
      {
         buffer.Dispose();
         buffer = null;
      }
      base.OnLayout(e);

      this.Invalidate();  // force a repaint after the layout has changed
   }

   protected override void OnPaint(PaintEventArgs e)
   {
      if (buffer == null)
      {
         buffer = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);

         using (Graphics g = Graphics.FromImage(buffer))
         {
            //g.Clear(Color.Transparent);  // pointless
            g.FillRectangle(Brushes.White, ClientRectangle);
            using (Font f = new System.Drawing.Font("Segoe UI", 12, FontStyle.Regular))
            {
               g.DrawString("Simple text 100", f, Brushes.Black, ClientRectangle);
            }
         }
      }
      e.Graphics.DrawImage(buffer, ClientRectangle);
      base.OnPaint(e);
   }
}

无论如何,这不是声称的错误,而只是对 Windows 中绘画子系统工作方式的误解。

顺便说一句,如果您删除对 FillRectangle 的调用(它用纯白色填充控件的工作区),您会看到您最初描述的模糊文本效果。发生这种情况的确切原因我已经说过:文本是抗锯齿的,并且在抗锯齿文本之上绘制抗锯齿文本会产生 "blurry" 效果。您无法以任何明智的方式解决此问题。抗锯齿文本绝对必须绘制在纯色背景之上;它 cannot be drawn on a transparent background。您需要填写表单的背景颜色。默认情况下,即 SystemColors.Control

除了建议如何解决这个问题,让我纠正两个误解..

  • Color.Transparent is essentially a no-op—it does absolutely nothing

Filling/clearing 和 Color.Transparent 将使所有像素成为 透明黑色: (0,0,0,0).

与某人的评论相反,这不是什么;当一个人真的想在透明的 canvas.

上绘画时,它很有用并且是一个正常的开始

因为你的结果看起来不对,你最好用你真正想要的颜色清除 canvas。如果你想显示类似 TabPage 的副本,你应该首先填写该页面的 BackColor

g.Clear(tapPage1.BackColor);

另外一句,你这次说的是:

  • using g.Clear(Color.Transparent) fills the bitmap with black color.

好吧,正如我所说,它确实填充了 透明 黑色 (0,0,0,0)。它不填充 real black (255,0,0,0).

并非所有成像软件都支持透明度。一个特别糟糕的程序是(否则很棒)IrfanView,直到今天,它仍会将所有透明度显示为黑色。 Paint .Net 将 'display' 它 'correctly' 意思是用在透明像素处闪耀的棋盘图案作为底层..

要在不清除 RBG 通道的情况下使图像 'just' 透明 (alpha=0),您可以使用例程 like this(尽管我更喜欢 Lockbits unsafe),但正如我所说,这不是你想要的..

更新

g.Clear(Color.Transparent);

其实可以让canvas透明;这是在 位图 ..

上使用它时发生的情况

..或者当你在控件上使用它时它会显示父级的颜色。如果你在一个没有父级的表单上使用它,它看起来是黑色的。