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

时间:2020-07-03 18:21:58

标签: .net vb.net winforms graphics gdi+

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

enter image description here

编辑:

我通常在绘制时使用Graphics.FromImage(image),即

Graphics.FromImage(image).DrawRectangle(...) 

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

1 个答案:

答案 0 :(得分:5)

此方法利用两个GraphicsPath对象和一个TextureBrush在位图内绘制透明的(请参见Worker methods中此功能的说明部分)。

在加载我们要使用的位图时(在这里,使用File.ReadAllBytes()MemoryStream以避免将图像文件锁定在磁盘上),将其分配给私有字段 drawingBitmap ,然后将其克隆以创建显示在PictureBox.Image属性中的对象(原始图像始终以某种方式重复,我们从不对其进行修改)。

selectionRect 字段跟踪所选区域(如可视示例中所示,采用不同的方式)。

shapeOfHole 字段是一个枚举器,用于指定selectionRect所描述的形状类型(此处为Rectangle或Ellipse,但可以是任何其他形状:将GraphicsPaths用作容器可以更轻松地添加多边形形状。

preserveImage 布尔字段是用于确定是将新的添加到现有图像还是新的孔< / em>每次创建。

在此处的示例代码中,使用两个按钮 btnLoadImage btnPaintHole 激活主要功能(加载和分配图像并在所选位图中绘制一个或多个

picCanvas 是用于显示图像的PictureBox。

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

功能的直观示例

GraphicsPath Draw Holes

Image used as the PictureBox.BackgroundImage模拟经典的透明背景

工作方法

DrawHole() 方法使用两个GraphicsPath对象。
imagePath 对象的大小与原始图像的大小相同, selectionPath 对象的大小与当前选择区域的大小(将缩放以匹配该图像)实际尺寸)。

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

然后,Graphics.FillPath()方法使用TextTureBrush用Bitmap对象填充图形路径(除XOR运算的部分),然后将包含一个抗锯齿的透明区域(Graphics对象使用SmoothingMode.AntiAlias模式。

GetScaledSelectionRect() 方法使用一种技巧来简化缩放图像内部选择矩形的未缩放坐标的计算(很可能设置了PictureBox控件SizeModePictureBoxSizeMode.Zoom):它读取.Net PictureBox类(谁知道为什么,privateImageRectangle属性来确定图像缩放范围,并根据此度量计算选择矩形的偏移量

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

C#版本

private Image drawingBitmap = null;
private RectangleF selectionRect = new RectangleF(100, 100, 50, 50);
private ShapeType shapeOfHole = ShapeType.Rectangle;
private bool preserveImage = false;

private void btnLoadImage_Click(object sender, EventArgs e)
{
    string imagePath = [Your Image Path];
    drawingBitmap = Image.FromStream(new MemoryStream(File.ReadAllBytes(imagePath)));
    picCanvas.Image?.Dispose();
    picCanvas.Image = drawingBitmap.Clone() as Bitmap;
}

private void btnPaintHole_Click(object sender, EventArgs e)
{
    Image newImage = null;
    if (preserveImage && picCanvas.Image != null) {
        newImage = DrawHole(picCanvas.Image, picCanvas, selectionRect, shapeOfHole);
    }
    else {
        newImage = DrawHole(drawingBitmap, picCanvas, selectionRect, shapeOfHole);
    }

    if (newImage != null) {
        picCanvas.Image?.Dispose();
        picCanvas.Image = newImage;
    }
}

工作方法

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

此处显示(例如)一些可能的实现:
Zoom and translate an Image from the mouse location
Translate Rectangle Position in a Picturebox with SizeMode.Zoom

internal enum ShapeType {
    Rectangle,
    Ellipse
}

internal Image DrawHole(Image srcImage, PictureBox canvas, RectangleF holeShape, ShapeType typeOfShape)
{
    var cropped = new Bitmap(srcImage.Width, srcImage.Height, PixelFormat.Format32bppArgb);
    var imageRect = new RectangleF(Point.Empty, srcImage.Size);
    RectangleF selectionRect = GetScaledSelectionRect(canvas, holeShape);

    using (var tBrush = new TextureBrush(srcImage))
    using (var imagePath = new GraphicsPath(FillMode.Alternate))
    using (var selectionPath = new GraphicsPath())
    using (var g = Graphics.FromImage(cropped)) {

        switch (typeOfShape) {
            case ShapeType.Ellipse:
                selectionPath.AddEllipse(selectionRect);
                break;
            case ShapeType.Rectangle:
                selectionPath.AddRectangle(selectionRect);
                break;
        }
        imagePath.AddRectangle(imageRect);
        imagePath.AddPath(selectionPath, true);

        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.FillPath(tBrush, imagePath);
        return cropped;
    }
}

internal RectangleF GetScaledSelectionRect(PictureBox canvas, RectangleF selectionRect)
{
    if (canvas.Image == null) return selectionRect;
    var flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty;

    var imageRect = (Rectangle)canvas.GetType().GetProperty("ImageRectangle", flags).GetValue(canvas);
    var scaleX = (float)canvas.Image.Width / imageRect.Width;
    var scaleY = (float)canvas.Image.Height / imageRect.Height;

    var 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);
}