【问题标题】:c# Drawing with gdi32.dll vs System.Drawing.Graphicsc# 使用 gdi32.dll 与 System.Drawing.Graphics 绘图
【发布时间】:2018-02-22 17:17:02
【问题描述】:

所以我有一些代码可以使用 gdi32.dll 在图片框顶部创建高亮效果,我想知道是否有更简单的方法可以使用 System.Drawing.Graphics 来实现它?基本上使用 gdi32.dll,我必须在绘制后捕获屏幕截图,将其发布到我的图片框,然后我可以绘制更多内容并更改我使用的笔的颜色。如果我只是尝试更改笔的粗细和颜色并再次在屏幕上绘制,如果更改了我已经绘制的内容。

现在我有一个版本,它使用System.Drawing.GraphicsFillPolygon 的大量数学,但如果我在我已经绘制的区域上绘制,它只会使我绘制的区域更暗。它不会对 gdi32.dll 执行此操作,只要您尚未使用鼠标对该区域进行着色,它就会进行着色。有什么建议吗?

public partial class Form9 : Form
{
    private bool is_mouse_down { get; set; } // Will check if the mouse is down or not.

    private Color Pen_Color = new Color();

    private int Pen_Type { get; set; }

    private int Thickness { get; set; }

    private bool Start { get; set; }

    List<Point> Points = new List<Point>();

    public Form9()
    {
        InitializeComponent();

        pictureBox1.Dock = DockStyle.Fill;

        Pen_Color = Color.Blue;
        Pen_Type = 13; // Type = 9 for highlighter, Type = 13 for solid.
        Thickness = 2;
        Start = false;

        pictureBox1.MouseDown += pictureBox1_MouseDown;
        pictureBox1.MouseUp += pictureBox1_MouseUp;
        pictureBox1.MouseMove += pictureBox1_MouseMove;
        pictureBox1.Paint += pictureBox1_OnPaint;
    }

    private void DrawHighlight(Graphics g, Point[] usePoints, int brushSize, int penType, Color brushColor)
    {
        int useColor = System.Drawing.ColorTranslator.ToWin32(brushColor);
        IntPtr pen = GetImage.GDI32.CreatePen(GetImage.GDI32.PS_SOLID, brushSize, (uint)useColor);
        IntPtr hDC = g.GetHdc();
        IntPtr xDC = GetImage.GDI32.SelectObject(hDC, pen);
        GetImage.GDI32.SetROP2(hDC, penType);//GetImage.GDI32.R2_MASKPEN);
        for (int i = 1; i <= usePoints.Length - 1; i++)
        {
            Point p1 = usePoints[i - 1];
            Point p2 = usePoints[i];
            GetImage.GDI32.MoveToEx(hDC, p1.X, p1.Y, IntPtr.Zero);
            GetImage.GDI32.LineTo(hDC, p2.X, p2.Y);
        }
        GetImage.GDI32.SetROP2(hDC, GetImage.GDI32.R2_COPYPEN);
        GetImage.GDI32.SelectObject(hDC, xDC);
        GetImage.GDI32.DeleteObject(pen);
        g.ReleaseHdc(hDC);
    }

    private void pictureBox1_OnPaint(object sender, PaintEventArgs e)
    {
        if (Start)
        {
            base.OnPaint(e);
            if (is_mouse_down)
            {
                DrawHighlight(e.Graphics, Points.ToArray(), Thickness, Pen_Type, Pen_Color);
            }
        }
    }

    private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
    {
        Points.Clear();

        Start = true;
        is_mouse_down = true;
    }

