ETH官方钱包

創(chuàng)作內(nèi)容

8 GP

【程式】讀取圖檔的方法-Windows篇

作者:Shark│2016-11-28 03:57:57│巴幣:212│人氣:4627
介紹一個開發(fā)引擎時用的技術。

PNG、JPG是通用的壓縮圖檔格式,不過自己寫編碼、解碼演算法很費工,一般都是借助別人寫好的函式庫。
讀寫圖檔的功能其實Windows API有內(nèi)建,而且並不難用,寫Windows程式的時候可以利用一下。

本篇只講讀檔,寫檔以後再介紹,不過對Windows API熟悉的話自己看MSDN就能學會了。
函式參數(shù)和class成員的意義就請自己查MSDN,這裡不一個一個解釋。一個通用函式庫為了能應用在多樣的情況有很多參數(shù)可以填,但簡單的應用只會用到一部分。
我的需求是輸入方法除了給檔名以外,也要能讀取一塊記憶體,就是把檔案原封不動讀到記憶體裡再把指標給解碼函式,因為引擎有自己的包裝格式,不會直接讀硬碟上的檔案。

寫下來之後發(fā)現(xiàn)如果想全部解釋清楚要講到很多Windows API的知識,雖然只要讀到圖檔就好其他先不管的話不難做。



方法1:用GDI+
適用版本:Windows XP以後

GDI+是從Windows XP開始有的,將舊有的GDI包裝一層並加強功能。

1.程式一開始要初始化GDI+。
include header,並啟用Gdiplus名稱空間。
#include<gdiplus.h>
#include<shlwapi.h>

using namespace Gdiplus;

呼叫GdiplusStartup,它會傳回兩個變數(shù)。
ULONG_PTR token;
GdiplusStartupInput startupInput;
GdiplusStartup(&token, &startupInput, NULL);
想讓函式一次傳回多個值要用指標來做是C/C++的常識,所以宣告兩個變數(shù)再取它們的指標傳入。
startupInput之後不會再使用,可以放在函式的區(qū)域變數(shù)(函式return後就消失),但token以後會用到,要找個地方保留。

接下來產(chǎn)生一個Bitmap物件,用它讀取圖檔,它有好幾個建構子用在不同的情況。
2a.如果輸入是檔名,用這個給檔名的
Bitmap gdiplusBitmap(L"fileName.png", 0);

2b.如果輸入是記憶體,它沒有直接給buffer的建構子,只有一個參數(shù)是(IStream*, BOOL)的可以用在此情況。
(查MSDN會看到有的是給pixel data的,不過這些要給解碼後的BGRA值,不是原始的圖檔)
所以把這塊記憶體用一個IStream物件包裝起來才能讀取,用SHCreateMemStream。
IStream* stream=SHCreateMemStream((const BYTE*)buffer,
  bufferSize);  //reference=1
Bitmap gdiplusBitmap(stream, 0);  //reference增加
stream->Release();  //reference減1
(其實我是很久以後才知道有SHCreateMemStream這個函式,以前只知道另一個CreateStreamOnHGlobal,要多一些步驟)

這裡介紹一下reference count,IStream有繼承IUnknown介面,這類物件釋放資源的方法是內(nèi)部有個計數(shù)器記錄有多少物件參考到它,計數(shù)器減到0才刪除而不是直接用delete關鍵字。
stream->Release()是把SHCreateMemStream增加的refernence扣掉,之後Bitmap物件刪除時將它用到的reference扣掉就會自動刪除stream。
有繼承IUnknown的物件都是用這種方法管理,Windows API裡很多這種東西,像之前的Direct3D 11也是,以後還會常常看到。

3.取得圖的尺寸,這樣才知道要malloc多少記憶體
UINT w=gdiplusBitmap.GetWidth();
UINT h=gdiplusBitmap.GetHeight();
UINT32* pixels=(UINT32*)malloc(w*h*4);

4.再來真正把圖檔解碼了,要用的是LockBits函式,要準備一些資料告訴它範圍和輸出格式。
Rect rect(0,0,w,h);
BitmapData bitmapData;
bitmapData.Scan0 = pixels;  //要把解出來的像素放到哪裡
bitmapData.Stride = w*4;
gdiplusBitmap.LockBits(&rect,
  ImageLockModeRead|ImageLockModeUserInputBuf,
  PixelFormat32bppARGB, &bitmapData);
gdiplusBitmap.UnlockBits(&bitmapData);
BitmapData還有其他member,不過經(jīng)實測此處要填的只有Scan0和Stride。
bitmapData.Stride是指一列的開頭到下一列開頭相隔幾個byte,可以不等於圖寬度×每個像素的byte數(shù)。
LockBits的第三參數(shù)是把原圖轉(zhuǎn)換成什麼格式,可以把BGR的原圖自動加上alpha變成BGRA,但是沒有直接支援灰階格式,如果原圖是灰階那還是要填BGR。

有stride這個值是因為將資料用4 byte對齊效能會比較好,但有些顏色格式不是每個像素4 byte(如BGR不含alpha是24 bit),想讓每列開頭都是4 byte對齊就會有「stride≠寬度×每像素byte數(shù)」的情況。

做完這些步驟pixels裡就是各像素的BGRA值了,可以任君取用。
gdiplusBitmap由於不是動態(tài)配置記憶體,離開所在的作用域後會自動釋放。

5.程式最後要把GDI+結束
GdiplusShutdown(token);
第一步取得的token要保留到這裡。

build時要連結這兩個library
gdiplus.lib
shlwapi.lib
第一個是GDI+本體,第二個是因為用到SHCreateMemStream函式。



