This tutorial shows how to use the Windows Imaging Component (WIC) to save image files. After completing the tutorial, register any or all of the LEAD WIC-Enabled Codecs. You will then be able to save any of these image formats without recompiling the demo.
Start with the tutorial that you created in WIC-Enabled Codecs C++ Tutorial: Loading an Image File
First, you will create a Save dialog by hooking into the standard Windows Save Common Dialog.
- In Solution Explorer, double-click on Tutorial.rc to display the Resource View.
- Right-click the Dialog tree view item and choose Insert Dialog from the menu.
- Open the Dialog tree view item. Select the IDD_DIALOG1 item in the tree view. In the properties, change the ID to IDD_DIALOG_SAVE_TEMPLATE
- Double-click Combo Box from the toolbox to add a combo box to the dialog.
Change the ID to IDC_COMBO_PIXEL_FORMAT
- Add a static text item with ID IDC_STATIC_PIXEL_FORMAT
- Close the Resource View, right-click Tutorial.rc and select View Code.
- Search for IDD_DIALOG_SAVE_TEMPLATE in Tutorial.rc
to find the dialog that you
just created. Replace it with the following:
IDD_DIALOG_SAVE_TEMPLATE DIALOGEX 0, 0, 423, 77 STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_CLIPSIBLINGS | WS_SYSMENU FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN LTEXT "&Pixel Format:",IDC_STATIC,67,2,53,8 COMBOBOX IDC_COMBO_PIXEL_FORMAT,130,0,147,213,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP END
- Open stdafx.h and add the following headers under the C RunTime Header Files section.
The reason for adding each header is shown as a comment to the left.
#include <vector> // To maintain a dynamic array with information about each encoder #include <dlgs.h> // Contains constants for accessing Save Dialog items
- Also add the following to the end of stdafx.h.
#include "resource.h"
- In Solution Explorer, right-click the Source Files tree view item. Choose Add->New Item. Choose C++(.cpp) file. For the name, enter SaveDlg.cpp
Next, customize the standard Windows Common Save dialog. The Save as type static text box will programmatically be changed to display Encoders. In this combo box, all the available WIC-Enabled Encoders on your system will be displayed. Also, a Pixel Format combo box is added. This will display the available pixel formats for the selected encoder.
- Add the following code to SaveDlg.cpp:
// SaveDlg.cpp #include "stdafx.h" extern HINSTANCE hInst; extern IWICBitmapSource *gpiBitmapSource; IWICImagingFactory *gpiImagingFactory = NULL; class CEncoderData { public: CEncoderData(IWICBitmapEncoderInfo *piBitmapEncoderInfo, WCHAR *pszExtensions) { m_piBitmapEncoderInfo = piBitmapEncoderInfo; m_csExtensions = pszExtensions; GetDefaultExtension(); } ~CEncoderData() { RELEASE_INTERFACE(m_piBitmapEncoderInfo); } public: IWICBitmapEncoderInfo *m_piBitmapEncoderInfo; CString m_csExtensions; CString m_csDefaultExtension; void GetDefaultExtension() { WCHAR seps[] = L","; WCHAR *psz = NULL; WCHAR *next_token = NULL; WCHAR *token = NULL; size_t uLen = CString::StringLength(m_csExtensions) + 1; if (uLen > 0) { psz = new WCHAR[uLen]; wcscpy_s(psz, uLen, m_csExtensions.GetBuffer()); token = wcstok_s(psz, seps, &next_token); while (token != NULL) { if (wcslen(token) == 4) m_csDefaultExtension = token; token = wcstok_s( NULL, seps, &next_token); } } if (psz == NULL) { delete psz; psz = NULL; } } }; std::vector <CEncoderData *> gEncoderData; HRESULT EnumerateEncoders(CString &csFilter) { HRESULT hr = S_OK; IEnumUnknown *piEnumUnknown = NULL; gEncoderData.clear(); IFS(gpiImagingFactory->CreateComponentEnumerator(WICEncoder, WICComponentEnumerateRefresh, &piEnumUnknown)); if (SUCCEEDED(hr)) { ULONG num = 0; IUnknown *piUnknown = NULL; piEnumUnknown->Reset(); while ((S_OK == piEnumUnknown->Next(1, &piUnknown, &num)) && (1 == num)) { CString csFriendlyName; IWICBitmapEncoderInfo *piBitmapEncoderInfo = NULL; IFS(piUnknown->QueryInterface(IID_IWICBitmapEncoderInfo, reinterpret_cast<void**>(&piBitmapEncoderInfo))); // Friendly name WCHAR *pszFriendlyName = NULL; UINT uActual = 0; IFS(piBitmapEncoderInfo->GetFriendlyName(0, NULL, &uActual)); if (uActual > 0) { pszFriendlyName = new WCHAR[uActual+1]; if (pszFriendlyName) { memset(pszFriendlyName, 0, sizeof(WCHAR) * (uActual + 1)); IFS(piBitmapEncoderInfo->GetFriendlyName(uActual, pszFriendlyName, &uActual)); } } // Extension WCHAR *pszExtensions = NULL; IFS(piBitmapEncoderInfo->GetFileExtensions(0, NULL, &uActual)); if (uActual>0) { pszExtensions = new WCHAR[uActual+1]; if (pszExtensions) { memset(pszExtensions, 0, sizeof(WCHAR) * (uActual + 1)); IFS(piBitmapEncoderInfo->GetFileExtensions(uActual, pszExtensions, &uActual)); } } CString cs; cs.Format(L"%s(%s)", pszFriendlyName, pszExtensions); csFilter = csFilter + cs + L"|" + pszExtensions + L"|"; CEncoderData *pData = new CEncoderData(piBitmapEncoderInfo, pszExtensions); gEncoderData.push_back(pData); // Cleanup DELETE_POINTER(pszFriendlyName); RELEASE_INTERFACE(piBitmapEncoderInfo); RELEASE_INTERFACE(piUnknown); } // Terminate and replace the "|" character with a NULL csFilter.Replace(L".", L"*."); csFilter = csFilter + L"|"; INT uLen = CString::StringLength(csFilter); for (INT i=0; i < uLen; i++) { if (csFilter[i] == L'|') csFilter.SetAt(i, L'\0'); if (csFilter[i] == L',') csFilter.SetAt(i, L';'); } } return hr; } //********************************************************************************** // PixelFormat Combo Box //********************************************************************************** class CPixelFormatData { public: CPixelFormatData(GUID guid) { m_guid = guid; } ~CPixelFormatData() { } GUID m_guid; }; // Cleanup for PixelFormat Combo Box void FreePixelFormatComboBox(HWND hDlg) { HWND hDlgItem = GetDlgItem(hDlg, IDC_COMBO_PIXEL_FORMAT); LRESULT nCount = SendMessage(hDlgItem, CB_GETCOUNT, 0, 0); CPixelFormatData* pData = NULL; for (LONG_PTR i=0; i < nCount; i++) { LRESULT lResult = SendMessage(hDlgItem, CB_GETITEMDATA, i, 0); if (lResult != CB_ERR) { pData = (CPixelFormatData*)lResult; if (pData) delete pData; } } SendMessage(hDlgItem, CB_RESETCONTENT, (WPARAM)0, (LPARAM)0); } CEncoderData *GetEncoderData(HWND hDlg) { CEncoderData *pData = NULL; HWND hWndParent = GetParent(hDlg); LRESULT nIndex = SendMessage(GetDlgItem(hWndParent, cmb1), CB_GETCURSEL, (WPARAM)0, (LPARAM)0); if (nIndex >=0) pData = (CEncoderData *)gEncoderData[nIndex]; return pData; } HRESULT EnumeratePixelFormat(HWND hDlg) { HRESULT hr = S_OK; FreePixelFormatComboBox(hDlg); CEncoderData *pData = GetEncoderData(hDlg); if (!pData) return hr; if (((LRESULT)pData != CB_ERR) && pData && pData->m_piBitmapEncoderInfo) { UINT uActual = 0; // Pixel Formats GUID *guids = NULL; IFS(pData->m_piBitmapEncoderInfo->GetPixelFormats(0, NULL, &uActual)); if (uActual > 0) { IWICComponentInfo *piComponentInfo = NULL; guids = new GUID[uActual]; IFS(pData->m_piBitmapEncoderInfo->GetPixelFormats(uActual, guids, &uActual)); for (UINT i = 0; i < uActual; i++) { IFS(gpiImagingFactory->CreateComponentInfo(guids[i], &piComponentInfo)); // Friendly name WCHAR *pszFriendlyName = NULL; UINT uActual = 0; IFS(piComponentInfo->GetFriendlyName(0, NULL, &uActual)); if (uActual > 0) { pszFriendlyName = new WCHAR[uActual+1]; if (pszFriendlyName) { memset(pszFriendlyName, 0, sizeof(WCHAR) * (uActual + 1)); IFS(piComponentInfo->GetFriendlyName(uActual, pszFriendlyName, &uActual)); } } LRESULT nIndex = SendMessage(GetDlgItem(hDlg, IDC_COMBO_PIXEL_FORMAT), CB_ADDSTRING, 0, (LPARAM)pszFriendlyName); CPixelFormatData *pData = new CPixelFormatData(guids[i]); SendMessage(GetDlgItem(hDlg, IDC_COMBO_PIXEL_FORMAT), CB_SETITEMDATA, nIndex, (LPARAM)pData); } DELETE_POINTER(guids); RELEASE_INTERFACE(piComponentInfo); } SendMessage(GetDlgItem(hDlg, IDC_COMBO_PIXEL_FORMAT), CB_SETCURSEL, 0, 0); } return hr; } GUID GetSelectedPixelFormat(HWND hDlg) { GUID guid = GUID_NULL; HWND hDlgItem = GetDlgItem(hDlg, IDC_COMBO_PIXEL_FORMAT); LRESULT nIndex = SendMessage(hDlgItem, CB_GETCURSEL, 0, 0); LRESULT lResult = SendMessage(hDlgItem, CB_GETITEMDATA, nIndex, 0); if (lResult == CB_ERR) return guid; CPixelFormatData *pData = (CPixelFormatData *)lResult; if (pData) { guid = pData->m_guid; } return guid; } //********************************************************************************** // Misc //********************************************************************************** class CMySaveDlgData { public: CMySaveDlgData() { m_guidContainer = GUID_NULL; m_guidPixelFormat = GUID_WICPixelFormatUndefined; m_nPixelFormatIndex = -1; } ~CMySaveDlgData() { } GUID m_guidContainer; GUID m_guidPixelFormat; // member variables for index of combo items INT m_nPixelFormatIndex; }; void SetComboBoxIndex(HWND hDlg, INT nID, INT nIndex) { if (nIndex > 0) SendMessage(GetDlgItem(hDlg, nID), CB_SETCURSEL, (WPARAM)nIndex, 0); } HRESULT InitDialog(HWND hDlg, LPARAM lParam) { HRESULT hr =S_OK; CMySaveDlgData *pData = NULL; if (!lParam) return hr; LPOPENFILENAME pOpenFileName = (LPOPENFILENAME)lParam; pData = (CMySaveDlgData*)pOpenFileName->lCustData; if (!pData) return hr; FreePixelFormatComboBox(hDlg); EnumeratePixelFormat(hDlg); SetComboBoxIndex(hDlg, IDC_COMBO_PIXEL_FORMAT, pData->m_nPixelFormatIndex); HWND hWndParent = GetParent(hDlg); SetWindowText(GetDlgItem(hWndParent, stc2), L"&Encoders:"); SetWindowText(GetDlgItem(hWndParent, IDC_STATIC_PIXEL_FORMAT), L"&Pixel Format:"); return hr; } void FreeDialog(HWND hDlg) { FreePixelFormatComboBox(hDlg); } // Append appropriate extension to file name void AppendAppropriateExtension(HWND hDlg) { HWND hWndParent = GetParent(hDlg); CEncoderData *pData = GetEncoderData(hDlg); if (!pData) return; WCHAR szFileName[MAX_PATH] = {0}; UINT uRet = GetDlgItemText(GetParent(hDlg), cmb13, szFileName, MAX_PATH); if (uRet > 0) { WCHAR drive[MAX_PATH] = {0}; WCHAR dir[MAX_PATH] = {0}; WCHAR fname[MAX_PATH] = {0}; WCHAR ext[MAX_PATH] = {0}; DWORD dwFlags = GetFileAttributes(szFileName); if (((dwFlags & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) || (dwFlags == INVALID_FILE_ATTRIBUTES)) { _wsplitpath_s(szFileName, drive, MAX_PATH, dir, MAX_PATH, fname, MAX_PATH, ext, MAX_PATH ); CString cs; cs.Format(L"%s%s%s%s", drive, dir ,fname, pData->m_csDefaultExtension); SetDlgItemText(hWndParent, cmb13, cs.GetBuffer()); } } } UINT_PTR CALLBACK MySaveHookProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam ) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: InitDialog(hDlg, lParam); return (INT_PTR)TRUE; case WM_DESTROY: FreeDialog(hDlg); return 0; //case WM_SIZE: // ArrangeDialogItems(hDlg); // return 0; case WM_NOTIFY: { LPOFNOTIFYW lpOfNotify = (LPOFNOTIFY) lParam; switch (lpOfNotify->hdr.code) { case CDN_TYPECHANGE: EnumeratePixelFormat(hDlg); AppendAppropriateExtension(hDlg); break; case CDN_FILEOK: { CMySaveDlgData *pMySaveDlgData = (CMySaveDlgData *)lpOfNotify->lpOFN->lCustData; if (pMySaveDlgData) { // Get container guid pMySaveDlgData->m_guidContainer = GUID_NULL; CEncoderData *pEncoderData = (CEncoderData *)gEncoderData[lpOfNotify->lpOFN->nFilterIndex-1]; pEncoderData->m_piBitmapEncoderInfo->GetContainerFormat(&pMySaveDlgData->m_guidContainer); // Get pixel format pMySaveDlgData->m_guidPixelFormat = GetSelectedPixelFormat(hDlg); pMySaveDlgData->m_nPixelFormatIndex = (INT)SendMessage(GetDlgItem(hDlg, IDC_COMBO_PIXEL_FORMAT),CB_GETCURSEL, (WPARAM)0, (LPARAM)0); } } break; } } return 0; // return value ignored case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return 0; } HRESULT MySaveDialog(HWND hWnd) { HRESULT hr = S_OK; static OPENFILENAME ofn = {0}; CString csFilters; WCHAR szTitle[MAX_PATH] = L"Save As..."; WCHAR szFileTitle[MAX_PATH] = L"MyFileTitle"; WCHAR szDefExt[4] = {0}; WCHAR szFile[MAX_PATH] = {0}; IFS(CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IWICImagingFactory, (LPVOID*) &gpiImagingFactory)); EnumerateEncoders(csFilters); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = csFilters.GetBuffer();//szFilter; // Find the first 'LEAD' filter ofn.nFilterIndex = 0; ofn.lpstrFile= szFile; ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = (LPWSTR)NULL; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_ENABLETEMPLATE | OFN_ENABLESIZING | OFN_ENABLEHOOK; ofn.lpstrTitle = szTitle; ofn.lpstrDefExt = szDefExt; ofn.hInstance = hInst; ofn.lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG_SAVE_TEMPLATE); ofn.lpfnHook = MySaveHookProc; static CMySaveDlgData mySaveDlgData; ofn.lCustData = (LPARAM)&mySaveDlgData; if (GetSaveFileName(&ofn)) { HCURSOR hCursorPrev = SetCursor(LoadCursor(NULL, IDC_WAIT)); //hr = EncoderSave(ofn.lpstrFile, &mySaveDlgData); SetCursor(hCursorPrev); } RELEASE_INTERFACE(gpiImagingFactory); return hr; }
-
Open tutorial.cpp. Add a forward declaration for MySaveDialog in the Forward
declarations of functions included in this code module section.
// Forward declarations of functions included in this code module: HRESULT MySaveDialog(HWND hWnd);
- Open tutorial.cpp and in WndProc add a case for ID_FILE_SAVE after the
ID_FILE_OPEN case.
case ID_FILE_SAVE: MySaveDialog(hWnd); break;
- Modify SetGlobalBitmaps to keep a copy of
the currently displayed image in the global variable gpiBitmapSource. Modify FreeGlobalBitmaps
to release the gpiBitmapSource.
BOOL FreeGlobalBitmaps() { BOOL bRet = FALSE; // Release any existing global bitmaps if (gpGdiPlusBitmap) { delete gpGdiPlusBitmap; gpGdiPlusBitmap= NULL; bRet = TRUE; } if (gpiBitmapSource) { gpiBitmapSource->Release(); gpiBitmapSource = NULL; bRet = TRUE; } return bRet; } void SetGlobalBitmaps(IWICBitmapSource *piBitmapSource) { Bitmap *pGdiPlusBitmap = NULL; BYTE *pbGdiPlusBuffer = NULL; if (!piBitmapSource) return; BitmapSourceToGdiPlusBitmap(piBitmapSource, &pGdiPlusBitmap, &pbGdiPlusBuffer); if (pGdiPlusBitmap) { FreeGlobalBitmaps(); gpGdiPlusBitmap= pGdiPlusBitmap; gpiBitmapSource = piBitmapSource; gpiBitmapSource->AddRef(); } }
- Compile and run your program.
- Open an image file. The File->Save menu item should now be enabled. Choose File->Save.
The file save dialog is displayed. An Encoders combo box with all avaiable WIC-enabled encoders is displayed. As you choose different encoders, the Pixel Format combo box is updated with the available pixel formats. The save dialog displays all the options, but does not yet save the image file.
- Search for the following line of code in the function MySaveDialog. It is commented out. Uncomment this line of code.
hr = EncoderSave(ofn.lpstrFile, &mySaveDlgData);
- Add the EncoderSave function immediately before the function MySaveDialog.
Also add the utility functions PaletteColorCount, CopyBitmapSourcePalette,
and BitmapSourceToBitmapFrameEncode.
This function creates a file using the encoder and pixel format that was selected in the save dialog.
UINT PaletteColorCount(GUID guidPixelFormat) { UINT uPaletteColorCount = 0; if (GUID_WICPixelFormat1bppIndexed == guidPixelFormat) { uPaletteColorCount = 2; } else if (GUID_WICPixelFormat2bppIndexed == guidPixelFormat) { uPaletteColorCount = 4; } else if (GUID_WICPixelFormat4bppIndexed == guidPixelFormat) { uPaletteColorCount = 16; } else if (GUID_WICPixelFormat8bppIndexed == guidPixelFormat) { uPaletteColorCount = 256; } return uPaletteColorCount; } HRESULT CopyBitmapSourcePalette(IWICBitmapSource *piSource, IWICPalette **ppiPalette, UINT uPaletteColorCount) { HRESULT hr = S_OK; if (!piSource) return E_INVALIDARG; IWICPalette *piPalette = NULL; IFS(gpiImagingFactory->CreatePalette(&piPalette)); // CopyPalette my fail, if the source does not have a palette hr = piSource->CopyPalette(piPalette); if (FAILED(hr)) { // Try to create a palette hr = S_OK; IFS(piPalette->InitializeFromBitmap(piSource, uPaletteColorCount, FALSE)); } if (SUCCEEDED(hr)) { if (ppiPalette) { (*ppiPalette) = piPalette; (*ppiPalette)->AddRef(); } } RELEASE_INTERFACE(piPalette); return hr; } // Converts a BitmapSource to a BitmapFrame HRESULT BitmapSourceToBitmapFrameEncode(IWICBitmapSource *piSource, IWICBitmapFrameEncode *piBitmapFrame, GUID pixelFormat) { HRESULT hr = S_OK; UINT uWidth = 0; UINT uHeight = 0; IWICPalette *piPalette = NULL; IFS(piSource->GetSize(&uWidth, &uHeight)); IFS(piBitmapFrame->SetSize(uWidth, uHeight)); UINT uPaletteColorCount = PaletteColorCount(pixelFormat); if (uPaletteColorCount > 0) { CopyBitmapSourcePalette(piSource, &piPalette, uPaletteColorCount); if (piPalette) { piBitmapFrame->SetPalette(piPalette); } } WICRect rc = {0,0,uWidth, uHeight}; IFS(piBitmapFrame->WriteSource(piSource, &rc)); RELEASE_INTERFACE(piPalette); return hr; } HRESULT EncoderSave( LPCWSTR pszFileSave, CMySaveDlgData *pMySaveDlgData) { IWICStream *piStream = NULL; HRESULT hr = S_OK; IWICBitmapEncoder *piEncoder = NULL; IWICBitmapFrameEncode *piBitmapFrame = NULL; IPropertyBag2 *piPropertyBag = NULL; // *********************************************** // Create the appropriate encoder //************************************************ IFS(gpiImagingFactory->CreateStream(&piStream)); IFS(piStream->InitializeFromFilename(pszFileSave, GENERIC_WRITE)); IFS(gpiImagingFactory->CreateEncoder(pMySaveDlgData->m_guidContainer, NULL, &piEncoder)); IFS(piEncoder->Initialize(piStream, WICBitmapEncoderNoCache)); IFS(piEncoder->CreateNewFrame(&piBitmapFrame, &piPropertyBag)); IFS(piBitmapFrame->Initialize(piPropertyBag)); IFS(piBitmapFrame->SetPixelFormat(&pMySaveDlgData->m_guidPixelFormat)); IFS(BitmapSourceToBitmapFrameEncode(gpiBitmapSource, piBitmapFrame, pMySaveDlgData->m_guidPixelFormat)); // *********************************************** // Save the file //************************************************ IFS(piBitmapFrame->Commit()); IFS(piEncoder->Commit()); // *********************************************** // Cleanup //************************************************ RELEASE_INTERFACE(piBitmapFrame); RELEASE_INTERFACE(piEncoder); RELEASE_INTERFACE(piStream); return hr; }
- Compile and run your program.
Programming Reference
Adding LEADTOOLS Controls to Microsoft Expression BlendDisplay Images Using Expression Blend
Creating Image Lists Using Expression Blend
Link an Image List to an Image Viewer Using Expression Blend
Add a Magnifying Glass Using Expression Blend
Adding Bitmap Effects Using Expression Blend
Working with Images Using Visual Studio
Loading an Image File Using WIC