跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

直线扫描转换算法(DDA、中点、Bresenham)实现

DDA、中点法和 Bresenham 三种直线扫描转换算法的原理与 C++ MFC 实现。通过代码对比了像素操作次数与耗时,结果显示 Bresenham 算法效率最高,DDA 最低。同时实现了用户交互绘图及颜色选择功能,并对后续多边形绘制等扩展进行了展望。

魔法巫师发布于 2026/3/29更新于 2026/9/1162 浏览
直线扫描转换算法(DDA、中点、Bresenham)实现

1. 实验目标

实现 DDA、MidPoint 和 Bresenham 三种直线绘制算法,并从像素操作次数、耗时等维度比较三种算法的效率;同时,尝试实现线形设计与多边形构造功能,优化用户交互体验。

2. 基本原理

(1)DDA 扫描转换算法

DDA 是计算机图形学中最早用于把连续直线段离散化为像素点的算法之一,又称数值微分法。它的核心思想是:在主方向(x 或 y)上以固定的单位步长前进,利用直线斜率的增量来逐步计算另一坐标的近似值,从而得到一系列整数像素坐标。

(2)中点画线法

中点画线法是计算机图形学中用于把连续直线离散化为像素点的经典算法,属于基于隐式直线方程的增量判别法。它通过判断当前像素右侧两候选点(正右方 P₁ 和右上方 P₂)的中点 M 相对于理想直线的位置,决定下一步取哪个像素,从而一步步逼近真实直线。

(3)Bresenham 画线算法

Bresenham 算法是一种仅使用整数运算的光栅化方法,用来在离散像素网格上逼近一条连续直线。它通过决策参数(误差项)来判断下一像素是保持当前行(或列)还是向上(或右)跨一步,从而在每一步只做一次加法和一次比较,避免了浮点运算和乘除操作。

3. 代码实现

