/**************************************************************************
 *
 *  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
 *  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
 *  PURPOSE.
 *
 *  Copyright (C) 1992 - 1996 Microsoft Corporation.  All Rights Reserved.
 *
 **************************************************************************/
/****************************************************************************
 *
 *  WRITEAVI.C
 *
 *  Creates the file OUTPUT.AVI, an AVI file consisting of a rotating clock
 *  face.  This program demonstrates using the functions in AVIFILE.DLL
 *  to make writing AVI files simple.
 *
 *  This is a stripped-down example; a real application would have a user
 *  interface and check for errors.
 *
 ***************************************************************************/


#define  STRICT
#define  INC_OLE2
#include <windows.h>
#include <windowsx.h>
#include <memory.h>
#include <mmsystem.h>
#include <vfw.h>

#include "writeavi.h"
//---------------------------------------------------------------------------
// Defines
//---------------------------------------------------------------------------
// Our movie is 160x120 and 15 frames long
//
#define BITMAP_X	160
#define BITMAP_Y	120
#define N_FRAMES	15

#define TEXT_HEIGHT	20

#define AVIIF_KEYFRAME	0x00000010L // this frame is a key frame.

#define BUFSIZE 260

#define LPLPBI	LPBITMAPINFOHEADER *

//---------------------------------------------------------------------------
// Function declarations
//---------------------------------------------------------------------------
static void FreeFrames(LPLPBI) ;
static void MakeFrames(LPLPBI, UINT, UINT, UINT) ;
static HANDLE MakeDib(HBITMAP, UINT);

//---------------------------------------------------------------------------
// A quick lookup table for Sin and Cos values
//---------------------------------------------------------------------------
//
static int aSin[N_FRAMES] = {
	0,    40,    74,    95,    99,
	86,    58,    20,    -20,    -58,
	-86,    -99,    -95,    -74,    -40,
	} ;

static int aCos[N_FRAMES] = {
	100,    91,    66,    30,    -10,
	-49,    -80,    -97,    -97,    -80,
	-50,    -10,    30,    66,    91,
	} ;

//----------------------------------------------------------------------------
//
// We don't have a window, we just pop up a dialog
// box, write the file, and quit
//
int PASCAL WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR szCmdLine,
int sw)
{
	LPBITMAPINFOHEADER alpbi[N_FRAMES];
	int i;
	AVISTREAMINFO strhdr;
	PAVIFILE pfile = NULL;
	PAVISTREAM ps = NULL, psCompressed = NULL, psText = NULL;
	char szText[BUFSIZE];
	int iLen;
	AVICOMPRESSOPTIONS opts;
	AVICOMPRESSOPTIONS FAR * aopts[1] = {&opts};
	HRESULT hr;
	DWORD dwTextFormat;
	WORD wVer;
	char szTitle[BUFSIZE];
	char szMessage[BUFSIZE];

	/* first let's make sure we are running on 1.1 */
	wVer = HIWORD(VideoForWindowsVersion());
	if (wVer < 0x010a){
		 /* oops, we are too old, blow out of here */
		LoadString(hInstance, IDS_APPERR, szTitle, BUFSIZE );
		LoadString(hInstance, IDS_VFWTOOOLD, szMessage, BUFSIZE );
		MessageBeep(MB_ICONHAND);
		MessageBox(NULL, szMessage, szTitle, MB_OK|MB_ICONSTOP);
		return FALSE;
	}

	alpbi[0] = NULL;

	LoadString(hInstance, IDS_APPNAME, szTitle, BUFSIZE );
	LoadString(hInstance, IDS_INTRO, szMessage, BUFSIZE );

	if (MessageBox(NULL, szMessage, szTitle, MB_OKCANCEL) == IDCANCEL)
		return 0;

	//
	// Set up the bitmaps for the file in an array
	//
	MakeFrames(alpbi, 8, BITMAP_X, BITMAP_Y);

	AVIFileInit();

	//
	// Open the movie file for writing....
	//
	LoadString(hInstance, IDS_FILENAME, szTitle, BUFSIZE );
	
	hr = AVIFileOpen(&pfile,		    // returned file pointer
		       szTitle,		            // file name
		       OF_WRITE | OF_CREATE,	    // mode to open file with
		       NULL);			    // use handler determined
						    // from file extension....
	if (hr != AVIERR_OK)
		goto error;

	// Fill in the header for the video stream....

	// The video stream will run in 15ths of a second....

	_fmemset(&strhdr, 0, sizeof(strhdr));
	strhdr.fccType                = streamtypeVIDEO;// stream type
	strhdr.fccHandler             = 0;
	strhdr.dwScale                = 1;
	strhdr.dwRate                 = 15;		    // 15 fps
	strhdr.dwSuggestedBufferSize  = alpbi[0]->biSizeImage;
	SetRect(&strhdr.rcFrame, 0, 0,		    // rectangle for stream
	    (int) alpbi[0]->biWidth,
	    (int) alpbi[0]->biHeight);

	// And create the stream;
	hr = AVIFileCreateStream(pfile,		    // file pointer
			         &ps,		    // returned stream pointer
			         &strhdr);	    // stream header
	if (hr != AVIERR_OK) {
		goto error;
	}

	_fmemset(&opts, 0, sizeof(opts));

	if (!AVISaveOptions(NULL, 0, 1, &ps, (LPAVICOMPRESSOPTIONS FAR *) &aopts))
	    goto error;

	hr = AVIMakeCompressedStream(&psCompressed, ps, &opts, NULL);
	if (hr != AVIERR_OK) {
		goto error;
	}

	hr = AVIStreamSetFormat(psCompressed, 0,
			       alpbi[0],	    // stream format
			       alpbi[0]->biSize +   // format size
			       alpbi[0]->biClrUsed * sizeof(RGBQUAD));
	if (hr != AVIERR_OK) {
	goto error;
	}

	// Fill in the stream header for the text stream....

	// The text stream is in 60ths of a second....

	_fmemset(&strhdr, 0, sizeof(strhdr));
	strhdr.fccType                = streamtypeTEXT;
	strhdr.fccHandler             = mmioFOURCC('D', 'R', 'A', 'W');
	strhdr.dwScale                = 1;
	strhdr.dwRate                 = 60;
	strhdr.dwSuggestedBufferSize  = sizeof(szText);
	SetRect(&strhdr.rcFrame, 0, (int) alpbi[0]->biHeight,
	    (int) alpbi[0]->biWidth, (int) alpbi[0]->biHeight + TEXT_HEIGHT);

	// ....and create the stream.
	hr = AVIFileCreateStream(pfile, &psText, &strhdr);
	if (hr != AVIERR_OK) {
		goto error;
	}

	dwTextFormat = sizeof(dwTextFormat);
	hr = AVIStreamSetFormat(psText, 0, &dwTextFormat, sizeof(dwTextFormat));
	if (hr != AVIERR_OK) {
		goto error;
	}

	//
	// Now write out each video frame, along with a text label.
	// The video frames are 2/3 of a second apart, which is 10
	// in the video time scale and 40 in the text stream's time scale.
	//
	for (i = 0; i < N_FRAMES; i++) {
		hr = AVIStreamWrite(psCompressed,	// stream pointer
			i * 10,				// time of this frame
			1,				// number to write
			(LPBYTE) alpbi[i] +		// pointer to data
				alpbi[i]->biSize +
				alpbi[i]->biClrUsed * sizeof(RGBQUAD),
				alpbi[i]->biSizeImage,	// size of this frame
			AVIIF_KEYFRAME,			 // flags....
			NULL,
			NULL);
		if (hr != AVIERR_OK)
			break;
	
		// Make some text to put in the file ...
		LoadString(hInstance, IDS_TEXTFORMAT, szMessage, BUFSIZE );
		
		iLen = wsprintf(szText, szMessage, (int)(i + 1));

		// ... and write it as well.
		hr = AVIStreamWrite(psText,
				i * 40,
				1,
				szText,
				iLen + 1,
				AVIIF_KEYFRAME,
				NULL,
				NULL);
		if (hr != AVIERR_OK)
			break;
	}
	
error:
	//
	// Now close the file
	//
	if (ps)
		AVIStreamClose(ps);

	if (psCompressed)
		AVIStreamClose(psCompressed);

	if (psText)
		AVIStreamClose(psText);

	if (pfile)
		AVIFileClose(pfile);

	AVIFileExit();
	FreeFrames(alpbi);

	if (hr != NOERROR) {
		LoadString(hInstance, IDS_APPERR, szTitle, BUFSIZE );
		LoadString(hInstance, IDS_WRITEERR, szMessage, BUFSIZE );
		
		MessageBox(NULL, szMessage, szTitle, MB_OK);
	}
	return 0;
}




//
// Fill an array of LPBI's with the frames for this movie
//
static void MakeFrames(LPLPBI alpbi, UINT bits, UINT wXSize,UINT wYSize )
{
	HDC         hdc ;
	HDC         hdcMem ;
	HBITMAP     hbitmap,hbitmapOld ;
	HPEN        hpen3,hpen1,hpenwhite,hpenOld ;
	HFONT       hfont,hfontOld ;
	HBRUSH      hbrush,hbrushOld ;
	RECT        rc ;
	RECT        rcFrameNo ;
	int         wXCent,wYCent ;
	int         cxPixInch ;
	int         cyPixInch ;
	int         cxPixels ;
	int         cyPixels ;
	int         radius ;
	int         x0,y0,x1,y1 ;
	int         i,j ;
	char        szNumber[3] ;

	//
	// Make sure our resources are freed
	//
	FreeFrames(alpbi);

	//
	// Find the center of the movie
	//
	wXCent = wXSize/2 ;
	wYCent = wYSize/2 ;

	hdc = GetDC(NULL) ;
	hdcMem = CreateCompatibleDC(NULL) ;

	//
	// We need some gray and white brushes and pens, and a bitmap
	//
	hpen3 = CreatePen(PS_SOLID,3,RGB(128,128,128)) ;
	hpen1 = CreatePen(PS_SOLID,1,RGB(64,64,64));
	hpenwhite = CreatePen(PS_SOLID,1,RGB(255,255,255));
	hpenOld = SelectPen(hdcMem, hpen3);
	hbrush = CreateSolidBrush(RGB(192,192,192)) ;
	hbrushOld = SelectBrush(hdcMem,hbrush) ;
	hbitmap = CreateCompatibleBitmap(hdc,wXSize,wYSize) ;

	cxPixInch = GetDeviceCaps(hdc,LOGPIXELSX) ;
	cyPixInch = GetDeviceCaps(hdc,LOGPIXELSY) ;

	//
	// What radius of circle can we fit in this frame?  Make sure it's round
	// regardless of the aspect ratio
	//
	radius = ( wXSize < wYSize ) ? wXSize : (wYSize*cxPixInch)/cyPixInch ;
	radius = ( radius * 95 ) / 200 ;

	//
	// Make a Rectangle in the center where the number will go
	//
	/* x0 = radius / sqrt(2) */
	x0 = (radius*100)/141 ;
	y0 = (x0*cyPixInch)/cxPixInch ;
	x0 = (x0*9)/10 ;
	y0 = (y0*9)/10 ;
	SetRect( &rcFrameNo,wXCent-x0,wYCent-y0,wXCent+x0,wYCent+y0 ) ;

	//
	// Move the rectangle in a little and make a font big enough for it
	//
	x0 = (x0*9)/10 ;
	y0 = (y0*9)/10 ;

	hfont = CreateFont(
		y0*2,
		x0,
		0,
		0,
		FW_BOLD,
		0,
		0,
		0,
		ANSI_CHARSET,
		OUT_DEVICE_PRECIS,
		CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY,
		DEFAULT_PITCH|FF_SWISS,
		NULL
	);

	hfontOld = SelectFont(hdcMem, hfont);

	//
	// Now walk through and make all the frames
	//
	for ( i=0; i<N_FRAMES; i++ ) {
		hbitmapOld = SelectBitmap(hdcMem, hbitmap);
	
		//
		// Fill the whole frame with white
		//
		SetRect(&rc,0,0,wXSize,wYSize) ;
		FillRect(hdcMem,&rc,GetStockBrush(WHITE_BRUSH)) ;

		//
		// Draw the circle inside the previously calculated radius
		//
		cxPixels = radius ;
		cyPixels = (cxPixels*cyPixInch)/cxPixInch ;

		SelectPen(hdcMem,hpen3) ;
		Ellipse(hdcMem,wXCent-cxPixels,wYCent-cyPixels,wXCent+cxPixels,
			wYCent+cyPixels) ;

		SelectPen(hdcMem,hpen1) ;

		//
		// Draw the number in the previously calculated area
		//
		wsprintf(szNumber,"%02u",i+1) ;

		SetBkColor(hdcMem,RGB(192,192,192)) ;
		SetTextColor(hdcMem,RGB(255,255,255)) ;
		ExtTextOut(
			hdcMem,
			rcFrameNo.left,
			rcFrameNo.top+(rcFrameNo.bottom-rcFrameNo.top)/20,
			ETO_CLIPPED,
			&rcFrameNo,
			szNumber,
			2,
			NULL);

		//
		// Draw tic marks around the inside of the circle in equal divisions
		//
		for ( j=0; j<N_FRAMES; j++ ) {
			x0 = (radius*aSin[j])/100 ;
			y0 = (radius*aCos[j])/100 ;
			x1 = (((radius*aSin[j])/100)*11)/12 ;
			y1 = (((radius*aCos[j])/100)*11)/12 ;

			y0 = -(y0*cyPixInch)/cxPixInch ;
			y1 = -(y1*cyPixInch)/cxPixInch ;

			MoveToEx(hdcMem,wXCent+x0,wYCent+y0,NULL) ;
			LineTo(hdcMem,wXCent+x1,wYCent+y1) ;
		}

		//
		// Now draw the hand of the clock in the appropriate position
		//
		x1 = (((radius*aSin[i])/100)*5)/8 ;
		y1 = (((radius*aCos[i])/100)*5)/8 ;
		y1 = -(y1*cyPixInch)/cxPixInch ;

		MoveToEx(hdcMem,wXCent,wYCent,NULL) ;
		LineTo(hdcMem,wXCent+x1,wYCent+y1) ;

		SelectBitmap(hdcMem, hbitmapOld);
		//
		// Make this into a DIB and stuff it into the array
		//
		alpbi[i] = (LPBITMAPINFOHEADER)GlobalLock(MakeDib(hbitmap, bits));

		//
		// For an error, just duplicate the last frame if we can
		//
		if (alpbi[i] == NULL && i )
			alpbi[i] = alpbi[i-1] ;
	}

	//
	// Select all the old objects back and delete resources
	//
	SelectPen(hdcMem, hpenOld);
	SelectBrush(hdcMem,hbrushOld) ;
	SelectFont(hdcMem,hfontOld) ;
	DeletePen(hpen1) ;
	DeletePen(hpen3) ;
	DeletePen(hpenwhite) ;
	DeleteBrush(hbrush) ;
	DeleteBitmap(hbitmap) ;
	DeleteFont(hfont) ;
	DeleteObject(hdcMem) ;
	ReleaseDC(NULL,hdc) ;
}

