681 lines
19 KiB
C++
681 lines
19 KiB
C++
/*************************************************************************/
|
|
/* 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"
|
|
|
|
#include "MSWebDVD.h"
|
|
#include "msdvd.h"
|
|
#include <shlobj.h>
|
|
#include "capture.h"
|
|
|
|
|
|
HRESULT WriteBitmapDataToJPEGFile(char * filename, CaptureBitmapData *bm);
|
|
HRESULT WriteBitmapDataToBMPFile(char * filename, CaptureBitmapData *bm);
|
|
|
|
// 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'
|
|
|
|
|
|
extern CComModule _Module;
|
|
|
|
//
|
|
// Save image file
|
|
//
|
|
|
|
static HRESULT
|
|
SaveFileDialog(HWND hwnd, CaptureBitmapData *bmpdata)
|
|
{
|
|
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
|
|
|
|
TCHAR* filter = _T("JPEG\0*.JPG\0Windows Bitmap\0*.BMP\0");
|
|
|
|
ofn.lpstrFilter = filter;
|
|
ofn.nFilterIndex = 1; // set format to JPG as default
|
|
|
|
// Present the file/save dialog
|
|
|
|
if (GetSaveFileName(&ofn))
|
|
{
|
|
switch (ofn.nFilterIndex)
|
|
{
|
|
case 2:
|
|
hr = WriteBitmapDataToBMPFile(T2A(filename), bmpdata);
|
|
break;
|
|
default:
|
|
hr = WriteBitmapDataToJPEGFile(T2A(filename), bmpdata);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// This block of code deals with converting YUV format to RGB bitmap
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
static 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 RGB
|
|
static inline void ConvertPixelToRGB(int y, int u, int v, BYTE *pBuf)
|
|
{
|
|
//
|
|
// 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)));
|
|
|
|
// RGB format, 3 bytes per pixel
|
|
|
|
pBuf[0] = red;
|
|
pBuf[1] = grn;
|
|
pBuf[2] = blu;
|
|
}
|
|
|
|
// Convert image in YUY2 format to RGB bitmap
|
|
|
|
static void ConvertYUY2ToBitmap(YUV_IMAGE* lpImage, CaptureBitmapData* bmpdata)
|
|
{
|
|
long y, x;
|
|
BYTE *pYUVBits;
|
|
BYTE *pRGB;
|
|
|
|
for (y = 0; y < lpImage->lHeight; y++)
|
|
{
|
|
pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
|
|
pRGB = (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++;
|
|
|
|
ConvertPixelToRGB(Y0, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
ConvertPixelToRGB(Y1, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert image in UYVY format to RGB bitmap
|
|
|
|
static void ConvertUYVYToBitmap(YUV_IMAGE* lpImage, CaptureBitmapData* bmpdata)
|
|
{
|
|
long y, x;
|
|
BYTE *pYUVBits;
|
|
BYTE *pRGB;
|
|
|
|
for (y = 0; y < lpImage->lHeight; y++)
|
|
{
|
|
pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
|
|
pRGB = (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++;
|
|
|
|
ConvertPixelToRGB(Y0, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
ConvertPixelToRGB(Y1, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert image in YVYU format to RGB bitmap
|
|
|
|
static void ConvertYVYUToBitmap(YUV_IMAGE* lpImage, CaptureBitmapData* bmpdata)
|
|
{
|
|
long y, x;
|
|
BYTE *pYUVBits;
|
|
BYTE *pRGB;
|
|
|
|
for (y = 0; y < lpImage->lHeight; y++)
|
|
{
|
|
pYUVBits = (BYTE *)lpImage + sizeof(YUV_IMAGE) + y * lpImage->lStride;
|
|
pRGB = (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++;
|
|
|
|
ConvertPixelToRGB(Y0, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
ConvertPixelToRGB(Y1, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Convert image in YV12 format to RGB bitmap
|
|
|
|
static void ConvertYV12ToBitmap(YUV_IMAGE* lpImage, CaptureBitmapData* bmpdata)
|
|
{
|
|
long y, x;
|
|
BYTE *pYBits;
|
|
BYTE *pUBits;
|
|
BYTE *pVBits;
|
|
BYTE *pRGB;
|
|
|
|
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);
|
|
|
|
pRGB = (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++;
|
|
}
|
|
|
|
ConvertPixelToRGB(Y0, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert image in YVU9 format to RGB bitmap
|
|
|
|
static void ConvertYVU9ToBitmap(YUV_IMAGE* lpImage, CaptureBitmapData* bmpdata)
|
|
{
|
|
long y, x;
|
|
BYTE *pYBits;
|
|
BYTE *pUBits;
|
|
BYTE *pVBits;
|
|
BYTE *pRGB;
|
|
|
|
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);
|
|
|
|
pRGB = (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++;
|
|
}
|
|
|
|
ConvertPixelToRGB(Y0, U0, V0, pRGB);
|
|
pRGB += BYTES_PER_PIXEL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static HRESULT InitBitmapData(CaptureBitmapData *bmpdata, int Width, int Height)
|
|
{
|
|
bmpdata->Width = Width;
|
|
bmpdata->Height = Height;
|
|
bmpdata->Stride = (BYTES_PER_PIXEL*Width + 3) & (~3); // align with word boundary
|
|
bmpdata->Scan0 = new BYTE[Height * bmpdata->Stride];
|
|
bmpdata->pBuffer = bmpdata->Scan0;
|
|
|
|
if (NULL == bmpdata->Scan0)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
static void FreeBitmapData(CaptureBitmapData *bmpdata)
|
|
{
|
|
delete[] bmpdata->pBuffer;
|
|
bmpdata->pBuffer = NULL;
|
|
bmpdata->Scan0 = NULL;
|
|
}
|
|
|
|
|
|
static HRESULT ConvertToBitmapImage(YUV_IMAGE *lpImage, CaptureBitmapData *bmp)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// create a bitmap object
|
|
|
|
hr = InitBitmapData(bmp, lpImage->lWidth, lpImage->lHeight);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
bool fSupported = true;
|
|
|
|
// convert different types of YUV formats to RGB
|
|
|
|
switch (lpImage->dwFourCC)
|
|
{
|
|
case FourCC_YUY2:
|
|
case FourCC_YUNV: // the two are equivalent
|
|
ConvertYUY2ToBitmap(lpImage, bmp);
|
|
break;
|
|
|
|
case FourCC_UYVY:
|
|
case FourCC_UYNV: // equivalent
|
|
ConvertUYVYToBitmap(lpImage, bmp);
|
|
break;
|
|
|
|
case FourCC_YVYU:
|
|
ConvertYVYUToBitmap(lpImage, bmp);
|
|
break;
|
|
|
|
case FourCC_YV12:
|
|
ConvertYV12ToBitmap(lpImage, bmp);
|
|
break;
|
|
|
|
case FourCC_YVU9:
|
|
ConvertYVU9ToBitmap(lpImage, bmp);
|
|
break;
|
|
|
|
default:
|
|
fSupported = false;
|
|
break;
|
|
}
|
|
|
|
if (!fSupported)
|
|
{
|
|
hr = E_FORMAT_NOT_SUPPORTED;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
static HRESULT ClipBitmap(CaptureBitmapData *bmpdata, RECT *rect)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if (NULL == rect)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
bmpdata->Width = rect->right - rect->left;
|
|
bmpdata->Height = rect->bottom - rect->top;
|
|
// bmpdata->Stride = bmpdata->Stride;
|
|
bmpdata->Scan0 = bmpdata->Scan0 +
|
|
rect->top * bmpdata->Stride + (rect->left * BYTES_PER_PIXEL);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
static HRESULT StretchBitmap(CaptureBitmapData *bmpdata, int newWidth, int newHeight)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
int nX, nY, nX0, nY0, nX1, nY1;
|
|
double dXRatio, dYRatio, dXCoor, dYCoor, dXR, dYR;
|
|
double pdRGB0[3];
|
|
double pdRGB1[3];
|
|
BYTE *pRow0;
|
|
BYTE *pRow1;
|
|
BYTE *pPix0;
|
|
BYTE *pPix1;
|
|
BYTE *pDest;
|
|
|
|
if (bmpdata->Width == newWidth && bmpdata->Height == newHeight)
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
int newStride = (newWidth*BYTES_PER_PIXEL + 3) & (~3); // align with word boundary
|
|
BYTE *pBuffer = new BYTE[newHeight * newStride];
|
|
|
|
if (NULL == pBuffer)
|
|
{
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
dXRatio = (double)(bmpdata->Width)/(double)(newWidth);
|
|
dYRatio = (double)(bmpdata->Height)/(double)(newHeight);
|
|
|
|
// bilinear stretching
|
|
// Note this is not the most efficient algorithm as it uses a lot of floating calc
|
|
// Nevertheless it is simple
|
|
|
|
for (nY = 0; nY < newHeight; nY++)
|
|
{
|
|
// determine two coordinates along Y direction for interpolation
|
|
|
|
dYCoor = (nY + 0.5)*dYRatio - 0.5;
|
|
|
|
if (dYCoor < 0)
|
|
{
|
|
nY0 = nY1 = 0;
|
|
dYR = 0.0;
|
|
}
|
|
else if (dYCoor >= bmpdata->Height - 1)
|
|
{
|
|
nY0 = nY1 = bmpdata->Height - 1;
|
|
dYR = 0.0;
|
|
}
|
|
else
|
|
{
|
|
nY0 = (int)dYCoor;
|
|
nY1 = nY0 + 1;
|
|
dYR = dYCoor - nY0;
|
|
}
|
|
|
|
pRow0 = bmpdata->Scan0 + nY0 * bmpdata->Stride;
|
|
pRow1 = bmpdata->Scan0 + nY1 * bmpdata->Stride;
|
|
pDest = pBuffer + nY * newStride;
|
|
|
|
for (nX = 0; nX < newWidth; nX++, pDest+=3)
|
|
{
|
|
// determine two coordinates along X direction for interpolation
|
|
|
|
dXCoor = (nX + 0.5)*dXRatio - 0.5;
|
|
|
|
if (dXCoor < 0)
|
|
{
|
|
nX0 = nX1 = 0;
|
|
dXR = 0.0;
|
|
}
|
|
else if (dXCoor >= bmpdata->Width - 1)
|
|
{
|
|
nX0 = nX1 = bmpdata->Width - 1;
|
|
dXR = 0.0;
|
|
}
|
|
else
|
|
{
|
|
nX0 = (int)dXCoor;
|
|
nX1 = nX0 + 1;
|
|
dXR = dXCoor - nX0;
|
|
}
|
|
|
|
// interpolate along X, in the upper row
|
|
pPix0 = pRow0 + nX0 * BYTES_PER_PIXEL;
|
|
pPix1 = pRow0 + nX1 * BYTES_PER_PIXEL;
|
|
pdRGB0[0] = pPix0[0] + (pPix1[0] - pPix0[0])*dXR;
|
|
pdRGB0[1] = pPix0[1] + (pPix1[1] - pPix0[1])*dXR;
|
|
pdRGB0[2] = pPix0[2] + (pPix1[2] - pPix0[2])*dXR;
|
|
|
|
// interpolate along X, in the lower row
|
|
pPix0 = pRow1 + nX0 * BYTES_PER_PIXEL;
|
|
pPix1 = pRow1 + nX1 * BYTES_PER_PIXEL;
|
|
pdRGB1[0] = pPix0[0] + (pPix1[0] - pPix0[0])*dXR;
|
|
pdRGB1[1] = pPix0[1] + (pPix1[1] - pPix0[1])*dXR;
|
|
pdRGB1[2] = pPix0[2] + (pPix1[2] - pPix0[2])*dXR;
|
|
|
|
// interpolate along Y
|
|
pDest[0] = (BYTE)(pdRGB0[0] + (pdRGB1[0] - pdRGB0[0])*dYR + 0.5);
|
|
pDest[1] = (BYTE)(pdRGB0[1] + (pdRGB1[1] - pdRGB0[1])*dYR + 0.5);
|
|
pDest[2] = (BYTE)(pdRGB0[2] + (pdRGB1[2] - pdRGB0[2])*dYR + 0.5);
|
|
}
|
|
}
|
|
|
|
// replace the bitmap buffer
|
|
|
|
delete[] bmpdata->pBuffer;
|
|
bmpdata->pBuffer = bmpdata->Scan0 = pBuffer;
|
|
bmpdata->Stride = newStride;
|
|
bmpdata->Width = newWidth;
|
|
bmpdata->Height = newHeight;
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 ConvertImageAndSave(YUV_IMAGE *lpImage, RECT *pViewClipRect, HWND hwnd)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
CaptureBitmapData bmpdata;
|
|
|
|
hr = ConvertToBitmapImage(lpImage, &bmpdata);
|
|
|
|
|
|
#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)
|
|
{
|
|
hr = ClipBitmap(&bmpdata, &rcClipImage);
|
|
}
|
|
|
|
// stretch the image to the right aspect ratio
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = StretchBitmap(&bmpdata, viewWidth, viewHeight);
|
|
}
|
|
|
|
// save final bitmap to a file
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = SaveFileDialog(hwnd, &bmpdata);
|
|
}
|
|
|
|
// clean up, release the image buffer
|
|
|
|
FreeBitmapData(&bmpdata);
|
|
|
|
return hr;
|
|
}
|