Демонстрационное приложение (WTL) DrawImg (50kb)
Приложение DrawImg
Сегодня практически все программы используют различные картинки в качестве элементов интерфейса. Даже существует API функция ::LoadImage(), умеющая загружать файлы в формате bmp, ico и cur. Этого достаточно для панелей управления и диалогов. Но если размер картинки превышает 100x100 пикселов и их нужно несколько, файлы формата bmp использовать не удобно. Хочется что-то вроде jpg или gif.
Тут ::LoadImage() нам уже не помошник. Придется использовать специальные библиотеки. Наибольшей популярностью пользуются:
Все они хорошо документированны и снабжены примерами. Подробное рассмотрение этих библиотек займет слишком много времени и выходит за рамки этой статьи. Давайте лучше уделим внимание некоторым специальным API, предназначенным для работы с файлами изображений.
Самый "официальный" способ. Появился вместе с OLE32 и работает до сих пор. Функции OleLoadPicture(Ex) и OleLoadPicturePath умеют загружать картинки в формате BMP, GIF, JPEG, ICO, WMF, и EMF:
#include <olectl.h> HRESULT Load(LPCTSTR szFile) { CComPtr<IStream> pStream; // Load the file to a memory stream HRESULT hr = FileToStream(szFile, &pStream); if (SUCCEEDED(hr)) { // Decode the picture hr = ::OleLoadPicture( pStream, // [in] Pointer to the stream that contains picture's data 0, // [in] Number of bytes read from the stream (0 == entire) true, // [in] Loose original format if true IID_IPicture, // [in] Requested interface (void**)&m_pPicture // [out] IPictire object on success ); } return hr; } HRESULT DrawImg(HDC hdc, const RECT& rcBounds) { if (m_pPicture) { // Get the width and the height of the picture long hmWidth = 0, hmHeight = 0; m_pPicture->get_Width(&hmWidth); m_pPicture->get_Height(&hmHeight); // Convert himetric to pixels int nWidth = MulDiv(hmWidth, ::GetDeviceCaps(hdc, LOGPIXELSX), HIMETRIC_INCH); int nHeight = MulDiv(hmHeight, ::GetDeviceCaps(hdc, LOGPIXELSY), HIMETRIC_INCH); // Display the picture using IPicture::Render return m_pPicture->Render( hdc, // [in] Handle of device context on which to render the image rcBounds.left, // [in] Horizontal position of image in hdc rcBounds.top, // [in] Vertical position of image in hdc rcBounds.right - rcBounds.left, // [in] Horizontal dimension of destination rectangle rcBounds.bottom - rcBounds.top, // [in] Vertical dimension of destination rectangle 0, // [in] Horizontal offset in source picture hmHeight, // [in] Vertical offset in source picture hmWidth, // [in] Amount to copy horizontally in source picture -hmHeight, // [in] Amount to copy vertically in source picture &rcBounds // [in, optional] Pointer to position of destination for a metafile hdc ); } return E_UNEXPECTED; } |
Достоинства: правильно работает с прозрачными картинками.
Недостатки: не поддерживает анимированный GIF (см. также CPicturEx). Не поддерживает PNG.
Недостаток ::LoadImage() с лихвой исправили в GDI+. Объект Gdiplus::Image умеет загружать картинки в формате BMP, GIF, JPEG, PNG, TIFF, EXIF, WMF, и EMF:
#include <gdiplus.h> HRESULT Load(LPCTSTR szFile) { USES_CONVERSION; // Create new Gdiplus::Image object m_pImage = new Gdiplus::Image(T2CW(szFile)); ATLASSERT(m_pImage); // Check for success if (Gdiplus::Ok == m_pImage->GetLastStatus()) return S_OK; // Cleanup on failure Destroy(); return E_FAIL; } HRESULT DrawImg(HDC hdc, RECT& rcBounds) { if (m_pImage) { // Create Gdiplus::Graphics object from HDC Gdiplus::Graphics graphics(hdc); // Create Gdiplus::Rect object from RECT Gdiplus::Rect rc(rcBounds.left, rcBounds.top, rcBounds.right, rcBounds.bottom); // Draw the image return Gdiplus::Ok == graphics.DrawImage( m_pImage, // [in] Gdiplus::Image object rc // [in] Position and dimensions ) ? S_OK : E_FAIL; } return E_UNEXPECTED; } |
Достоинства: понимает множество форматов, в том числе анимированный GIF, правильно работает с прозрачными картинками.
Недостатки: На сегодняшний момент реализован только в WindowsXP. Хотя простое копирование gdiplus.dll в system32 делает ее доступной, как минимум, в Windows2000. Скорее всего, в обозримом будущем ожидаются версии и для Win9x.
Не так давно Майкрософт предоставила заголовочные и библиотечные файлы к объекту ImgCtx, появившемуся еще в Internet Explorer 4.0. Он умеет заргужать картинки в формате BMP, GIF, JPEG, ICO, WMF, EMF, PNG, XBM, ICO, TIFF и, возможно, некоторых других:
#include <IImgCtx.h> HRESULT Load(LPCTSTR szFile) { // Create IImgCtx object HRESULT hr = ::CoCreateInstance(CLSID_IImgCtx, NULL, CLSCTX_ALL, IID_IImgCtx, (void**)&m_pImage); if (SUCCEEDED(hr)) { // Load URL USES_CONVERSION; hr = m_pImage->Load( T2COLE(szFile), // [in] URL 0 // [in] Flags and preffered color format ); } return hr; } HRESULT DrawImg(HDC hdc, RECT& rcBounds) { if (m_pImage) { // Check download state DWORD dwState = 0; HRESULT hr = m_pImage->GetStateInfo(&dwState, NULL, true); if (SUCCEEDED(hr)) { if (IMGLOAD_LOADING & dwState) { // Still loading - wait 50 msec and request again ::DrawText(hdc, _T("Loading, please wait..."), -1, &rcBounds, DT_SINGLELINE); ::Sleep(50); Invalidate(false); hr = S_FALSE; } else if (IMGLOAD_COMPLETE & dwState) { // Download successfully complete hr = m_pImage->Draw( hdc, // [in] Handle of device context on which to render the image &rcBounds // [in] Position and dimensions ); } else { // Download failed hr = E_UNEXPECTED; } } return hr; } return E_UNEXPECTED; } |
Достоинства: правильно работает с прозрачными и анимированными картинками. Понимает URL (даже res:// и sysimage://).
Недостатки: не поддерживает загрузку из IStream. Не умеет загружать файлы синхронно.
ПРИМЕЧАНИЕ Форматов, распознаваемых этим объектом, может быть меньше, например, если при установке IE4 пользователь отключил поддержку PNG файлов. |
Не смотря на название, эта технология не имеет ничего общего с DirectX. Зато является частью Internet Explorer, внутри которого даже имется набор простеньких классов, реализующих IDirectDraw для нужд DirectXTransform. Этот способ поддерживает тот же набор форматов, что и предыдущий, более того, для этого используется один и тот же код. Разве что синхронно и на выходе получается IDXSurface объект.
#include <dxtrans.h> HRESULT DrawImg(HDC hdc, const RECT& rcBounds) { if (m_pDCLock) { HDC hdcImage = m_pDCLock->GetDC(); // Get the bitmap HGDIOBJ hObj = ::GetCurrentObject(hdcImage, OBJ_BITMAP); BITMAP bm = {0}; // Get the size of the bitmap if (hObj && ::GetObject(hObj, sizeof(BITMAP), &bm)) { // Draw the image return ::StretchBlt( hdc, rcBounds.left, rcBounds.top, rcBounds.right - rcBounds.left, rcBounds.bottom - rcBounds.top, hdcImage, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY ) ? S_OK : E_FAIL; } } return E_UNEXPECTED; } HRESULT Load(LPCTSTR szFile) { CComPtr<IDXTransformFactory> pTransFact; CComPtr<IDXSurfaceFactory> pSurfFact; // Create the Transform Factory. HRESULT hr = ::CoCreateInstance(CLSID_DXTransformFactory, NULL, CLSCTX_INPROC, IID_IDXTransformFactory, (void **)&pTransFact); if (SUCCEEDED(hr)) hr = pTransFact->QueryService(SID_SDXSurfaceFactory, IID_IDXSurfaceFactory, (void **)&pSurfFact); if (SUCCEEDED(hr)) { CComBSTR bstrFile(szFile); CComPtr<IDXSurface> pDXSurf; // Load DX surface. hr = pSurfFact->LoadImage(bstrFile, NULL, NULL, NULL, IID_IDXSurface, (void**)&pDXSurf); if (SUCCEEDED(hr)) { // Get IDXDCLock object hr = pDXSurf->LockSurfaceDC(NULL, INFINITE, DXLOCKF_READ, &m_pDCLock); } } return hr; } |
Достоинства: Прост в использовании. Поддерживает загрузку из IStream.
Недостатки: Медленный и ресурсоемкий. Это связянно с тем, что сначала для картинки создается обертка в виде IDirectDrawSurface, а затем еще одна для IDXSurface, которые нам совершенно не нужны.
Многие программы (например PaintBrush или WinWord) при инсталляции кладут в каталог %ProgramFiles%\Common Files\Microsoft Shared\Grphflt некоторое количество файлов, предназначенных для чтения файлов картинок. Способ не документированный и сильно устаревший. Полный список установленных в системе фильтров находится в реестре по адресу SOFTWARE\\Microsoft\\Shared Tools\\Graphics Filters\\Import
Я не буду рассматривать этот способ подробно, поскольку он сильно устарел и очень неудобен. Тем не менее, в приложении DrawImg этот способ реализован наравне с другими.
Майкрософт Офис, начиная с версии 8.0 (97) использует новый API с теми же фильтрами.
HRESULT Load(LPCTSTR szFile) { HMODULE hModule = g_pMapExtToFilter->LoadFilter(szFile); if (NULL == hModule) return E_FAIL; struct NameStruct { DWORD dwHead[2]; char szName[MAX_PATH]; DWORD dwTail[2]; }; typedef DWORD (__stdcall *GetFilterInfo_t)(DWORD dwVersion, DWORD dwReserved, HGLOBAL *phFilterData, DWORD dwReserved2); typedef DWORD (__stdcall *SetFilterPref_t)(HGLOBAL hFilterData, LPCSTR szOption, LPCSTR szValue, DWORD dwReserved2, DWORD dwReserved1); typedef DWORD (__stdcall *ImportGr_t)(DWORD dwReserved, NameStruct *pFile, ImgInfo *pInfo, HGLOBAL hFilterData); GetFilterInfo_t pGetFilterInfo = (GetFilterInfo_t)::GetProcAddress(hModule, "GetFilterInfo"); SetFilterPref_t pSetFilterPref = (SetFilterPref_t)::GetProcAddress(hModule, "SetFilterPref"); ImportGr_t pImportGr = (ImportGr_t)::GetProcAddress(hModule, "ImportGr"); if (NULL == pImportGr) pImportGr = (ImportGr_t)::GetProcAddress(hModule, "ImportGR"); if (pImportGr) { NameStruct name = {0}; HGLOBAL hFilterData = NULL; if (pGetFilterInfo) { DWORD dwVer = pGetFilterInfo(2, 0, &hFilterData, 0x00170000); ATLASSERT(2 == dwVer); if (2 != dwVer) { ::FreeLibrary(hModule); return E_UNEXPECTED; } } // PB 01/26/2001 Turn off dialogs if (pSetFilterPref) { pSetFilterPref(hFilterData, "ShowProgressDialog", "No", 2, 1); pSetFilterPref(hFilterData, "ShowOptionsDialog", "No", 2, 1); } USES_CONVERSION; ::lstrcpynA(name.szName, T2CA(szFile), MAX_PATH); DWORD dwRet = pImportGr(0, &name, &m_Image, hFilterData); if (hFilterData) ::GlobalFree(hFilterData); if (0 != dwRet || NULL == m_Image.hObj) { ::FreeLibrary(hModule); return E_FAIL; } if (OBJ_METAFILE != ::GetObjectType(m_Image.hObj)) { HGLOBAL hObj = (HGLOBAL)m_Image.hObj; LPBYTE pObj = (LPBYTE)::GlobalLock(hObj); m_Image.hObj = ::SetMetaFileBitsEx(::GlobalSize(hObj), pObj); ::GlobalUnlock(hObj); ::GlobalFree(hObj); } if (NULL == m_Image.hObj) { ::FreeLibrary(hModule); return E_FAIL; } return S_OK; } ::FreeLibrary(hModule); return E_UNEXPECTED; } HRESULT DrawImg(HDC hdc, const RECT& rcBounds) { if (m_Image.hObj) { ::SetMapMode(hdc, MM_ANISOTROPIC); ::SetViewportExtEx(hdc, rcBounds.right - rcBounds.left, rcBounds.bottom - rcBounds.top, NULL); ::PlayMetaFile(hdc, m_Image.hObj); return S_OK; } return E_UNEXPECTED; } |
Достоинства: понимает очень редкие форматы. Например wpg или cdr
Недостатки: Нет никакой гарантии, что на компьютере пользователя будет установлен нужный фильтр.
Direct3D версии 8.0 и выше умеет загружать картинки в формате BMP, JPEG, PNG:
#include <d3dx8.h>
HRESULT hr = ::D3DXCreateTextureFromFile(m_pD3DDevice, szFile, &ppTexture);
|
Достоинства: если вы разрабатываете 3D-приложение, то это наиболее удобный способ создания текстур (D3DXCreateTextureFromFile автоматически создает необходимое количество MipMap уровней).
Недостатки: если вы не разрабатываете 3D-приложение, то этот способ крайне неудобен, так как предназначен для работы с 3D объектами. На входе нужен IDirect3DDevice8 объект, а на выходе получаем IDirect3DTexture8, который очень не просто вывести в HDC.
Не реализован в демонстрационном приложении.