Back
Featured image of post ImagingS 开发笔记

ImagingS 开发笔记

来源

最初的想法来自 计算机图形学的课程项目作业,其要求基于 Python + Qt 实现一个支持简单图元绘制的 GUI+CLI 程序。

之前一般仅使用 Python 写一些简单的小型项目。曾写过一个较大项目,但即使使用了类型标注,也很快随着项目复杂度的提升,遇到了维护瓶颈,大大影响开发效率,遂中断了开发。

这次是第一次使用 Python 开发图形用户界面的软件,也是第一次使用 Qt,考虑到这个项目的需求较为明确并且有足够的扩展可能,决定将此项目作为一个 Python 较大单人项目的练手,并规避以前 Python 项目遇到的一些问题。

Idea

根据课程项目的模板代码,发现此项目非常适合 OOP 模式设计。结合以前的 WPF 学习经历,决定依照 WPF 的设计思路,试图在 ImagingS 中复刻 WPF 的呈现模型。

  1. 图元 Geometry 对象描述图元及其绘制算法
  2. 绘图 Drawing 将各种图元绘制到 DrawingContext 上
  3. 绘图上下文 DrawingContext 提供具体绘图的抽象,统一 GUI 绘图和图片文件绘图。

通过这三个层次,将呈现系统的两部分:定义与呈现分离。

设计

核心的 API 集中在以下几个类中,其中与绘制抽象相关的大部分类均能在 WPF 绘制模型中找到对应。

  1. 图元类:各种图元的基类,定义了绘制算法(strokePoints)和变换。
class Geometry(PropertySerializable, ABC):
    def __init__(self) -> None: pass

    def transform(self) -> Optional[Transform]: pass

    def strokePoints(self, pen: Pen) -> Iterable[Point]: pass

    def fillPoints(self) -> Iterable[Point]: pass

    def inStroke(self, pen: Pen, point: Point) -> bool: pass

    def inFill(self, point: Point) -> bool: pass

    def transformed(self) -> Geometry: pass

    def bounds(self) -> Rect: pass
  1. 绘制类:所有可绘制元素的基类,定义了绘制函数(render)。对于图元对象,实现了 GeometryDrawing 来实际完成图元的绘制任务。
class Drawing(PropertySerializable, IdObject, ABC):
    def __init__(self) -> None: pass

    @abstractmethod
    def render(self, context: RenderContext) -> None: pass

    @property
    @abstractmethod
    def bounds(self) -> Rect: pass
  1. 绘图上下文类:对实际绘制的目标的抽象
class RenderContext(ABC):
    @abstractmethod
    def _point(self, position: Point, color: Color) -> None: pass

    @abstractmethod
    def bounds(self) -> Rect: pass

    def point(self, position: Point, color: Color) -> None: pass

    def points(self, positions: Iterable[Point], brush: Brush)
     -> None: pass
  1. 变换类:所有变换的基类,定义了如何将一个点变换到另一个点
class Transform(PropertySerializable, IdObject, ABC):
    def __init__(self) -> None: pass

    @abstractmethod
    def transform(self, origin: Point) -> Point: pass
  1. 文档类:定义了当前文档所包含的画刷,图元,画布大小等信息,实现了序列化操作
class Document(PropertySerializable, IdObject):
    def __init__(self) -> None: pass

    @property
    def brushes(self) -> IdObjectList[Brush]: pass

    @property
    def drawings(self) -> DrawingGroup: pass

    @property
    def size(self) -> Size: pass

    def save(self, file, format: DocumentFormat = DocumentFormat.ISD)
     -> None: pass

    @staticmethod
    def load(file, format: DocumentFormat = DocumentFormat.ISD)
     -> Document: pass
  1. 画布类:将抽象绘制对象包装成 Qt 中的绘制对象
class Canvas(QGraphicsView): pass

class DrawingItem(QGraphicsItem):
    def __init__(self, drawing: Drawing, 
        size: QSizeF, parent: Optional[QGraphicsItem] = None): pass
  1. 交互类:定义了 GUI 上与图元交互的所有操作,提供给画布一个统一的接口来处理用户交互
class Interactivity(QObject):
    started = pyqtSignal(QObject)
    ended = pyqtSignal(QObject)
    updated = pyqtSignal(QObject)

    def __init__(self) -> None: pass

    def start(self) -> None: pass

    def end(self, success: bool) -> None: pass

    def update(self) -> None: pass

    @property
    def viewItem(self) -> Optional[QGraphicsItem]: pass

    @property
    def state(self) -> InteractivityState: pass

    def onMousePress(self, point: QPointF) -> None: pass

    def onMouseMove(self, point: QPointF) -> None: pass

    def onMouseRelease(self, point: QPointF) -> None: pass

    def onMouseDoubleClick(self, point: QPointF) -> None: pass

    def onKeyPress(self, key: QKeyEvent) -> None: pass

    def onKeyRelease(self, key: QKeyEvent) -> None: pass

实现

  • To be done

总结

这个项目借鉴了 WPF 呈现模型的设计,一个成熟的模型的确是富有扩展性和较易维护的。Python + Qt 的组合开发效率也是很高的,但是运行效率有一定损失。

项目目前的主要不足,一是图形学相关算法实现较少;二是部分类的 API 设计不够准确,这部分可能来源于 Qt 绘图模型和 WPF 依赖的 DirectX 两者的不一致,复刻的过程中部分设计需要适配 Qt ,造成一定妥协(例如 PyQt 的绘制效率低下,导致部分功能不容易在此模型下高效实现)。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy