//========================================================================
//TITLE:
// 詳聊IImage的圖片顯示
//AUTHOR:
// norains
//DATE:
// Sunday 26-August-2007
//Environment:
// EVC4.0 + Windows CE 5.0 Standard SDK
//========================================================================
IImage是WinCE5.0之後才加入的COM組件,用處正如其名,更為方便顯示圖片.WinCE 5.0之後,我們就可以喜新厭舊,拋棄imgdecmp,轉投IImage,呵呵~雖然個人覺得這兩個解碼,論速度不相伯仲,都是一樣的----慢,特別是繪製大圖片的時候還有限制,但畢竟有總好過無,所以我們就來看看今天的主角吧!
用IImage來顯示圖片真是異乎尋常的簡單,無非就是調用幾個函數而已:
IImage * m_pImage;
IImagingFactory * m_pImagingFactory;
HRESULT hr;
//COM初始化
if (FAILED(hr = CoInitializeEx(NULL, COINIT_MULTITHREADED)))
{
goto END;
}
//建立COM執行個體
if(FAILED(hr = CoCreateInstance(CLSID_ImagingFactory,NULL,CLSCTX_INPROC_SERVER,IID_IImagingFactory,(void**) &m_pImagingFactory)))
{
goto END;
}
//從檔案中建立圖片
if(FAILED(hr = m_pImagingFactory->CreateImageFromFile(TEXT("測試.bmp"), &m_pImage)))
{
goto END;
}
//繪製圖片
if(FAILED(hr = m_pImage->Draw(hdc,&rcWnd,NULL)))
{
goto END;
}
END:
//釋放資源
if(m_pImage != NULL)
{
m_pImage->Release();
m_pImage = NULL;
}
if(m_pImagingFactory != NULL)
{
m_pImagingFactory->Release();
m_pImagingFactory = NULL;
}
CoUninitialize();
OK,就是這麼簡單,只要OS中只要包含了相應的解碼器組件,以上代碼顯示大部分的圖片應該是沒問題的.好吧,既然如此,那麼這篇豆腐塊也就可以結束了~:-)
然而可惜的是,微軟向來不會將事情做得很完滿,要不然這篇豆腐塊到這裡確實是盡善盡美了.
這段代碼之中,本應風平浪靜,但偏偏有一個函數表現怪異,且很容易調用失敗,它乃IImage::Draw()!
1)顯示源檔案特定地區
顯示整副圖片沒啥問題,只要將srcRect參數賦值NULL即可.但如果是顯示特定的地區呢?比如說,有一副800*600的圖片,我只想顯示一半,那該咋辦?估計很多人(恩,也包括我),不會仔細看文檔,而會是憑藉經驗直接這麼寫: RECT rcSrc = {0,0,400,600};
m_pImage->Draw(hdc,&rcWnd,&rcSrc);
噢,我的上帝,運行這段代碼吧!你會看到什麼?如果幸運的話,你會一個一個小方塊!這時候,估計很多人會問候微軟的父母,噢,等等,微軟的東西是有不少bug,但這麼簡單的東西,還不至於讓其如此糊塗的.
仔細看看文檔關於該參數的說明:An optional pointer to a RECT that specifies, in 0.01mm units, the portion of the image to be drawn in dstRect.
看到最重要的一句了麼:in 0.01mm units !! 以0.01mm為單位,非像素點! 雖然微軟的出發點是好的,讓其顯示的精度更為準確,但這個確實給我們帶來不少的麻煩.但既然微軟介面都如此定義了,與其在這裡幹跺腳,抱怨微軟,還不如我們做點實際的,畢竟我們還是要完成任務,領取每個月的薪水養家糊口.:-(
天無絕人之路,OK,讓我們看看單位的轉換吧:
m_pImage->GetImageInfo(&ImageInfo);
double dDotPermmX = ImageInfo.Xdpi / 25.4;
double dDotPermmY = ImageInfo.Ydpi / 25.4;
//pSrcRect指向以像素點為單位的地區
RECT rcSrc = {(LONG)(pSrcRect->left / dDotPermmX / 0.01),
(LONG)(pSrcRect->top / dDotPermmY / 0.01),
(LONG)(pSrcRect->right / dDotPermmX / 0.01),
(LONG)(pSrcRect->bottom / dDotPermmY / 0.01)};
m_pImage->Draw(hdc,&rcDraw,&rcSrc);
雖然這樣能夠基本正常顯示指定的地區,但還是有一定的誤差,不過接下來討論的另一個問題,恰好可以解決.
2)IImage::Draw()耗時久
這是一個沒有辦法的問題,因為IImage::Draw()需要經曆一些的解碼才能繪製到目標DC,特別是在顯示一些特別大的JPEG(如果能夠顯示的話:-))時更為明顯.如果圖片僅僅是顯示一次,也許問題尚且不那麼嚴重,但如果是需要多次調用,比方說拖動圖片,那麼這種速度絕對無法忍受的.當然咯,這個問題在將來是肯定不會出現的,並且這個將來也不會很久,不用等到人老珠黃或是為伊消得人憔悴,只要嵌入式裝置的解碼速度和目前主流的PC相差無幾.這一天不會遠了,只是對於解決今天的問題卻是遠水救不了近火,還是乖乖地用技巧解決吧!
方法其實很簡單,IImage::Draw()耗時久嘛,那我們只要第一次繪製時將映像儲存到記憶體DC中,然後再把記憶體DC的資料繪製到目標DC即可.這樣一來,僅僅是第一次繪圖時感到不愉快(如果你脾氣暴躁,也許會感到煩躁),但之後的每次都是暢快之旅.以一次換來一勞永逸,還有比這更便宜的事嘛?//擷取圖片屬性
m_pImage->GetImageInfo(&ImageInfo);
//建立一個記憶體DC,用來儲存圖片資料
hBitmap = CreateCompatibleBitmap(hdc,ImageInfo.Width,ImageInfo.Height);
hdcMem = CreateCompatibleDC(hdc);
hOldSel = SelectObject(hdcMem,hBitmap);
...
//將圖片資料存放區到記憶體DC中
rcMemDC = {0,0,ImageInfo.Width,ImageInfo.Height};
m_pImage->Draw(hdcMem,&rcMemDC,NULL);
...
//將圖片繪製到目標DC
StretchBlt(hdc,
pDstRect->left,
pDstRect->top,
pDstRect->right - pDstRect->left,
pDstRect->bottom - pDstRect->top,
hdcMem,
pSrcRect->left,
pSrcRect->top,
pSrcRect->right - pSrcRect->left,
pSrcRect->bottom - pSrcRect->top,
SRCCOPY);
只要第一次將圖片資料儲存到hdcMem中,以後繪製圖片時只要繪製hdcMem資料即可,這比還需要經曆一次解碼過程的IImage::Draw()快多了!
也許有細心的朋友可能會注意到StretchBlt()的形參.恩,在此對這些朋友表示我由衷的敬意.沒錯,是的,將圖片資料儲存到hdcMem後,帶來的額外一個好處就是,繪製圖片某個地區時再也不用轉換單位啦!
因為是將整張圖片資料存放區到hdcMem,所以參數只要設定為NULL;儲存到hedMem後,採用StretchBlt()函數繪製只需要以像素為單位.呵呵,是的,沒錯,無形中我們就避免了單位轉換可能帶來的損失.所謂的一箭雙鵰乎?
3)繪製大圖片失敗
在調用IImage::Draw()繪製大圖片時,很有可能導致的後果是:繪製失敗.而提示的錯誤,更多是:E_OUTOFMEMORY,記憶體不足.這是一個很無奈的現實.雖然我的裝置記憶體已經很明顯大於圖片所需的,但由於受限制於WinCE5.0 32M虛擬記憶體,即使是採用旁門左道分配更多的虛擬記憶體,其結果無非也是:失敗!沒辦法,恩,至少我是沒找到解決的方法.
當然咯,萬事沒絕對,如果對圖片品質要求不高,倒還是有法子讓其顯示的.很簡單,只要擷取縮圖執行個體,然後再繪製即可.
m_pImage->GetThumbnail(WIDTH,HEIGHT,&m_pThum);
m_pThum->Draw(hdc,&rcDC,NULL);
這次IImage::Draw()應該不會失敗了.當然,如果還是那麼倒黴,那唯一的方法就是將WIDTH和HEIGHT減小,直到能正常顯示為止.不過,隨著數值的減小,圖片的品質也會隨之降低.