【问题标题】:Erase Part of Bitmap with another Bitmap用另一个位图擦除位图的一部分
【发布时间】:2015-11-28 17:50:12
【问题描述】:

让我以一个真实的产品作为开头;你可能还记得在小学时,他们有草稿纸,基本上是一张彩虹色的纸,上面有一层黑色的薄膜。你会拿一个锋利的物体,撕掉黑色薄膜,露出彩色纸。

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

我的想法包括以下几点:

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

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

  • 我的问题- 我想不出任何方法来擦除(设置透明度)我单击的黑色矩形的一部分。

在我的一生中,我不知道有什么方法可以在图像中切出一个窗口。它几乎就像一个反向裁剪,我保留外部元素而不是内部元素,露出下面的纹理图像。

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

我应该提一下,我不想在每个像素的基础上更改 alpha。如果将其用作伪画家,它会大大降低程序的速度。但是,如果这是唯一的方法,请随意展示。

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

【问题讨论】:

  • Windows 窗体可能无法帮助您实现这一点,尽管您始终可以自定义绘制控件。 WPF 可能会为您提供更好的工具来完成这项工作,尽管我不确定这项具体任务。

标签: c# image winforms alpha erase


【解决方案1】:

这并不难:

  • 将彩色图像设置为PictureBoxBackgroundImage
  • 将黑色图像设置为其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 对象..:

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

【讨论】:

  • 这几乎就是我要找的所有东西,只有一个例外。有没有办法将 PNG 图像用作“画笔笔尖”?例如,使用鱼形刷子而不是圆笔来切割。我会满足于它只是每次单击鼠标时的一个剪切。
  • 这是完美的。非常感谢您的帮助。我现在可以拥抱你了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多