基于C语言来实现图形界面画板的功能
♻️ 资源大小4.16MB➡️资源下载https://download.csdn.net/download/s1t16/87430265画板程序二、实验目的在编写画板程序的过程中综合运用并提升 C 语言编程技能与规范为以后的学习做好铺垫。三、实验内容利用 EGE (Easy Graphics Engine) 和 C 语言来实现图形界面画板的功能。四、实验环境OSWindows 10 Enterprise LTSC version 1809IDEVisual Studio 2019 Community 16.3.9五、附录5.1 问题分析画板即一个可以将用户输入的数据转化为屏幕上的图像的应用程序。本项目中使用 C 语言完成。它需要具有以下几个功能1、拥有自然的数据输入方式。2、拥有存档功能。3、拥有人性化的用户界面。4、拥有多种图形的作画功能。5、可以自定义作图参数。5.1.1 目标使用 C 语言和 EGE 库完成画板程序即一个可以将用户输入的数据转化为屏幕上的图像的应用程序。5.1.2 功能5.1.2 撤回用户可以在图形绘制界面点击撤回按钮实现无限制的撤回操作。5.1.2 前景和填充色自定义用户可以从调色盘中精确点击需要的颜色或者从预设中选择。5.1.2 自定义存档保存与打开用户可以自定义保存位置并保存当前绘图存档并且可以自由选择要加载的存档打开后仍可以进行撤回等编辑存档的操作。5.1.2 用鼠标绘制圆、线、矩形用户只需在屏幕上点击即可选定起点/终点、圆心/半径、矩形的左上角/右下角通过极为快速简单的操作即可绘制图形并且用户在移动鼠标时会有即将绘制的图形的实时预览非常直观不用担心点击的位置不准确。5.1.2 用鼠标绘制多边形用户只需在屏幕上点击各个顶点最后一个顶点靠近第一个顶点时即可自动吸附并且闭合多边形直观地完成绘图5.1.2 输入坐标绘制多边形通过较为完整的输入格式检查和提醒用户可以轻易地通过输入坐标来绘制多边形。5.1.3 性能以下性能测试基于的硬件平台Processor: Intel Core i5-7200U at 2.50GHz with TurboBoost Up to 3.10GHzMemory: 32GB DDR4 at 2133MHz Dual Channel InterlacedGraphics: Intel HD Graphics 6205.1.3 画面更新性能在鼠标移动或者画图形后的画面更新帧率至少 1000FPS (Frame Per Second)即更新一帧画面的时间至多 1ms。无可感知的延迟。5.1.4 健壮性5.1.4 写入或读取存档时的异常处理5.1.4 在读取到非存档文件时的处理考虑到在程序中调用了 Windows API 来让用户自由读取存档因此有必要在用户选择错误的文件时报错。在本项目中通过在保存文件时添加校验位来标记该文件是本程序创建的。在读取时通过对检验位的比较来辨别用户选择的文件正确与否。在用户选择错误的文件时会弹出警告窗口来使用户重试。5.1.4 读取或写入失败时的异常处理在写入或读取存档时会对成功写入或读取的数据块的数量与应该成功的数据块数量进行比较在写入或读取时有数据块出错时弹出警告提醒用户重新操作5.1.4 在读取或写入文件后关闭文件失败的处理若关闭文件失败则提示用户重新操作5.1.4 在用户键入多边形边数或坐标时的异常处理5.1.4 对于边数的异常处理5.1.4 检查输入字符串是否全为数字并提示错误点作为边数自然都必须为数字程序将检查输入的字符串是否都为数字再使用 atoi()函数转换否则警告边数只能包含数字5.1.4 检查边数在几何意义上的合理性并提示错误点从几何意义上说多边形的边数必须大于等于 3若用户输入的边数小于 3则警告不能构成多边形若多边形的边数大于 24即超出定义的数组能够存储的坐标范围为防止溢出将警告“坐标个数过多”。5.1.4 对于坐标的异常处理在按照 ”,” 分割输入的字符串后将检查5.1.4 检查分割后的字符串是否全为数字并提示错误点作为坐标自然都必须为数字程序将检查输入的字符串是否都为数字再使用 atoi()函数转换否则警告坐标只能包含数字5.1.4 检查坐标个数并提示错误点一个边数为 sides 的多边形只能有 sides 个坐标若读取的坐标数量过多则警告坐标个数过多若读取的坐标数量过少则警告坐标个数过少5.1.4 检查坐标范围并提示错误点若输入的坐标超出屏幕的显示范围则警告坐标范围无效5.1.4 对于用鼠标画多边形时坐标个数溢出的处理由于数组的限制程序能够存储的坐标个数有限在用户画图时坐标的个数将被实时检查在即将溢出时会提示用户将多边形封闭停止作画。5.1.4 对于图形数量过多溢出的处理介于对内存空间节省和正常情况下会存储的图形数量的考虑程序设计时保存图形的最大数量为 512 个在用户画的图形数量即将超出这个限制时将提示用户及时停止绘画并保存图形。5.2 设计方案整个项目分为 9 个模块对应 9 个 C 源代码文件和 9 个头文件每个模块有 2-5 个函数共计 25 个函数。5.2.1 模块、文件、函数的功能5.2.1 文件功能color_selector.cpp包含颜色选取功能颜色转换算法打印颜色选取菜单color_selector.h对应的头文件coord_draw.cpp包含用坐标来进行绘画的功能即用坐标绘制多边形coord_draw.h对应的头文件draw.cpp包含最主要的根据数据绘制图形的功能打印绘图菜单随机颜色算法draw.h对应的头文件ege_based_painter.cpp包含 main() 函数全局变量global.h包含宏定义结构体定义等menu.cpp包含功能菜单选取打印菜单的功能menu.h对应的头文件mouse_draw.cpp包含用鼠标来进行绘画的功能即用鼠标来绘制圆、线、矩形、多边形mouse_draw.h对应的头文件read_file.cpp包含加载存档、将存档写入内存的功能read_file.h对应的头文件save_file.cpp包含将当前绘图状态保存至存档的功能save_file.h对应的头文件UI.cpp包含对于基本用户界面的初始化、显示状态栏显示系统状态、图形数量分类统计、鼠标坐标、获取鼠标所在的菜单、清除图形等的功能UI.h对应的头文件5.2.1 函数功能函数名称即该函数的用途无需赘述。5.2.2 运行简化主体流程图5.3 重要算法由于有很多变量的读写不方便用流程图表示故贴源代码。5.3.1 算法一对于特定图形类型的数量统计算法static WORD tmp_totalShapes; static WORD nLines; static WORD nCircles; static WORD nRectangles; static WORD nPolygons; // count the number of each shape if (tmp_totalShapes ! g_nTotalShapes) { fileEdited true; // indicates whether the picture is edited // initialize variables for counting nLines 0; nCircles 0; nRectangles 0; nPolygons 0; for (int i 0; i g_nTotalShapes; i) { switch (shapeData[i].shapeType) { case shape_line: nLines; break; case shape_circle: nCircles; break; case shape_rectangle: nRectangles; break; case shape_polygon: nPolygons; break; default: break; } }}5.3.2 算法二鼠标移动时菜单高亮与鼠标画图的算法void mouse_DrawPoly(void) { const short int TOTAL_LN 4; // total items in the menu bar bool isInProgress false; // To determine whether the mouse click is the first step or the second step WORD polyCoords[50]; WORD sides 0; printf(已进入鼠标画多边形模式\n); printf(操作指南\n); printf(用鼠标点选顶点最后一个点靠近起始点来结束\n); DrawAllPrevShapes(true); DrawMenuOutline(1, TOTAL_LN, 1); setcolor(0x000000); PrintMouseDrawingInsideMenu(0); mouse_msg msg; for (; is_run(); delay_fps(REFRESH_RATE)) // Using for statement to draw multiple circles at a time and refresh the screen { msg getmouse(); switch (msg.msg) { case mouse_msg_down: switch (GetMouseCurrentLnAndCol(1, TOTAL_LN, 1, 1).ln) { case 1: return; break; case 2: // undo if (g_nTotalShapes 0) { if (!isInProgress) { g_nTotalShapes--; } } // refresh the windows with menu contents cleardevice(); InitUI(0); DrawMenuOutline(1, TOTAL_LN, 1); setcolor(0x000000); PrintMouseDrawingInsideMenu(0); setcolor(0x50AA50); xyprintf(678, 582, 当前坐标: (%03d, %03d), msg.x, msg.y); DrawAllPrevShapes(true); goto move; break; case 3: // choose foreground color ChooseColor_EGE(0); cleardevice(); InitUI(0); DrawAllPrevShapes(true); goto move; break; case 4: // choose fill color ChooseColor_EGE(1); cleardevice(); InitUI(0); DrawAllPrevShapes(true); goto move; break; default: break; } /* if the mouse click indicates the first dot, store the position data of it */ if (!isInProgress) { // store coordinate data polyCoords[0] msg.x; polyCoords[1] msg.y; sides 0; printf( 您已选中点 (%d, %d)\n, polyCoords[0], polyCoords[1]); isInProgress true; break; } /* if the mouse click indicates another dot, store the position data of it */ if (isInProgress) { // store coordinate data sides; polyCoords[2 * sides] msg.x; polyCoords[2 * sides 1] msg.y; printf( 您已选中点 (%d, %d)\n, polyCoords[2 * sides], polyCoords[2 * sides 1]); // when the distance between // the last dot and // the first dot // is smaller than 8 px // then does the following things if (sqrt(pow(polyCoords[0] - polyCoords[2 * sides], 2) pow(polyCoords[1] - polyCoords[2 * sides 1], 2)) 8 sides 3) { // closes the polygon polyCoords[2 * sides] polyCoords[0]; polyCoords[2 * sides 1] polyCoords[1]; g_nTotalShapes; shapeData[g_nTotalShapes - 1].shapeType shape_polygon; shapeData[g_nTotalShapes - 1].extraData[0] sides; // record the sides // prepare the coordinate data for storage for (int j 0; j shapeData[g_nTotalShapes - 1].extraData[0]; j) { shapeData[g_nTotalShapes - 1].coords[j].x polyCoords[2 * j]; shapeData[g_nTotalShapes - 1].coords[j].y polyCoords[2 * j 1]; } // store current color settings if (!g_isUserSetColor) { shapeData[g_nTotalShapes - 1].foregroundColor RandColor(); } else { shapeData[g_nTotalShapes - 1].foregroundColor g_customColor; } if (!g_isUserSetFillColor) { shapeData[g_nTotalShapes - 1].isFill false; } else { shapeData[g_nTotalShapes - 1].isFill true; if (g_isFillColorRandom) { shapeData[g_nTotalShapes - 1].fillColor RandColor(); } else { shapeData[g_nTotalShapes - 1].fillColor g_customFillColor; } } DrawAllPrevShapes(true); isInProgress false; printf( 已完成%d边形的绘图\n, sides); goto move; } if (sides 23) { MessageBox(NULL, TEXT(边数过多请立即将正在绘画的多边形封口), TEXT(即将溢出), MB_OK | MB_SYSTEMMODAL | MB_ICONEXCLAMATION); } break; } break; // not needed case mouse_msg_move: move: if (!isInProgress) { InitUI(0); //DrawMenuOutline(1, TOTAL_LN, 1); //setcolor(0x000000); //PrintMouseDrawingInsideMenu(0); setcolor(0x50AA50); xyprintf(678, 582, 当前坐标: (%03d, %03d), msg.x, msg.y); switch (GetMouseCurrentLnAndCol(1, TOTAL_LN, 1, 1).ln) { case 0: DrawMenuOutline(1, TOTAL_LN, 1); setcolor(0x000000); PrintMouseDrawingInsideMenu(0); break; case 1: setcolor(0x000000); PrintMouseDrawingInsideMenu(0); setcolor(0x5050AA); PrintMouseDrawingInsideMenu(1); break; case 2: setcolor(0x000000); PrintMouseDrawingInsideMenu(0); setcolor(0x5050AA); PrintMouseDrawingInsideMenu(2); break; case 3: setcolor(0x000000); PrintMouseDrawingInsideMenu(0); setcolor(0x5050AA); PrintMouseDrawingInsideMenu(3); break; case 4: setcolor(0x000000); PrintMouseDrawingInsideMenu(0); setcolor(0x5050AA); PrintMouseDrawingInsideMenu(4); break; default: break; } } if (isInProgress) { // refresh continuously the screen to show live shape preview cleardevice(); delay_fps(10000); InitUI(1); setcolor(0x50AA50); xyprintf(678, 582, 当前坐标: (%03d, %03d), msg.x, msg.y); DrawAllPrevShapes(true); setcolor(0x909090); // draws a line which is the line connecting // the last dot of the polygon and the mouse pointer if (sides 1) { for (int i 0; i sides * 2; i 2) { line(polyCoords[i], polyCoords[i 1], polyCoords[i 2], polyCoords[i 3]); } } // closes the polgon automatically // when the distance between // the mouse pointer and the first dot of the polygon // is smaller than 8px if (sqrt(pow((double)polyCoords[0] - (double)msg.x, 2) pow((double)polyCoords[1] - (double)msg.y, 2)) 8 sides 2) { line(polyCoords[sides * 2], polyCoords[sides * 2 1], polyCoords[0], polyCoords[1]); } else { line(polyCoords[sides * 2], polyCoords[sides * 2 1], msg.x, msg.y); } } break; default: break; } }5.3.3 算法三对于鼠标所在菜单的采集算法struct MenuLnAndCol GetMouseCurrentLnAndCol( WORD lnStart, WORD lnEnd, WORD colNeeded, WORD colTotal) { int x, y; struct MenuLnAndCol coord; coord.ln 0; coord.col 0; mousepos(x, y); /* at first, i used the getmouse() function but a i encountered a issue where the function keeps waiting mouse input when there is no mouse input thus causing possible delays later i switched to the mousepos() function finally!!!! there is no delay */ if ((x 5 (colNeeded -1) * (MENU_LENGTH / colTotal)) (y 5 (lnStart - 1) * MENU_HIGHT) (x 5 (colNeeded) * (MENU_LENGTH / colTotal)) (y 5 lnEnd * MENU_HIGHT)) { coord.ln (y - 5) / MENU_HIGHT 1; coord.col (x - 5) / (MENU_LENGTH / colTotal) 1; } return coord;}5.3.4 算法四将读取的输入数组与(x,y)坐标数组互转的算法坐标数组转多边形绘图使用的数组// prepare the data in shapeData for drawing for (int j 0; j *((shapeData i)-extraData); j) { *(coordData 2 * j) ((shapeData i)-coords j)-x; *(coordData 2 * j 1) ((shapeData i)-coords j)-y; } *(coordData 2 * (*((shapeData i)-extraData))) ((shapeData i)-coords)-x; *(coordData 2 * (*((shapeData i)-extraData)) 1) ((shapeData i)-coords)-y;多边形绘图所用的数组转(x,y)坐标数组// prepare the coordinate data for storage for (int j 0; j shapeData[g_nTotalShapes - 1].extraData[0]; j) { shapeData[g_nTotalShapes - 1].coords[j].x polyCoords[2 * j]; shapeData[g_nTotalShapes - 1].coords[j].y polyCoords[2 * j 1]; for (int j 0; j shapeData[g_nTotalShapes - 1].extraData[0]; j) { shapeData[g_nTotalShapes - 1].coords[j].x polyCoords[2 * j]; shapeData[g_nTotalShapes - 1].coords[j].y polyCoords[2 * j 1]; }5.3.5 算法五随机颜色算法int RandColor(void) { int R 0, G 0, B 0; int hexColorValue; // the final hexadecimal output do { randomize(); random(255);// randInt(0, 255); randomize(); random(255);// randInt(0, 255); randomize(); random(255);// randInt(0, 255); } while ((R 180) || (G 180) || (B 180)); // condition added to avoid the colors that are too dark. readibility improved. hexColorValue (R 16) (G 8) B; // convert separate RGB channels into a single value that represents colors // printf(%x, hexValue); // display the output value of the function for debug purposes return hexColorValue;5.4 实验结果5.4.1 基本功能5.4.1 读取文件菜单5.4.1 读取存档调用 Windows API 文件函数实现文件的选择。5.4.1 加载存档后菜单图片已加载背景为图片黑白预览菜单会在鼠标覆盖时变色表示鼠标5.4.1 绘图界面与菜单鼠标绘图菜单背景即这样画出的鼠标绘画实时反馈非常容易特别地在用鼠标画多边形时拥有鼠标吸附封口功能。输入坐标绘图菜单附有完整的错误检查。5.4.1 撤回如上图第二个撤回按钮可按顺序依次撤回已画的图形无限撤回直至画面上无图形。5.4.1 颜色选择界面可在彩虹色上精确点击选色5.4.1 绘图提示5.4.1 状态栏实时变化状态栏5.4.1 保存存档5.4.2 异常情况5.4.2 读取了其他非存档文件或读取存档失败5.4.2 保存存档失败5.4.2 打开文件后关闭异常5.4.2 图形数量溢出5.4.2 多边形边数溢出5.4.2 输入的多边形边数过少5.4.2 输入的多边形边数或坐标中有非数字5.4.2 输入的多边形坐标个数过多或过少5.4.2 输入的多边形坐标范围溢出5.4.2 用户在未保存文件的情况下尝试关闭程序5.5 调试心得总结你在调试程序时的收获对于某一类或者某几类警告、错误的处理方法。这些心得能够对学习 C 语言程序设计的新手具有一些指导作用5.5.1 Run-Time Check Failure此问题多为数组越界等其他非法行为导致。5.5.2 Initialization skipped by ‘case’label此问题多为变量的初始化在 case 内部被跳过导致。5.5.3 Uninitialized variable在使用一个变量之前初始化它。