如何在图像上绘制透明形状
How to draw a transparent shape over an Image
如何在图像上绘制形状以覆盖图像并使其透明?
就像下图中间那个透明的洞
编辑:
我画图一般用Graphics.FromImage(image)
,即
Graphics.FromImage(image).DrawRectangle(...)
但我想在图像中间制作一个透明的孔或矩形。
此方法利用两个 GraphicsPath objects and a TextureBrush 在 Bitmap 中绘制透明的 holes(请参阅 Worker methods
部分对此功能的描述)。
当我们要使用的位图被加载时,(这里,使用 File.ReadAllBytes() 和 MemoryStream
以避免锁定磁盘上的图像文件),它被分配给一个私有字段,drawingBitmap
然后克隆以创建 PictureBox.Image
属性 中显示的对象(原始图像总是以某种方式复制,我们从不修改它)。
▶ selectionRect
字段跟踪所选区域(使用不同的方法,如视觉示例所示)。
▶ shapeOfHole
字段是一个枚举器,用于指定 selectionRect
描述的形状类型(此处为矩形或椭圆形,但它可以是任何其他形状:使用 GraphicsPaths 作为容器可以更简单地添加多边形形状)。
▶ preserveImage
boolean 字段是一个选择器,用来判断是否在现有Image中添加新的holes或者每次创建一个新的洞。
这里的示例代码中,两个Button,btnLoadImage
和btnPaintHole
用于激活主要功能(加载和分配图像并在所选位图中绘制一个或多个孔)。
picCanvas
是用来显示Image的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模拟classic 透明背景 .
工人方法:
▶ DrawHole()
方法使用两个 GraphicsPath 对象。
imagePath
对象大小为原始Image,selectionPath
对象大小为当前选择区域(将缩放以匹配之后的图像实际大小)。
使用FillMode.Alternate mode, the imagePath.AddPath(selectionPath, True)方法将connect
参数设置为True
,指定添加的selectionPath
成为imagePath
。由于 FillMode.Alternate
是异或运算,我们 在 imagePath
中创建一个洞 。
Graphics.FillPath() method then uses a TextureBrush 用 Bitmap 对象填充 GraphicsPath,异或部分除外,然后它将包含抗锯齿透明区域(Graphics 对象使用 SmoothingMode.AntiAlias
模式).
▶ GetScaledSelectionRect()
方法使用了一个技巧来简化缩放图像(PictureBox 控件 SizeMode
最有可能设置为 PictureBoxSizeMode.Zoom
):它读取 .Net PictureBox class 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()
,如上所述,使用反射从 .净控.
由于此方法是从绘图过程中调用的,因此最好在自定义 PictureBox 控件中重新实现此方法,或者在不调用 the underlying method 的情况下执行计算(反射并不像有时宣传的那样慢,但当然比在这里直接使用一些数学运算要慢。
这里显示了一些可能的实现(例如):
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);
}
如何在图像上绘制形状以覆盖图像并使其透明?
就像下图中间那个透明的洞
编辑:
我画图一般用Graphics.FromImage(image)
,即
Graphics.FromImage(image).DrawRectangle(...)
但我想在图像中间制作一个透明的孔或矩形。
此方法利用两个 GraphicsPath objects and a TextureBrush 在 Bitmap 中绘制透明的 holes(请参阅 Worker methods
部分对此功能的描述)。
当我们要使用的位图被加载时,(这里,使用 File.ReadAllBytes() 和 MemoryStream
以避免锁定磁盘上的图像文件),它被分配给一个私有字段,drawingBitmap
然后克隆以创建 PictureBox.Image
属性 中显示的对象(原始图像总是以某种方式复制,我们从不修改它)。
▶ selectionRect
字段跟踪所选区域(使用不同的方法,如视觉示例所示)。
▶ shapeOfHole
字段是一个枚举器,用于指定 selectionRect
描述的形状类型(此处为矩形或椭圆形,但它可以是任何其他形状:使用 GraphicsPaths 作为容器可以更简单地添加多边形形状)。
▶ preserveImage
boolean 字段是一个选择器,用来判断是否在现有Image中添加新的holes或者每次创建一个新的洞。
这里的示例代码中,两个Button,btnLoadImage
和btnPaintHole
用于激活主要功能(加载和分配图像并在所选位图中绘制一个或多个孔)。
picCanvas
是用来显示Image的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模拟classic 透明背景 .
工人方法:
▶ DrawHole()
方法使用两个 GraphicsPath 对象。
imagePath
对象大小为原始Image,selectionPath
对象大小为当前选择区域(将缩放以匹配之后的图像实际大小)。
使用FillMode.Alternate mode, the imagePath.AddPath(selectionPath, True)方法将connect
参数设置为True
,指定添加的selectionPath
成为imagePath
。由于 FillMode.Alternate
是异或运算,我们 在 imagePath
中创建一个洞 。
Graphics.FillPath() method then uses a TextureBrush 用 Bitmap 对象填充 GraphicsPath,异或部分除外,然后它将包含抗锯齿透明区域(Graphics 对象使用 SmoothingMode.AntiAlias
模式).
▶ GetScaledSelectionRect()
方法使用了一个技巧来简化缩放图像(PictureBox 控件 SizeMode
最有可能设置为 PictureBoxSizeMode.Zoom
):它读取 .Net PictureBox class 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()
,如上所述,使用反射从 .净控.
由于此方法是从绘图过程中调用的,因此最好在自定义 PictureBox 控件中重新实现此方法,或者在不调用 the underlying method 的情况下执行计算(反射并不像有时宣传的那样慢,但当然比在这里直接使用一些数学运算要慢。
这里显示了一些可能的实现(例如):
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);
}