【问题标题】:How to draw a transparent shape over an Image如何在图像上绘制透明形状
【发布时间】:2020-10-24 11:57:00
【问题描述】:

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

编辑:

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

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

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

【问题讨论】:

  • 图片在哪里?在画框里?是直接画在表格上的吗?此外,在另一个上绘制图像以使后者透明似乎并不可行。
  • clipping 看起来像你想要的吗?
  • 如果您使用 Circle 构建 GraphicsPath,则可以从该 GraphicsPath 创建一个区域。之后,您可以将该区域传递给ExcludeClip,这样当您绘制苹果时,它就不会出现在该圆圈中...
  • 如果您希望表单本身在该位置完全消失,那么您可以创建一个表示整个矩形的区域,减去圆,然后将表单的 Region() 属性设置为该值。您将使用 Region.Exclude() 和一些 GraphicsPath 实例。
  • 这个概念在这里应用:How to crop an elliptical region of an Image with smooth borders(这取决于你先画什么)。不使用区域和剪辑,以提供更好的抗锯齿效果。

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


【解决方案1】:

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

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

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

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

preserveImage boolean Field 是一个选择器,用于确定新的holes是添加到现有的Image还是新的hole 每次都会创建。

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

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

功能的视觉示例

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

工作方法

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

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

Graphics.FillPath() 方法然后使用TextureBrush 用 Bitmap 对象填充 GraphicsPath,除了 XOR ed 部分,该对象将包含一个消除锯齿的透明区域(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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-06
    • 2014-03-13
    • 2020-08-17
    • 1970-01-01
    • 2017-09-22
    • 1970-01-01
    相关资源
    最近更新 更多