计算机图形学——利用MFC库绘制直线(DDA算法和Bresenham算法)

问题描述

利用MFC库实现直线的绘制(分别使用DDA算法和Bresenham算法)

求解思路

创建MFC APP项目,选择Dialog Based模板,如下:

在这里插入图片描述

1. 对话框设计

利用TOOL BOX中的工具修改主对话框(如果TOOL BOX没有的话,在view中打开)

  • 添加两个绘图框,分别对应DDA算法和Bresenham算法,ID为IDC_STATIC_DRAW_AREA1IDC_STATIC_DRAW_AREA2

  • 添加两个按钮,ID为IDC_BUTTON_DDAIDC_BUTTON_BRESENHAM

  • 添加四个文本编辑框便于输入坐标值,ID为IDC_EDIT_START1, IDC_EDIT_START2, IDC_EDIT_END1, IDC_EDIT_END2

  • 添加静态文本框

注:修改ID需要选中对象,点击属性,然后在属性中修改ID

效果如下图:

在这里插入图片描述

2. 算法设计

DDA算法

算法描述略

Bresenham算法

  1. 获取起点和终点坐标:首先获取直线的起点坐标 (x0, y0) 和终点坐标 (x1, y1)

  2. 计算dx和dy:计算 dx = x1 - x0dy = y1 - y0

  3. 计算p0初始值p_0 = 2*dy - dx,这是初始的决策参数。p_k 用于决定下一个要绘制的像素的位置

  4. Bresenham主循环:在每一步中,根据 p_k 的值来决定下一个要绘制的像素

  • 如果 p_k 小于0,则下一个像素位于 (x_k + 1, y_k),并且 p_{k+1} = p_k + 2*dy

  • 如果 p_k 大于等于0,则下一个像素位于 (x_k + 1, y_k + 1),并且 p_{k+1} = p_k + 2*dy - 2*dx

  1. 重复绘制直线:重复步骤4的过程,共计算 dx 次,以绘制整条直线

程序代码

需要修改LineDlg.cppLineDlg.h两个文件

注意:在点击按钮获取坐标后,需要将坐标映射到两个绘图框内,并在坐标超出绘图框范围时给出提示

1. LineDlg.cpp

主要需要添加四个函数,分别为两种算法绘制的函数和对应的按钮点击函数,实现代码如下:

DDA算法

// 点击DDA按钮将执行的操作
void CLineDlg::OnBnClickedButtonDDA() {
    // 从编辑框中获取坐标
    CString strX0, strY0, strX1, strY1;
    // 起点坐标
    GetDlgItemText(IDC_EDIT_START1, strX0);
    GetDlgItemText(IDC_EDIT_START2, strY0);
    // 终点坐标
    GetDlgItemText(IDC_EDIT_END1, strX1);
    GetDlgItemText(IDC_EDIT_END2, strY1);

    int startX = _ttoi(strX0);
    int startY = _ttoi(strY0);
    int endX = _ttoi(strX1);
    int endY = _ttoi(strY1);

    // 获取绘图框的边界矩形
    CRect rectDrawArea1;
    GetDlgItem(IDC_STATIC_DRAW_AREA1)->GetClientRect(rectDrawArea1);

    // 映射坐标到绘图框的坐标系
    startX -= rectDrawArea1.left;
    startY -= rectDrawArea1.top;
    endX -= rectDrawArea1.left;
    endY -= rectDrawArea1.top;

    // 在绘制直线之前检查坐标是否在绘图框内
    if (startX >= 0 && startX < rectDrawArea1.Width() &&
        startY >= 0 && startY < rectDrawArea1.Height() &&
        endX >= 0 && endX < rectDrawArea1.Width() &&
        endY >= 0 && endY < rectDrawArea1.Height()) {
        // 坐标在绘图框内,可以绘制直线
        // 调用DDA函数
        DrawLineDDA(startX, startY, endX, endY);
    } else {
        // 坐标超出了绘图框的边界,错误提示
        CString message = _T("坐标超出绘图框的边界,请重新输入");
        CString caption = _T("错误");
        MessageBox(message, caption, MB_ICONERROR);
    }
}
// DDA算法
void CLineDlg::DrawLineDDA(int x1, int y1, int x2, int y2) {
    CDC *pDC = GetDlgItem(IDC_STATIC_DRAW_AREA1)->GetDC();

    int dx = x2 - x1;
    int dy = y2 - y1;
    int L = abs(dx) > abs(dy) ? abs(dx) : abs(dy);

    float dx = static_cast<float>(dx) / static_cast<float>(L);
    float dy = static_cast<float>(dy) / static_cast<float>(L);

    float x = static_cast<float>(x1);
    float y = static_cast<float>(y1);

    pDC->MoveTo(x1, y1);

    for (int i = 0; i < L; i++) {
        x += dx;
        y += dy;

        pDC->LineTo(static_cast<int>(x), static_cast<int>(y));
    }
}

