02 用Windows API使用Bresenham算法通过画直线的方式实现圆填充算法

用Windows API使用Bresenham算法通过画直线的方式实现圆填充算法

作者 将狼才鲸
创建日期 2023-01-31

Gitee源码地址
CSDN文章阅读地址

  • 备注:在Windows电脑上,使用VS软件,使用C语言风格,使用Windows API函数接口(以前叫Win32 API)实现画圆和圆的填充。

  • 显示效果:

在这里插入图片描述

  • 源码展示(工程文件可以在上方Gitee中下载,点开即用):
/******************************************************************************
 * \brief   画一个圆,燃用用Bresenham算法对圆进行填充
 * \details 在Windows下使用VS编译,直接使用Windows API(C/C++),不使用MFC、WPF等框架;
 *          画圆使用Windows API,填充用画线的API自行实现
 * \note    UTF-8 BOM编码
 * \author  将狼才鲸
 * \date    2023-01-31
 * \remarks
 *      参考网址:
 *      [基于 Bresenham 算法画填充圆](http://www.javashuo.com/article/p-oiizeyjd-de.html)
 ******************************************************************************/

/*********************************** 头文件 ***********************************/
#include <windows.h>    /* Windows API的头文件 */
#include <stdio.h>      /* printf */

/*********************************** 宏定义 ***********************************/
#define FPS 60  /* 帧率,Frames per Second */
#define DEFAULT_RADIUS_RATIO    4   /* 默认的气泡半径是窗口宽度的多少分之1 */

/********************************** 类型定义 **********************************/

/********************************** 全局变量 **********************************/
static WCHAR titleName[] = L"Windows API Demo"; /* 窗口标题文字 */
static int g_window_x, g_window_y;      /* 窗口宽度和高度,单位为像素 */
static int g_x = 100, g_y = 100;        /* 圆心坐标,单位为像素 */
static int g_fno;                       /* 帧序号,对每一帧显示进行计数 */
static int default_radius = 10;         /* 当前圆的默认半径,单位为像素 */
static HANDLE hThread;  /* 创建的绘图线程 */
static HPEN hPenWhite;  /* 白色画笔 */
static HPEN hPenRed;    /* 红色画笔 */
static HPEN hPenGreen;  /* 绿色画笔 */
static int ms_per_frame = 1000 / FPS;   /* 每帧所占据的时间,单位为ms */

/********************************** 私有函数 **********************************/
/**
 * \brief   基于 Bresenham 算法画填充圆
 * \param   x,y = 圆心坐标;r = 半径
 */
void FillCircle_Bresenham(HDC hdc, int x, int y, int r)
{
    
    
    int tx = 0, ty = r, d = 3 - 2 * r;

    while (tx < ty)
    {
    
    
        // 小于 45 度横线
        MoveToEx(hdc, x - ty, y - tx, NULL);    LineTo(hdc, x + ty, y - tx);
        if (tx != 0)    // 防止水平线重复绘制
        {
    
    
            MoveToEx(hdc, x - ty, y + tx, NULL);    LineTo(hdc, x + ty, y + tx);
        }

        if (d < 0)      // 取上面的点
        {
    
    
            d += 4 * tx + 6;
        }
        else            // 取下面的点
        {
    
    
            // 大于 45 度横线
            MoveToEx(hdc, x - tx, y - ty, NULL);    LineTo(hdc, x + tx, y - ty);
            MoveToEx(hdc, x - tx, y + ty, NULL);    LineTo(hdc, x + tx, y + ty);
            d += 4 * (tx - ty) + 10;
            ty--;
        }
        tx++;
    }

    if(tx == ty)        // 45 度横线
    {
    
    
        MoveToEx(hdc, x - ty, y - tx, NULL);    LineTo(hdc, x + ty, y - tx);
        MoveToEx(hdc, x - ty, y + tx, NULL);    LineTo(hdc, x + ty, y + tx);
    }
}

/**
 * \brief   在窗口中刷新一帧内容
 */
static int window_update(HDC hdc)
{
    
    
    static WCHAR text_info[64];     /* 屏幕上显示文字时使用 */
    BOOL ret;   /* 用于拷机时画面无显示时调试用 */

    /* 1. 帧数文字信息显示 */
    wsprintf((LPWSTR)text_info, (LPCWSTR)L"当前帧:%d", g_fno++);
    ret = TextOut(hdc, 10, 10, (LPCWSTR)text_info, wcslen(text_info));
    if (ret != 1)
        printf("error");

    /* 2. 画圆 */
    HPEN hOldPen1 = (HPEN)::SelectObject(hdc, hPenRed);
    /* Windows API画圆和椭圆函数,参数为圆占据的矩形左上角和右下角坐标;横轴向右是增加,纵轴向上是减少 */
    Ellipse(hdc, g_x - default_radius, g_y - default_radius,
        g_x + default_radius, g_y + default_radius);

    /* 3. 填充圆:用一条条直线进行填充 */
    HPEN hOldPen2 = (HPEN)::SelectObject(hdc, hPenGreen);
    MoveToEx(hdc, 1, 1, NULL);    LineTo(hdc, 100, 100);    /* 画一条直线,测试用 */
    FillCircle_Bresenham(hdc, g_x, g_y, default_radius);

    /* 4. 显示BMP图片:LoadImage() */

    /* 5. 拷贝图片(贴图):BitBlt() */

    return 0;
}

/**
 * \brief   执行窗口内容更新的线程
 */
DWORD WINAPI ThreadProcessFunc(LPVOID lpParamter)
{
    
    
    HWND hWnd = (HWND)lpParamter;
    HDC hdc;        /* 窗口信息 */

    /* 窗口合法性判断 */
    hdc = GetDC(hWnd);  /* 选中当前绘图区域,同样也是当前窗口 */
    if (IsIconic(hWnd))
        return 0;

    /* 画笔设置 */
    hPenWhite = (HPEN)::CreatePen(PS_SOLID, 2, RGB(255, 255, 255)); /* 白色线 */
    hPenRed = (HPEN)::CreatePen(PS_SOLID, 2, RGB(176, 48, 96));     /* 红色线 */
    hPenGreen = (HPEN)::CreatePen(PS_SOLID, 2, RGB(34, 139, 34));   /* 绿色线 */

    /* 死循环,持续运行 */
	while (TRUE)
	{
    
    
        window_update(hdc);
        //GdiFlush(); /* 及时将绘图区绘制的内容写入到窗口显存中去 */
		Sleep(ms_per_frame); //单位是毫秒
	}

    //TODO: 此处未释放hdc窗口绘图区域,自己临时绘图时,记得GetDC和ReleaseDC成对使用

    return 0;
}

/**
 * \brief   窗口消息处理回调函数
 * \details 如响应窗口的大小变化
 */
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    
    
    RECT rect;      /* 窗口大小 */

    /* 1. 处理想要处理的窗口消息 */
    switch (message)
    {
    
    
    case WM_CREATE:     /* 窗口被创建时的消息 */
        return 0;

    case WM_SIZE:   /* 适配窗口大小的改变 */
        {
    
    
            /* 程序刚运行,窗口刚打开的时候,会自动进入一次 */
            GetClientRect(hWnd, &rect);  /* 获取窗口的大小 */
            g_window_x = rect.right - rect.left;
            g_window_y = rect.bottom - rect.top;
            default_radius = g_window_y / DEFAULT_RADIUS_RATIO;
            g_x = default_radius * 2;
            g_y = default_radius * 2;
        }
        return 0;

    case WM_TIMER:  /* 定时器消息(中断)处理 */
        {
    
    
        }
        return 0;

    case WM_PAINT:  /* 最开始绘制窗口中默认显示的内容 */
        break; 

    case WM_CLOSE:      /* 程序退出,先close再destroy */
        {
    
    
            TerminateThread(hThread, 0);    /* 强制退出绘图线程 */
            DestroyWindow(hWnd);
        }
        return 0;

    case WM_DESTROY:    /* 程序退出 */
        PostQuitMessage(0); /* 该函数向消息队列中插入一条WM_QUIT消息,由GetMessage函数捕获返回0而退出程序 */
        break;
    }

    /* 为应用程序没有处理的任何窗口消息提供缺省的处理,该函数确保每一个消息都得到处理 */
    return DefWindowProc(hWnd, message, wParam, lParam);
}

