如何在图像上绘制透明形状

kam*_*and 3 .net vb.net graphics gdi+ winforms

如何在图像上绘制形状以覆盖那里的内容并使其透明?
就像下图中间的透明孔。

在此处输入图片说明

编辑:

我一般Graphics.FromImage(image)画图的时候用,即

Graphics.FromImage(image).DrawRectangle(...) 
Run Code Online (Sandbox Code Playgroud)

但我想在图像中间做一个透明的孔或矩形。

Jim*_*imi 5

此方法使用两个GraphicsPath对象和一个TextureBrush在 Bitmap 内绘制透明(请参阅Worker methods部分中对此功能的描述)。

当我们想要处理的 Bitmap 被加载时,(这里使用File.ReadAllBytes()和 aMemoryStream来避免锁定磁盘上的图像文件),它被分配给一个私有字段,drawingBitmap然后将其克隆以创建显示在PictureBox.Image属性(原始图像总是以某种方式复制,我们从不修改它)。

? 该selectionRect字段跟踪选择的区域(具有不同的装置,如所示的视觉样本中)。

? 该shapeOfHole字段是一个枚举,指定该形状的类型selectionRect被描述(在此,矩形或椭圆形,但它可以是任何其它形状:使用GraphicsPaths作为容器使得它更简单添加多边形形状)。

? 的preserveImage布尔字段是用于确定是否新选择的孔被添加到现有的图像或新的创建的每个时间。

在这里的示例代码,两个按键,btnLoadImagebtnPaintHole用于激活的主要功能(装载和分配所述图像和绘图的一个或多个中选择的位图)。

picCanvas 是用于显示图像的图片框。

Private drawingBitmap As Image = Nothing
Private selectionRect As RectangleF = New RectangleF(100, 100, 50, 50)
Private shapeOfHole As ShapeType = ShapeType.Rectangle
Private preserveImage as Boolean = False

Private Sub btnLoadImage_Click(sender As Object, e As EventArgs) Handles btnLoadImage.Click
    Dim imagePath As String = [Your Image Path]
    drawingBitmap = Image.FromStream(New MemoryStream(File.ReadAllBytes(imagePath)))
    picCanvas.Image?.Dispose()
    picCanvas.Image = DirectCast(drawingBitmap.Clone(), Bitmap)
End Sub

Private Sub btnPaintHole_Click(sender As Object, e As EventArgs) Handles btnPaintHole.Click
    Dim newImage As Image = Nothing
    If preserveImage AndAlso picCanvas.Image IsNot Nothing Then
        newImage = DrawHole(picCanvas.Image, picCanvas, selectionRect, shapeOfHole)
    Else
        newImage = DrawHole(drawingBitmap, picCanvas, selectionRect, shapeOfHole)
    End If

    If newImage IsNot Nothing Then
        picCanvas.Image?.Dispose()
        picCanvas.Image = newImage
    End If
End Sub
Run Code Online (Sandbox Code Playgroud)

功能的可视化示例

GraphicsPath 绘制孔

? 在用作PictureBox.BackgroundImage图像来模拟经典透明背景

工人方法

? 该DrawHole()方法使用两个GraphicsPath对象。
所述imagePath对象的尺寸为原始图像,所述selectionPath对象的大小作为当前选择区域(将被调整后,以匹配图像的实际尺寸)。

使用FillMode.Alternate模式,imagePath.AddPath(selectionPath, True)方法将connect参数设置为True,指定添加的selectionPath成为 的一部分imagePath。由于FillMode.Alternate是XOR运算,我们创建一个洞imagePath

然后,Graphics.FillPath()方法使用TextureBrush用 Bitmap 对象填充 GraphicsPath(XOR 部分除外),然后该对象将包含抗锯齿透明区域(Graphics 对象使用该SmoothingMode.AntiAlias模式)。

? 该GetScaledSelectionRect()方法使用一种技巧来简化缩放图像内选择矩形的未缩放坐标的计算(PictureBox 控件SizeMode最有可能设置为PictureBoxSizeMode.Zoom):它读取 .Net PictureBox 类ImageRectangle属性(谁知道为什么private),以确定图像缩放边界并根据此度量计算选择矩形的偏移量和缩放比例。

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Reflection

Friend Enum ShapeType
    Rectangle
    Ellipse
End Enum

Friend Function DrawHole(srcImage As Image, canvas As PictureBox, holeShape As RectangleF, typeOfShape As ShapeType) As Image
    Dim cropped = New Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb)

    Dim imageRect = New RectangleF(Point.Empty, srcImage.Size)
    Dim selectionRect = GetScaledSelectionRect(canvas, holeShape)

    Using tBrush = New TextureBrush(srcImage),
        imagePath = New GraphicsPath(FillMode.Alternate),
        selectionPath = New GraphicsPath(),
        g = Graphics.FromImage(cropped)

        Select Case typeOfShape
            Case ShapeType.Ellipse
                selectionPath.AddEllipse(selectionRect)
            Case ShapeType.Rectangle
                selectionPath.AddRectangle(selectionRect)
        End Select
        imagePath.AddRectangle(imageRect)
        imagePath.AddPath(selectionPath, True)
        g.SmoothingMode = SmoothingMode.AntiAlias
        g.FillPath(tBrush, imagePath)
        Return cropped
    End Using