//
// Walk through our array of LPBI's and free them
//
static void FreeFrames(LPLPBI alpbi)
{
	UINT        w ;

	if (!alpbi[0])
		return ;
	//
	// Don't free a frame if it's a duplicate of the previous one
	//
	for (w=0; w<N_FRAMES; w++)
		if (alpbi[w] && alpbi[w] != alpbi[w-1])
			GlobalFreePtr(alpbi[w]);
	for (w=0; w<N_FRAMES; w++)
		alpbi[w] = NULL;
}

/*
** MakeDib(hbitmap)
**
** Take the given bitmap and transform it into a DIB with parameters:
**
** BitsPerPixel:    8
** Colors:          palette
**
*/
static HANDLE  MakeDib( HBITMAP hbitmap, UINT bits )
{
	HANDLE              hdib ;
	HDC                 hdc ;
	BITMAP              bitmap ;
	UINT                wLineLen ;
	DWORD               dwSize ;
	DWORD               wColSize ;
	LPBITMAPINFOHEADER  lpbi ;
	LPBYTE              lpBits ;
	
	GetObject(hbitmap,sizeof(BITMAP),&bitmap) ;

	//
	// DWORD align the width of the DIB
	// Figure out the size of the colour table
	// Calculate the size of the DIB
	//
	wLineLen = (bitmap.bmWidth*bits+31)/32 * 4;
	wColSize = sizeof(RGBQUAD)*((bits <= 8) ? 1<<bits : 0);
	dwSize = sizeof(BITMAPINFOHEADER) + wColSize +
		(DWORD)(UINT)wLineLen*(DWORD)(UINT)bitmap.bmHeight;

	//
	// Allocate room for a DIB and set the LPBI fields
	//
	hdib = GlobalAlloc(GHND,dwSize);
	if (!hdib)
		return hdib ;

	lpbi = (LPBITMAPINFOHEADER)GlobalLock(hdib) ;

	lpbi->biSize = sizeof(BITMAPINFOHEADER) ;
	lpbi->biWidth = bitmap.bmWidth ;
	lpbi->biHeight = bitmap.bmHeight ;
	lpbi->biPlanes = 1 ;
	lpbi->biBitCount = (WORD) bits ;
	lpbi->biCompression = BI_RGB ;
	lpbi->biSizeImage = dwSize - sizeof(BITMAPINFOHEADER) - wColSize ;
	lpbi->biXPelsPerMeter = 0 ;
	lpbi->biYPelsPerMeter = 0 ;
	lpbi->biClrUsed = (bits <= 8) ? 1<<bits : 0;
	lpbi->biClrImportant = 0 ;

	//
	// Get the bits from the bitmap and stuff them after the LPBI
	//
	lpBits = (LPBYTE)(lpbi+1)+wColSize ;

	hdc = CreateCompatibleDC(NULL) ;

	GetDIBits(hdc,hbitmap,0,bitmap.bmHeight,lpBits,(LPBITMAPINFO)lpbi, DIB_RGB_COLORS);

	// Fix this if GetDIBits messed it up....
	lpbi->biClrUsed = (bits <= 8) ? 1<<bits : 0;

	DeleteDC(hdc) ;
	GlobalUnlock(hdib);

	return hdib ;
}
