完整版請參考:
我們之前討論到了我們是如何設計程式的程式架構,
以大概念來說,我們主軸還是圍繞在
三大面向,而 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)