2024年11月2日 星期六

MicroPython 學習筆記 : 使用 Adafruit 的 GFX 函式庫繪圖

今天在網路上搜尋 MicroPython 指針時鐘資料時找到下面這篇 : 


範例程式使用了 GFX 這個模組來繪圖, 查詢網路發現原來這也是 Adafruit 的作品, GFX 旨在為各種尺寸的 OLED 及 LCD 營幕提供一個通用繪圖函式庫, 其原始碼使用 C 語言寫成, 參考 :


GFX 的 MicroPython 驅動程式 gfx.py 可從 GitHub 下載 :


GFX 運作原理參考下面這兩篇 :


以下測試使用 128*64 解析度的 SSD1306 OLED 顯示器, 搭配燒錄 MicroPython v1.23.0 韌體的 LOLIN D32 開發板 (ESP32). SSD1306 透過 I2C 介面與 ESP32 開發板的 GPIO21 (SDA) 與 GPIO22 (SCL) 連接, 這是 ESP32 預設的 I2C 接腳, 但也可以用 SoftI2C 模組將一般 GPIO 腳模擬成 I2C 的 SCL 與 SDA 接腳, 參考 :


本測試使用的 Adafruit SSD1306 驅動程式可從 GitHub 下載 :


首先將下載的 ssd1306.py 與 gfx.py 上傳到開發板 : 

MicroPython v1.23.0 on 2024-06-02; Generic ESP32 module with ESP32
Type "help()" for more information.

用 dir() 檢視 gfx 模組內容 :

>>> import gfx     
>>> dir(gfx)      
['__class__', '__name__', '__dict__', '__file__', 'GFX']   

其中 GFX 類別就是用來建立繪圖物件的類別, 為了方便通常直接從 gfx 匯入 GFX 即可 :

>>> from gfx import GFX   

先匯入模組 : 

>>> from machine import I2C, Pin      
>>> import ssd1306   

建立 SSD1306_I2C 物件 :

>>> i2c=I2C(0, scl=Pin(22), sda=Pin(21)) # ESP32 I2C   
>>> oled=ssd1306.SSD1306_I2C(128, 64, i2c) # 0.96 吋解析度 128*64  

這樣就可以呼叫 GFX 類別的建構式 GFX() 來建立 GFX 繪圖物件了, 呼叫時必須傳入螢幕寬度, 高度, 以及繪製像素的函式, 參數結構如下 :

GFX(width, height, pixel_function)   
 
因為 GFX 是一個通用的繪圖函式庫, 不是專用於特定螢幕 (可用於 SSD1306 或 ILI9341 等螢幕), 因此必須傳入螢幕尺寸與繪製像素的函式, 以 SSD1306 而言, 這些參數都可在 SSD1306_I2C 物件中取得, 檢視 oled 物件 : 

>>> dir(oled)     
['__class__', '__init__', '__module__', '__qualname__', '__dict__', 'addr', 'buffer', 'fill', 'framebuf', 'invert', 'pixel', 'scroll', 'text', 'width', 'height', 'external_vcc', 'pages', 'poweron', 'init_display', 'write_cmd', 'show', 'poweroff', 'contrast', 'write_framebuf', 'i2c', 'temp']

其中 width 與 height 屬性就是螢幕尺寸; 而 pixel() 就是像素繪製函式 : 

>>> oled.width   
128
>>> oled.height   
64
>>> oled.pixel      
<bound_method>

只要將此三個參數傳入 GFX() 即可建立 GFX 物件 : 

>>> gfx=GFX(oled.width, oled.height, oled.pixel)     
>>> type(gfx)     
<class 'GFX'>   

檢視 GFX 物件內容 :

>>> dir(gfx)    
['__class__', '__init__', '__module__', '__qualname__', '__dict__', 'fill_rect', 'hline', 'line', 'rect', 'vline', 'width', 'height', '_pixel', '_slow_hline', '_slow_vline', 'circle', 'fill_circle', 'triangle', 'fill_triangle']

其中 width 與 height 為傳入隻螢幕尺寸 :

>>> gfx.width   
128
>>> gfx.height      
64

GFX 物件提供許多繪圖函式, 摘要說明如下表 :


 GFX 物件的方法 說明
 line(x0, y0, x1, y1, color)  在座標 (x0, y0) 與 (x1, y1) 間畫一直線, color=0 (暗)/1 (亮)
 hline(x, y, w, color) 從座標 (x, y) 開始向右畫一段長 w 的水平線, color=0 (暗)/1 (亮)
 vline(x, y, h, color) 從座標 (x, y) 開始向下畫一段高 h 的水平線, color=0 (暗)/1 (亮)
 rect(x, y, w, h, color) 從座標 (x, y) 開始繪製寬 w 高 h 的矩形, color=0 (暗)/1 (亮)
 fill_rect(x, y, w, h, color) 從座標 (x, y) 開始繪製寬 w 高 h 的填滿矩形, color=0 (暗)/1 (亮)
 triangle(x0, y0, x1, y1, x2, y2, color) 連接座標 (x0, y0), (x1, y1), (x2, y2) 三點繪製三角形, color=0 (暗)/1 (亮)
 fill_triangle(x0, y0, x1, y1, x2, y2, color) 連接座標 (x0, y0), (x1, y1), (x2, y2) 三點繪製填滿三角形, color=0 (暗)/1 (亮)
 circle(x0, y0, r, color) 以 (x0, y0) 為圓心, r 為半徑繪製圓形, color=0 (暗)/1 (亮)
 fill_circle(x0, y0, r, color) 以 (x0, y0) 為圓心, r 為半徑繪製填滿圓形, color=0 (暗)/1 (亮)


