# MineSketchpad
**Repository Path**: March__rain/MineSketchpad
## Basic Information
- **Project Name**: MineSketchpad
- **Description**: c++课程设计项目
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2021-09-03
- **Last Updated**: 2025-11-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 课程设计报告
* 组员: 李昱辉, 王梓萌
* 题材:类似sai2的具有绘图功能的图像处理软件
* 视频地址:[中国海洋大学夏季学期c++实践项目_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1ag411c78V/)
* 仓库地址:[March-rain233/MineSketchpad: c++课程设计项目 (github.com)](https://github.com/March-rain233/MineSketchpad)/[MineSketchpad: c++课程设计项目 (gitee.com)](https://gitee.com/March__rain/MineSketchpad)
### 设计概述
---
我不是太会学术性的总结一些东西,这个文档我就口水化啦。首先是显示图片功能的制作,是通过QPainter的DrawImage接口来实现,他需要一个QImage的对象,这个我通过实现Image类的ToQImage来生成一个QImage对象实现。
在画板类中维护着一个图层栈,是画板的核心数据,这个图层有自己定义的一个类LayerModel,这个类是画板的模型,具备可视、锁定、以及图像数据与图层效果等属性,画面显示是通过画板从最底层的图层向上传递一张画布(Image)每个图层根据自己的图层效果将图像数据渲染到画布上,当所有图层渲染结束后,画布将被转换为QImage来进行显示。每个图层具备一个源数据以及一个缓冲区,当对图层进行修改时需要调用BeginPaint来通知图层将源数据复制进缓冲区内,让外界修改缓冲区,并在修改结束后调用EndPaint来将缓冲区数据读入源数据内,或是调用CancelPaint来取消这次的修改。
我自行编写了MyEvent事件类实现消息订阅机制,来辅助qt的槽与信号系统。其灵感来自于c#的event语法,我因为日常使用c#与unity开发游戏,所以对event语法比较熟悉且具有好感(滥用)。该类尽可能的提供与event类似的调用感受,通过维护函数对象的队列来实现消息订阅。但是当前的实现仍然具有局限性,为了保证event只能被发布者激活通知,event将会把Invoke方法设置为私有,并将发布者设置为友元以便发布者调用。但是由于友元不具备继承能力,为了使发布者的派生类同样具备发布通知的能力,就不得不在发布者内部编写调用函数对其进行封装,显得略微不便。且该类不像qt的信号与槽基于其背后高效的元对象系统无需考虑订阅者销毁的情况,当订阅者被销毁时,原先所绑定的消息处理函数将变为野指针。所以在使用该类时,仍然需要在析构时自行取消订阅。而且该类也不是线程安全的。虽然限制蛮多的,但是耐不住造轮子真的就是很快乐,所以我还是将其加入了项目中。
为了实现在使用不同绘图工具时外界输入效果的差异化,我以状态模式来设计工具类。画板将持有一个工具类基类的指针,该基类为抽象类向外提供响应不同输入状态的接口,画板通过改变该工具指针的绑定来切换工具的使用。从而实现了再不同的工具选择下,相同的用户输入产生不同的结果输出。
由于课程要求上不允许使用Qt原生的库函数,所以我判断不可以使用QPen等Qt原生工具来实现画笔,于是我便自己重新编写了画笔。画笔类采用以点成线的方式来实现图像绘制。当绘制时,鼠标每一次的移动都将纪录下坐标点。并以上一次移动坐标点为起点该次坐标点为终点绘制直线。画笔将以两点之间的每一个像素点为圆心绘制圆点不断叠加来产生直线。直线的填充算法为当k(斜率)<1时,以x为自变量,y为因变量,寻找每一个x所对应的y并以此为圆点圆心绘制,当k>1时,y的分布相对于x的变化过于稀疏,所以将选择以y为自变量,x为因变量来寻找圆点圆心。而圆的填充算法为,将圆八等份。此时角度45°-90°之间区域的每一个x都能找到一个y与之一一对应,即具有x对y的映射。此时按行填充每一行计算得来的y将不会重复绘制。并借助于圆的对称性,只需计算这八分之一的区域就能高效填充一整个圆形。
撤回重做功能来源于我基于命令模式实现的DrawCommand命令类(至于说为什么我不用qt自带的命令类,我才不会说是因为我已经写了一大堆了才发现有这个类而且我喜欢造轮子所以就没用),他储存了撤销与重做所需要的数据与方法。在画板类中维护着一个历史命令栈和重做命令栈,当用户进行撤回重做功能时,画板类将从栈中取出命令并执行函数从而实现了功能的撤回与重做。
对于绘图工具或是滤波器、颜色混合算法这些在运行周期内被大量创建获取的并且构造、对应复杂的对象,我都建了一个带有对象池的单例的工厂类,并借助Map(字典)来储存枚举与构筑函数的对应关系,从而大大增强了创建对象的灵活性。