Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

737 lines
20 KiB

  1. /*************************************************************************/
  2. /* Copyright (C) 1999 Microsoft Corporation */
  3. /* File: capture.cpp */
  4. /* Description: Convert a captured DVD frame from YUV formats to RGB, */
  5. /* and save to file in various formats. */
  6. /* Author: phillu */
  7. /*************************************************************************/
  8. #include "stdafx.h"
  9. // This version of capture is for Millennium where GDI+ is installed
  10. #include "MSWebDVD.h"
  11. #include "msdvd.h"
  12. #include <initguid.h>
  13. #include "imaging.h"
  14. #include <shlobj.h>
  15. // YUV FourCC Formats (byte-swapped). We support a subset of them.
  16. // Ref: http://www.webartz.com/fourcc/
  17. // packed formats
  18. #define FourCC_IYU1 '1UYI'
  19. #define FourCC_IYU2 '2UYI'
  20. #define FourCC_UYVY 'YVYU' // supported
  21. #define FourCC_UYNV 'VNYU' // supported
  22. #define FourCC_cyuv 'vuyc'
  23. #define FourCC_YUY2 '2YUY' // supported
  24. #define FourCC_YUNV 'VNUY' // supported
  25. #define FourCC_YVYU 'UYVY' // supported
  26. #define FourCC_Y41P 'P14Y'
  27. #define FourCC_Y211 '112Y'
  28. #define FourCC_Y41T 'T14Y'
  29. #define FourCC_Y42T 'T24Y'
  30. #define FourCC_CLJR 'RJLC'
  31. // planar formats
  32. #define FourCC_YVU9 '9UVY'
  33. #define FourCC_IF09 '90FI'
  34. #define FourCC_YV12 '21VY' // supported
  35. #define FourCC_I420 '024I'
  36. #define FourCC_IYUV 'VUYI'
  37. #define FourCC_CLPL 'LPLC'
  38. // global variables
  39. IImagingFactory* g_pImgFact = NULL; // pointer to IImageingFactory object
  40. // helper: calls release on a Non-NULL pointer, and sets it to NULL
  41. #define SAFE_RELEASE(ptr) \
  42. { \
  43. if (ptr) \
  44. { \
  45. ptr->Release(); \
  46. ptr = NULL; \
  47. } \
  48. }
  49. extern CComModule _Module;
  50. ///////////////////////////////////////////////////////////////////////////
  51. // This block of code handles saving a GDI+ image object to a file,
  52. // allowing user to select a format.
  53. ///////////////////////////////////////////////////////////////////////////
  54. //
  55. // Save the current image to a file
  56. //
  57. static HRESULT
  58. SaveImageFile(IImage *pImage, const TCHAR* filename, const CLSID* clsid)
  59. {
  60. USES_CONVERSION;
  61. HRESULT hr = S_OK;
  62. if (!pImage || !g_pImgFact)
  63. return E_FAIL;
  64. // Create an encoder object
  65. IImageEncoder* encoder = NULL;
  66. hr = g_pImgFact->CreateImageEncoderToFile(clsid, T2CW(filename), &encoder);
  67. if (FAILED(hr))
  68. return hr;
  69. // Get an IImageSink interface to the encoder
  70. IImageSink* sink = NULL;
  71. hr = encoder->GetEncodeSink(&sink);
  72. if (SUCCEEDED(hr))
  73. {
  74. hr = pImage->PushIntoSink(sink);
  75. SAFE_RELEASE(sink);
  76. }
  77. encoder->TerminateEncoder();
  78. SAFE_RELEASE(encoder);
  79. return hr;
  80. }
  81. //
  82. // Compose a file type filter string given an array of
  83. // ImageCodecInfo structures; also find the index of JPG format
  84. //
  85. static TCHAR*
  86. MakeFilterFromCodecs(UINT count, const ImageCodecInfo* codecs, UINT *jpgIndex)
  87. {
  88. USES_CONVERSION;
  89. // Figure out the total size of the filter string
  90. UINT index, size;
  91. for (index=size=0; index < count; index++)
  92. {
  93. size += wcslen(codecs[index].FormatDescription) + 1
  94. + wcslen(codecs[index].FilenameExtension) + 1;
  95. }
  96. size += 1; // for the double trailing '\0'
  97. // Allocate memory
  98. TCHAR *filter = (TCHAR*) malloc(size*sizeof(TCHAR));
  99. UINT strSize = size;
  100. if (!filter)
  101. return NULL;
  102. TCHAR* p = filter;
  103. const WCHAR* ws;
  104. *jpgIndex = 0;
  105. LPCTSTR strTemp = NULL;
  106. for (index=0; index < count; index++)
  107. {
  108. ws = codecs[index].FormatDescription;
  109. size = wcslen(ws) + 1;
  110. strTemp = W2CT(ws);
  111. if (NULL != strTemp)
  112. {
  113. lstrcpyn(p, strTemp, strSize - lstrlen(p));
  114. p += size;
  115. }
  116. ws = codecs[index].FilenameExtension;
  117. size = wcslen(ws) + 1;
  118. strTemp = W2CT(ws);
  119. if (NULL != strTemp)
  120. {
  121. lstrcpyn(p, strTemp, strSize - lstrlen(p));
  122. p += size;
  123. }
  124. // find the index of jpg format
  125. if (wcsstr(ws, L"JPG"))
  126. {
  127. *jpgIndex = index + 1;
  128. }
  129. }
  130. *((TCHAR*) p) = _T('\0');
  131. return filter;
  132. }
  133. //
  134. // Save image file
  135. //
  136. static HRESULT
  137. SaveFileDialog(HWND hwnd, IImage *pImage)
  138. {
  139. USES_CONVERSION;
  140. HRESULT hr = S_OK;
  141. OPENFILENAME ofn;
  142. TCHAR filename[MAX_PATH];
  143. TCHAR FolderPath[MAX_PATH];
  144. const ciBufSize = 256;
  145. TCHAR titlestring[ciBufSize];
  146. // get the path of "My Pictures" and use it as default location
  147. if (SHGetSpecialFolderPath(NULL, FolderPath, CSIDL_MYPICTURES, FALSE) == FALSE)
  148. {
  149. // if My Pictures doesn't exist, try My Documents
  150. if (SHGetSpecialFolderPath(NULL, FolderPath, CSIDL_PERSONAL, FALSE) == FALSE)
  151. {
  152. // use current directory as last resort
  153. lstrcpyn(FolderPath, _T("."), sizeof(FolderPath) / sizeof(FolderPath[0]));
  154. }
  155. }
  156. ZeroMemory(&ofn, sizeof(ofn));
  157. ofn.lStructSize = sizeof(ofn);
  158. ofn.hwndOwner = hwnd;
  159. ofn.hInstance = _Module.m_hInstResource;
  160. ofn.lpstrFile = filename;
  161. ofn.lpstrDefExt = _T("jpg"); // it appears it doesn't matter what string to use
  162. // it will use the ext in lpstrFilter according to selected type.
  163. ofn.nMaxFile = MAX_PATH;
  164. ::LoadString(_Module.m_hInstResource, IDS_SAVE_FILE, titlestring, ciBufSize);
  165. ofn.lpstrTitle = titlestring;
  166. ofn.lpstrInitialDir = FolderPath;
  167. ofn.Flags = OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT | OFN_EXPLORER;
  168. lstrcpyn(filename, _T("capture"), sizeof(filename) / sizeof(filename[0]));
  169. // Make up the file type filter string
  170. ImageCodecInfo* codecs;
  171. UINT count;
  172. hr = g_pImgFact->GetInstalledEncoders(&count, &codecs);
  173. if (FAILED(hr))
  174. return hr;
  175. UINT jpgIndex;
  176. TCHAR* filter = MakeFilterFromCodecs(count, codecs, &jpgIndex);
  177. if (!filter)
  178. {
  179. hr = HRESULT_FROM_WIN32(GetLastError());
  180. return hr;
  181. }
  182. else
  183. {
  184. ofn.lpstrFilter = filter;
  185. ofn.nFilterIndex = jpgIndex; // set format to JPG as default
  186. // Present the file/save dialog
  187. if (GetSaveFileName(&ofn))
  188. {
  189. UINT index = ofn.nFilterIndex;
  190. if (index == 0 || index > count)
  191. index = 0;
  192. else
  193. index--;
  194. hr = SaveImageFile(pImage, filename, &codecs[index].Clsid);
  195. }
  196. free(filter);
  197. }
  198. CoTaskMemFree(codecs);
  199. return hr;
  200. }
  201. ///////////////////////////////////////////////////////////////////////
  202. // This block of code deals with converting YUV format to RGB bitmap
  203. ///////////////////////////////////////////////////////////////////////
  204. inline BYTE Clamp(float x)
  205. {
  206. if (x < 0.0f)
  207. return 0;
  208. else if (x > 255.0f)
  209. return 255;
  210. else
  211. return (BYTE)(x + 0.5f);
  212. }
  213. // Convert YUV to ARGB
  214. static inline ARGB ConvertPixelToARGB(int y, int u, int v)
  215. {
  216. //
  217. // This equation was taken from Video Demystified (2nd Edition)
  218. // by Keith Jack, page 43.
  219. //
  220. BYTE red = Clamp((1.1644f * (y-16)) + (1.5960f * (v-128)) );
  221. BYTE grn = Clamp((1.1644f * (y-16)) - (0.8150f * (v-128)) - (0.3912f * (u-128)));
  222. BYTE blu = Clamp((1.1644f * (y-16)) + (2.0140f * (u-128)));
  223. return MAKEARGB(0xff, red, grn, blu);
  224. }
  225. // Convert image in YUY2 format to RGB bitmap
  226. static void ConvertYUY2ToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata)
  227. {
  228. long y, x;
  229. BYTE *pYUVBits;
  230. ARGB *pARGB;
  231. for (y = 0; y < lpImage->lHeight; y++)
  232. {
  233. pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
  234. pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride);
  235. for (x = 0; x < lpImage->lWidth; x += 2)
  236. {
  237. int Y0 = (int) *pYUVBits++;
  238. int U0 = (int) *pYUVBits++;
  239. int Y1 = (int) *pYUVBits++;
  240. int V0 = (int) *pYUVBits++;
  241. *pARGB++ = ConvertPixelToARGB(Y0, U0, V0);
  242. *pARGB++ = ConvertPixelToARGB(Y1, U0, V0);
  243. }
  244. }
  245. }
  246. // Convert image in UYVY format to RGB bitmap
  247. static void ConvertUYVYToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata)
  248. {
  249. long y, x;
  250. BYTE *pYUVBits;
  251. ARGB *pARGB;
  252. for (y = 0; y < lpImage->lHeight; y++)
  253. {
  254. pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
  255. pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride);
  256. for (x = 0; x < lpImage->lWidth; x += 2)
  257. {
  258. int U0 = (int) *pYUVBits++;
  259. int Y0 = (int) *pYUVBits++;
  260. int V0 = (int) *pYUVBits++;
  261. int Y1 = (int) *pYUVBits++;
  262. *pARGB++ = ConvertPixelToARGB(Y0, U0, V0);
  263. *pARGB++ = ConvertPixelToARGB(Y1, U0, V0);
  264. }
  265. }
  266. }
  267. // Convert image in YVYU format to RGB bitmap
  268. static void ConvertYVYUToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata)
  269. {
  270. long y, x;
  271. BYTE *pYUVBits;
  272. ARGB *pARGB;
  273. for (y = 0; y < lpImage->lHeight; y++)
  274. {
  275. pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
  276. pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride);
  277. for (x = 0; x < lpImage->lWidth; x += 2)
  278. {
  279. int Y0 = (int) *pYUVBits++;
  280. int V0 = (int) *pYUVBits++;
  281. int Y1 = (int) *pYUVBits++;
  282. int U0 = (int) *pYUVBits++;
  283. *pARGB++ = ConvertPixelToARGB(Y0, U0, V0);
  284. *pARGB++ = ConvertPixelToARGB(Y1, U0, V0);
  285. }
  286. }
  287. }
  288. // Convert image in YV12 format to RGB bitmap
  289. static void ConvertYV12ToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata)
  290. {
  291. long y, x;
  292. BYTE *pYBits;
  293. BYTE *pUBits;
  294. BYTE *pVBits;
  295. ARGB *pARGB;
  296. for (y = 0; y < lpImage->lHeight; y++)
  297. {
  298. pYBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
  299. pVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride
  300. + (y/2) * (lpImage->lStride/2);
  301. pUBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride
  302. + ((lpImage->lHeight + y)/2) * (lpImage->lStride/2);
  303. pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride);
  304. for (x = 0; x < lpImage->lWidth; x ++)
  305. {
  306. int Y0 = (int) *pYBits++;
  307. int V0 = (int) *pVBits;
  308. int U0 = (int) *pUBits;
  309. // U, V are shared by 2x2 pixels. only advance pointers every two pixels
  310. if (x&1)
  311. {
  312. pVBits++;
  313. pUBits++;
  314. }
  315. *pARGB++ = ConvertPixelToARGB(Y0, U0, V0);
  316. }
  317. }
  318. }
  319. // Convert image in YVU9 format to RGB bitmap
  320. static void ConvertYVU9ToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata)
  321. {
  322. long y, x;
  323. BYTE *pYBits;
  324. BYTE *pUBits;
  325. BYTE *pVBits;
  326. ARGB *pARGB;
  327. for (y = 0; y < lpImage->lHeight; y++)
  328. {
  329. pYBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
  330. pVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride
  331. + (y/4) * (lpImage->lStride/4);
  332. pUBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride
  333. + ((lpImage->lHeight + y)/4) * (lpImage->lStride/4);
  334. pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride);
  335. for (x = 0; x < lpImage->lWidth; x ++)
  336. {
  337. int Y0 = (int) *pYBits++;
  338. int V0 = (int) *pVBits;
  339. int U0 = (int) *pUBits;
  340. // U, V are shared by 4x4 pixels. only advance pointers every 4 pixels
  341. if ((x&3) == 3)
  342. {
  343. pVBits++;
  344. pUBits++;
  345. }
  346. *pARGB++ = ConvertPixelToARGB(Y0, U0, V0);
  347. }
  348. }
  349. }
  350. static HRESULT ConvertToBitmapImage(YUV_IMAGE *lpImage, IBitmapImage **bmp)
  351. {
  352. IBitmapImage* bmpimg = NULL;
  353. BitmapData bmpdata;
  354. HRESULT hr = S_OK;
  355. // create a bitmap object
  356. if (!g_pImgFact || bmp == NULL)
  357. {
  358. return E_FAIL;
  359. }
  360. hr = g_pImgFact->CreateNewBitmap(
  361. lpImage->lWidth,
  362. lpImage->lHeight,
  363. PIXFMT_32BPP_ARGB,
  364. &bmpimg);
  365. bool fSupported = true;
  366. if (SUCCEEDED(hr)) // bmpimg created
  367. {
  368. hr = bmpimg->LockBits(
  369. NULL,
  370. IMGLOCK_WRITE,
  371. PIXFMT_DONTCARE,
  372. &bmpdata);
  373. if (SUCCEEDED(hr))
  374. {
  375. // convert different types of YUV formats to RGB
  376. switch (lpImage->dwFourCC)
  377. {
  378. case FourCC_YUY2:
  379. case FourCC_YUNV: // the two are equivalent
  380. ConvertYUY2ToBitmap(lpImage, &bmpdata);
  381. break;
  382. case FourCC_UYVY:
  383. case FourCC_UYNV: // equivalent
  384. ConvertUYVYToBitmap(lpImage, &bmpdata);
  385. break;
  386. case FourCC_YVYU:
  387. ConvertYVYUToBitmap(lpImage, &bmpdata);
  388. break;
  389. case FourCC_YV12:
  390. ConvertYV12ToBitmap(lpImage, &bmpdata);
  391. break;
  392. case FourCC_YVU9:
  393. ConvertYVU9ToBitmap(lpImage, &bmpdata);
  394. break;
  395. default:
  396. fSupported = false;
  397. break;
  398. }
  399. hr = bmpimg->UnlockBits(&bmpdata);
  400. }
  401. if (!fSupported)
  402. {
  403. SAFE_RELEASE(bmpimg);
  404. hr = E_FORMAT_NOT_SUPPORTED;
  405. }
  406. }
  407. *bmp = bmpimg;
  408. // Addref() and Release() cancels out
  409. bmpimg = NULL;
  410. return hr;
  411. }
  412. #ifdef _DEBUG
  413. static void AlertUnsupportedFormat(DWORD dwFourCC, HWND hwnd)
  414. {
  415. char buf[256];
  416. StringCchPrintf(buf, sizeof(buf), "YUV format %c%c%c%c not supported\n",
  417. dwFourCC & 0xff,
  418. (dwFourCC >> 8) & 0xff,
  419. (dwFourCC >> 16) & 0xff,
  420. (dwFourCC >> 24) & 0xff);
  421. MessageBoxA(hwnd, buf, "", MB_OK);
  422. }
  423. #endif
  424. // This helper function does several things.
  425. //
  426. // First, it determines if clipping is necessary, return true if it is,
  427. // and false otherwise.
  428. //
  429. // Second, it maps the ViewClipRect (clipping rect in the view coordinates,
  430. // i.e. the one after correcting aspect ratio) back to the raw captured
  431. // image coordinates. Return it in ImageClipRect. This step is skipped (and
  432. // ImageClipRect will be invalid) if clipping is not necessary.
  433. //
  434. // Third, it calculates the stretched image size. It should be in the same
  435. // aspect ratio as the ViewClipRect. It will also be made as full-size as possible
  436. static bool ClipAndStretchSizes(YUV_IMAGE *lpImage, const RECT *pViewClipRect,
  437. RECT *pImageClipRect, int *pViewWidth, int *pViewHeight)
  438. {
  439. float aspectRaw = (float)lpImage->lHeight / (float)lpImage->lWidth;
  440. float aspectView = (float)lpImage->lAspectY / (float)lpImage->lAspectX;
  441. int viewWidth = lpImage->lWidth;
  442. int viewHeight = (int)(viewWidth * aspectView + 0.5f);
  443. // the rect is given in the stretched (aspect-ratio corrected) window
  444. // we will adjust it back to the raw image space
  445. bool fClip = false;
  446. if (pViewClipRect)
  447. {
  448. RECT rc;
  449. rc.left = pViewClipRect->left;
  450. rc.right = pViewClipRect->right;
  451. rc.top = (int)(pViewClipRect->top * aspectRaw / aspectView + 0.5f);
  452. rc.bottom = (int)(pViewClipRect->bottom * aspectRaw / aspectView + 0.5f);
  453. RECT rcFullImage;
  454. ::SetRect(&rcFullImage, 0, 0, lpImage->lWidth, lpImage->lHeight);
  455. if (! ::EqualRect(&rc, &rcFullImage) &&
  456. ::IntersectRect(pImageClipRect, &rc, &rcFullImage))
  457. {
  458. fClip = true;
  459. }
  460. }
  461. // adjust the stretched image size according to the rect aspect ratio
  462. if (fClip)
  463. {
  464. float aspectRect = (float)(RECTHEIGHT(pViewClipRect))
  465. / (float)(RECTWIDTH(pViewClipRect));
  466. if (aspectRect < aspectView)
  467. {
  468. // clip rect has a wider aspect ratio.
  469. // keep the width, adjust the height
  470. viewHeight = (int)(viewWidth * aspectRect + 0.5f);
  471. }
  472. else
  473. {
  474. // clip rect has a taller aspect ratio.
  475. // keep the height, adjust width
  476. viewWidth = (int)(viewHeight / aspectRect + 0.5f);
  477. }
  478. }
  479. *pViewWidth = viewWidth;
  480. *pViewHeight = viewHeight;
  481. return fClip;
  482. }
  483. /////////////////////////////////////////////////////////////////////////////
  484. //
  485. // ConvertImageAndSave: this is the main function to be called by the player.
  486. //
  487. // Convert a captured YUV image to a GDI BitmapImage, and save it to a file
  488. // allowing user to choose file format and file name.
  489. // The clipping rectangle should be in the full size view coordinate system
  490. // with corrected aspect ratio (i.e. 720x540 for 4:3).
  491. HRESULT GDIConvertImageAndSave(YUV_IMAGE *lpImage, RECT *pViewClipRect, HWND hwnd)
  492. {
  493. IBitmapImage* bmpimg = NULL;
  494. IBitmapImage* bmpStretched = NULL;
  495. HRESULT hr = S_OK;
  496. // Create an IImagingFactory object
  497. hr = CoCreateInstance(
  498. CLSID_ImagingFactory,
  499. NULL,
  500. CLSCTX_INPROC_SERVER,
  501. IID_IImagingFactory,
  502. (VOID**) &g_pImgFact);
  503. if (FAILED(hr))
  504. {
  505. return hr;
  506. }
  507. hr = ConvertToBitmapImage(lpImage, &bmpimg);
  508. #ifdef _DEBUG
  509. if (E_FORMAT_NOT_SUPPORTED == hr)
  510. {
  511. AlertUnsupportedFormat(lpImage->dwFourCC, hwnd);
  512. }
  513. #endif
  514. // calculate size and rectangles for clipping and stretching
  515. int viewWidth, viewHeight; // size of the clipped and stretch image
  516. bool fClip; // is clipping necessary
  517. RECT rcClipImage; // view clipping rect mapped to image space
  518. fClip = ClipAndStretchSizes(lpImage, pViewClipRect, &rcClipImage,
  519. &viewWidth, &viewHeight);
  520. // crop the image to the clip rectangle.
  521. if (SUCCEEDED(hr) && fClip) // by now we have valid bits in bmpimg
  522. {
  523. IBasicBitmapOps *bmpops = NULL;
  524. IBitmapImage* bmpClipped = NULL;
  525. hr = bmpimg->QueryInterface(IID_IBasicBitmapOps, (VOID**) &bmpops);
  526. if (SUCCEEDED(hr))
  527. {
  528. hr = bmpops->Clone(&rcClipImage, &bmpClipped);
  529. SAFE_RELEASE(bmpops);
  530. }
  531. if (SUCCEEDED(hr)) // valid bmpClipped
  532. {
  533. // replace bmpimg with bmpClipped
  534. SAFE_RELEASE(bmpimg);
  535. bmpimg = bmpClipped;
  536. bmpimg->AddRef();
  537. SAFE_RELEASE(bmpClipped);
  538. }
  539. }
  540. // stretch the image to the right aspect ratio
  541. if (SUCCEEDED(hr)) // valid bits in bmpimg
  542. {
  543. IImage *image = NULL;
  544. hr = bmpimg->QueryInterface(IID_IImage, (VOID**) &image);
  545. if (SUCCEEDED(hr))
  546. {
  547. hr = g_pImgFact->CreateBitmapFromImage(
  548. image,
  549. viewWidth,
  550. viewHeight,
  551. PIXFMT_DONTCARE,
  552. INTERP_BILINEAR,
  553. &bmpStretched);
  554. SAFE_RELEASE(image);
  555. }
  556. SAFE_RELEASE(bmpimg);
  557. }
  558. // save final bitmap to a file
  559. if (SUCCEEDED(hr)) // bmpStretched valid
  560. {
  561. IImage *image = NULL;
  562. hr = bmpStretched->QueryInterface(IID_IImage, (VOID**) &image);
  563. if (SUCCEEDED(hr))
  564. {
  565. hr = SaveFileDialog(hwnd, image);
  566. SAFE_RELEASE(image);
  567. }
  568. SAFE_RELEASE(bmpStretched);
  569. }
  570. // clean up, release the imaging factory
  571. SAFE_RELEASE(g_pImgFact);
  572. return hr;
  573. }