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.Clear
或 Color.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透明;这是在 位图 ..
上使用它时发生的情况
..或者当你在控件上使用它时它会显示父级的颜色。如果你在一个没有父级的表单上使用它,它看起来是黑色的。
我在选项卡中设置了用户控件。用户控件内的文本模糊。请在下面找到用户控件绘制事件的代码
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.Clear
或 Color.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透明;这是在 位图 ..
上使用它时发生的情况..或者当你在控件上使用它时它会显示父级的颜色。如果你在一个没有父级的表单上使用它,它看起来是黑色的。