有Windows篇當然也會寫一篇Linux的。
Windows篇在此Linux有兩個常用的視窗系統函式庫:GTK和QT,其他跟圖形介面有關的函式庫也大約分成這兩個體系,開發Linux程式基本上要在兩者之間選一個然後學習它的用法。
Linux的視窗系統不是只有這兩種,不過要考慮使用者的電腦上可能預設沒有安裝,還要先安裝函式庫才能用你的軟體,儘量用普遍的東西比較好。
本篇使用GTK體系的圖檔讀取函式庫gdk-pixbuf,QT我就沒試過了。
GTK雖然語言是純C,但有物件繼承多型的概念,下面就可以看到GdkPixbuf繼承GObject,所以操作GObject的函式也可以用在GdkPixbuf。
不過為了用沒有物件概念的C語言去模擬物件,程式碼就很冗長。
例如這個函式,名稱裡有gdk_pixbuf代表class名稱,物件放在第一個參數,有的函式還要多一個macro用來轉型。
gdk_pixbuf_get_width(pixbuf); |
C++的寫法是這樣。
需求跟Windows篇同樣,輸入要能給檔名和記憶體區塊兩種。
實驗用的發行版是LinuxMint 17.2,別的發行版可能套件名稱和編譯時的參數不一樣,要自己試。
同樣不詳細說明每個函式、struct、常數的用法,本篇下面有官方文件連結,請自己查。
1.首先要安裝開發用套件
名稱通常是函式庫名稱後面加上-dev或-devel,依發行版而異,自己用套件管理器找找看,例如筆者的是libgdk-pixbuf2.0-dev。
2.include header
#include<gdk-pixbuf/gdk-pixbuf.h> #include<gio/gio.h> |
gdk-pixbuf不需要呼叫特別的函式初始化。
再來產生一個GdkPixbuf物件
3a.如果輸入是檔名
GError* error=NULL; GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file("fileName.png",&error); |
3b.輸入是記憶體的話,雖然有gdk_pixbuf_new_from_bytes和gdk_pixbuf_new_from_data可以傳入指標,但這些要的是解碼後的RGBA值。
圖檔原封不動放在記憶體的情況則跟Windows篇一樣,用一個stream包起來再丟給GdkPixbuf,使用的class是GInputStream
GError* error=NULL; GInputStream* stream=g_memory_input_stream_new_from_data(buffer, bufferSize,NULL); //reference=1 GdkPixbuf* pixbuf=gdk_pixbuf_new_from_stream(stream,NULL, &error); //reference增加 g_object_unref(stream); //reference減1 |
可以看到為了用C語言去模擬物件,建立物件的方法不是用建構子,而是呼叫名稱裡有new的函式。
這裡跟Windows篇一樣是用reference count,可能不是所有人都看過Windows版,所以再介紹一次:物件內部有個計數器,記錄有多少物件參考到它,釋放資源的方法是計數器減到0才刪除,而不是直接用delete關鍵字。
建好pixbuf之後就把g_memory_input_stream_new_from_data的reference扣掉,之後刪除pixbuf時就自動刪除stream。
GObject是GTK的一個底層class ,整個GTK分成從基礎到高階的很多函式庫,用底層的當基礎架構出上層。有繼承GObject的物件管理方式是用g_object_ref和g_object_unref增減reference count。
4.取得圖的尺寸,這樣才知道要malloc多少記憶體
int w = gdk_pixbuf_get_width(pixbuf); int h = gdk_pixbuf_get_height(pixbuf); uint32_t* pixels = (uint32_t*)malloc(w*h*4); |
5.解出RGBA值,建立另一個GdkPixbuf物件用來轉換格式
GdkPixbuf* outPixbuf=gdk_pixbuf_new_from_data((uint8_t*)pixels, GDK_COLORSPACE_RGB, 1,8,w,h,w*4,NULL,0); gdk_pixbuf_copy_area(pixbuf,0,0,w,h,outPixbuf,0,0); |
第一行的參數是指定輸出格式,以及輸出到剛剛malloc的記憶體。
第二行其實是把一個點陣圖複製到另一個,它會根據目的圖的格式自動轉換格式,這時候pixels就是解碼出來的RGBA值了。
byte順序只支援RGBA,如果想要BGRA要之後另外處理。
6.此時可以把物件刪除了,GdkPixbuf也繼承了GObject所以刪除方法是g_object_unref
g_object_unref(pixbuf); g_object_unref(outPixbuf); |
gdk-pixbuf沒有初始化函式,同樣也不需要呼叫函式結束系統。
筆者的發行版要用這個命令列產生可執行檔
gcc test.c -o test -O2 -s `pkg-config --cflags --libs gdk-pixbuf-2.0 gio-2.0`
如果不是編譯同時連結,那分別把這兩個放在compiler和linker參數
`pkg-config --cflags gdk-pixbuf-2.0 gio-2.0`
`pkg-config --libs gdk-pixbuf-2.0 gio-2.0`
gio-2.0是因為有用到GInputStream。
編譯這個程式時參數要加很多include路徑和library,pkg-config是一個命令列工具幫你加上這些參數,會把它的stdout填入gcc的命令列。
在終端機打pkg-config --cflags --libs gdk-pixbuf-2.0 gio-2.0,看它跳出什麼就知道是怎麼回事了。
Cyber Sprite一代不是用這個方法而是直接用libpng和libjpeg讀檔,這樣每個格式都要寫不同的code去呼叫函式庫挺費事的。
另外Cyber Sprite發售後發生一個問題,新出的Linux發行版把預設的libjpeg版本從6.2換成8,這樣遊戲讀不到6.2版就無法執行,1.04 patch有一項就是解決這個問題。
……Linux縱向相容性差我已經司空見慣了,縱向相容性是指同一發行版的新舊版之間,不是不同發行版的相容性。(這是個人覺得Linux軟體難開發的原因之一)
想說能不能像Windows的GDI+一樣,可以自動判斷圖檔格式,然後找系統裡有安裝的函式庫,於是決定改用gdk-pixbuf。
接下來的問題是,找說明文件只找到reference(解說每個函式是做什麼用的),沒找到tutorial(從零開始一步步教你用)
gdk-pixbuf官方文件,主要看The GdkPixbuf Structure和File Loading這兩頁
GInputStream官方文件只能看函式名稱並根據自己對GObject的了解猜用途,再自己寫程式,試著把參數改來改去摸索它的性能。
雖然可能也有比較用心的函式庫,但是寫Linux程式常常遇到說明文件沒寫清楚的,就只能靠自己做實驗,什麼都試試看它什麼情況會掛掉這種的。(Linux軟體難開發的原因之二)
相較之下Windows對軟體作者很親切,官方文件MSDN寫得清楚明白,向後相容也維護得很好。
利用gdk-pixbuf自動找解碼函式庫應該能減輕相容性問題了,再來祈禱gdk-pixbuf不要哪天改library名稱,讓相容性斷掉。