C# WinForm 实战:GDI+ 实现鼠标拖拽橡皮擦功能

C++
本文将介绍如何在 C# WinForms 项目中,利用 GDI+ 图形接口实现一个简单的“鼠标拖拽橡皮擦”效果。你可以把它应用在简易绘图程序、图片批注工具或自定义涂鸦组件中。

🎯 功能说明

我们实现的核心功能包括:
  • 在 PictureBox 上加载或绘制底图;
  • 按下鼠标左键并移动时,以鼠标当前位置为中心,清除一个小圆形区域(模拟橡皮擦);
  • 实时更新显示,形成平滑擦除效果。

📐 实现思路

  1. 使用 PictureBox承载图像,在其 Paint事件中用 GDI+ 绘制。
  2. 维护两个 Bitmap
    • baseImage:原始底图(不会被修改);
    • drawingImage:当前显示画面(可被擦除)。
  3. 在鼠标拖动时,在 drawingImage上用 Graphics.FillEllipse()填充背景色,覆盖原像素。
  4. 开启双缓冲,减少闪烁。

🔧 完整代码示例

1. 新建 WinForm 项目与界面

添加控件:
  • pictureBox(Dock = Fill)
  • 可选:按钮用于加载图片 / 重置画布
设置窗体属性:
public Form1()
{
    InitializeComponent();
    this.DoubleBuffered = true;        // 窗体双缓冲
    pictureBox.BackColor = Color.White;
}

2. 定义成员变量

private Bitmap baseImage;          // 原始底图
private Bitmap drawingImage;       // 正在绘制的画面
private bool isDrawing = false;
private Point lastPoint;

// 橡皮擦大小
private const int EraserSize = 20;

3. 初始化画布

提供两种方式:纯色画布或加载图片。
private void InitCanvas(int width, int height)
{
    baseImage = new Bitmap(width, height);
    using (var g = Graphics.FromImage(baseImage))
    {
        g.Clear(Color.White);
        // 可在此绘制测试图形
        g.DrawString("Try erasing here", 
            new Font("Arial", 16), Brushes.Black, 50, 50);
    }

    drawingImage = (Bitmap)baseImage.Clone();
    pictureBox.Image = drawingImage;
}

// 若从文件加载:
private void LoadImage(string path)
{
    baseImage?.Dispose();
    baseImage = new Bitmap(path);

    drawingImage?.Dispose();
    drawingImage = (Bitmap)baseImage.Clone();

    pictureBox.Image = drawingImage;
}

4. 鼠标事件处理

MouseDown

private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        isDrawing = true;
        lastPoint = e.Location;
        DoErase(e.Location);  // 立即擦除第一点
    }
}

MouseMove

private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
    if (!isDrawing) return;

    // 简单线性插值,使连续擦除更平滑
    var points = GetInterpolatedPoints(lastPoint, e.Location, EraserSize / 2f);
    foreach (var pt in points)
        DoErase(pt);

    lastPoint = e.Location;
}
辅助方法:两点间插值
private List<Point> GetInterpolatedPoints(Point p1, Point p2, float stepDist)
{
    var list = new List<Point>();
    float dx = p2.X - p1.X;
    float dy = p2.Y - p1.Y;
    float dist = (float)Math.Sqrt(dx * dx + dy * dy);
    int steps = Math.Max(1, (int)(dist / stepDist));

    for (int i = 0; i <= steps; i++)
    {
        float t = (float)i / steps;
        int x = (int)(p1.X + dx * t);
        int y = (int)(p1.Y + dy * t);
        list.Add(new Point(x, y));
    }
    return list;
}

MouseUp

private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
    isDrawing = false;
}

5. 执行擦除操作

private void DoErase(Point loc)
{
    if (drawingImage == null) return;

    // 计算橡皮擦矩形范围
    Rectangle eraseRect = new Rectangle(
        loc.X - EraserSize / 2,
        loc.Y - EraserSize / 2,
        EraserSize,
        EraserSize
    );

    // 限制在图片范围内
    eraseRect.Intersect(new Rectangle(0, 0, drawingImage.Width, drawingImage.Height));
    if (eraseRect.IsEmpty) return;

    using (var g = Graphics.FromImage(drawingImage))
    {
        // 用背景色填充圆形,实现“擦除”
        g.SmoothingMode = SmoothingMode.AntiAlias;
        g.FillEllipse(Brushes.White, eraseRect);
    }

    pictureBox.Invalidate(); // 触发重绘
}

6. Paint 事件(可选优化)

private void pictureBox_Paint(object sender, PaintEventArgs e)
{
    // 若已在 drawingImage 上绘制,此处可不做额外工作
    // 如需叠加临时预览,可在这里加代码
}

✅ 运行效果

运行后,按住鼠标左键在图片上拖动,会看到一个白色圆形跟随鼠标移动,所到之处露出下方背景色(或底图的白色部分),形成擦除效果。

💡 扩展建议

  • 透明 PNG 支持:若需真正透明擦除,可将 drawingImage设为带 Alpha 通道的 PixelFormat.Format32bppArgb,并用 Color.Transparent填充。
  • 纹理橡皮擦:改用 TextureBrush替代纯色填充。
  • 撤销/重做:记录每次擦除的区域或使用图层栈。
  • 性能优化:频繁擦除时可限制刷新频率,或用 Region局部刷新。

📦 完整窗体类代码(可直接粘贴测试)

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace WinformEraserDemo
{
    public partial class Form1 : Form
    {
        private Bitmap baseImage;
        private Bitmap drawingImage;
        private bool isDrawing = false;
        private Point lastPoint;
        private const int EraserSize = 25;

        public Form1()
        {
            InitializeComponent();
            DoubleBuffered = true;
            SetupPictureBox();
            InitCanvas(600, 400);
        }

        private void SetupPictureBox()
        {
            pictureBox = new PictureBox();
            pictureBox.Dock = DockStyle.Fill;
            pictureBox.BackColor = Color.LightGray;
            Controls.Add(pictureBox);

            pictureBox.MouseDown += pictureBox_MouseDown;
            pictureBox.MouseMove += pictureBox_MouseMove;
            pictureBox.MouseUp += pictureBox_MouseUp;
        }

        private void InitCanvas(int w, int h)
        {
            baseImage = new Bitmap(w, h);
            using (var g = Graphics.FromImage(baseImage))
            {
                g.Clear(Color.White);
                g.DrawRectangle(Pens.Blue, 100, 80, 300, 150);
                g.DrawString("Drag to erase", new Font("Arial", 18), Brushes.Red, 120, 110);
            }

            drawingImage = (Bitmap)baseImage.Clone();
            pictureBox.Image = drawingImage;
        }

        private void pictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                isDrawing = true;
                lastPoint = e.Location;
                DoErase(e.Location);
            }
        }

        private void pictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isDrawing) return;

            var pts = GetInterpolatedPoints(lastPoint, e.Location, EraserSize / 3f);
            foreach (var pt in pts)
                DoErase(pt);

            lastPoint = e.Location;
        }

        private void pictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            isDrawing = false;
        }

        private List<Point> GetInterpolatedPoints(Point a, Point b, float step)
        {
            var list = new List<Point>();
            float dx = b.X - a.X, dy = b.Y - a.Y;
            float len = (float)Math.Sqrt(dx * dx + dy * dy);
            int cnt = Math.Max(1, (int)(len / step));

            for (int i = 0; i <= cnt; i++)
            {
                float t = (float)i / cnt;
                list.Add(new Point((int)(a.X + dx * t), (int)(a.Y + dy * t)));
            }
            return list;
        }

        private void DoErase(Point pos)
        {
            if (drawingImage == null) return;

            var rect = new Rectangle(pos.X - EraserSize / 2, pos.Y - EraserSize / 2, EraserSize, EraserSize);
            rect.Intersect(new Rectangle(0, 0, drawingImage.Width, drawingImage.Height));
            if (rect.IsEmpty) return;

            using (var g = Graphics.FromImage(drawingImage))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.FillEllipse(Brushes.White, rect);
            }

            pictureBox.Invalidate(rect); // 局部刷新提升性能
        }

        // 重置画布
        private void btnReset_Click(object sender, EventArgs e)
        {
            drawingImage?.Dispose();
            drawingImage = (Bitmap)baseImage.Clone();
            pictureBox.Image = drawingImage;
        }
    }
}

免责声明:
1.本站所有源码支持免费互换,所有资源来源于网络,分享目的仅供大家学习和交流!不得使用于非法商业用途,不得违反国家法律。否则后果自负!(下载即表示同意遵守此条例!) 所有资源,不能保证完全去除后门和源码的完整性!(建议先用D盾 等查杀软件先扫描一遍!)且都不包含技术服务请大家谅解!
2.根据二○○二年一月一日《计算机软件保护条例》规定:为了学习和研究软件内含的设计思想和原理, 通过安装、显示、传输或者存储软件等方式使用软件的,可以不经软件著作权人许可, 不向其支付报酬!鉴于此,也希望大家按此说明研究!
3.本站所有源码均收集来源于网络,若此源码资源等文章侵犯您的合法权益,请私信联系站长,并于24小时内删除下架。
4.本站所有源码仅限学习,交流使用,请勿上线或非法使用,一切法律责任均于此站无关。
5.侵权联系邮箱:188773464@qq.com
6.若您最终确认购买,则视为您100%认同并接受以上所述全部内容。

源码下载网 C++ C# WinForm 实战:GDI+ 实现鼠标拖拽橡皮擦功能 https://svipm.com.cn/21981.html

相关文章

猜你喜欢