    private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
    {
        is_mouse_down = false;

        using (Image img = CaptureScreen())
        {
            try
            {
                if (System.IO.File.Exists(Program.ProgramPath + @"\Temp\marked.bmp"))
                {
                    System.IO.File.Delete(Program.ProgramPath + @"\Temp\marked.bmp");
                }
            }
            catch (Exception Ex)
            {
                MessageBox.Show("File Delete Error" + Environment.NewLine + Convert.ToString(Ex));
            }

            try
            {
                img.Save(Program.ProgramPath + @"\Temp\marked.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
            }
            catch (Exception Ex)
            {
                MessageBox.Show("Unable to save Screenshot" + Environment.NewLine + Convert.ToString(Ex));
            }
        }

        if (System.IO.File.Exists(Program.ProgramPath + @"\Temp\marked.bmp"))
        {
            using (FileStream fs = new System.IO.FileStream(Program.ProgramPath + @"\Temp\marked.bmp", System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.Read))
            {
                pictureBox1.Image = Image.FromStream(fs);
            }
        }

        pictureBox1.Invalidate(); // Refreshes picturebox image.
    }

    public Image CaptureScreen()
    {
        GetImage gi = new GetImage();

        return gi.CaptureWindow(GetImage.User32.GetDesktopWindow());
    }

    private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
        if (is_mouse_down == true) // Check to see if the mouse button is down while moving over the form.
        {
            Points.Add(new Point(e.X, e.Y));

            pictureBox1.Invalidate(); // Refreshes picturebox image.
        }
    }

以下是我所说的几张照片:

使用System.Drawing.Graphics

使用gdi32.dll

更新

在测试了你的一些代码之后……我得到了一些奇怪的东西。

【问题讨论】:

  • 不确定您想要或拥有什么效果,但当然您可以使用 半透明 颜色来突出显示控件上的部分。应该收集/纠正在 Paint 事件中绘制的点。
  • @TaW 我添加了一些照片示例。
  • 啊,看起来您正在将单独的笔画堆叠在一起。相反,您需要一口气完成所有工作,例如通过仅使用一个 DrawLines 调用或通过创建和填充一个 GraphicsPath 与尽可能多的图形,例如你需要的笔触......

标签: c# gdi system.drawing graphic


【解决方案1】:

这是一种在不堆积alpha的情况下绘制多个半透明颜色的独立笔划的方法:

它使用两个列表,一个用于笔划,一个用于当前笔划:

List<List<Point>> strokes = new List<List<Point>>();
List<Point> currentStroke = new List<Point>();

它们以通常的方式填充

private void canvas_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button.HasFlag(MouseButtons.Left))
    {
        currentStroke.Add(e.Location);
        if (currentStroke.Count == 1)
            currentStroke.Add(new Point(currentStroke[0].X + 1, 
                                        currentStroke[0].Y));
        canvasInvalidate();

    }
}

private void canvas_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button.HasFlag(MouseButtons.Left))
    {
        currentStroke.Add(e.Location);
        canvas.Invalidate();
    }
}

private void canvas_MouseUp(object sender, MouseEventArgs e)
{
    if (currentStroke.Count > 1)
    {
        strokes.Add(currentStroke.ToList());
        currentStroke.Clear();
    }
    canvas.Invalidate();
}

在这个版本中,我们通过在一次调用中绘制所有像素来避免重叠笔划的叠加效果。通过从笔划创建GraphicsPath 并填充它来绘制所有像素:

private void canvas_Paint(object sender, PaintEventArgs e)
{
    if (strokes.Count > 0 || currentStroke.Count > 0)
    {
        GraphicsPath gp = new GraphicsPath();
        gp.FillMode = FillMode.Winding;
        if (currentStroke.Count > 0)
        {
            gp.AddCurve(currentStroke.ToArray());
            gp.CloseFigure();
        }

        foreach (var stroke in strokes)
        {
            gp.AddCurve(stroke.ToArray());
            gp.CloseFigure();
        }
        using (SolidBrush b = new SolidBrush(Color.FromArgb(77, 177, 99, 22)))
        {
            e.Graphics.FillPath(b, gp);
        }
    }
}

请注意,在绘制时请注意不要移回当前笔划,否则划线的路径部分会产生孔洞!

一个ClearSaveButton很简单,前者Clears这两个列表并失效,后者会使用DrawToBitmap来保存控件..

注意:为避免闪烁,请使用make sure,canval 面板为DoubleBuffered

更新:

这是另一种使用Pen 绘制叠加层的方法。为了避免堆积 alpha 和改变颜色值(取决于PixelFormat),它使用一个快速函数来修改覆盖中的所有设置像素以具有相同的覆盖颜色:

