c# 使用 gdi32.dll 与 System.Drawing.Graphics 绘图

c# Drawing with gdi32.dll vs System.Drawing.Graphics

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

现在我有一个使用 System.Drawing.Graphics 和大量数学 FillPolygon 的版本,但是如果我在我已经绘制的区域上绘制,它只会使我绘制的区域更暗.它不会用 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

更新

在测试了您的一些代码后...我发现了一些奇怪的东西。

这是一种在不堆积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);
        }
    }
}

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

A ClearSave Button 很简单,前者 Clears 两个列表并失效,后者将使用 DrawToBitmap 保存控制..

注意:为了避免闪烁 make sure 画布面板是 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);
}

它还调用了'flattens'所有设置像素为叠加颜色的函数:

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,所以速度相当快..

这是实际操作:

更新二:

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

填充模式通过两次使用第一个元素存储在一个廉价 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());

还有一个演示: