在 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.DefaultPixelOffsetMode.HighSpeedPixelOffsetMode.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

如评论中所述,对于最近邻模式,您需要设置 PixelOffsetModeHalf。老实说,我不明白他们为什么要公开它而不是让它成为内部渲染过程中的自动选择。

大小可以通过设置控件的SizeMode属性来控制。将其设置为 Zoom 将使其自动居中并展开而不会裁剪控件的设置大小。