/*************************************************************************/ /* Copyright (C) 1999 Microsoft Corporation */ /* File: capture.cpp */ /* Description: Convert a captured DVD frame from YUV formats to RGB, */ /* and save to file in various formats. */ /* Author: phillu */ /*************************************************************************/ #include "stdafx.h" // This version of capture is for Millennium where GDI+ is installed #include "MSWebDVD.h" #include "msdvd.h" #include #include "imaging.h" #include // YUV FourCC Formats (byte-swapped). We support a subset of them. // Ref: http://www.webartz.com/fourcc/ // packed formats #define FourCC_IYU1 '1UYI' #define FourCC_IYU2 '2UYI' #define FourCC_UYVY 'YVYU' // supported #define FourCC_UYNV 'VNYU' // supported #define FourCC_cyuv 'vuyc' #define FourCC_YUY2 '2YUY' // supported #define FourCC_YUNV 'VNUY' // supported #define FourCC_YVYU 'UYVY' // supported #define FourCC_Y41P 'P14Y' #define FourCC_Y211 '112Y' #define FourCC_Y41T 'T14Y' #define FourCC_Y42T 'T24Y' #define FourCC_CLJR 'RJLC' // planar formats #define FourCC_YVU9 '9UVY' #define FourCC_IF09 '90FI' #define FourCC_YV12 '21VY' // supported #define FourCC_I420 '024I' #define FourCC_IYUV 'VUYI' #define FourCC_CLPL 'LPLC' // global variables IImagingFactory* g_pImgFact = NULL; // pointer to IImageingFactory object // helper: calls release on a Non-NULL pointer, and sets it to NULL #define SAFE_RELEASE(ptr) \ { \ if (ptr) \ { \ ptr->Release(); \ ptr = NULL; \ } \ } extern CComModule _Module; /////////////////////////////////////////////////////////////////////////// // This block of code handles saving a GDI+ image object to a file, // allowing user to select a format. /////////////////////////////////////////////////////////////////////////// // // Save the current image to a file // static HRESULT SaveImageFile(IImage *pImage, const TCHAR* filename, const CLSID* clsid) { USES_CONVERSION; HRESULT hr = S_OK; if (!pImage || !g_pImgFact) return E_FAIL; // Create an encoder object IImageEncoder* encoder = NULL; hr = g_pImgFact->CreateImageEncoderToFile(clsid, T2CW(filename), &encoder); if (FAILED(hr)) return hr; // Get an IImageSink interface to the encoder IImageSink* sink = NULL; hr = encoder->GetEncodeSink(&sink); if (SUCCEEDED(hr)) { hr = pImage->PushIntoSink(sink); SAFE_RELEASE(sink); } encoder->TerminateEncoder(); SAFE_RELEASE(encoder); return hr; } // // Compose a file type filter string given an array of // ImageCodecInfo structures; also find the index of JPG format // static TCHAR* MakeFilterFromCodecs(UINT count, const ImageCodecInfo* codecs, UINT *jpgIndex) { USES_CONVERSION; // Figure out the total size of the filter string UINT index, size; for (index=size=0; index < count; index++) { size += wcslen(codecs[index].FormatDescription) + 1 + wcslen(codecs[index].FilenameExtension) + 1; } size += 1; // for the double trailing '\0' // Allocate memory TCHAR *filter = (TCHAR*) malloc(size*sizeof(TCHAR)); UINT strSize = size; if (!filter) return NULL; TCHAR* p = filter; const WCHAR* ws; *jpgIndex = 0; LPCTSTR strTemp = NULL; for (index=0; index < count; index++) { ws = codecs[index].FormatDescription; size = wcslen(ws) + 1; strTemp = W2CT(ws); if (NULL != strTemp) { lstrcpyn(p, strTemp, strSize - lstrlen(p)); p += size; } ws = codecs[index].FilenameExtension; size = wcslen(ws) + 1; strTemp = W2CT(ws); if (NULL != strTemp) { lstrcpyn(p, strTemp, strSize - lstrlen(p)); p += size; } // find the index of jpg format if (wcsstr(ws, L"JPG")) { *jpgIndex = index + 1; } } *((TCHAR*) p) = _T('\0'); return filter; } // // Save image file // static HRESULT SaveFileDialog(HWND hwnd, IImage *pImage) { USES_CONVERSION; HRESULT hr = S_OK; OPENFILENAME ofn; TCHAR filename[MAX_PATH]; TCHAR FolderPath[MAX_PATH]; const ciBufSize = 256; TCHAR titlestring[ciBufSize]; // get the path of "My Pictures" and use it as default location if (SHGetSpecialFolderPath(NULL, FolderPath, CSIDL_MYPICTURES, FALSE) == FALSE) { // if My Pictures doesn't exist, try My Documents if (SHGetSpecialFolderPath(NULL, FolderPath, CSIDL_PERSONAL, FALSE) == FALSE) { // use current directory as last resort lstrcpyn(FolderPath, _T("."), sizeof(FolderPath) / sizeof(FolderPath[0])); } } ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hwnd; ofn.hInstance = _Module.m_hInstResource; ofn.lpstrFile = filename; ofn.lpstrDefExt = _T("jpg"); // it appears it doesn't matter what string to use // it will use the ext in lpstrFilter according to selected type. ofn.nMaxFile = MAX_PATH; ::LoadString(_Module.m_hInstResource, IDS_SAVE_FILE, titlestring, ciBufSize); ofn.lpstrTitle = titlestring; ofn.lpstrInitialDir = FolderPath; ofn.Flags = OFN_CREATEPROMPT | OFN_OVERWRITEPROMPT | OFN_EXPLORER; lstrcpyn(filename, _T("capture"), sizeof(filename) / sizeof(filename[0])); // Make up the file type filter string ImageCodecInfo* codecs; UINT count; hr = g_pImgFact->GetInstalledEncoders(&count, &codecs); if (FAILED(hr)) return hr; UINT jpgIndex; TCHAR* filter = MakeFilterFromCodecs(count, codecs, &jpgIndex); if (!filter) { hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } else { ofn.lpstrFilter = filter; ofn.nFilterIndex = jpgIndex; // set format to JPG as default // Present the file/save dialog if (GetSaveFileName(&ofn)) { UINT index = ofn.nFilterIndex; if (index == 0 || index > count) index = 0; else index--; hr = SaveImageFile(pImage, filename, &codecs[index].Clsid); } free(filter); } CoTaskMemFree(codecs); return hr; } /////////////////////////////////////////////////////////////////////// // This block of code deals with converting YUV format to RGB bitmap /////////////////////////////////////////////////////////////////////// inline BYTE Clamp(float x) { if (x < 0.0f) return 0; else if (x > 255.0f) return 255; else return (BYTE)(x + 0.5f); } // Convert YUV to ARGB static inline ARGB ConvertPixelToARGB(int y, int u, int v) { // // This equation was taken from Video Demystified (2nd Edition) // by Keith Jack, page 43. // BYTE red = Clamp((1.1644f * (y-16)) + (1.5960f * (v-128)) ); BYTE grn = Clamp((1.1644f * (y-16)) - (0.8150f * (v-128)) - (0.3912f * (u-128))); BYTE blu = Clamp((1.1644f * (y-16)) + (2.0140f * (u-128))); return MAKEARGB(0xff, red, grn, blu); } // Convert image in YUY2 format to RGB bitmap static void ConvertYUY2ToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata) { long y, x; BYTE *pYUVBits; ARGB *pARGB; for (y = 0; y < lpImage->lHeight; y++) { pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride; pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride); for (x = 0; x < lpImage->lWidth; x += 2) { int Y0 = (int) *pYUVBits++; int U0 = (int) *pYUVBits++; int Y1 = (int) *pYUVBits++; int V0 = (int) *pYUVBits++; *pARGB++ = ConvertPixelToARGB(Y0, U0, V0); *pARGB++ = ConvertPixelToARGB(Y1, U0, V0); } } } // Convert image in UYVY format to RGB bitmap static void ConvertUYVYToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata) { long y, x; BYTE *pYUVBits; ARGB *pARGB; for (y = 0; y < lpImage->lHeight; y++) { pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride; pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride); for (x = 0; x < lpImage->lWidth; x += 2) { int U0 = (int) *pYUVBits++; int Y0 = (int) *pYUVBits++; int V0 = (int) *pYUVBits++; int Y1 = (int) *pYUVBits++; *pARGB++ = ConvertPixelToARGB(Y0, U0, V0); *pARGB++ = ConvertPixelToARGB(Y1, U0, V0); } } } // Convert image in YVYU format to RGB bitmap static void ConvertYVYUToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata) { long y, x; BYTE *pYUVBits; ARGB *pARGB; for (y = 0; y < lpImage->lHeight; y++) { pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride; pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride); for (x = 0; x < lpImage->lWidth; x += 2) { int Y0 = (int) *pYUVBits++; int V0 = (int) *pYUVBits++; int Y1 = (int) *pYUVBits++; int U0 = (int) *pYUVBits++; *pARGB++ = ConvertPixelToARGB(Y0, U0, V0); *pARGB++ = ConvertPixelToARGB(Y1, U0, V0); } } } // Convert image in YV12 format to RGB bitmap static void ConvertYV12ToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata) { long y, x; BYTE *pYBits; BYTE *pUBits; BYTE *pVBits; ARGB *pARGB; for (y = 0; y < lpImage->lHeight; y++) { pYBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride; pVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride + (y/2) * (lpImage->lStride/2); pUBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride + ((lpImage->lHeight + y)/2) * (lpImage->lStride/2); pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride); for (x = 0; x < lpImage->lWidth; x ++) { int Y0 = (int) *pYBits++; int V0 = (int) *pVBits; int U0 = (int) *pUBits; // U, V are shared by 2x2 pixels. only advance pointers every two pixels if (x&1) { pVBits++; pUBits++; } *pARGB++ = ConvertPixelToARGB(Y0, U0, V0); } } } // Convert image in YVU9 format to RGB bitmap static void ConvertYVU9ToBitmap(YUV_IMAGE* lpImage, BitmapData* bmpdata) { long y, x; BYTE *pYBits; BYTE *pUBits; BYTE *pVBits; ARGB *pARGB; for (y = 0; y < lpImage->lHeight; y++) { pYBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride; pVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride + (y/4) * (lpImage->lStride/4); pUBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + lpImage->lHeight * lpImage->lStride + ((lpImage->lHeight + y)/4) * (lpImage->lStride/4); pARGB = (ARGB *)((BYTE *)(bmpdata->Scan0) + y * bmpdata->Stride); for (x = 0; x < lpImage->lWidth; x ++) { int Y0 = (int) *pYBits++; int V0 = (int) *pVBits; int U0 = (int) *pUBits; // U, V are shared by 4x4 pixels. only advance pointers every 4 pixels if ((x&3) == 3) { pVBits++; pUBits++; } *pARGB++ = ConvertPixelToARGB(Y0, U0, V0); } } } static HRESULT ConvertToBitmapImage(YUV_IMAGE *lpImage, IBitmapImage **bmp) { IBitmapImage* bmpimg = NULL; BitmapData bmpdata; HRESULT hr = S_OK; // create a bitmap object if (!g_pImgFact || bmp == NULL) { return E_FAIL; } hr = g_pImgFact->CreateNewBitmap( lpImage->lWidth, lpImage->lHeight, PIXFMT_32BPP_ARGB, &bmpimg); bool fSupported = true; if (SUCCEEDED(hr)) // bmpimg created { hr = bmpimg->LockBits( NULL, IMGLOCK_WRITE, PIXFMT_DONTCARE, &bmpdata); if (SUCCEEDED(hr)) { // convert different types of YUV formats to RGB switch (lpImage->dwFourCC) { case FourCC_YUY2: case FourCC_YUNV: // the two are equivalent ConvertYUY2ToBitmap(lpImage, &bmpdata); break; case FourCC_UYVY: case FourCC_UYNV: // equivalent ConvertUYVYToBitmap(lpImage, &bmpdata); break; case FourCC_YVYU: ConvertYVYUToBitmap(lpImage, &bmpdata); break; case FourCC_YV12: ConvertYV12ToBitmap(lpImage, &bmpdata); break; case FourCC_YVU9: ConvertYVU9ToBitmap(lpImage, &bmpdata); break; default: fSupported = false; break; } hr = bmpimg->UnlockBits(&bmpdata); } if (!fSupported) { SAFE_RELEASE(bmpimg); hr = E_FORMAT_NOT_SUPPORTED; } } *bmp = bmpimg; // Addref() and Release() cancels out bmpimg = NULL; return hr; } #ifdef _DEBUG static void AlertUnsupportedFormat(DWORD dwFourCC, HWND hwnd) { char buf[256]; StringCchPrintf(buf, sizeof(buf), "YUV format %c%c%c%c not supported\n", dwFourCC & 0xff, (dwFourCC >> 8) & 0xff, (dwFourCC >> 16) & 0xff, (dwFourCC >> 24) & 0xff); MessageBoxA(hwnd, buf, "", MB_OK); } #endif // This helper function does several things. // // First, it determines if clipping is necessary, return true if it is, // and false otherwise. // // Second, it maps the ViewClipRect (clipping rect in the view coordinates, // i.e. the one after correcting aspect ratio) back to the raw captured // image coordinates. Return it in ImageClipRect. This step is skipped (and // ImageClipRect will be invalid) if clipping is not necessary. // // Third, it calculates the stretched image size. It should be in the same // aspect ratio as the ViewClipRect. It will also be made as full-size as possible static bool ClipAndStretchSizes(YUV_IMAGE *lpImage, const RECT *pViewClipRect, RECT *pImageClipRect, int *pViewWidth, int *pViewHeight) { float aspectRaw = (float)lpImage->lHeight / (float)lpImage->lWidth; float aspectView = (float)lpImage->lAspectY / (float)lpImage->lAspectX; int viewWidth = lpImage->lWidth; int viewHeight = (int)(viewWidth * aspectView + 0.5f); // the rect is given in the stretched (aspect-ratio corrected) window // we will adjust it back to the raw image space bool fClip = false; if (pViewClipRect) { RECT rc; rc.left = pViewClipRect->left; rc.right = pViewClipRect->right; rc.top = (int)(pViewClipRect->top * aspectRaw / aspectView + 0.5f); rc.bottom = (int)(pViewClipRect->bottom * aspectRaw / aspectView + 0.5f); RECT rcFullImage; ::SetRect(&rcFullImage, 0, 0, lpImage->lWidth, lpImage->lHeight); if (! ::EqualRect(&rc, &rcFullImage) && ::IntersectRect(pImageClipRect, &rc, &rcFullImage)) { fClip = true; } } // adjust the stretched image size according to the rect aspect ratio if (fClip) { float aspectRect = (float)(RECTHEIGHT(pViewClipRect)) / (float)(RECTWIDTH(pViewClipRect)); if (aspectRect < aspectView) { // clip rect has a wider aspect ratio. // keep the width, adjust the height viewHeight = (int)(viewWidth * aspectRect + 0.5f); } else { // clip rect has a taller aspect ratio. // keep the height, adjust width viewWidth = (int)(viewHeight / aspectRect + 0.5f); } } *pViewWidth = viewWidth; *pViewHeight = viewHeight; return fClip; } ///////////////////////////////////////////////////////////////////////////// // // ConvertImageAndSave: this is the main function to be called by the player. // // Convert a captured YUV image to a GDI BitmapImage, and save it to a file // allowing user to choose file format and file name. // The clipping rectangle should be in the full size view coordinate system // with corrected aspect ratio (i.e. 720x540 for 4:3). HRESULT GDIConvertImageAndSave(YUV_IMAGE *lpImage, RECT *pViewClipRect, HWND hwnd) { IBitmapImage* bmpimg = NULL; IBitmapImage* bmpStretched = NULL; HRESULT hr = S_OK; // Create an IImagingFactory object hr = CoCreateInstance( CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, (VOID**) &g_pImgFact); if (FAILED(hr)) { return hr; } hr = ConvertToBitmapImage(lpImage, &bmpimg); #ifdef _DEBUG if (E_FORMAT_NOT_SUPPORTED == hr) { AlertUnsupportedFormat(lpImage->dwFourCC, hwnd); } #endif // calculate size and rectangles for clipping and stretching int viewWidth, viewHeight; // size of the clipped and stretch image bool fClip; // is clipping necessary RECT rcClipImage; // view clipping rect mapped to image space fClip = ClipAndStretchSizes(lpImage, pViewClipRect, &rcClipImage, &viewWidth, &viewHeight); // crop the image to the clip rectangle. if (SUCCEEDED(hr) && fClip) // by now we have valid bits in bmpimg { IBasicBitmapOps *bmpops = NULL; IBitmapImage* bmpClipped = NULL; hr = bmpimg->QueryInterface(IID_IBasicBitmapOps, (VOID**) &bmpops); if (SUCCEEDED(hr)) { hr = bmpops->Clone(&rcClipImage, &bmpClipped); SAFE_RELEASE(bmpops); } if (SUCCEEDED(hr)) // valid bmpClipped { // replace bmpimg with bmpClipped SAFE_RELEASE(bmpimg); bmpimg = bmpClipped; bmpimg->AddRef(); SAFE_RELEASE(bmpClipped); } } // stretch the image to the right aspect ratio if (SUCCEEDED(hr)) // valid bits in bmpimg { IImage *image = NULL; hr = bmpimg->QueryInterface(IID_IImage, (VOID**) &image); if (SUCCEEDED(hr)) { hr = g_pImgFact->CreateBitmapFromImage( image, viewWidth, viewHeight, PIXFMT_DONTCARE, INTERP_BILINEAR, &bmpStretched); SAFE_RELEASE(image); } SAFE_RELEASE(bmpimg); } // save final bitmap to a file if (SUCCEEDED(hr)) // bmpStretched valid { IImage *image = NULL; hr = bmpStretched->QueryInterface(IID_IImage, (VOID**) &image); if (SUCCEEDED(hr)) { hr = SaveFileDialog(hwnd, image); SAFE_RELEASE(image); } SAFE_RELEASE(bmpStretched); } // clean up, release the imaging factory SAFE_RELEASE(g_pImgFact); return hr; }