方法2:用WIC(Windows Imaging Component)
適用版本:Windows Vista以後

研究過一些API的感覺是,微軟在Vista把多媒體功能整個翻新,WIC就是這些新系統(tǒng)的其中之一,其他新API還有控制顯卡的Direct3D 10、影音處理的Media Foundation等等,舊的API如GDI+、Direct3D 9只是為了向後相容而保留下來。

1.初始化
include header
#include<wincodec.h>
#include<shlwapi.h>

呼叫這兩個函式產(chǎn)生一個IWICImagingFactory物件
IWICImagingFactory* factory;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL,
  CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (void**)&factory);
這兩個函式是關於一個Windows裡的系統(tǒng):COM,現(xiàn)在先不解釋。
另外WIC(或其他有用到COM的系統(tǒng))呼叫函式建立物件時,通常函式本身傳回一個錯誤碼代表是什麼原因失敗,如果hr==S_OK就代表執(zhí)行成功,要建立的物件用一個指標傳回。

之後其他的物件都用這個factory來產(chǎn)生。

再來建立一個IWICBitmapDecoder物件用來解碼
2a.如果輸入是檔名
IWICBitmapDecoder* decoder;
hr = factory->CreateDecoderFromFilename(L"fileName.png", NULL,
  GENERIC_READ,WICDecodeMetadataCacheOnDemand ,&decoder);

2b.如果輸入是buffer,跟上面一樣要包在一個IStream物件裡
IStream* stream=SHCreateMemStream((const BYTE*)buffer, bufferSize);
IWICBitmapDecoder* decoder;
hr = factory->CreateDecoderFromStream(stream,NULL,
  WICDecodeMetadataCacheOnDemand,&decoder);
stream->Release();
這裡也一樣,CreateDecoderFromStream傳回錯誤碼,IWICBitmapDecoder是用指標傳回。
建好decoder之後就如GDI+篇一樣,把stream release掉。

3.取得frame再取得frame的尺寸,這樣才知道要malloc多少記憶體
WIC可以用在一個檔案裡有多張圖的情況(如GIF),所以要先取出frame再從它取出pixel data,這裡只取出第一個frame。
IWICBitmapFrameDecode* frame;
decoder->GetFrame(0,&frame);
UINT w,h;
frame->GetSize(&w,&h);
UINT32* pixels=(UINT32*)malloc(w*h*4);

4.把圖檔解碼,要建立一個IWICFormatConverter物件指定輸出格式
IWICFormatConverter* converter;
factory->CreateFormatConverter(&converter);
converter->Initialize(frame, GUID_WICPixelFormat32bppBGRA,
  WICBitmapDitherTypeNone, NULL,0, WICBitmapPaletteTypeCustom);
converter->CopyPixels(NULL, w*4,w*h*4, (BYTE*)pixels);
這時候pixels裡就是各像素的BGRA值。
GUID_WICPixelFormat32bppBGRA是指定輸出格式,可以設定byte順序是RGB或BGR、一個像素幾bit、灰階等等,查MSDN裡Native Pixel Formats這一頁可以看到能填的值。
frame其實也有一個frame->CopyPixels()可以用,但只能取得圖檔原始的格式,不會幫你轉(zhuǎn)換。

此時可以把物件刪除了。
converter->Release();
frame->Release();
decoder->Release();
跟IStream一樣是繼承IUnknown的物件,呼叫Release()。

5.把WIC系統(tǒng)結束的方法,先刪除factory,再結束COM系統(tǒng)。
factory->Release();
CoUninitialize();

build時要連結這三個library
windowscodecs.lib
shlwapi.lib
ole32.lib
第一個是使用WIC定義的常數(shù),第三個是要用CoInitializeEx、CoCreateInstance、CoUninitialize這三個函式。



艾莉兒:不用帶那麼多裝備,我可以利用周圍的自然之力。

我對艾莉兒的設計理念是保持輕巧,能從作業(yè)系統(tǒng)現(xiàn)地調(diào)達的功能就不要自己攜帶函式庫,Cyber Sprite程式很複雜但.exe檔能減到只有300多K這是原因之一。
主要是以前換新電腦重架開發(fā)環(huán)境時,除了Windows SDK以外還要額外裝一堆函式庫,就決定儘量用作業(yè)系統(tǒng)內(nèi)建的功能,免去手動裝一堆東西的工。

研究之後發(fā)掘了很多Windows的潛能,像是音檔解碼、Deflate演算法Windows也有內(nèi)建。
引用網(wǎng)址:http://www.jamesdambrosio.com/TrackBack.php?sn=3398535
All rights reserved. 版權所有,保留一切權利

相關創(chuàng)作

同標籤作品搜尋:程式|Windows API|Windows程式設計

留言共 2 篇留言

Shark
贊助:104,有人給了100GP嗎?

11-28 20:09

新手方
喜歡那邊 如果不按下去的話可以選擇要給的8b

04-26 20:23

Shark
我知道啊,只是好奇是誰給的。04-28 01:26
我要留言提醒:您尚未登入,請先登入再留言

8喜歡★shark0r 可決定是否刪除您的留言,請勿發(fā)表違反站規(guī)文字。

前一篇:【進度】NPC:裝甲運輸... 後一篇:「淺談遊戲引擎」觀後感...


face基於日前微軟官方表示 Internet Explorer 不再支援新的網(wǎng)路標準,可能無法使用新的應用程式來呈現(xiàn)網(wǎng)站內(nèi)容,在瀏覽器支援度及網(wǎng)站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現(xiàn)和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業(yè)系統(tǒng)版本才可使用)

face我們了解您不想看到廣告的心情? 若您願意支持巴哈姆特永續(xù)經(jīng)營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】