/********************************** 接口函数 **********************************/
/**
 * \brief   Windows图形界面程序固定的入口函数
 * \details 如果是命令行函数,则入口函数不一样
 * \remarks
 *      1、注册窗口类 (RegisterClassEx)
 *      2、创建窗口 (CreateWindowsEx)
 *      3、在桌面显示窗口 (ShowWindows)
 *      4、更新窗口客户区 (UpdataWindows)
 *      5、进入无限循环的消息获取和处理的循环:
 *          GetMessage,获取消息
 *          TranslateMessage,转换键盘消息
 *          DispatchMessage,将消息发送到相应的窗口函数
 */
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    
    
    /* 1. 设置窗口属性和注册窗口 */
    WNDCLASSEX wcex; /* 定义窗口属性结构体 */
    wcex.cbSize         = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;  /* 允许窗口缩放 */
    wcex.lpfnWndProc    = (WNDPROC)WndProc;         /* 窗口消息处理回调函数 */
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = NULL;                     /* 窗口左上角图标的句柄 */
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName   = NULL;
    wcex.lpszClassName  = (LPCWSTR)titleName;       /* 类名称 */
    wcex.hIconSm        = NULL;                     /* 小图标句柄 */
    RegisterClassEx(&wcex);

    /* 2. 创建窗口 */
    HWND hWnd;
#ifdef COLLISION_ALGORITHM_TEST
    hWnd = CreateWindow((LPCWSTR)titleName, (LPCWSTR)titleName, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, 400, 400, NULL, NULL, hInstance, NULL);   /* 窗口指定宽高 */
#else
    /* 当前我的电脑上默认初始窗口大小 1424 * 720,测试碰撞时基于此设置初始位置和速度 */
    hWnd = CreateWindow((LPCWSTR)titleName, (LPCWSTR)titleName, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);   /* 窗口使用默认宽高 */
#endif

    if (!hWnd)
        return FALSE;   /* 如果创建窗口失败则返回 */

    /* 3. 显示窗口和刷新窗口 */
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    /* 4. 添加线程,通过hWnd持续绘制显示区域 */
    /* CreateThread()函数参数介绍:
     * LPSECURITY_ATTRIBUTESlpThreadAttributes, 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置
     * DWORDdwStackSize,    表示线程栈空间大小,传入0表示使用默认大小(1MB)
     * LPTHREAD_START_ROUTINElpStartAddress,    表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址
     * LPVOIDlpParameter,   是传给线程函数的参数
     * DWORDdwCreationFlags,指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()
     * LPDWORDlpThreadId    将返回线程的ID号,传入NULL表示不需要返回该线程ID号 */
    hThread = CreateThread(NULL, 0, ThreadProcessFunc, hWnd, 0, NULL);

    /* 5. 循环获取和处理窗口上的消息 */
    MSG msg;
    while (GetMessage(&msg, 0, 0, 0))
    {
    
    
        TranslateMessage(&msg);   /* 转换键盘等产生的消息 */
        DispatchMessage(&msg);    /* 将消息发送到窗口函数 */
    }

    return (int)msg.wParam;
}

/*********************************** 文件尾 ***********************************/

猜你喜欢

转载自blog.csdn.net/qq582880551/article/details/128813147