新建一个名为Test的MFC应用

计算机图形学2:Bresenham直线绘制


计算机图形学2:Bresenham直线绘制

一.添加三个类RGB Point2 Line

计算机图形学2:Bresenham直线绘制


计算机图形学2:Bresenham直线绘制


二.设计颜色类CRGB

RGB.h代码

#pragma once
class CRGB
{
public:
    CRGB(void);
    CRGB(double red, double green, double blue, double alpha = 0.0);
    virtual ~CRGB(void);
    friend CRGB operator + (const CRGB& c0, const CRGB& c1);//运算符重载
    friend CRGB operator - (const CRGB& c0, const CRGB& c1);
    friend CRGB operator * (const CRGB& c0, const CRGB& c1);
    friend CRGB operator * (const CRGB& c, double scalar);
    friend CRGB operator * (double scalar, const CRGB& c);
    friend CRGB operator / (const CRGB& c1, double scalar);
    friend CRGB operator += (CRGB& c0, CRGB& c1);
    friend CRGB operator -= (CRGB& c0, CRGB& c1);
    friend CRGB operator *= (CRGB& c0, CRGB& c1);
    friend CRGB operator /= (CRGB& c, double scalar);
    void Normalize(void); //颜色归一化处理到[0,1]区间

public:
    double red;
    double green;
    double blue;
    double alpha;
};


分析:

CRGB 类:用于表示颜色包含红色 (red)、绿色 (green)、蓝色 (blue) 以及透明度 (alpha) 四个属性均为 double 类型
构造函数和析构函数:
默认构造函数 CRGB(void) 初始化颜色值
参数化构造函数 CRGB(double red double green double blue double alpha) 允许用户设置颜色值alpha 有默认值 00
虚析构函数 ~CRGB(void)确保在继承时能正确析构
运算符重载:通过友元函数重载了加、减、乘、除等运算符支持颜色之间的数学运算以及颜色与标量之间的运算
Normalize 方法:用于将颜色的 RGB 值归一化到 [01] 的区间防止颜色值超过范围

RGB.cpp代码

#include "pch.h"
#include "RGB.h"

CRGB::CRGB(void)
{
    red = 1.0;
    green = 1.0;
    blue = 1.0;
    alpha = 1.0;
}

CRGB::~CRGB(void)
{

}

CRGB::CRGB(double red, double green, double blue, double alpha)
{
    this->red = red;
    this->green = green;
    this->blue = blue;
    this->alpha = alpha;
}

CRGB operator + (const CRGB& c0, const CRGB& c1)
{
    CRGB color;
    color.red = c0.red + c1.red;
    color.green = c0.green + c1.green;
    color.blue = c0.blue + c1.blue;
    return color;
}

CRGB operator - (const CRGB& c0, const CRGB& c1)
{
    CRGB color;
    color.red = c0.red - c1.red;
    color.green = c0.green - c1.green;
    color.blue = c0.blue - c1.blue;
    return color;
}

CRGB operator * (const CRGB& c0, const CRGB& c1)
{
    CRGB color;
    color.red = c0.red * c1.red;
    color.green = c0.green * c1.green;
    color.blue = c0.blue * c1.blue;
    return color;
}

CRGB operator * (const CRGB& c, double scalar)
{
    CRGB color;
    color.red = scalar * c.red;
    color.green = scalar * c.green;
    color.blue = scalar * c.blue;
    return color;
}

CRGB operator * (double scalar, const CRGB& c)
{
    CRGB color;
    color.red = scalar * c.red;
    color.green = scalar * c.green;
    color.blue = scalar * c.blue;
    return color;
}

CRGB operator / (const CRGB& c, double scalar)
{
    CRGB color;
    color.red = c.red / scalar;
    color.green = c.green / scalar;
    color.blue = c.blue / scalar;
    return color;
}

CRGB operator += (CRGB& c0, CRGB& c1)
{
    c0.red += c1.red;
    c0.green += c1.green;
    c0.blue += c1.blue;
    return c0;
}

CRGB operator -= (CRGB& c0, CRGB& c1)
{
    c0.red -= c1.red;
    c0.green -= c1.green;
    c0.blue -= c1.blue;
    return c0;
}

CRGB operator *= (CRGB& c0, CRGB& c1)
{
    c0.red *= c1.red;
    c0.green *= c1.green;
    c0.blue *= c1.blue;
    return c0;
}

CRGB operator /= (CRGB& c, double scalar)
{
    c.red /= scalar;
    c.green /= scalar;
    c.blue /= scalar;
    return c;
}

void CRGB::Normalize(void)
{
    red = (red < 0.0) ? 0.0 : ((red > 1.0) ? 1.0 : red);
    blue = (blue < 0.0) ? 0.0 : ((blue > 1.0) ? 1.0 : blue);
    green = (green < 0.0) ? 0.0 : ((green > 1.0) ? 1.0 : green);
}


