Featured image of post 【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

紀錄 PyQt5 多執行緒 — QThread、背景執行、防止凍結、PyQt5 應用開發、使用者介面。

看完這篇文章你會得到的成果圖

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

此篇文章的範例程式碼 github

https://github.com/howarder3/ironman2021_PyQt5_photoshop/tree/main/day28-30_final_project

之前內容的重點複習 (前情提要)

我們接下來的討論,會基於讀者已經先讀過我 day5 文章 的架構下去進行程式設計
如果還不清楚我程式設計的邏輯 (UI.py、controller.py、start.py 分別在幹麻)
建議先閱讀 day5 文章後再來閱讀此文。
【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)
https://wongwongnotes.com/posts/python/gui/pyqt/pyqt5-5/

設計我們的 UI

雖然原本我有想直接拿 Day17 的內容繼續改,
後來覺得架構上還不夠漂亮,最後決定乾脆直接砍掉重練比較快哈哈哈。

既然要搞就一次搞到最大吧!

這次我設計的 final project UI 初版長這樣

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

欸等等先別吐血啊!!! 這 UI 不是一天搞出來的啊!!
UI 就請大家自己慢慢刻哈哈哈哈哈!!!

(你說突然這也突然跳太高階了吧?!
其實只是設定一些顏色而已XD,之前因為這個相對簡單就沒特別介紹)

排版控制 Layout 系列

如果還有機會的話有空在介紹,不過這邊的內容多半是用於排版,
所以也難能以單一例子舉出 demo 在幹嘛,
總之可以自己玩玩看 Layouts 這一塊,
先「拉一個 Layout」,再把「要排版的元件丟進去」,就對自動對整齊了!

  • Layouts 系列:

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

設定字體顏色,背景顏色的地方位於 styleSheet

使用的應該是類似 css 語法? 我不確定XD
總之有興趣可以直接參考下面的延伸閱讀進行修改,
設定位置在 Qt desinger 的這邊

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

延伸閱讀:給想研究更多怎麼設定顏色的人,請參考 Qt Style Sheets Examples

設定 Logo 的地方

Qlabel 就是一個我們可以輸入圖片的地方,
透過右邊的「…」,選擇一張我們要的圖片,就可以直接把 LOGO 嵌入 UI 中了。

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

執行看看 UI.py 畫面是否如同我們想像

一樣,這程式只有介面 (視覺上的呈現),沒有任何互動功能

  • 看看我們製作出來的介面
python UI.py

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