DDA 算法
// DDA 算法
void CDrawLineView::DDALine(CDC *pDC, int x1, int y1, int x2, int y2, COLORREF color) {
    double dx, dy, e, x, y;
    dx = x2 - x1;
    dy = y2 - y1;
    // 取绝对值大的 dx 或 dy 赋值给 e,需增加 math.h 头文件
    e = (fabs(dx) > fabs(dy)) ? fabs(dx) : fabs(dy);
    dx /= e;
    dy /= e;
    x = x1;
    y = y1;
    for(int i = 0; i <= e; i++) {
        pDC->SetPixel((int)(x + 0.5), (int)(y + 0.5), color);
        x += dx;
        y += dy;
    }
}
中点画线算法
// 中点画线算法
void CDrawLineView::MidLine(CDC *pDC, int x0, int y0, int x1, int y1, COLORREF color) {
     a, b, delta1, delta2, d, x, y;
    
     (x0 == x1) {
         (y0 < y1) {
             ( i = y0; i <= y1; i++) pDC->(x0, i, color);
        }  {
             ( i = y1; i <= y0; i++) pDC->(x0, i, color);
        }
        ;
    }
    
     m = ((y1 - y0) <= (x1 - x0));
    
     (x0 > x1) {
        d = x0; x0 = x1; x1 = d;
        d = y0; y0 = y1; y1 = d;
    }
    a = y0 - y1;
    b = x1 - x0;
    x = x0;
    y = y0;
    pDC->(x, y, color);
    
     (m) {
        
         (y0 <= y1) {
            d =  * a + b;
            delta1 =  * a;
            delta2 =  * (a + b);
             (x < x1) {
                 (d < ) {x++; y++; d += delta2;}
                 {x++; d += delta1;}
                pDC->(x, y, color);
            }
        }
        
         {
            d =  * a - b;
            delta1 =  * a;
            delta2 =  * (a - b);
             (x < x1) {
                 (d < ) {x++; d += delta1;}
                 {x++; y--; d += delta2;}
                pDC->(x, y, color);
            }
        }
    }
    
     {
        
         (y0 <= y1) {
            d = a +  * b;
            delta1 =  * b;
            delta2 =  * (a + b);
             (y < y1) {
                 (d < ) {y++; d += delta1;}
                 {y++; x++; d += delta2;}
                pDC->(x, y, color);
            }
        }
        
         {
            d = a -  * b;
            delta1 =  * b;
            delta2 =  * (a - b);
             (y > y1) {
                 (d < ) {y--; x++; d += delta2;}
                 {y--; d += delta1;}
                pDC->(x, y, color);
            }
        }
    }
}
int
// 处理斜率无穷大
if
if
for
int
SetPixel
else
for
int
SetPixel
return
// 斜率断定,如绝对值大于 1,则 m 为 false,否则为 true
bool
abs
abs
// 确保 x1 大于 x0
if
SetPixel
// 斜率绝对值小于等于 1
if
// 第一种情况,y 值递增
if
2
2
2
while
if
0
else
SetPixel
// 第三种情况,y 值递减
else
2
2
2
while
if
0
else
SetPixel
// 斜率绝对值大于 1
else
// 第二种情况,y 值递增
if
2
2
2
while
if
0
else
SetPixel
// 第四种情况,y 值递减
else
2
-2
2
while
if
0
else
SetPixel
Bresenham 画线算法
// Bresenham 画线算法
void CDrawLineView::BHLine(CDC *pDC, int x1, int y1, int x2, int y2, COLORREF color) {
    int x, y, dx, dy, p;
    // 处理斜率无穷大的情况
    if (x1 == x2) {
        if (y1 < y2) {
            for (int i = y1; i < y2; i++) { pDC->SetPixel(x1, i, color); }
        } else {
            for (int i = y2; i < y1; i++) { pDC->SetPixel(x1, i, color); }
        }
        return;
    }
    // 斜率判定,当绝对值大于 1,m 为 false,否则为 true
    bool m = (abs(y2 - y1)) <= abs(x2 - x1);
    // 如果 x1 大于 x2 交换坐标值,确保 x2 大于 x1
    if (x1 > x2) {
        p = x1; x1 = x2; x2 = p;
        p = y1; y1 = y2; y2 = p;
    }
    x = x1;
    y = y1;
    dx = x2 - x1;
    dy = y2 - y1;
    // 斜率绝对值小于等于 1
    if (m) {
        // 第一种情况,y 值递增
        if (y1 <= y2) {
            p = (dy << 1) - dx; // 位运算符,提高效率
            while (x <= x2) {
                pDC->SetPixel(x, y, color);
                if (p < 0) { x++; p = p + (dy << 1); }
                else { x++; y++; p = p + ((dy-dx) << 1); }
            }
        }
        // 第三种情况,y 值递减
        else {
            p = -dx - (dy << 1);
            while (x <= x2) {
                pDC->SetPixel(x, y, color);
                if (p < 0) { x++; p = p - (dy << 1); }
                else { x++; y--; p = p - ((dy + dx) << 1); }
            }
        }
    }
    // 斜率绝对值大于 1
    else {
        // 第二种情况,y 值递增
        if (y1 <= y2) {
            p = (dx << 1) - dy;
            while (y <= y2) {
                pDC->SetPixel(x, y, color);
                if (p < 0) { y++; p = p + (dx << 1); }
                else { x++; y++; p = p + ((dx - dy) << 1); }
            }
        }
        // 第四种情况,y 值递减
        else {
            p = (dx << 1) + dy;
            while (y >= y2) {
                pDC->SetPixel(x, y, color);
                if (p < 0) { y--; p = p + (dx << 1); }
                else { x++; y--; p = p + ((dx + dy) << 1); }
            }
        }
    }
}

4. 效率比较

首先,在头文件中添加统计变量:

// 效率统计
static LONGLONG g_cntDDA;   // DDA 像素操作次数
static LONGLONG g_cntMid;   // MidPoint
static LONGLONG g_cntBres;  // Bresenham
static double g_msDDA;      // 累计耗时 (ms)
static double g_msMid;
static double g_msBres;

其次,在源文件顶部初始化变量,并增加计时函数 StartCounter、StopCounter。在三个算法的首尾添加计时函数、相关变量,每调用一次 SetPixel 就把局部计数器 ++cnt。

最后,在绘图结束后输出统计结果:

// 三种算法绘制直线
DDALine(pDC, 300, 600, 500, 100, RGB(255, 0, 0));
MidLine(pDC, 300, 600, 500, 100, RGB(255, 0, 0));
BHLine(pDC, 300, 600, 500, 100, RGB(255, 0, 0));

// 输出效率统计
CString msg;
msg.Format(_T("DDA: 像素=%I64d 耗时=%.3f ms 平均%.3f μs/像素\r\n")
           _T("Mid: 像素=%I64d 耗时=%.3f ms 平均%.3f μs/像素\r\n")
           _T("Bres: 像素=%I64d 耗时=%.3f ms 平均%.3f μs/像素\r\n"),
           g_cntDDA, g_msDDA, g_msDDA * 1000.0 / g_cntDDA,
           g_cntMid, g_msMid, g_msMid * 1000.0 / g_cntMid,
           g_cntBres, g_msBres, g_msBres * 1000.0 / g_cntBres);
OutputDebugString(msg);

调试运行代码,得到三组算法的效率比较结果如下: [图 2 算法效率比较]