End Function

Friend Function GetScaledSelectionRect(canvas As PictureBox, selectionRect As RectangleF) As RectangleF
    If canvas.Image Is Nothing Then Return selectionRect
    Dim flags = BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.GetProperty

    Dim imageRect = DirectCast(canvas.GetType().GetProperty("ImageRectangle", flags).GetValue(canvas), Rectangle)

    Dim scaleX = CSng(canvas.Image.Width) / imageRect.Width
    Dim scaleY = CSng(canvas.Image.Height) / imageRect.Height

    Dim selectionOffset = RectangleF.Intersect(imageRect, selectionRect)
    selectionOffset.Offset(-imageRect.X, -imageRect.Y)
    Return New RectangleF(selectionOffset.X * scaleX, selectionOffset.Y * scaleY,
        selectionOffset.Width * scaleX, selectionOffset.Height * scaleY)
End Function
Run Code Online (Sandbox Code Playgroud)

C# 版本

Private drawingBitmap As Image = Nothing
Private selectionRect As RectangleF = New RectangleF(100, 100, 50, 50)
Private shapeOfHole As ShapeType = ShapeType.Rectangle
Private preserveImage as Boolean = False

Private Sub btnLoadImage_Click(sender As Object, e As EventArgs) Handles btnLoadImage.Click
    Dim imagePath As String = [Your Image Path]
    drawingBitmap = Image.FromStream(New MemoryStream(File.ReadAllBytes(imagePath)))
    picCanvas.Image?.Dispose()
    picCanvas.Image = DirectCast(drawingBitmap.Clone(), Bitmap)
End Sub

Private Sub btnPaintHole_Click(sender As Object, e As EventArgs) Handles btnPaintHole.Click
    Dim newImage As Image = Nothing
    If preserveImage AndAlso picCanvas.Image IsNot Nothing Then
        newImage = DrawHole(picCanvas.Image, picCanvas, selectionRect, shapeOfHole)
    Else
        newImage = DrawHole(drawingBitmap, picCanvas, selectionRect, shapeOfHole)
    End If

    If newImage IsNot Nothing Then
        picCanvas.Image?.Dispose()
        picCanvas.Image = newImage
    End If
End Sub
Run Code Online (Sandbox Code Playgroud)

工人方法

注意:GetScaledSelectionRect()如上所述,使用反射private ImageRectangle从 .Net 控件读取 PictureBox属性。
由于此方法是从绘图过程中调用的,因此最好在自定义 PictureBox 控件中重新实现此方法,或者在不调用底层方法的情况下执行计算(反射并不像有时宣传的那么慢,但当然比直接使用一些数学,在这里)。

此处显示了一些可能的实现(例如):
从鼠标位置缩放和平移图像
使用 SizeMode.Zoom 在图片框中平移矩形位置

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Reflection

Friend Enum ShapeType
    Rectangle
    Ellipse
End Enum

Friend Function DrawHole(srcImage As Image, canvas As PictureBox, holeShape As RectangleF, typeOfShape As ShapeType) As Image
    Dim cropped = New Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb)

    Dim imageRect = New RectangleF(Point.Empty, srcImage.Size)
    Dim selectionRect = GetScaledSelectionRect(canvas, holeShape)

    Using tBrush = New TextureBrush(srcImage),
        imagePath = New GraphicsPath(FillMode.Alternate),
        selectionPath = New GraphicsPath(),
        g = Graphics.FromImage(cropped)

        Select Case typeOfShape
            Case ShapeType.Ellipse
                selectionPath.AddEllipse(selectionRect)
            Case ShapeType.Rectangle
                selectionPath.AddRectangle(selectionRect)
        End Select
        imagePath.AddRectangle(imageRect)
        imagePath.AddPath(selectionPath, True)
        g.SmoothingMode = SmoothingMode.AntiAlias
        g.FillPath(tBrush, imagePath)
        Return cropped
    End Using
End Function

Friend Function GetScaledSelectionRect(canvas As PictureBox, selectionRect As RectangleF) As RectangleF
    If canvas.Image Is Nothing Then Return selectionRect
    Dim flags = BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.GetProperty

    Dim imageRect = DirectCast(canvas.GetType().GetProperty("ImageRectangle", flags).GetValue(canvas), Rectangle)

    Dim scaleX = CSng(canvas.Image.Width) / imageRect.Width
    Dim scaleY = CSng(canvas.Image.Height) / imageRect.Height

    Dim selectionOffset = RectangleF.Intersect(imageRect, selectionRect)
    selectionOffset.Offset(-imageRect.X, -imageRect.Y)
    Return New RectangleF(selectionOffset.X * scaleX, selectionOffset.Y * scaleY,
        selectionOffset.Width * scaleX, selectionOffset.Height * scaleY)
End Function
Run Code Online (Sandbox Code Playgroud)