分析:

构造函数实现:
默认构造函数将颜色初始化为白色(红、绿、蓝均为 10)alpha 也设为 10
参数化构造函数按传入值设置颜色属性
运算符重载:
operator + 实现了两个 CRGB 对象的颜色相加分别对 RGB 分量进行相加
其他运算符(减、乘、除)类似实现对应的数学运算
Normalize 方法实现:通过三元运算符确保每个颜色分量保持在 [01] 范围内


三.设计二维整数点类CPoint2

Point2.h代码

#pragma once
#include "RGB.h"

class CPoint2
{
public:
    CPoint2(void);
    CPoint2(int x, int y);
    CPoint2(int x, int y, CRGB c);
    ~CPoint2(void); // 声明析构函数

public:
    int x, y;
    CRGB c;
};


分析:

CPoint2 类:表示二维整数坐标点包含 x 和 y 坐标以及一个 CRGB 类型的颜色属性 c
构造函数和析构函数:
默认构造函数 CPoint2(void) 初始化点的位置和颜色
参数化构造函数允许设置点的 x、y 坐标以及颜色 c
析构函数 ~CPoint2(void)目前无特殊操作

Point2.cpp代码

#include "pch.h"
#include "Point2.h"
CPoint2::CPoint2(void)
{
    x = 0;
    y = 0;
    c = CRGB(0.0, 0.0, 0.0);
}
CPoint2::CPoint2(int x, int y)
{
    this->x = x;
    this->y = y;
    this->c = CRGB(0.0, 0.0, 0.0);
}
CPoint2::CPoint2(int x, int y, CRGB c)
{
    this->x = x;
    this->y = y;
    this->c = c;
}
CPoint2::~CPoint2(void)
{
}


分析:

构造函数实现:
默认构造函数将点初始化为原点 (00)颜色为黑色 (CRGB(00 00 00))
第二个构造函数允许设置 x 和 y 坐标颜色仍默认黑色
第三个构造函数允许同时设置坐标和颜色
析构函数实现:当前为空无需执行特定操作


四.设计Cline直线类

Line.h代码

#pragma once
#include "Point2.h"

class CLine
{
public:
    CLine(void);
    virtual ~CLine(void);
    void MoveTo(CDC* pDC, CPoint2 p0); //移动到指定位置
    void MoveTo(CDC* pDC, int x0, int y0, CRGB c0);//重载函数
    void LineTo(CDC* pDC, CPoint2 p1);//绘制直线,不含终点
    void LineTo(CDC* pDC, int x1, int y1, CRGB c1);//重载函数
    CRGB LinearInterp(double t, double tStart, double tEnd, CRGB cStart, CRGB cEnd);

private:
    CPoint2 P0;
    CPoint2 P1;
};


分析:

CLine 类:用于绘制直线包含起点 P0 和终点 P1每个点具有位置和颜色属性
方法:
MoveTo:设置直线的起点可以接受 CPoint2 对象或单独的坐标和颜色
LineTo:绘制直线到指定终点可以接受 CPoint2 对象或单独的坐标和颜色
LinearInterp:用于在两点之间进行颜色的线性插值实现颜色渐变效果

Line.cpp代码

#include "pch.h"
#include "Line.h"
#include"Point2.h"

#define COLOR(c) int(RGB(c.red*255,c.green*255,c.blue*255))

CLine::CLine(void)
{

}
CLine::~CLine(void)
{
    this->P0 = CPoint2(0, 0, CRGB(0.0, 0.0, 0.0));
    this->P1 = CPoint2(0, 0, CRGB(0.0, 0.0, 0.0));
}

void CLine::MoveTo(CDC* pDC, CPoint2 p0)
{
    P0 = p0;
}

void CLine::MoveTo(CDC* pDC, int x0, int y0, CRGB c0)
{
    P0 = CPoint2(x0, y0, c0);
}

void CLine::LineTo(CDC* pDC, CPoint2 p1)
{
    P1 = p1;
    int dx= abs(P1.x-P0.x);
    int dy= abs(P1.y-P0.y);
    BOOL bInterChange =FALSE;
    int e, signX, signY, temp;
    signX = (P1.x > P0.x) ? 1 : ((P1.x < P0.x) ? -1 : 0);
    signY=(P1.y>P0.y)?1:((P1.y<P0.y)?-1:0);
    if (dy > dx)
    {
        temp = dx;
        dx = dy;
        dy = temp;
        bInterChange = TRUE;
    }
    e = -dx;
    CPoint2 p = P0;
    for (int i = 1; i <= dx; i++)
    {
        p.c = LinearInterp(p.x, P0.x, P1.x, P0.c, P1.c);
        if(P0.x == P1.x)
         p.c = LinearInterp(p.y, P0.y, P1.y, P0.c, P1.c);
        pDC->SetPixel(p.x, p.y, COLOR(p.c));
        if (bInterChange)
        {
            p.y+= signY;
        }
        else
        {
            p.x+=signX;
        }
        e+=2 * dy;
        if (e >= 0)
        {
            if(bInterChange)
                p.x+=signX;
            else
                p.y+=signY;
            e-=2 * dx;
        }
    }
    P0= P1;

}

void CLine::LineTo(CDC* pDC, int x1, int y1, CRGB c1)
{
    LineTo(pDC, CPoint2(x1, y1, c1));
}
CRGB CLine::LinearInterp(double t, double tStart, double tEnd, CRGB cStart, CRGB cEnd) {
    CRGB color;
    color =(t-tEnd)/(tStart-tEnd)*cStart+(t-tStart)/(tEnd-tStart)*cEnd;
    return color;
}


分析:

定义宏 COLOR(c):将 CRGB 类型的颜色转换为 COLORREF 类型用于在设备上下文中设置像素颜色
构造函数和析构函数:
默认构造函数为空无特殊初始化
析构函数将起点和终点重置为原点且颜色为黑色
MoveTo 方法:
设置直线的起点 P0支持通过 CPoint2 对象或单独的坐标和颜色设置
LineTo 方法:
实现了 Bresenham 直线绘制算法用于在设备上下文 pDC 上绘制从 P0 到 P1 的直线
颜色插值:在绘制过程中根据当前点的位置进行颜色的线性插值实现颜色渐变效果
使用 SetPixel 方法在对应坐标处设置像素颜色
LinearInterp 方法:
根据当前参数 t 在 [tStart tEnd] 范围内进行线性插值计算当前点的颜色值


五.初始化三角形顶点颜色

TestView.h代码

#pragma once

#include "TestDoc.h" // 确保包含 TestDoc.h 头文件
#include "Point2.h" // 确保包含 Point2.h 头文件
class CTestView : public CView
{
protected: // 仅从序列化创建
    CTestView() noexcept;
    DECLARE_DYNCREATE(CTestView)

    // 特性
public:
    CTestDoc* GetDocument() const; // 声明 GetDocument 成员函数

    // 操作
public:

    // 重写
public:
    virtual void OnDraw(CDC* pDC);  // 重写以绘制此视图
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
    virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
    virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

    // 实现
public:
    virtual ~CTestView();
#ifdef _DEBUG
    virtual void AssertValid() const;
    virtual void Dump(CDumpContext& dc) const;
#endif

protected:
    int i;
    CPoint2 p[3];

    // 生成的消息映射函数
protected:
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // TestView.cpp 中的调试版本
inline CTestDoc* CTestView::GetDocument() const
{
    return reinterpret_cast<CTestDoc*>(m_pDocument);
}
#endif


分析:

CTestView 类:继承自 CView用于处理视图的绘制和用户交互
成员变量:
int i:用于计数可能用于记录当前已点击的点数
CPoint2 p[3]:数组存储三个二维点代表三角形的三个顶点
方法:
OnDraw:重写的绘图函数用于在视图上进行自定义绘制
OnLButtonDown 和 OnMouseMove:鼠标事件处理函数响应鼠标左键点击和移动
消息映射:通过 DECLARE_MESSAGE_MAP() 宏声明消息映射表用于将 Windows 消息映射到类的成员函数

TestView.cpp代码


// TestView.cpp: CTestView 类的实现
//
#include "pch.h"
#include "framework.h"
#include "RGB.h"
#include "Line.h"
#include "TestView.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义,并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "Test.h"
#endif
#include "TestDoc.h"
#include "TestView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#define PI 3.1415926
#define ROUND(d) int(d+0.5)
// CTestView
IMPLEMENT_DYNCREATE(CTestView, CView)
BEGIN_MESSAGE_MAP(CTestView, CView)
    // 标准打印命令
    ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
    ON_WM_LBUTTONDOWN()
    ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
// CTestView 构造/析构
CTestView::CTestView() noexcept
{
    // TODO: 在此处添加构造代码
    i = 0;
    p[0].c = CRGB(1.0, 0.0, 0.0);
}
CTestView::~CTestView()
{
}
BOOL CTestView::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: 在此处通过修改
    //  CREATESTRUCT cs 来修改窗口类或样式
    return CView::PreCreateWindow(cs);
}
// CTestView 绘图
void CTestView::OnDraw(CDC* /*pDC*/)
{
    CTestDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;
    // TODO: 在此处为本机数据添加绘制代码
}
// CTestView 打印
BOOL CTestView::OnPreparePrinting(CPrintInfo* pInfo)
{
    // 默认准备
    return DoPreparePrinting(pInfo);
}
void CTestView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: 添加额外的打印前进行的初始化过程
}
void CTestView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: 添加打印后进行的清理过程
}
// CTestView 诊断
#ifdef _DEBUG
void CTestView::AssertValid() const
{
    CView::AssertValid();
}
void CTestView::Dump(CDumpContext& dc) const
{
    CView::Dump(dc);
}
CTestDoc* CTestView::GetDocument() const // 非调试版本是内联的
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CTestDoc)));
    return (CTestDoc*)m_pDocument;
}
#endif //_DEBUG


