完整版请参考:
我们之前讨论到了我们是如何设计程序的程序架构,
以大概念来说,我们主轴还是围绕在
三大面向,而 UI 我们已经透过 Qt desinger 设定完成,
而 start 没什麽好说。
我们开始着重讨论 controller 的细节。
我们选择独立「图片本身」与「图片处理方法」,
我们想避免把所有图片的功能全部都做在我们的图像中心 (image center) 里面,
这样会变成一个超级巨大的 class (又名为 god class),
功能太多之後要维护一个特定功能太难了,所以我们才独立「图像处理方法」进行操作。
这部分是套用 design pattern 的设计原则 (使用 Interface Segregation Principle(ISP) 介面隔离原则)
我们可以把介面分离出来,更方便之後功能的维护。
套用 design pattern 的 Interface Segregation Principle(ISP) 介面隔离原则後,
我们把「修改图片的方法」这个介面独立出来,更方便我们维护「图片修改」的部分。
而继承的部分,从变更图片的「所有共通方法 -> 滑条类方法/笔类方法 -> 各项细节方法」。
day28,我们讲解了我们系统的大架构,与 UI 的设计。
而 day 29,我们把每个细节的功能全部都介绍完了。
那今天我们还有什麽事情可以做呢?
我们仔细观察不论是 小画家 或 photoshop 的程序,
都会有提供「还原」或「重做」的功能。
我们该如何在我们自己的 photoshop 实作出这种功能呢?
首先,如果用最简单的方法,也许我们可以存图片?
也就是说,我们开一个 queue,「每更新一次画面,就存一个 frame」,
这样听起来简单暴力,但是可行XDDD
不过,如果我们再更仔细的观察,因为如果「把每张图都存起来」,
势必会消耗大量的储存空间,因此应该会有更好的优化方法,
我们思考有没有可能对方存的只有「原图 + 修改步骤」,
换句话说,也就是「原图 + (图片的变化量)」。
我们可以常常在还原功能那边看到「上一个步骤」具体进行了什麽的操作,
而不是「保存的上一张图片」,因此,我们也乾脆来实作一个保存「变更的方法」。
如果这样子做,我们就可以省下大量的储存图片空间,
而且我们也可以直接知道上一个「步骤内容」是什麽。
我们新增了可以记录步骤的框框,「还原」或「重做」的按钮。
我们宣告了一个新的 class method_steps_recoder
class method_steps_recoder(object):
def __init__(self, text_recordsteps):
self.method_steps = []
self.text_recordsteps = text_recordsteps
def add_each_method_step(self, each_method_step):
self.method_steps.append(each_method_step)
def update_recordsteps(self):
msg = f"All saved steps: \n"
for idx, ele in enumerate(self.method_steps):
msg +=(f"{idx+1}: {ele}\n")
self.text_recordsteps.setText(msg)
稍微想了一下,这个保存机制初始化的时间,
应该与图片刚初始化的时间同时,
因此我们也在 class image_center 开始读档的时候,
宣告 method_steps_recoder(),同时传入要修改的参数。
self.method_steps_recoder = method_steps_recoder(self.ui.text_recordsteps) # record steps
我们上面已经把介面继承写得非常有架构了,因此这次要记录步骤的功能,
我们只需要去更新上层的介面即可。
我们在 slider_method_interface 新增一个函数 append_each_method_step(),
并修改 slider_release_event(滑条释放的时间),会呼叫这个函数,保存这次的更新内容。
就完成了这部分的所有功能了!
class slider_method_interface(method_interface):
# final update back to image center (not necessary, for double check)
def slider_release_event(self):
img = self.setimage(self.tmp_origin_img)
self.append_each_method_step() # append all the methods include variables in to method_steps_recoder
self.image_center.update_img(img)
def append_each_method_step(self):
self.image_center.method_steps_recoder.add_each_method_step(self) # append all the methods include variables in to method_steps_recoder
因此,现在只要有滑条值的变化,都会启动一次纪录 (每拖曳并放开滑鼠时纪录一次),
如下所示:
实作到此的我,发现目前想要实现出「还原」或「重做」,
还存在一些问题:
依照演算法,很多对图片的变更可能都是「对图片的破坏性变更」,
也就是说,替图片「减少一笔」的难度远比「增加一笔」高出非常多。
例如像是滑条显示、步骤显示,这些可能需要都被还原。
目前实作上只有处理图片的架构比较完整,
但这些内容并没有被好好的封装起来,导致还原上有困难。
这大概是我目前系统架构做不出还原功能的致命伤。
因为滑条只有一个,而照理来说「每进行一次滑条的变动」,
就应该要 「new 一个新的滑条变动的 instance」,
因为目前这部分我是绑死再一起的,所以这边确实应该还要再拆分。
上面三个问题,也可以浓缩成一个设计问题。
基本上我会考虑将机制改为,存「原图 + 所有变化的方法」,
与上述不同的是不只是存「图片的变化」,
这次连 UI 当下的状态可能也需要被储存下来。
因此,未来如果要继续实作这部分的功能。
我会考虑把「储存的方法」改为存「UI 变化设定 & 当下图的图片变化」
系统运作的逻辑会类似保存:
原图 -> 图面&UI变化 -> 图面&UI变化 -> 图面&UI变化...
所以我们纪录的东西反而是「步骤」,
至於还原的时候,可以以当时保存最旧的图片,
依照「步骤」全部重新运算,
应该就能够如我们预期的完成「还原」或「重做」的功能。
此外,我们要处理一下我们系统的效能优化,
昨天的程序执行後,如果是不够强大的 CPU,或解析度太大的图片,
会没有办法应付「移动滑条」造成「图片的连续变化」运算。
主要是因为,滑条跟图片一起变,图片解析度太大,
我们电脑处理不来,会导致程序严重卡在运算上。
於是我们就先移除这个「动态演饰」的效果,
我们只保留「变动前的样子」、「变动後的结果」。
透过这样的方式大幅减少中间过程的对电脑效能上的负担。
# trigger function, get your signal from here
def setsliderlabel(self):
self.label.setText(f"{self.prefix}{self.slider.value():+}")
# self.update_img() # for the efficiency reason, we don't let the picture change with our slider
这样就完成了我们的效能优化。
class slider_method_interface(method_interface):
# final update back to image center (not necessary, for double check)
def slider_release_event(self):
img = self.setimage(self.tmp_origin_img)
self.append_each_method_step() # append all the methods include variables in to method_steps_recoder
self.image_center.update_img(img)r
这次我们先完成了图片的滑条优化功能,我们把因为滑条的连续变动,
导致图片的连续变化,电脑计算跟不上的问题进行了修正。
我们考虑到现有机制如果真的想要实现「还原」或「重做」的功能,
我们必须把「滑条」与「滑条影响图片的内容」介面整个进行修改,
也就是说XD,我们之前设计的介面还不够细、想得不够周全XD
应该是要:
滑条控制(只有一个) ->
new 一个新的图片变化方法(方法应该要多组,可改多次) ->
保存图片修改方法、UI变化内容(最好可以附带一段此方法的说明,含操作的变数) ->
保存此方法(存进 list)。
最後就是不断循环。
有机会我们再把程序的这部分架购进行优化,目前这个做下去预计又是一个架构上的大改了XD
今天算是铁人赛这三年中最辛苦的一年,
老实说我有先囤了一些稿才来报名,因为今年公司比较忙碌,
而且甚至到铁人赛的最後一天才开赛XD,
但没想到最後居然还是在最後几天被迫熬夜加班才跟得上进度XD。
不过我抱持的心情就是,既然都参加了,就一定要好好的把它完成!
所以才会想先屯稿、拖到最後一天开赛XD
说真的我觉得写铁人赛最大的受惠者永远是作者,
30天前我根本连Qt都没用过,现在我也能变成这样XD
真的信不信由你,我真的是30天内从零开始学的XD,
所以才说如果真的有心想学,铁人赛最终会是让自己受惠最多的短时间高度成长体验。
★ 本文也同步发於我的个人网站(会有内容目录与显示各个小节,阅读起来更流畅):【PyQt5】Day 30 - final project - 3 / 来搞一个自己的 photoshop 吧!把每个方法封装起来制作出还原功能吧! (结合 PyQt + OpenCV)
<<: Day 30 - 3D绘图篇 - 噪声地形演算II - 成为Canvas Ninja ~ 理解2D渲染的精髓
>>: Day30 ATT&CK for ICS - Inhibit Response Function(2)
集成学习 (ensemble learning) 的概念在於透过结合多个不同的模型来达到不同模型之间...
扩充 MAUtility,让原来的 func 能计算 n 条均线 在原来的 func 上加上 ran...
Day 11 - 用Kotlin解数学题:考拉兹猜想 今天我们会用我前面所教的,来解今天的数学题,顺...
早起运动Day12 - 你不想动的点在哪里 《试错力》这本书,讲的是创新如何从无到有。 试错的重点有...
最近看了很多滚动动画,实在让人惊叹!! 查了背後逻辑发现是用 Gsap 里的 ScrollTrigg...