整个项目的算是基于 Paint.Net 开发吧,将它对 Gdi 封装的还有调用重新整理了一下,这篇文档要介绍一下项目的一些概念和结构。
要介绍的有以下三点:
- GDI 绘图原理以及优化
- 位图的表示和渲染
- 文档、文件加载与保存
- 项目结构
GDI 的绘图其实很好理解,就是在内存里边操作一个数组,然后在特定的时间将这个数组复制到显存中,这样的操作也注定了它效率不高,不能利用 GPU 帮助运算还得二次存储,在 Window 7 以上的系统已经能将 GDI 的操作直接放到显存了,但是依旧不能使用 GPU 运算。
对 GDI 操作的优化基本都大同小异,主要是替换掉 System.Drawing 默认的一些访问方式,比如 DrawImage 效率实在低,所以改用 Bitblt 直接复制内存,还有 SetPixel 和 GetPixel 改成用指针访问像素等,从相对底层又频繁的操作开始优化总是最有效的。
这些优化在项目中大部分由 PtnGraphics 和 Surface 类负责。
因为在项目中操作图像不是每个操作都需要显示出来的,所以一直直接访问 Bitmap 实例是相当费时且无意义的,这些操作直接在内存进行,需要的时候再转移到 Bitmap 实例上,因此我们需要用一个结构来表示位图。
首先位图是许多颜色像素的集合,根据颜色表示方式的不同我们有不同的位图格式,最常见的是 Rgba 4个通道每个8位总共32位的颜色格式,另外还有按索引存储的8位颜色格式,这些颜色格式集中在项目的 Photo.Net.Core.Color 命名空间下。
确定好颜色后我们看看如何表示一张位图,,很明显最好抽象成一个二维数组,每个元素都是一个颜色结构,对于二维数组我们实现了 BitVector2D 类,再由 Surface 包含它来表示一张内存中的位图。
位图的渲染相对简单,使用一个 SurfaceBox 继承自 Control 作为绘制平面,在其内部包含 Surface 实例,当然考虑到不同位图格式的渲染方式不同,这里有必要弄一个 SurfaceBoxRenderer 抽象类放到 SurfaceBox 的实例字段中。
Document、DocumentView、DocumentWorkSpace 一层一层上来都是对 SurfaceBox 的封装,加入了图层、坐标换算、缩放各种功能,最终一个打开的文件就是 DocumentWorkSpace 实例,在应用层使用一个 AppWorkSpace 管理所有 DocumentWorkSpace。
文件加载和保存都由 FileType 类负责,它需要从文件中实例化一个 Document 实例出来。
文件保存格式其实有很多需要考量的,我觉得直接序列化一个 Document 实例到文件中通用性不高,这样其他应用没有 Document 的元数据根本别想打开这个文件。
这个结构只是初步设计,希望大家提提建议:
- Photo.Net --- 主项目
- Photo.Net.Core --- 定义了 Color、Surface 类等核心结构。
- Photo.Net.Base --- 辅助层,P/Invoke 封装、系统信息等。
- Photo.Net.Gdi --- 提供 GDI 操作的封装。
- Photo.Net.Tool --- 项目的工具,画笔、画刷、选区等。
- Photo.Net.Algorithm --- 算法层,目前无实现
- Photo.Net.Resource --- 项目的资源管理。