這些繪圖方法都是利用呼叫 GFX() 時傳入的第三參數, 即顯示器的像素繪製函式 oled.pixel() 將圖描出來的, 不同的螢幕有各自的像素繪製函式, 因此 GFX 是一個可用於各型螢幕的通用函式庫. 


1. 繪製直線 :   

GFX 物件的 line(), hline(), vline() 可用來繪製直線, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()     

呼叫 line() 方法繪製直線, 先畫一條左上角到右下角的對角線 : 

>>> gfx.line(0, 0, gfx.width-1, gfx.height-1, 1)  # 從左上角到右下角的對角線
>>> oled.show()    

再畫一條右上角到左下角的對角線 : 

>>> gfx.line(gfx.width-1, 0, 0, gfx.height-1, 1)   # 從右上角到左下角的對角線
>>> oled.show()    

結果如下 : 




接下來測試 vline() 與 hline(), 先清螢幕 :

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()   

先畫一條起點為 (0, 31) 寬度為 128 的水平線  (即左邊中央往右) : 

>>> gfx.hline(0, 31, 128, 1)     
>>> oled.show()   

然後畫一條起點為 (63, 0) 高度為 63 的垂直線 (即最上面中央往下) : 

>>> gfx.vline(63, 0, 64, 1)    
>>> oled.show()     

結果如下 :




當然這也可以用 line() 方法來畫, 只是要傳入直線兩端座標較麻煩 : 

>>> gfx.line(0, 31, 127, 31, 1)   
>>> gfx.line(63, 0, 63, 63, 1)   
>>> oled.show()    

結果與上面是一樣的. 


2. 繪製矩形 :   

GFX 物件的 rect() 與 fill_rect()  可用來繪製矩形, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()     

繪製一個沿螢幕邊框的方框矩形 : 

>>> gfx.rect(0, 0, 128, 64, 1)    
>>> oled.show()   

結果是一個有 1px 寬度的方框 :




如果要讓框邊加粗 1 個 px, 可以在此矩形內畫一個內縮 1px 的矩形, 這時左上角座標要用 (1, 1), 尺寸因兩邊各縮 1px 變成 128-2=126 與 64-2=62 : 

>>> gfx.rect(1, 1, 126, 62, 1)      
>>> oled.show()     

結果如下 :

 


下面測試 fill_rect() 效果, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()      

然後在螢幕四周畫一個 1px 邊框 : 

>>> gfx.rect(0, 0, 128, 64, 1)     
>>> oled.show()     

接著往內縮一個 px 用 fill_rect() 畫一個填滿內部的矩形, 因邊框佔據 1px, 內縮 1px, 故左上角座標是 (2, 2), 尺寸要減 4 : 

>>> gfx.fill_rect(2, 2, 124, 60, 1)    
>>> oled.show()  

結果如下 : 




由於填滿效果會不斷繪製畫素, 故照相時會因掃描頻率較低產生 aliasing 現象, 實際顯示效果是白邊框內有一黑框, 裡面才是白色填滿. 


3. 繪製三角形 :   

GFX 物件的 triangle() 與 fill_triangle()  繪製三角形, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點
>>> oled.show()     

用 triangle() 在螢幕繪製一個等腰三角形 :

>>> gfx.triangle(0, 0, 127, 0, 63, 64, 1)   
>>> oled.show()      

結果如下 : 




用 fill_triangle() 在螢幕繪製一個填滿的等腰三角形 :

>>> oled.fill(0)      # 填滿暗點清除螢幕
>>> oled.show()     
>>> gfx.fill_triangle(0, 0, 127, 0, 63, 64, 1)    
>>> oled.show()    

結果如下 : 




4. 繪製圓形 :   


GFX 物件的 triangle() 與 fill_triangle()  繪製三角形, 先清除螢幕 : 

>>> oled.fill(0)      # 填滿暗點清除螢幕
>>> oled.show()     

呼叫 circle() 在螢幕左半邊畫一個圓形, 圓心為 (31, 31); 再呼叫 fill_circle() 在右半邊畫一個填滿的圓形, 圓心為 (96, 31), 半徑都是 31 : 

>>> gfx.circle(31, 31, 31, 1)   
>>> gfx.fill_circle(96, 31, 31, 1)   
>>> oled.show()     

結果如下 : 




沒有留言:

張貼留言