在 PictureBox 上禁用图像混合
Disable Image blending on a PictureBox
在我的 Windows 表单程序中,我有一个 PictureBox
包含一个小图像,5 x 5
像素。
当这个Bitmap赋值给PictureBox.Image
属性的时候,就变得很模糊了。
我试图找到混合模式、模糊模式或抗锯齿模式之类的东西,但我没有运气。
This is what I want This is not what I want
问题:
位图的尺寸远小于用于显示它的容器,因此 模糊 并且明确定义的颜色区域的锐利边缘被粗鲁地混合。
这只是在放大时对非常小的图像(几个像素)应用双线性滤镜的结果。
期望的结果是在放大图像时保持单个像素的原始颜色。
要实现此结果,只需将 Graphics 对象的 InterpolationMode 设置为:
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
此滤镜也称为 Point Filter
,只需选择一种最接近正在评估的像素颜色的颜色。在评估颜色的均匀区域时,结果是所有像素的像素颜色相同。
只有一个问题,Graphics 对象的 PixelOffsetMode 的默认值是:
e.Graphics.PixelOffsetMode = PixelOffsetMode.None
启用此模式后,对应于图像(在正常图像采样中)的顶部和左侧边界的外部像素绘制在图像边缘的中间容器定义的矩形区域(目标位图或设备上下文)。
正因为如此,由于源图像很小,像素被放大了很多,第一条水平线和垂直线的像素被明显切成两半。
这可以使用 the other PixelOffsetMode
:
来解决
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
此模式向后移动 图像的渲染位置半个像素。
结果的示例图片可以更好地解释这一点:
Default Filter InterpolationMode InterpolationMode
InterpolationMode NearestNeighbor NearestNeighbor
Bilinear PixelOffsetMode.None PixelOffsetMode.Half
注:
.Net 的 MSDN 文档没有很好地描述 PixelOffsetMode
参数。您可以找到 6 个明显不同的选择。 Pixel Offset模式其实只有两种:
PixelOffsetMode.None
(默认)和PixelOffsetMode.Half
.
PixelOffsetMode.Default
和 PixelOffsetMode.HighSpeed
与 PixelOffsetMode.None
相同。
PixelOffsetMode.HighQuality
等同于 PixelOffsetMode.Half
.
阅读 .Net 文档,在选择一个而不是另一个时似乎有 speed 的含义。差别其实可以忽略不计。
C++ documentation about this matter(和一般的 GDI+)更加明确和精确,应该使用它来代替 .Net。
如何进行:
我们可以将小源 Bitmap 绘制到一个新的、更大的 Bitmap 并将其分配给 PictureBox.Image
属性.
但是,假设 PictureBox 大小在某个时候发生变化(因为布局因 DPI 感知妥协而发生变化 and/or),我们(几乎)回到原点。
一个简单的解决方案是直接在控件的表面上绘制新的位图并将其保存到光盘 when/if 必要。
这也将允许在需要时缩放位图而不会降低质量:
Imports System.Drawing
Imports System.Drawing.Drawing2D
Private pixelBitmap As Bitmap = Nothing
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
pixelBitmap = DirectCast(New Bitmap("File Path").Clone(), Bitmap)
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
e.Graphics.DrawImage(pixelBitmap, GetScaledImageRect(pixelBitmap, DirectCast(sender, Control)))
End Sub
Private Sub PictureBox1_Resize(sender As Object, e As EventArgs) Handles PictureBox1.Resize
PictureBox1.Invalidate()
End Sub
GetScaledImageRect
是一种用于在容器内缩放图像的辅助方法:
Public Function GetScaledImageRect(image As Image, canvas As Control) As RectangleF
Return GetScaledImageRect(image, canvas.ClientSize)
End Function
Public Function GetScaledImageRect(image As Image, containerSize As SizeF) As RectangleF
Dim imgRect As RectangleF = RectangleF.Empty
Dim scaleFactor As Single = CSng(image.Width / image.Height)
Dim containerRatio As Single = containerSize.Width / containerSize.Height
If containerRatio >= scaleFactor Then
imgRect.Size = New SizeF(containerSize.Height * scaleFactor, containerSize.Height)
imgRect.Location = New PointF((containerSize.Width - imgRect.Width) / 2, 0)
Else
imgRect.Size = New SizeF(containerSize.Width, containerSize.Width / scaleFactor)
imgRect.Location = New PointF(0, (containerSize.Height - imgRect.Height) / 2)
End If
Return imgRect
End Function
我见过几次的解决方案是覆盖 PictureBox
的 class,其中 InterpolationMode
为 class 属性.然后,您需要做的就是在 UI 上使用此 class 而不是 .Net 自己的 PictureBox
,并将该模式设置为 NearestNeighbor
.
Public Class PixelBox
Inherits PictureBox
<Category("Behavior")>
<DefaultValue(InterpolationMode.NearestNeighbor)>
Public Property InterpolationMode As InterpolationMode = InterpolationMode.NearestNeighbor
Protected Overrides Sub OnPaint(pe As PaintEventArgs)
Dim g As Graphics = pe.Graphics
g.InterpolationMode = Me.InterpolationMode
' Fix half-pixel shift on NearestNeighbor
If Me.InterpolationMode = InterpolationMode.NearestNeighbor Then _
g.PixelOffsetMode = PixelOffsetMode.Half
MyBase.OnPaint(pe)
End Sub
End Class
如评论中所述,对于最近邻模式,您需要设置
PixelOffsetMode
到 Half
。老实说,我不明白他们为什么要公开它而不是让它成为内部渲染过程中的自动选择。
大小可以通过设置控件的SizeMode
属性来控制。将其设置为 Zoom
将使其自动居中并展开而不会裁剪控件的设置大小。
在我的 Windows 表单程序中,我有一个 PictureBox
包含一个小图像,5 x 5
像素。
当这个Bitmap赋值给PictureBox.Image
属性的时候,就变得很模糊了。
我试图找到混合模式、模糊模式或抗锯齿模式之类的东西,但我没有运气。
This is what I want This is not what I want
问题:
位图的尺寸远小于用于显示它的容器,因此 模糊 并且明确定义的颜色区域的锐利边缘被粗鲁地混合。
这只是在放大时对非常小的图像(几个像素)应用双线性滤镜的结果。
期望的结果是在放大图像时保持单个像素的原始颜色。
要实现此结果,只需将 Graphics 对象的 InterpolationMode 设置为:
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
此滤镜也称为 Point Filter
,只需选择一种最接近正在评估的像素颜色的颜色。在评估颜色的均匀区域时,结果是所有像素的像素颜色相同。
只有一个问题,Graphics 对象的 PixelOffsetMode 的默认值是:
e.Graphics.PixelOffsetMode = PixelOffsetMode.None
启用此模式后,对应于图像(在正常图像采样中)的顶部和左侧边界的外部像素绘制在图像边缘的中间容器定义的矩形区域(目标位图或设备上下文)。
正因为如此,由于源图像很小,像素被放大了很多,第一条水平线和垂直线的像素被明显切成两半。
这可以使用 the other PixelOffsetMode
:
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
此模式向后移动 图像的渲染位置半个像素。
结果的示例图片可以更好地解释这一点:
Default Filter InterpolationMode InterpolationMode
InterpolationMode NearestNeighbor NearestNeighbor
Bilinear PixelOffsetMode.None PixelOffsetMode.Half
注:
.Net 的 MSDN 文档没有很好地描述 PixelOffsetMode
参数。您可以找到 6 个明显不同的选择。 Pixel Offset模式其实只有两种:
PixelOffsetMode.None
(默认)和PixelOffsetMode.Half
.
PixelOffsetMode.Default
和 PixelOffsetMode.HighSpeed
与 PixelOffsetMode.None
相同。
PixelOffsetMode.HighQuality
等同于 PixelOffsetMode.Half
.
阅读 .Net 文档,在选择一个而不是另一个时似乎有 speed 的含义。差别其实可以忽略不计。
C++ documentation about this matter(和一般的 GDI+)更加明确和精确,应该使用它来代替 .Net。
如何进行:
我们可以将小源 Bitmap 绘制到一个新的、更大的 Bitmap 并将其分配给 PictureBox.Image
属性.
但是,假设 PictureBox 大小在某个时候发生变化(因为布局因 DPI 感知妥协而发生变化 and/or),我们(几乎)回到原点。
一个简单的解决方案是直接在控件的表面上绘制新的位图并将其保存到光盘 when/if 必要。
这也将允许在需要时缩放位图而不会降低质量:
Imports System.Drawing
Imports System.Drawing.Drawing2D
Private pixelBitmap As Bitmap = Nothing
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
pixelBitmap = DirectCast(New Bitmap("File Path").Clone(), Bitmap)
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
e.Graphics.DrawImage(pixelBitmap, GetScaledImageRect(pixelBitmap, DirectCast(sender, Control)))
End Sub
Private Sub PictureBox1_Resize(sender As Object, e As EventArgs) Handles PictureBox1.Resize
PictureBox1.Invalidate()
End Sub
GetScaledImageRect
是一种用于在容器内缩放图像的辅助方法:
Public Function GetScaledImageRect(image As Image, canvas As Control) As RectangleF
Return GetScaledImageRect(image, canvas.ClientSize)
End Function
Public Function GetScaledImageRect(image As Image, containerSize As SizeF) As RectangleF
Dim imgRect As RectangleF = RectangleF.Empty
Dim scaleFactor As Single = CSng(image.Width / image.Height)
Dim containerRatio As Single = containerSize.Width / containerSize.Height
If containerRatio >= scaleFactor Then
imgRect.Size = New SizeF(containerSize.Height * scaleFactor, containerSize.Height)
imgRect.Location = New PointF((containerSize.Width - imgRect.Width) / 2, 0)
Else
imgRect.Size = New SizeF(containerSize.Width, containerSize.Width / scaleFactor)
imgRect.Location = New PointF(0, (containerSize.Height - imgRect.Height) / 2)
End If
Return imgRect
End Function
我见过几次的解决方案是覆盖 PictureBox
的 class,其中 InterpolationMode
为 class 属性.然后,您需要做的就是在 UI 上使用此 class 而不是 .Net 自己的 PictureBox
,并将该模式设置为 NearestNeighbor
.
Public Class PixelBox
Inherits PictureBox
<Category("Behavior")>
<DefaultValue(InterpolationMode.NearestNeighbor)>
Public Property InterpolationMode As InterpolationMode = InterpolationMode.NearestNeighbor
Protected Overrides Sub OnPaint(pe As PaintEventArgs)
Dim g As Graphics = pe.Graphics
g.InterpolationMode = Me.InterpolationMode
' Fix half-pixel shift on NearestNeighbor
If Me.InterpolationMode = InterpolationMode.NearestNeighbor Then _
g.PixelOffsetMode = PixelOffsetMode.Half
MyBase.OnPaint(pe)
End Sub
End Class
如评论中所述,对于最近邻模式,您需要设置
PixelOffsetMode
到 Half
。老实说,我不明白他们为什么要公开它而不是让它成为内部渲染过程中的自动选择。
大小可以通过设置控件的SizeMode
属性来控制。将其设置为 Zoom
将使其自动居中并展开而不会裁剪控件的设置大小。