首页 > 解决方案 > 禁用 PictureBox 上的图像混合

问题描述

在我的 Windows 窗体程序中,我有一个PictureBox包含小图像的5 x 5像素。
当这个 Bitmap 分配给PictureBox.Image属性时,它变得非常模糊。

我试图找到混合模式、模糊模式或抗锯齿模式之类的东西,但我没有运气。

图片1 图片2

  This is what I want     This is not what I want

标签: vb.netwinformsbitmapgdi+picturebox

解决方案


问题
一个位图,其尺寸比用于显示它的容器小得多,是模糊的,并且明确定义的颜色区域的其他锐利边缘被毫不客气地混合。
这只是放大时应用于非常小的图像(几个像素)的双线性过滤器的结果。

期望的结果是在放大图像时保持单个像素的原始颜色。

要达到这个结果,只需将 Graphics 对象的InterpolationMode设置为:

e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor

此过滤器也称为Point Filter,仅选择最接近正在评估的像素颜色的颜色。在评估颜色的均匀区域时,结果是所有像素的像素颜色相同。
只有一个问题,Graphics 对象的PixelOffsetMode的默认值,即:

e.Graphics.PixelOffsetMode = PixelOffsetMode.None

激活此模式后,对应于图像(在正常图像采样中)的顶部和左侧边界的外部像素被绘制在由容器(目标位图或设备上下文)定义的矩形区域边缘的中间.

正因为如此,由于源图像很小,而且它的像素被放大了很多,所以第一条水平和垂直线的像素明显被切成了两半。
这可以使用其他 PixelOffsetMode解决:

e.Graphics.PixelOffsetMode = PixelOffsetMode.Half

此模式将图像的渲染位置向后移动半个像素。
结果的示例图像可以更好地解释这一点:

插值模式最近邻

     Default Filter        InterpolationMode        InterpolationMode
   InterpolationMode        NearestNeighbor          NearestNeighbor
        Bilinear          PixelOffsetMode.None     PixelOffsetMode.Half
                                     

注意
.Net 的 MSDN Docs 没有很好地描述PixelOffsetMode参数。您可以找到 6 个明显不同的选择。像素偏移模式实际上只有两种:(
PixelOffsetMode.None默认)和PixelOffsetMode.Half.

PixelOffsetMode.Default并且PixelOffsetMode.HighSpeed与 相同PixelOffsetMode.None
PixelOffsetMode.HighQuality是一样的PixelOffsetMode.Half。阅读 .Net 文档,在选择其中一个时
似乎会影响速度。差异实际上可以忽略不计。

关于这个问题的C++ 文档(以及一般的 GDI+)更加明确和精确,应该使用它而不是 .Net 文档。

如何进行

我们可以将小的源 Bitmap 绘制到一个新的、更大的 Bitmap 并将其分配给一个PictureBox.Image属性。

但是,假设 PictureBox 的大小在某个时候发生了变化(因为布局发生了变化和/或由于 DPI 意识妥协),我们(几乎)回到了第一格。

一个简单的解决方案是直接在控件的表面上绘制新的位图,并在必要时/如果需要将其保存到磁盘。

这也将允许在需要时缩放位图而不会损失质量:

PixelOffsetMode 缩放位图

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

推荐阅读