用另一个位图擦除部分位图
Erase Part of Bitmap with another Bitmap
让我用一个现实生活中的产品作为序言;你可能还记得在小学时,他们有草稿纸,基本上是由彩虹色 sheet 纸和上面有黑色薄膜组成的。你会拿一个尖锐的物体剥掉黑色的薄膜,露出彩色的纸。
我正在尝试使用图片框中的图像做同样的事情。
我的想法包括以下内容:
- 带纹理的图像。
- 图片框大小的黑色矩形。
- 圆形图片。
我想要实现的是打开一个程序,将一个图像绘制到一个图片框上,上面有一个黑色矩形。单击图片框后,它使用圆圈反转矩形的 alpha,我使用圆圈作为参考单击该矩形。
- 我的问题-
我想不出任何方法来擦除(设置透明度)我单击的黑色矩形的一部分。
对于我来说,我不知道有什么方法可以在图像中剪切 window。它几乎就像一个反向裁剪,我保留外部元素而不是内部元素,露出下面的纹理图像。
WinForms 不能这样做吗?我疯了吗?我应该放弃吗?
我应该提一下,我不想在每个像素的基础上更改 alpha。用作伪画家会大大降低程序速度。但是,如果这是唯一的方法,请随时展示。
这是我要实现的目标的图像:
这并不难:
- 将彩色图像设置为
PictureBox
的 BackgroundImage
。
- 设置黑色图像作为其
Image
。
- 并使用普通鼠标事件和透明
Pen
.. 绘制到图像中
我们需要一个点列表来使用DrawCurve
:
List<Point> currentLine = new List<Point>();
我们需要准备并清除黑色图层:
private void ClearSheet()
{
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
using (Graphics G = Graphics.FromImage(bmp)) G.Clear(Color.Black);
pictureBox1.Image = bmp;
currentLine.Clear();
}
private void cb_clear_Click(object sender, EventArgs e)
{
ClearSheet();
}
要绘制到 Image
中,我们需要使用关联的 Graphics
对象..:[=44=]
void drawIntoImage()
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
// we want the tranparency to copy over the black pixels
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
using (Pen somePen = new Pen(Color.Transparent, penWidth))
{
somePen.MiterLimit = penWidth / 2;
somePen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
somePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
somePen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
if (currentLine.Count > 1)
G.DrawCurve(somePen, currentLine.ToArray());
}
}
// enforce the display:
pictureBox1.Image = pictureBox1.Image;
}
常用的鼠标事件:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
currentLine.Add(e.Location);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
currentLine.Add(e.Location);
drawIntoImage();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
currentLine.Clear();
}
这就是所有需要的。确保保留 PB 的 SizeMode = Normal
否则像素将不匹配..!
请注意,当您想要获得柔化边缘、更多绘画工具、让简单的点击绘制一个点或撤消或其他更精细的细节时,会遇到一些挑战。但是基础一点都不难..
顺便说一下,更改 Alpha
与更改颜色通道 没有什么不同。
作为替代方案,您可能想玩 TextureBrush
:
TextureBrush brush = new TextureBrush(pictureBox1.BackgroundImage);
using (Pen somePen = new Pen(brush) )
{
// basically
// the same drawing code..
}
但我发现这相当慢。
更新:
使用 png
文件作为自定义提示有点困难;主要原因是画反了:我们不想画像素,我们想清除它们。 GDI+
不支持任何此类组合模式,因此我们需要在代码中完成。
为了快速,我们使用了两个技巧:LockBits
将尽可能快,并且将区域限制在我们的自定义画笔笔尖将防止浪费时间。
假设您有一个要使用的文件并将其加载到位图中:
string stampFile = @"yourStampFile.png";
Bitmap stamp = null;
private void Form1_Load(object sender, EventArgs e)
{
stamp = (Bitmap) Bitmap.FromFile(stampFile);
}
现在我们需要一个新的函数来将它绘制到我们的Image
;而不是 DrawCurve
我们需要使用 DrawImage
:
void stampIntoImage(Point pt)
{
Point point = new Point(pt.X - stamp.Width / 2, pt.Y - stamp.Height / 2);
using (Bitmap stamped = new Bitmap(stamp.Width, stamp.Height) )
{
using (Graphics G = Graphics.FromImage(stamped))
{
stamp.SetResolution(stamped.HorizontalResolution, stamped.VerticalResolution);
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
G.DrawImage(pictureBox1.Image, 0, 0,
new Rectangle(point, stamped.Size), GraphicsUnit.Pixel);
writeAlpha(stamped, stamp);
}
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
G.DrawImage(stamped, point);
}
}
pictureBox1.Image = pictureBox1.Image;
}
一些注意事项:我发现我不得不做一个明确的 SetResolution
,因为我用 Photoshop 处理的图章文件是 72dpi
,而我程序中的默认位图是 120dpi
。注意这些差异!
我开始通过复制 当前 图像的右侧部分来绘制位图。
然后我调用一个快速例程,将图章的 alpha 应用于它:
void writeAlpha(Bitmap target, Bitmap source)
{
// this method assumes the bitmaps both are 32bpp and have the same size
int Bpp = 4;
var bmpData0 = target.LockBits(
new Rectangle(0, 0, target.Width, target.Height),
ImageLockMode.ReadWrite, target.PixelFormat);
var bmpData1 = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, source.PixelFormat);
int len = bmpData0.Height * bmpData0.Stride;
byte[] data0 = new byte[len];
byte[] data1 = new byte[len];
Marshal.Copy(bmpData0.Scan0, data0, 0, len);
Marshal.Copy(bmpData1.Scan0, data1, 0, len);
for (int i = 0; i < len; i += Bpp)
{
int tgtA = data0[i+3]; // opacity
int srcA = 255 - data1[i+3]; // transparency
if (srcA > 0) data0[i + 3] = (byte)(tgtA < srcA ? 0 : tgtA - srcA);
}
Marshal.Copy(data0, 0, bmpData0.Scan0, len);
target.UnlockBits(bmpData0);
source.UnlockBits(bmpData1);
}
我使用一个简单的规则:通过源透明度降低目标不透明度并确保我们不会得到负值。您可能想尝试一下。
现在我们只需要调整 MouseMove
;对于我的测试,我添加了两个 RadioButtons
以在原始圆笔和自定义邮票笔尖之间切换:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (rb_pen.Checked)
{
currentLine.Add(e.Location);
drawIntoImage();
}
else if (rb_stamp.Checked) { stampIntoImage(e.Location); };
}
}
我没有用鱼,但你可以看到柔软的边缘:
更新 2:这是一个 MouseDown
允许简单的点击:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (rb_pen.Checked) currentLine.Add(e.Location);
else if (rb_stamp.Checked)
{
{ stampIntoImage(e.Location); };
}
}
让我用一个现实生活中的产品作为序言;你可能还记得在小学时,他们有草稿纸,基本上是由彩虹色 sheet 纸和上面有黑色薄膜组成的。你会拿一个尖锐的物体剥掉黑色的薄膜,露出彩色的纸。
我正在尝试使用图片框中的图像做同样的事情。
我的想法包括以下内容:
- 带纹理的图像。
- 图片框大小的黑色矩形。
- 圆形图片。
我想要实现的是打开一个程序,将一个图像绘制到一个图片框上,上面有一个黑色矩形。单击图片框后,它使用圆圈反转矩形的 alpha,我使用圆圈作为参考单击该矩形。
- 我的问题- 我想不出任何方法来擦除(设置透明度)我单击的黑色矩形的一部分。
对于我来说,我不知道有什么方法可以在图像中剪切 window。它几乎就像一个反向裁剪,我保留外部元素而不是内部元素,露出下面的纹理图像。
WinForms 不能这样做吗?我疯了吗?我应该放弃吗?
我应该提一下,我不想在每个像素的基础上更改 alpha。用作伪画家会大大降低程序速度。但是,如果这是唯一的方法,请随时展示。
这是我要实现的目标的图像:
这并不难:
- 将彩色图像设置为
PictureBox
的BackgroundImage
。 - 设置黑色图像作为其
Image
。 - 并使用普通鼠标事件和透明
Pen
.. 绘制到图像中
我们需要一个点列表来使用DrawCurve
:
List<Point> currentLine = new List<Point>();
我们需要准备并清除黑色图层:
private void ClearSheet()
{
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
using (Graphics G = Graphics.FromImage(bmp)) G.Clear(Color.Black);
pictureBox1.Image = bmp;
currentLine.Clear();
}
private void cb_clear_Click(object sender, EventArgs e)
{
ClearSheet();
}
要绘制到 Image
中,我们需要使用关联的 Graphics
对象..:[=44=]
void drawIntoImage()
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
// we want the tranparency to copy over the black pixels
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
using (Pen somePen = new Pen(Color.Transparent, penWidth))
{
somePen.MiterLimit = penWidth / 2;
somePen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
somePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
somePen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
if (currentLine.Count > 1)
G.DrawCurve(somePen, currentLine.ToArray());
}
}
// enforce the display:
pictureBox1.Image = pictureBox1.Image;
}
常用的鼠标事件:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
currentLine.Add(e.Location);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
currentLine.Add(e.Location);
drawIntoImage();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
currentLine.Clear();
}
这就是所有需要的。确保保留 PB 的 SizeMode = Normal
否则像素将不匹配..!
请注意,当您想要获得柔化边缘、更多绘画工具、让简单的点击绘制一个点或撤消或其他更精细的细节时,会遇到一些挑战。但是基础一点都不难..
顺便说一下,更改 Alpha
与更改颜色通道 没有什么不同。
作为替代方案,您可能想玩 TextureBrush
:
TextureBrush brush = new TextureBrush(pictureBox1.BackgroundImage);
using (Pen somePen = new Pen(brush) )
{
// basically
// the same drawing code..
}
但我发现这相当慢。
更新:
使用 png
文件作为自定义提示有点困难;主要原因是画反了:我们不想画像素,我们想清除它们。 GDI+
不支持任何此类组合模式,因此我们需要在代码中完成。
为了快速,我们使用了两个技巧:LockBits
将尽可能快,并且将区域限制在我们的自定义画笔笔尖将防止浪费时间。
假设您有一个要使用的文件并将其加载到位图中:
string stampFile = @"yourStampFile.png";
Bitmap stamp = null;
private void Form1_Load(object sender, EventArgs e)
{
stamp = (Bitmap) Bitmap.FromFile(stampFile);
}
现在我们需要一个新的函数来将它绘制到我们的Image
;而不是 DrawCurve
我们需要使用 DrawImage
:
void stampIntoImage(Point pt)
{
Point point = new Point(pt.X - stamp.Width / 2, pt.Y - stamp.Height / 2);
using (Bitmap stamped = new Bitmap(stamp.Width, stamp.Height) )
{
using (Graphics G = Graphics.FromImage(stamped))
{
stamp.SetResolution(stamped.HorizontalResolution, stamped.VerticalResolution);
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
G.DrawImage(pictureBox1.Image, 0, 0,
new Rectangle(point, stamped.Size), GraphicsUnit.Pixel);
writeAlpha(stamped, stamp);
}
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
G.DrawImage(stamped, point);
}
}
pictureBox1.Image = pictureBox1.Image;
}
一些注意事项:我发现我不得不做一个明确的 SetResolution
,因为我用 Photoshop 处理的图章文件是 72dpi
,而我程序中的默认位图是 120dpi
。注意这些差异!
我开始通过复制 当前 图像的右侧部分来绘制位图。
然后我调用一个快速例程,将图章的 alpha 应用于它:
void writeAlpha(Bitmap target, Bitmap source)
{
// this method assumes the bitmaps both are 32bpp and have the same size
int Bpp = 4;
var bmpData0 = target.LockBits(
new Rectangle(0, 0, target.Width, target.Height),
ImageLockMode.ReadWrite, target.PixelFormat);
var bmpData1 = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, source.PixelFormat);
int len = bmpData0.Height * bmpData0.Stride;
byte[] data0 = new byte[len];
byte[] data1 = new byte[len];
Marshal.Copy(bmpData0.Scan0, data0, 0, len);
Marshal.Copy(bmpData1.Scan0, data1, 0, len);
for (int i = 0; i < len; i += Bpp)
{
int tgtA = data0[i+3]; // opacity
int srcA = 255 - data1[i+3]; // transparency
if (srcA > 0) data0[i + 3] = (byte)(tgtA < srcA ? 0 : tgtA - srcA);
}
Marshal.Copy(data0, 0, bmpData0.Scan0, len);
target.UnlockBits(bmpData0);
source.UnlockBits(bmpData1);
}
我使用一个简单的规则:通过源透明度降低目标不透明度并确保我们不会得到负值。您可能想尝试一下。
现在我们只需要调整 MouseMove
;对于我的测试,我添加了两个 RadioButtons
以在原始圆笔和自定义邮票笔尖之间切换:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (rb_pen.Checked)
{
currentLine.Add(e.Location);
drawIntoImage();
}
else if (rb_stamp.Checked) { stampIntoImage(e.Location); };
}
}
我没有用鱼,但你可以看到柔软的边缘:
更新 2:这是一个 MouseDown
允许简单的点击:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (rb_pen.Checked) currentLine.Add(e.Location);
else if (rb_stamp.Checked)
{
{ stampIntoImage(e.Location); };
}
}