我尝试找到类似混合模式、模糊模式或抗锯齿模式之类的东西,但是没有成功。
请见下方图片1和图片2。
This is what I want This is not what I want
This is what I want This is not what I want
期望的结果是在放大图像的同时保持单个像素的原始颜色。
为了实现这个结果,只需要将 Graphics 对象的 InterpolationMode 设置为:
e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor
这个滤镜,也被称为点过滤器
,简单地选择一个颜色,该颜色是最接近正在评估的像素颜色的颜色。当评估颜色均匀的区域时,所有像素的结果都是相同的像素颜色。
只有一个问题,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文档没有很好地描述PixelOffsetMode
参数。你可以找到6个显然不同的选项。像素偏移模式实际上只有两种:
PixelOffsetMode.None
(默认)和PixelOffsetMode.Half
。
PixelOffsetMode.Default
和PixelOffsetMode.HighSpeed
与PixelOffsetMode.None
相同。
PixelOffsetMode.HighQuality
与PixelOffsetMode.Half
相同。
阅读 .Net 文档,似乎在选择其中一个时有速度影响。实际上差别微不足道。
C++文档(以及GDI+总体),更加明确、精确,应该代替 .Net 文档。
如何进行:
我们可以将小源位图绘制到新的、较大的位图上,并将其分配给PictureBox.Image
属性。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 = Image.FromStream(New MemoryStream(File.ReadAllBytes("[File Path]")), True, False)
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
PictureBox1.Image
can change in my program, by clicking Button1
, should I change Private Sub Form1_Load(...) Handles MyBase.Load
to Private Sub Button1_Click(...) Handles Button1.Click
- TheRealSuicuneButton2_Click(...)
函数,它会检查是否有图像,如果有,会将其保存到由 SaveFileDialog1
指定的文件中,当然,前提是点击了“确定”,而不是“取消”。 - TheRealSuicuneImage.Width <> Image.Height
时存在问题。我认为这是因为 Function GetScaledImageRect(...)
没有返回正确的宽高比。 - TheRealSuicuneDrawImage
调用。InterpolationMode
和PixelOffsetMode
仍然适用;这就是子类化的原因。 - Nyerguds我曾经看到过一个解决方案,就是创建一个覆盖 PictureBox
的类,该类具有 InterpolationMode
作为类属性。然后,您只需要在UI上使用此类而不是 .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
会使其自动居中并扩展而不会在控件的设置大小中裁剪。
PixelOffsetMode.Half
重新定位像素偏移量,并将其设置为位图边界。但是,当在绘制前设置时,它将成为过程的一部分:没有速度降低。 - JimiMe.
不是必要的。 - TheRealSuicuneDrawing2D.Graphics.
怎么样? - TheRealSuicuneMe.
来引用本地属性总是更加清晰,尤其是当它们与所引用的类型名称相同时。如果您希望,我可以将所需的导入添加到代码中,但并不难找到。 - Nyerguds
Graphics.InterpolationMode
设置为NearestNeighbor
,以实现所需的结果。 - Visual VincentInterpolationMode
为NearestNeighbor
是必要的,但这还不够。你还需要设置e.Graphics.PixelOffsetMode = PixelOffsetMode.Half
。这是设计上的考虑,在NearestNeighbor
模式下,绘制的矩形会偏移半个像素:如果你不这样做,顶部/右侧和顶部/底部行中的像素将被裁剪。 - Jimi