讚到不行XDDDD (自己講

程式架構篇

這次的 final project 真的內容會做太多,如果沒有好好規劃一下架構,
很有可能寫出來的東西會一團亂XDDD
所以我們要先趁今天來規劃,明天再來慢慢把功能實現

先從大方向切入,我們有那些大的「重要元素」?

依照 day5 的設計,我們至少也會有

  • UI (作為前端顯示介面)
  • controller (做為後端邏輯控制)
  • start (啟動用,沒什麼好討論的)

而 UI 因為我們在 Qtdesigner 已經設計得夠複雜了,也不太需要另外寫程式,
所以基本上在經由 pyuic 的轉換後,就已經完成了 UI.py

pyuic5 -x day28.ui -o UI.py

controller 是我們這邊要討論的重點,在我們的 photoshop 後端邏輯裡面,
我之前在 day17 實作的最終版是把「圖像+圖像變化方法」寫在同一個 class 當中,
我們如果繼續讓我們的「變化方法增加」,不適不能這樣實作,
但最終我們會有一個超級巨大的 class 共同實作 「圖像+圖像變化方法」,
於是我決定把「變化方法」拆出去獨立一個 class,
使得「圖像」與「變化方法」分開成兩套獨立機制,而能互向協助。

這也符合 design pattern 的 Interface Segregation Principle(ISP) 介面隔離原則
以遊戲來說,我們也可以舉例:
我們大可以把一個角色的所有「屬性、裝備、技能」全部都包在這個角色當中,
但如果另外一個玩家也玩了同一個職業,我們何必把技能「整個複製進去另外一個角色當中呢?
於是我們可以把「技能」這個介面分離出去,讓有需要的類別再去呼叫這個介面即可。

因此,這時候就能透過這樣的拆分方式,把我們實作的彈性與擴充功能的彈性提升。

  • 舉例:

套用 design pattern 前

套用 design pattern 前,基本上這個設計沒什麼問題,
硬要說缺點只是維護職業技能時,「需要一個個角色去調整內部的技能

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

套用 design pattern 後 (使用 Interface Segregation Principle(ISP) 介面隔離原則)

套用 design pattern 的 Interface Segregation Principle(ISP) 介面隔離原則後,
我們把技能獨立出來維護,這樣的好處就是我們可以調整一次職業技能,
所有的角色都能得到修正!

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

而這邊我們也要做同樣的事情,我們要把「修改圖片的方法」這個介面獨立出來!

於是整理一下,得到我們目前想設計的架構

獨立「圖片本身」與「圖片處理方法」,
另外因為滑鼠事件的資訊比較特別,需要從圖片上獲取,所以我們也另外獨立出來處理

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

【重要】抓出誰會是觸發事件的 trigger

第一次設計這個系統的時候,就碰到一個最關鍵的問題,
沒有提早先想好架構就開始寫,導致繞了很多彎路」,

應該要先想好再下去寫的問題就是,在這個系統裡面,「什麼事情會是觸發事件的 trigger?

如果我們不知道「什麼事件會被使用者觸發」,
就不知道要從哪個點開始啟動後續的修改」,

我一開始就是瘋狂地開始寫功能XDD,然後沒注意誰呼叫誰,
結果功能都有了,但是 trigger 位置寫在錯誤的 class,
所以要重搞順序XDD

以結論來說,這個系統的 trigger 位置如下

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

所以在寫系統時,我們需要特別注意,
我們箭頭指的地方就是「會觸發事件的 trigger」,
凡經過此處的程式都是「一次修改的起點」,引導我們進行往後的修改。

圖形修改介面 (interface) 設計

基本上我們設計的「修改圖片」都有符合一些同樣的原則,
我們可以使用「介面繼承」的方式來實現,
為了達到這樣的效果,我們需要 「import abc」 這個 python 酷酷的 library,
細節的部分我會再另外寫一篇文章,這邊我們先直接實作。

註:冷知識(?) abc = the infrastructure for defining 「abstract base classes (ABCs)」 in Python
簡單來說,就是「抽象的類別 (class)」定義

我們預計設計的架構如下,一共會有三層,底下會一層層介紹:

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

介面的大祖宗 (method_interface),一切介面方法從此開始實作

我們可以大致歸納我們全部的變化介面都會有兩個基本的要素:

  • 初始化參數:「init
  • 更新圖片: update_img

另外,我們強制這兩個介面在繼承後都必須被定義,
所以我們透過「@abc.abstractmethod」,定義這個抽象的方法 (未實作功能),
以及「return NotImplemented」,如果使用者繼承介面後未定義這個函數,
會跳 「NotImplemented error」,強制跳錯 (逼使用者一定要定義介面內容)。

import abc

class method_interface(abc.ABC):
    @abc.abstractmethod
    def __init__(self):
        return NotImplemented

    @abc.abstractmethod
    def update_img(self):
        return NotImplemented 

介面的父母 (滑條方法介面 slider_method_interface, 畫筆方法介面 pen_method_interface)

我們會實作的「圖像變化方法」,能夠產生變化的方法大概有兩種,
一種是滑條類,另一種是畫筆類。

這兩類在實作時,都會繼承我們的介面大祖宗 (method_interface),
然後再分別基於滑條的特性與畫筆的特性,實作各自的介面。

這所有的介面方法都屬於「圖片變化方法」,所以我們會強烈推薦用繼承的方法撰寫。
會比起一個個慢慢定義有更好的架構與維護性
(維護一個父類別,也許底下的所有子類別都能全部受惠到這次的更動。不用一個個慢慢改。)

最大的差別就是:

  • 滑條拉回去後,可以復原 (不會是破壞原圖的變動)
  • 畫筆畫下去後,不可復原 (會破壞原圖的變動)

因此這兩個方法我們要分開實作,實作細節一樣我們明日再提,
今天光是講大架構就已經夠累了。

各個細部的方法實作

在看該方法是「滑條方法介面 slider_method_interface」或是「畫筆方法介面 pen_method_interface」後,
我們就可以開始實作細部功能了,這部分的實作細節我們就明日在提吧。
(今天光是講完大架構相信大家已經都累了XDDD)

最終系統架構圖

這個是舊版,滑鼠那部分因為是新加的,目前還沒更新上去,
不過相信上面已經說明得相當完整了,應該能大約類推出他在圖形上會呈現的細節

【PyQt5】Day 28 final project - 1 / 來搞一個自己的 photoshop 吧!UI 篇 + 純程式架構篇 (結合 PyQt + OpenCV)

Reference

使用 Hugo 建立
主題 StackJimmy 設計