C#自定义画刷实现:基于DDA直线算法的绘制原理与代码

月亮慢半拍
月亮慢半拍
发布时间:2026-10-15| 4人浏览过
原创

C#并未直接提供类似图像编辑软件的高级画刷类,但可以通过图像叠加和坐标算法自行实现。本文将解析基于图片移位重叠的画刷原理,介绍如何使用DDA(数字微分分析器)算法计算鼠标路径上的连续点,并通过Mouse事件控制固定间隔绘制,从而完成具有间隔效果的自定义画刷。

结果或目标

实现一个自定义画刷功能,用户可以在界面上拖动鼠标,根据设定的间隔和尺寸,像Photoshop中的纹理画笔一样绘制图案。该方法适用于需要模拟手绘质感或非连续笔触的工具开发。

准备条件

  • 开发环境:Windows Forms 或 WPF 应用程序框架。
  • 依赖库:System.Drawing 用于图形处理,System.Collections.Generic 用于列表管理。
  • 素材:一张半透明或带边缘效果的纹理图片(.png或.jpg)作为“笔刷”源图。

真实操作顺序

第1步:理解画刷底层原理

自定义画刷的本质并非像素级别的复杂混合,而是图片的移位重叠显示。通常使用的笔刷图片是半透明的,只有特定区域有颜色。当鼠标移动时,程序不需要在每个像素点都绘制图片,而是计算移动轨迹,每隔固定距离(间隔)在轨迹上放置一次图片。

观察上图可知,通过调整间隔参数,可以清晰看到原图的重复排列效果。除了基于空间位移的画刷,还有基于时间(如喷枪随机分布)的效果,但本文聚焦于最通用的基于位置的纹理绘制。

第2步:使用DDA算法获取路径点

当鼠标快速移动时,相邻两个 MouseMove 事件的坐标点可能不相邻,直接连接会产生空隙。因此,需要一种算法来计算起始点和结束点之间的所有整数坐标点,确保绘制连续。

这里采用经典的 DDA (Digital Differential Analyzer) 直线生成算法。该算法通过计算斜率和增量,逐步生成直线上均匀分布的点。

/// <summary>
/// 使用DDA算法顺序获取两点间直线上的所有点
/// </summary>
private List<Point> GetPointsOnLine(Point pStart, Point pEnd)
{
    List<Point> linePoint = new List<Point>();
    
    // 如果起点和终点重合,仅返回起点
    if (pStart.X == pEnd.X && pStart.Y == pEnd.Y)
    {
        linePoint.Add(pStart);
        return linePoint;
    }

    int x0 = pStart.X, y0 = pStart.Y;
    int x1 = pEnd.X, y1 = pEnd.Y;
    
    int dx = x1 - x0;
    int dy = y1 - y0;
    
    // 确定最大步数,防止过密或过疏
    int steps = Math.Max(Math.Abs(dx), Math.Abs(dy));
    
    float xIncrement = (float)dx / steps;
    float yIncrement = (float)dy / steps;
    
    float x = x0;
    float y = y0;
    
    for (int k = 0; k <= steps; k++)
    {
        // 四舍五入取整,添加当前点到列表
        linePoint.Add(new Point((int)(x + 0.5), (int)(y + 0.5)));
        x += xIncrement;
        y += yIncrement;
    }
    
    return linePoint;
}

第3步:处理鼠标交互事件

需要在主绘图区域(如 PictureBox 或 Panel)绑定以下三个事件:

  • MouseDown:标记开始绘制,清空历史点集,记录起始位置。
  • MouseMove:核心逻辑。判断是否正在绘制,获取当前点与前一点的中间路径点,按设定间隔执行绘制。
  • MouseUp:标记停止绘制,清理临时数据。

以下是完整的事件处理逻辑实现:

// 成员变量定义
bool isDrawing = false;       // 是否正在绘制状态
Point lastPoint = Point.Empty; // 上一帧的鼠标位置
List<Point> pathPoints = new List<Point&gt(); // 当前笔画的所有累积点
Bitmap brushImage;             // 预设的画刷图片对象
int brushSpacing = 5;          // 画刷间隔(像素),越小越密集
int brushSize = 30;            // 画刷直径/大小

// 1. 鼠标按下:开始绘制
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        isDrawing = true;
        lastPoint = e.Location;
        pathPoints.Clear();
        pathPoints.Add(lastPoint);
    }
}

// 2. 鼠标移动:实时绘制
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (!isDrawing) return;

    Point currentPoint = e.Location;
    PictureBox pb = sender as PictureBox;
    
    // 刷新画布以清除上一帧的临时痕迹(如果需要擦除效果)或直接叠加
    // 注意:实际生产中建议使用双缓冲或在内存位图上直接修改后一次性赋值
    // 此处为简化演示,直接操作 Graphics
    
    // 获取两点间的所有插值点
    List<Point> linePoints = GetPointsOnLine(lastPoint, currentPoint);
    
    // 移除第一个点,避免重复绘制(lastPoint已在上一帧或初始状态绘制)
    if (linePoints.Count > 0)
        linePoints.RemoveAt(0);

    // 将新计算的点添加到累积路径中
    pathPoints.AddRange(linePoints);

    // 创建 Graphics 对象进行绘制
    using (Graphics gs = Graphics.FromImage(pb.Image))
    {
        // 遍历累积路径,按间隔绘制画刷
        // penmod 用于处理最后一笔不完整的间隔
        int mod = pathPoints.Count % brushSpacing;
        
        for (int i = mod; i < pathPoints.Count; i += brushSpacing)
        {
            Point pt = pathPoints[i];
            // 绘制中心点周围的正方形区域,可根据图片形状调整矩形
            gs.DrawImage(brushImage, pt.X - brushSize/2, pt.Y - brushSize/2, brushSize, brushSize);
        }
    }

    // 更新最后位置
    lastPoint = currentPoint;
}

// 3. 鼠标抬起:结束绘制
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    if (isDrawing)
    {
        isDrawing = false;
        // 可选:在此处将最终图像保存到文件或继续编辑
        pathPoints.Clear(); // 笔触结束,清空路径缓存
    }
}

完成判断与优化

当代码运行后,在 PictureBox 中按住左键拖动,应能看到半透明纹理按照设定的 brushSpacing 间距排列。上图展示了不同间隔(1, 10, 20, 40, 80)下的视觉效果。间隔数值越大,笔触越稀疏;越小则越接近实线。

进一步优化的方向

  • 角度跟随:目前的实现是垂直向下绘制。若要更逼真,可根据向量 (currentPoint - lastPoint) 计算旋转角度,对 brushImage 进行仿射变换或使用 Matrix 旋转后再绘制。
  • 性能优化:上述示例直接在 Control 上绘制较为简单,但在长距离绘画时会导致闪烁。建议将所有绘制操作在一个离屏 Bitmap 上进行,最后将 Bitmap 赋值给 PictureBox,并启用 DoubleBuffered。

以上就是通过 C# 结合 DDA 算法实现自定义画刷的详细内容,更多关于 AI 编程与图像处理技术的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。