笔画集合代码相同。 Paint 被简化为调用一个函数来创建一个覆盖位图并绘制它:

private void canvas_Paint(object sender, PaintEventArgs e)
{
    using (Bitmap bmp = new Bitmap(canvas.ClientSize.Width, 
                                   canvas.ClientSize.Height, PixelFormat.Format32bppPArgb))
    {
        PaintToBitmap(bmp);
        e.Graphics.DrawImage(bmp, 0, 0);
    }

第一个函数进行绘图,与之前非常相似,但使用了简单的笔划:

private void PaintToBitmap(Bitmap bmp)
{
    Color overlayColor = Color.FromArgb(77, 22, 99, 99);
    using (Graphics g = Graphics.FromImage(bmp))
    using (Pen p = new Pen(overlayColor, 15f))
    {
        p.MiterLimit = p.Width / 2;
        p.EndCap = LineCap.Round;
        p.StartCap = LineCap.Round;
        p.LineJoin = LineJoin.Round;
        g.SmoothingMode = SmoothingMode.AntiAlias;
        if (currentStroke.Count > 0)
        {
            g.DrawCurve(p, currentStroke.ToArray());
        }

        foreach (var stroke in strokes)
            g.DrawCurve(p, stroke.ToArray());
    }
    SetAlphaOverlay(bmp, overlayColor);
}

它还调用了将所有设置的像素“展平”为覆盖颜色的函数:

void SetAlphaOverlay(Bitmap bmp, Color col)
{
    Size s = bmp.Size;
    PixelFormat fmt = bmp.PixelFormat;
    Rectangle rect = new Rectangle(Point.Empty, s);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt);
    int size1 = bmpData.Stride * bmpData.Height;
    byte[] data = new byte[size1];
    System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size1);
    for (int y = 0; y < s.Height; y++)
    {
        for (int x = 0; x < s.Width; x++)
        {
            int index = y * bmpData.Stride + x * 4;
            if (data[index + 0] + data[index + 1] + data[index + 2] > 0)
            {

                data[index + 0] = col.B;
                data[index + 1] = col.G;
                data[index + 2] = col.R;
                data[index + 3] = col.A;
            }
        }
    }
    System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
    bmp.UnlockBits(bmpData);
}

它使用LockBits,所以它非常快..

它在行动:

更新 2:

只是为了好玩,这里仅对几行进行了扩展,添加了绘制填充曲线的选项:

填充模式通过两次使用第一个元素来存储在一个cheapo hack中。这些是变化:

MouseDown

        currentStroke.Add(e.Location);
        if (cbx_Fill.Checked) 
            currentStroke.Add(e.Location);

而在PaintToBitmap

        g.SmoothingMode = SmoothingMode.AntiAlias;
        if (currentStroke.Count > 0)
        {
            if (cbx_Fill.Checked)
                g.FillClosedCurve(b,  currentStroke.ToArray());
            else
                g.DrawCurve(p, currentStroke.ToArray());
        }

        foreach (var stroke in strokes)
            if (stroke[0]==stroke[1])
                g.FillClosedCurve(b,  stroke.ToArray());
            else
                g.DrawCurve(p, stroke.ToArray());

还有一个演示:

【讨论】:

  • 我不确定如何将其应用于我想要的。我将不得不更多地研究你在做什么。本质上我是在使用鼠标来绘制类似于 MS 截图工具的高光。
  • 我正在研究一种使用笔而不是图形路径的替代方案。对性能感到好奇..
  • 天哪……这更简单。我正要说什么让用户处理堆积的 alpha。谢谢!
  • 哇...我们需要成为朋友。
  • 嗯,看起来很有趣。您能否添加更多有关情况的信息,例如图像中有哪些控件以及您在什么上绘制......?
猜你喜欢
  • 2011-09-08
  • 2011-10-21
  • 1970-01-01
  • 2012-10-24
  • 2015-02-17
  • 2016-02-22
  • 1970-01-01
  • 2011-02-08
  • 2014-01-05
相关资源
最近更新 更多