Bresenham算法

// Bresenham算法
void CLineDlg::DrawLineBresenham(int x1, int y1, int x2, int y2) {

        // 计算dx和dy
        int dx = abs(x2 - x1);
        int dy = abs(y2 - y1);

        // 计算步长方向,根据步长方向来决定递增还是递减
        int stepX = (x1 < x2) ? 1 : -1;
        int stepY = (y1 < y2) ? 1 : -1;

        // 初始化p0和起始点
        int p = 2 * dy - dx;
        int x = x1;
        int y = y1;

        // 获取IDC_STATIC_DRAW_AREA2
        CStatic* pStaticDrawArea2 = (CStatic*)GetDlgItem(IDC_STATIC_DRAW_AREA2);
        CDC* pDC = pStaticDrawArea2->GetDC();

        // 绘制起点
        pDC->SetPixel(x, y, RGB(0, 0, 0)); // 在指定位置绘制像素点

        // 循环绘制直线
        for (int k = 0; k < dx; k++)
        {
            // 根据pk决定下一个点的位置
            if (p < 0)
            {
                x += stepX;
                p += 2 * dy;
            }
            else
            {
                x += stepX;
                y += stepY;
                p += 2 * (dy - dx);
            }

            // 绘制像素点
            pDC->SetPixel(x, y, RGB(0, 0, 0));
        }

        // 释放设备
        pStaticDrawArea2->ReleaseDC(pDC);
}
// 点击Bresenham按钮将执行的操作
void CLineDlg::OnBnClickedButtonBresenham() {
    // 从编辑框中获取坐标
    CString strX0, strY0, strX1, strY1;
    // 起点坐标
    GetDlgItemText(IDC_EDIT_START1, strX0);
    GetDlgItemText(IDC_EDIT_START2, strY0);
    // 终点坐标
    GetDlgItemText(IDC_EDIT_END1, strX1);
    GetDlgItemText(IDC_EDIT_END2, strY1);

    int startX = _ttoi(strX0);
    int startY = _ttoi(strY0);
    int endX = _ttoi(strX1);
    int endY = _ttoi(strY1);

    // 获取绘图框的边界矩形
    CRect rectDrawArea2;
    GetDlgItem(IDC_STATIC_DRAW_AREA2)->GetClientRect(rectDrawArea2);

    // 映射坐标到绘图框的坐标系
    startX -= rectDrawArea2.left;
    startY -= rectDrawArea2.top;
    endX -= rectDrawArea2.left;
    endY -= rectDrawArea2.top;

    // 在绘制直线之前检查坐标是否在绘图框内
    if (startX >= 0 && startX < rectDrawArea2.Width() &&
        startY >= 0 && startY < rectDrawArea2.Height() &&
        endX >= 0 && endX < rectDrawArea2.Width() &&
        endY >= 0 && endY < rectDrawArea2.Height()) {
        // 坐标在绘图框内,可以绘制直线
        // 调用Bresenham函数
        DrawLineBresenham(startX, startY, endX, endY);
    } else {
        // 坐标超出了绘图框的边界,错误提示
        CString message = _T("坐标超出绘图框的边界,请重新输入");
        CString caption = _T("错误");
        MessageBox(message, caption, MB_ICONERROR);
    }
}

2. LineDlg.h

主要需要增加cpp文件中使用的几个函数(添加到public属性中),见下图红框中的四个函数

在这里插入图片描述

实验结果

运行整个项目后,弹出对话框

分别在文本框中输入起点和终点的坐标,并点击DDA或Bresenham按钮,相应的直线就会在对应的绘图框中展示,如下图:

在这里插入图片描述

如果输入的坐标超出了绘图框的限制,则弹出错误提示,如下图:

在这里插入图片描述