用另一个位图擦除部分位图

时间:2015-09-03 03:18:39

标签: c# image winforms alpha erase

让我以真实的产品为前言;你可能还记得在小学时,他们的草稿纸基本上是一张彩虹色的纸,上面是黑色的薄膜。你会拿一个尖锐的物体剥掉黑色胶片,露出彩色纸。

我试图在图片框中使用图片做同样的事情。

我的想法包含以下内容:

  • 纹理图像。
  • 图片框大小的黑色矩形。
  • 圆圈图片。

我想要实现的是打开一个程序,将图像绘制到一个带有黑色矩形的图片框上。单击图片框后,它使用圆圈反转矩形的alpha,我点击该圆圈作为参考。

  • 我的问题 - 我无法找到任何方法来擦除(设置透明度)我点击的黑色矩形的一部分。

对于我的生活,我不知道在图像中切割窗口的任何方法。它几乎像一个反向裁剪,我保留外部元素而不是内部,露出下面的纹理图像。

WinForms可以不这样做吗?我疯了吗?我应该放弃吗?

我应该提一下,我不想在每像素像素的基础上更改alpha。它会使程序放慢太多而无法用作伪画家。如果这是唯一的方式,请随意展示。

以下是我想要实现的目标的图像:

enter image description here

1 个答案:

答案 0 :(得分:7)

这不是很难:

  • 将彩色图像设为PictureBox的{​​{1}}。
  • 将黑色图像设为BackgroundImage
  • 使用正常的鼠标事件和透明的Image ..
  • 绘制到图像中

enter image description here

我们需要一个点列表来使用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

通常的鼠标事件:

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

这就是所需要的。确保保持PB的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(); } 或者像素不匹配..!

请注意,当您想要获得柔化边缘,更多绘画工具,让简单点击绘制点或撤消或其他更精细的细节时,有一些挑战。但基础知识并不难......

顺便说一句,更改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,我程序中的默认位图是72dpi。注意这些差异!

我开始通过复制 当前图像的右侧部分来绘制位图。

然后我调用一个快速例程,将例章的alpha应用于它:

120dpi

我使用一个简单的规则:通过源透明度降低目标不透明度,并确保我们不会消极。你可能想要玩它。

现在我们所需要的是调整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

我没有使用鱼,但你可以看到柔软的边缘:

enter image description here

更新2 :这是一个允许简单点击的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); }; } }

MouseDown