5. 交互功能

增加功能控件实现交互。在菜单栏增加'画线'与'颜色'选项,并调整至合适位置。在画线选项下增加'DDALine'、'MidLine'、'BHLine'三个下拉子按钮。

在鼠标事件中进行模态判定,并关联算法的实现。增加颜色选择功能,优化用户体验。

// 鼠标左键按下
void CDrawLineView::OnLButtonDown(UINT nFlags, CPoint point) {
    m_ptStart = point;
    CView::OnLButtonDown(nFlags, point);
}

// 鼠标左键抬起
void CDrawLineView::OnLButtonUp(UINT nFlags, CPoint point) {
    m_ptEnd = point;
    CDC *pDC = GetDC();
    switch(m_mode) {
        case 1: DDALine(pDC, m_ptStart.x, m_ptStart.y, m_ptEnd.x, m_ptEnd.y, m_userColor); break;
        case 2: MidLine(pDC, m_ptStart.x, m_ptStart.y, m_ptEnd.x, m_ptEnd.y, m_userColor); break;
        case 3: BHLine(pDC, m_ptStart.x, m_ptStart.y, m_ptEnd.x, m_ptEnd.y, m_userColor); break;
        default: break;
    }
    ReleaseDC(pDC);
    CView::OnLButtonUp(nFlags, point);
}

// '颜色'主菜单功能实现
void CDrawLineView::OnColor() {
    CColorDialog dlg;
    if (IDOK == dlg.DoModal()) {
        m_userColor = dlg.GetColor();
    }
}

调试运行,使用不同的颜色与直线绘制算法进行绘图,绘图结果如下: [图 4 交互绘图结果]

6. 总结

通过此次实验,掌握了 DDA、MidLine、BHLine 三种算法的思想逻辑。在考虑四种不同的斜率的情况时,需要特别考虑与处理斜率不存在的情况。通过渲染的像素总点数与使用的时间,计算出单个像素渲染的平均时间。总体来看,Bresenham 画线算法效率优于中点画线法,DDA 直线扫描转换算法的效率最低。但这只尝试了绘制一两种情况,针对不同的情景,三种算法的效率优劣或发生改变。此外,还进行了基于用户体验优化的代码改进,用户可在应用程序中选择不同的算法与颜色进行线段绘制。

后续计划搭建线宽选项框,让用户选择不同的绘线颜色外,还可以选择不同的线宽。并使用堆栈存储鼠标点击事件的点集以及相关的事件函数,用以相邻多线段或多边形的绘制。并搭建预设样式的选项框,预设好各种基本图元,用户选择后便能进行多样式的绘制,以更贴合实际应用场景。

目录

  1. 1. 实验目标
  2. 2. 基本原理
  3. (1)DDA 扫描转换算法
  4. (2)中点画线法
  5. (3)Bresenham 画线算法
  6. 3. 代码实现
  7. DDA 算法
  8. 中点画线算法
  9. Bresenham 画线算法
  10. 4. 效率比较
  11. 5. 交互功能
  12. 6. 总结

更多推荐文章

查看全部
  • 模拟算法专题:替换所有问号、提莫攻击、Z 字形变换等 5 题解析
  • HarmonyOS6 RcButton 组件交互逻辑与事件处理机制
  • Github 2FA 认证失效解决方案及账号恢复指南
  • 飞算 JavaAI 智能编码工具功能解析与实战案例
  • 基于 AR 眼镜的饮水提醒应用开发实践
  • Meta Llama 系列深度拆解:全球开源大模型事实标准与 AI 普惠化
  • Apache IoTDB 跨「端 - 边 - 云」全场景部署与 DB+AI 实践
  • Python 与 PyCharm 环境搭建实战指南
  • OpenClaw WebUI 空白页问题修复指南
  • LIBERO:面向终身机器人学习的综合基准数据集
  • AIGC 技术解析:市场现状、挑战与实战代码示例
  • LINUX DO 社区 2025 年注册指南:填写自述与加入缘由
  • 嵌入式CAN通信:C++与SocketCAN的现代封装实践
  • 基于 Llama-Factory 的 OTA 行程规划微调实践
  • 本地化部署 GraphRAG+LangChain+Ollama 驱动 Llama 3.1 集成 Neo4j 实战
  • TurboQuant 与 RWKV-6:大模型部署的两条降本路线
  • deepyr 鸿蒙化适配指南:基于 Jaspr 构建类型安全 Web 应用
  • 本地部署 Kimi K2:llama.cpp、vLLM、Docker 三种方案
  • 大语言模型推理端架构与 llama.cpp 核心实现解析
  • Linux 网络基础入门:协议、分层与传输流程

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online