如何在图像上绘制透明形状 [英] How to draw a transparent shape over an Image

查看:69
本文介绍了如何在图像上绘制透明形状的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How do I draw a shape over an image to overwrite what's there and make it transparent?
Like the transparent hole in the middle of the image below.

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

I generally use Graphics.FromImage(image) when drawing, i.e.

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

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

but I want to make a transparent hole or rectangle in the middle of an image.

推荐答案

此方法利用了两个纹理画笔在位图内绘制透明的(请参见Worker methods部分中有关此功能的说明).

This method makes use of two GraphicsPath objects and a TextureBrush to draw transparent holes inside a Bitmap (see the description of this functionality in the Worker methods part).

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

When the Bitmap we want to work with is loaded, (here, using File.ReadAllBytes() and a MemoryStream to avoid locking the image file on disk), it's assigned to a private Field, drawingBitmap which is then cloned to create the object shown in a PictureBox.Image property (the original Image is always duplicated in a way or another, we never modify it).

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

► The selectionRect Field keeps track of the area selected (with different means, as shown in the visual sample).

shapeOfHole 字段是一个枚举器,用于指定selectionRect所描述的形状的类型(此处为Rectangle或Ellipse,但也可以是其他任何形状:作为容器的GraphicsPaths使添加多边形形状变得更加简单.

► The shapeOfHole Field is an Enumerator that specifies the type of the shape that selectionRect is describing (here, a Rectangle or an Ellipse, but it could be any other shape: using GraphicsPaths as containers makes it even simpler to add polygon shapes).

preserveImage 布尔字段是一个选择器,用于确定将新的添加到现有图像还是新的每次都会创建.

► The preserveImage boolean Field is a selector used to determine whether the new holes are added to the existing Image or a new hole is created each time.

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

In the sample code here, two Buttons, btnLoadImage and btnPaintHole are used to activate the main functions (loading and assigning the Image and drawing one or more holes in the selected Bitmap).

picCanvas 是用于显示图像的PictureBox.

picCanvas is the PictureBox used to show the Image.

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

功能的直观示例:

用作PictureBox.BackgroundImage 的图像用于模拟经典的透明背景 .

► The Image used as the PictureBox.BackgroundImage to simulate the classic transparent background .

工作方法:

DrawHole() 方法使用两个 GraphicsPath 对象.
imagePath 对象的大小设置为原始图像, selectionPath 对象的大小设置为当前选择区域(缩放后将与图像的实际大小匹配)

► The DrawHole() method uses two GraphicsPath objects.
The imagePath object is sized as the original Image, the selectionPath object is sized as the current selection area (will be scaled to match the Image real size after).

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

Using the FillMode.Alternate mode, the imagePath.AddPath(selectionPath, True) method sets the connect argument to True, specifying that the added selectionPath becomes part of imagePath. Since FillMode.Alternate is an XOR operation, we create a hole in imagePath.

Graphics.FillPath()方法然后使用 TextTureBrush 填充GraphicsPath ,除了带有位图"对象的异或"部分之外,该部分随后将包含一个抗锯齿的透明区域(图形"对象使用SmoothingMode.AntiAlias模式).

The Graphics.FillPath() method then uses a TextTureBrush to fill the GraphicsPath, except the XOR-ed part, with the Bitmap object, which will then contain an anti-aliased transparent area (the Graphics object uses the SmoothingMode.AntiAlias mode).

GetScaledSelectionRect() 方法使用一种技巧来简化缩放图像内部选择矩形的未缩放坐标的计算(PictureBox控件SizeMode最有可能设置为PictureBoxSizeMode.Zoom ):它读取.Net PictureBox类(谁知道为什么,private)

► The GetScaledSelectionRect() method uses a trick to simplify the calculation of the unscaled coordinates of the selection Rectangle inside a scaled Image (the PictureBox Control SizeMode is most probably set to PictureBoxSizeMode.Zoom): it reads the .Net PictureBox class (who knows why, private) ImageRectangle property to determine the Image scaled bounds and calculate the offset of the selection rectangle based on this measure.

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控件中重新实现此方法,或者执行计算而不调用

Note: GetScaledSelectionRect(), as described, uses Reflection to read the PictureBox private ImageRectangle property from the .Net control.
Since this method is called from the drawing procedure, it's probably better to re-implement this method in a custom PictureBox control, or perform the calculations without invoking the underlying method (reflection is not as slow as sometimes advertised, but it's of course slower than using some math directly, here).

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

Some possible implementations are shown (for example) here:
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);
}

这篇关于如何在图像上绘制透明形状的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