|
|
/*************************************************************************/ /* 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 <initguid.h>
#include "imaging.h"
#include <shlobj.h>
// 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; }
|