Disable Image blending on a PictureBox

百般思念 提交于 2020-04-29 09:52:39

问题


In my Windows Forms program, I have a PictureBox. The bitmap image inside it is small, 5 x 5 pixels.
When assigned to a PictureBox, it becomes very blurry.

I tried finding something like blending mode, blurring mode, or anti-aliasing mode, but I had no luck.

Image1Image2

   This is what I want     This is not what I want

回答1:


The problem:
A Bitmap, with a size that is much smaller than the container used to show it, is blurred and the otherwise sharp edges of the well-defined areas of color are unceremoniously blended.
This is just the result of a Bilinear filter applied to a really small image (a few pixels) when zoomed in.

The desired result is to instead maintain the original color of the single pixels while the Image is enlarged.

To achieve this result, it's enough to set the Graphics object's InterpolationMode to:

e.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor

This filter, also known as Point Filter, simply selects a color which is the nearest to the pixel color that is being evaluated. When evaluating homogeneous areas of color, the result is the same pixel color for all the pixels.
There's just one problem, the default value of the Graphics object's PixelOffsetMode, which is:

e.Graphics.PixelOffsetMode = PixelOffsetMode.None

With this mode active, the outer pixels, corresponding to the top and left borders of an Image (in the normal image sampling) are drawn in the middle of the edges of the rectangular area defined by the container (the destination Bitmap or device context).

Because of this, since the source Image is small and its pixels are enlarged quite a lot, the pixels of the first horizontal and vertical lines are visibly cut in half.
This can be resolved using the other PixelOffsetMode:

e.Graphics.PixelOffsetMode = PixelOffsetMode.Half

This mode moves back the image's rendering position by half a pixel.
A sample image of the results can explain this better:

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

Note:
The .Net's MSDN Docs do not describe the PixelOffsetMode parameter very well. You can find 6, apparently different, choices. The Pixel Offset modes are actually only two:
PixelOffsetMode.None (the default) and PixelOffsetMode.Half.

PixelOffsetMode.Default and PixelOffsetMode.HighSpeed are the same as PixelOffsetMode.None.
PixelOffsetMode.HighQuality is the same as PixelOffsetMode.Half.
Reading the .Net Docs, there seems to be speed implications when choosing one over the other. The difference is actually negligible.

The C++ documentation about this matter (and GDI+ in general), is much more explicit and precise, it should be used instead of the .Net one.

How to proceed:

We could draw the small source Bitmap to a new, larger Bitmap and assign it to a PictureBox.Image property.

But, assume that the PictureBox size changes at some point (because the layout changes and/or because of DPI Awareness compromises), we're (almost) back at square one.

A simple solution is to draw the new Bitmap directly on the surface of a control and save it to disc when/if necessary.

This will also allow to scale the Bitmap when needed without losing quality:

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 is a helper method used to scale an Image inside a container:

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



回答2:


A solution I've seen around a couple of times is to make an overriding class of PictureBox which has the InterpolationMode as class property. Then all you need to do is use this class on the UI instead of .Net's own PictureBox, and set that mode to 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

As was remarked in the comments, for Nearest Neighbor mode, you need to set the PixelOffsetMode to Half. I honestly don't understand why they bothered exposing that rather than making it an automatic choice inside the internal rendering process.

The size can be controlled by setting the control's SizeMode property. Putting it to Zoom will make it automatically center and expand without clipping in the control's set size.



来源:https://stackoverflow.com/questions/54720916/disable-image-blending-on-a-picturebox

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!