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.
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"
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"ssss", 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(); } }
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; }