分析:

构造函数 CTestView::CTestView():
初始化计数器 i 为 0
将第一个点 p[0] 的颜色设置为红色 (CRGB(10 00 00))
OnDraw 方法:
获取文档指针 pDoc确保其有效后可用于绘制内容目前绘制代码未实现
消息映射:
处理打印相关命令和鼠标事件(左键点击和鼠标移动)
预处理和打印相关方法:
PreCreateWindow、OnPreparePrinting、OnBeginPrinting、OnEndPrinting 等与窗口创建和打印功能相关当前大部分为空或仅调用基类方法


六.鼠标左键消息映射

添加消息映射函数:OnLButtonDown、OnMouseMove
计算机图形学2:Bresenham直线绘制


计算机图形学2:Bresenham直线绘制

编写OnLButtonDown代码

void CTestView::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    CLine *pLine=new CLine;
    CDC *pDC = GetDC();
    if (i < 3)
    {
        p[i].x = point.x;
        p[i].y = point.y;
        pDC->Ellipse(ROUND(p[i].x - 5), ROUND(p[i].y - 5),
            ROUND(p[i].x + 5), ROUND(p[i].y + 5));
        i++;
    }
    if (i >= 2)
    {
        pLine->MoveTo(pDC,CPoint2(p[i-2].x,p[i-2].y,p[i-2].c));
        pLine->LineTo(pDC, CPoint2(p[i-1].x, p[i-1].y, p[i-1].c));
    }
    delete pLine;
    ReleaseDC(pDC);
    CView::OnLButtonDown(nFlags, point);
}


分析:

功能:
处理鼠标左键点击事件记录点击的点并绘制小圆点和线段
步骤: 1 创建一个 CLine 对象用于绘制直线 2 获取设备上下文 pDC 以进行绘图操作 3 如果当前记录的点数 i 小于 3:
将鼠标点击的坐标存储到 p[i] 中
绘制一个半径为 5 像素的圆圈表示该点
增加点计数 i 4 如果已记录至少两个点(i >= 2):
使用 CLine 对象的 MoveTo 方法设置直线的起点
使用 LineTo 方法绘制到当前点的直线不含终点 5 删除 CLine 对象释放设备上下文 6 调用基类的 OnLButtonDown 方法

编写OnMouseMove代码

void CTestView::OnMouseMove(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    CDC *pDC = GetDC();
    CLine *pLine = new CLine;
    if (i > 2)
    {
        if (p[0].x - 5 < point.x && point.x < p[0].x + 5 && p[0].y - 5 < point.y && point.y < p[0].y + 5)
        {
            pLine->MoveTo(pDC, CPoint2(p[2].x, p[2].y, p[2].c));
            pLine->LineTo(pDC, CPoint2(p[0].x, p[0].y, p[0].c));
        }
    }
    delete pLine;
    ReleaseDC(pDC);
    CView::OnMouseMove(nFlags, point);
}


分析:

功能:
处理鼠标移动事件检测鼠标是否接近第一个点并在特定条件下绘制线段
步骤: 1 获取设备上下文 pDC 2 创建一个 CLine 对象用于绘制直线 3 如果已记录超过两个点(i > 2):
检查当前鼠标位置是否在第一个点 p[0] 的周围 5 像素范围内
如果在范围内使用 CLine 对象绘制从第三个点 p[2] 到第一个点 p[0] 的直线 4 删除 CLine 对象释放设备上下文 5 调用基类的 OnMouseMove 方法


七.运行测试

计算机图形学2:Bresenham直线绘制


八.总体分析总结

1 颜色类 CRGB:提供颜色表示和基础的颜色运算功能支持颜色的加减乘除以及归一化处理
2 二维点类 CPoint2:表示带颜色的二维点存储位置和颜色信息
3 直线类 CLine:支持带颜色的直线绘制包含颜色渐变插值功能通过 Bresenham 算法实现绘制
4 视图类 CTestView:

记录用户点击的点并在点击时绘制小圆点和连接线
在鼠标移动时如果靠近某个点可以绘制特定的线段实现交互性的绘图效果
通过这些类和功能的协同作用程序能够实现用户通过鼠标点击来定义三角形顶点并动态绘制带有颜色渐变的直线最终形成彩色三角形的效果