/****************************************************************************
 *
 *  AVIBALL.C
 *
 *  Sample AVIStream handler for a bouncing ball.  This code demonstrates
 *  how to write a custom stream handler so an application can deal with
 *  your custom file/data/whatever by using the standard AVIStream functions.
 *
 *  Copyright (c) 1992 Microsoft Corporation.  All Rights Reserved.
 *
 *  You have a royalty-free right to use, modify, reproduce and
 *  distribute the Sample Files (and/or any modified version) in
 *  any way you find useful, provided that you agree that
 *  Microsoft has no warranty obligations or liability for any
 *  Sample Application Files which are modified.
 *
 ***************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include <vfw.h>
#include <coguid.h>

///////////////////////////////////////////////////////////////////////////
//
// default parameters
//
///////////////////////////////////////////////////////////////////////////

#define DEFAULT_WIDTH   240
#define DEFAULT_HEIGHT  120
#define DEFAULT_LENGTH  100
#define DEFAULT_SIZE    6
#define DEFAULT_COLOR   RGB(255,0,0)
#define XSPEED		7
#define YSPEED		5

///////////////////////////////////////////////////////////////////////////
//
// useful macros
//
///////////////////////////////////////////////////////////////////////////

#define ALIGNULONG(i)     ((i+3)&(~3))                  /* ULONG aligned ! */
#define WIDTHBYTES(i)     ((unsigned)((i+31)&(~31))/8)  /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define DIBPTR(lpbi) ((LPBYTE)(lpbi) + \
	    (int)(lpbi)->biSize + \
	    (int)(lpbi)->biClrUsed * sizeof(RGBQUAD) )

///////////////////////////////////////////////////////////////////////////
//
// custom video stream instance structure
//
///////////////////////////////////////////////////////////////////////////

typedef struct {

    //
    // The Vtbl must come first
    //
    IAVIStreamVtbl FAR * lpvtbl;

    //
    //  private ball instance data
    //
    ULONG	ulRefCount;

    DWORD       fccType;        // is this audio/video

    int         width;          // size in pixels of each frame
    int         height;
    int         length;         // length in frames of the pretend AVI movie
    int         size;
    COLORREF    color;          // ball color

} AVIBALL, FAR * PAVIBALL;

///////////////////////////////////////////////////////////////////////////
//
// custom stream methods
//
///////////////////////////////////////////////////////////////////////////

HRESULT STDMETHODCALLTYPE AVIBallQueryInterface(PAVISTREAM ps, REFIID riid, LPVOID FAR* ppvObj);
HRESULT STDMETHODCALLTYPE AVIBallCreate       (PAVISTREAM ps, LONG lParam1, LONG lParam2);
ULONG   STDMETHODCALLTYPE AVIBallAddRef       (PAVISTREAM ps);
ULONG   STDMETHODCALLTYPE AVIBallRelease      (PAVISTREAM ps);
HRESULT STDMETHODCALLTYPE AVIBallInfo         (PAVISTREAM ps, AVISTREAMINFO FAR * psi, LONG lSize);
LONG    STDMETHODCALLTYPE AVIBallFindSample (PAVISTREAM ps, LONG lPos, LONG lFlags);
HRESULT STDMETHODCALLTYPE AVIBallReadFormat   (PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG FAR *lpcbFormat);
HRESULT STDMETHODCALLTYPE AVIBallSetFormat    (PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG cbFormat);
HRESULT STDMETHODCALLTYPE AVIBallRead         (PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG FAR * plBytes,LONG FAR * plSamples);
HRESULT STDMETHODCALLTYPE AVIBallWrite        (PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten, LONG FAR *plBytesWritten);
HRESULT STDMETHODCALLTYPE AVIBallDelete       (PAVISTREAM ps, LONG lStart, LONG lSamples);
HRESULT STDMETHODCALLTYPE AVIBallReadData     (PAVISTREAM ps, DWORD fcc, LPVOID lp,LONG FAR *lpcb);
HRESULT STDMETHODCALLTYPE AVIBallWriteData    (PAVISTREAM ps, DWORD fcc, LPVOID lp,LONG cb);

IAVIStreamVtbl AVIBallHandler = {
    AVIBallQueryInterface,
    AVIBallAddRef,
    AVIBallRelease,
    AVIBallCreate,
    AVIBallInfo,
    AVIBallFindSample,
    AVIBallReadFormat,
    AVIBallSetFormat,
    AVIBallRead,
    AVIBallWrite,
    AVIBallDelete,
    AVIBallReadData,
    AVIBallWriteData
};


//
// This is the function an application would call to create a PAVISTREAM to
// reference the ball.  Then the standard AVIStream function calls can be
// used to work with this stream.
//
PAVISTREAM FAR PASCAL NewBall(void)
{
    PAVIBALL pball;

    //
    // Create a pointer to our private structure which will act as our
    // PAVISTREAM
    //
    pball = (PAVIBALL) GlobalAllocPtr(GHND, sizeof(AVIBALL));

    if (!pball)
	return 0;

    //
    // Fill the function table
    //
    pball->lpvtbl = &AVIBallHandler;

    //
    // Call our own create code to create a new instance (calls AVIBallCreate)
    // For now, don't use any lParams.
    //
    pball->lpvtbl->Create((PAVISTREAM) pball, 0, 0);

    return (PAVISTREAM) pball;
}

///////////////////////////////////////////////////////////////////////////
//
// This function is called to initialize an instance of the bouncing ball.
//
// When called, we look at the information possibly passed in <lParam1>,
// if any, and use it to determine the length of movie they want. (Not
// supported by NewBall right now, but it could be).
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE AVIBallCreate(PAVISTREAM ps, LONG lParam1, LONG lParam2)
{
    PAVIBALL pball = (PAVIBALL) ps;

    //
    // what type of data are we? (audio/video/other stream)
    //
    pball->fccType = streamtypeVIDEO;

    //
    // We define lParam1 as being the length of movie they want us to pretend
    // to be.
    //
    if (lParam1)
	pball->length = (int) lParam1;
    else
	pball->length = DEFAULT_LENGTH;

    switch (pball->fccType) {

	case streamtypeVIDEO:
	    pball->color  = DEFAULT_COLOR;
	    pball->width  = DEFAULT_WIDTH;
	    pball->height = DEFAULT_HEIGHT;
	    pball->size   = DEFAULT_SIZE;
	    pball->ulRefCount = 1;	// note that we are opened once
	    return AVIERR_OK;           // success

	case streamtypeAUDIO:
	    return ResultFromScode(AVIERR_UNSUPPORTED); // we don't do audio

	default:
	    return ResultFromScode(AVIERR_UNSUPPORTED); // or anything else
    }
}


//
// Increment our reference count
//
ULONG STDMETHODCALLTYPE AVIBallAddRef(PAVISTREAM ps)
{
    PAVIBALL pball = (PAVIBALL) ps;
    return (++pball->ulRefCount);
}


//
// Decrement our reference count
//
ULONG STDMETHODCALLTYPE AVIBallRelease(PAVISTREAM ps)
{
    PAVIBALL pball = (PAVIBALL) ps;
    if (--pball->ulRefCount)
	return pball->ulRefCount;

    // Free any data we're keeping around - like our private structure
    GlobalFreePtr(pball);

    return 0;
}


//
// Fills an AVISTREAMINFO structure
//
HRESULT STDMETHODCALLTYPE AVIBallInfo(PAVISTREAM ps, AVISTREAMINFO FAR * psi, LONG lSize)
{
    PAVIBALL pball = (PAVIBALL) ps;

    if (lSize < sizeof(AVISTREAMINFO))
	return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    _fmemset(psi, 0, lSize);

    // Fill out a stream header with information about us.
    psi->fccType                = pball->fccType;
    psi->fccHandler             = mmioFOURCC('B','a','l','l');
    psi->dwScale                = 1;
    psi->dwRate                 = 15;
    psi->dwLength               = pball->length;
    psi->dwSuggestedBufferSize  = pball->height * ALIGNULONG(pball->width);
    psi->rcFrame.right          = pball->width;
    psi->rcFrame.bottom         = pball->height;
    lstrcpy(psi->szName, TEXT("Bouncing ball video"));

    return AVIERR_OK;
}

///////////////////////////////////////////////////////////////////////////
//
// AVIBallReadFormat: needs to return the format of our data.
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE AVIBallReadFormat   (PAVISTREAM ps, LONG lPos,LPVOID lpFormat,LONG FAR *lpcbFormat)
{
    PAVIBALL pball = (PAVIBALL) ps;
    LPBITMAPINFO    lpbi = (LPBITMAPINFO) lpFormat;

    if (lpFormat == NULL || *lpcbFormat == 0) {
	*lpcbFormat = sizeof(BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD);
	return AVIERR_OK;
    }

    if (*lpcbFormat < sizeof(BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD))
	return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    // This is a relatively odd example: we build up our
    // format from scratch every time.

    lpbi->bmiHeader.biSize              = sizeof(BITMAPINFOHEADER);
    lpbi->bmiHeader.biCompression       = BI_RGB;
    lpbi->bmiHeader.biWidth             = pball->width;
    lpbi->bmiHeader.biHeight            = pball->height;
    lpbi->bmiHeader.biBitCount          = 8;
    lpbi->bmiHeader.biPlanes            = 1;
    lpbi->bmiHeader.biClrUsed           = 2;
    lpbi->bmiHeader.biSizeImage         = pball->height * DIBWIDTHBYTES(lpbi->bmiHeader);

    lpbi->bmiColors[0].rgbRed           = 0;
    lpbi->bmiColors[0].rgbGreen         = 0;
    lpbi->bmiColors[0].rgbBlue          = 0;
    lpbi->bmiColors[1].rgbRed           = GetRValue(pball->color);
    lpbi->bmiColors[1].rgbGreen         = GetGValue(pball->color);
    lpbi->bmiColors[1].rgbBlue          = GetBValue(pball->color);

    *lpcbFormat = sizeof(BITMAPINFOHEADER) + 2 * sizeof(RGBQUAD);

    return AVIERR_OK;
}

///////////////////////////////////////////////////////////////////////////
//
// AVIBallRead: needs to return the data for a particular frame.
//
///////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE AVIBallRead (PAVISTREAM ps, LONG lStart,LONG lSamples,LPVOID lpBuffer,LONG cbBuffer,LONG FAR * plBytes,LONG FAR * plSamples)
{
    PAVIBALL pball = (PAVIBALL) ps;
    LONG   lSize = pball->height * ALIGNULONG(pball->width); // size of frame
							     // in bytes
    int x, y;
    BYTE _huge *hp = lpBuffer;
    int xPos, yPos;

    // Reject out of range values
    if (lStart < 0 || lStart >= pball->length)
	return ResultFromScode(AVIERR_BADPARAM);

    // Did they just want to know the size of our data?
    if (lpBuffer == NULL || cbBuffer == 0)
	goto exit;

    // Will our frame fit in the buffer passed?
    if (lSize > cbBuffer)
	return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    // Figure out the position of the ball.
    // It just bounces back and forth.

    xPos = 5 + XSPEED * (int) lStart;			    // x = x0 + vt
    xPos = xPos % ((pball->width - pball->size) * 2);	    // limit to 2xwidth
    if (xPos > (pball->width - pball->size))		    // reflect if
	xPos = 2 * (pball->width - pball->size) - xPos;	    //   needed

    yPos = 5 + YSPEED * (int) lStart;
    yPos = yPos % ((pball->height - pball->size) * 2);
    if (yPos > (pball->height - pball->size))
	yPos = 2 * (pball->height - pball->size) - yPos;

    //
    // Build a DIB from scratch by writing in 1's where the ball is, 0's
    // where it isn't.
    //
    // Notice that we just build it in the buffer we've been passed.
    //
    // This is pretty ugly, I have to admit.
    //
    for (y = 0; y < pball->height; y++)
	{
	if (y >= yPos && y < yPos + pball->size)
	{
	    for (x = 0; x < pball->width; x++)
	    {
		*hp++ = (BYTE) ((x >= xPos && x < xPos + pball->size) ? 1 : 0);
	    }
	}
	else
	{
	    for (x = 0; x < pball->width; x++)
	    {
		*hp++ = 0;
	    }
	}
	
	hp += pball->width - ALIGNULONG(pball->width);
    }

exit:
    // We always return exactly one frame
    if (plSamples)
	*plSamples = 1;

    // Return the size of our frame
    if (plBytes)
	*plBytes = lSize;

    return AVIERR_OK;
}


HRESULT STDMETHODCALLTYPE AVIBallQueryInterface(PAVISTREAM ps, REFIID riid, LPVOID FAR* ppvObj)
{
    PAVIBALL pball = (PAVIBALL) ps;

    // We support the Unknown interface (everybody does) and our Stream
    // interface.

    if (_fmemcmp(riid, &IID_IUnknown, sizeof(GUID)) == 0)
        *ppvObj = (LPVOID)pball;

    else if (_fmemcmp(riid, &IID_IAVIStream, sizeof(GUID)) == 0)
        *ppvObj = (LPVOID)pball;

    else {
        *ppvObj = NULL;
        return ResultFromScode(E_NOINTERFACE);
    }

    AVIBallAddRef(ps);

    return AVIERR_OK;
}

LONG    STDMETHODCALLTYPE AVIBallFindSample (PAVISTREAM ps, LONG lPos, LONG lFlags)
{
    // The only format change is frame 0
    if ((lFlags & FIND_TYPE) == FIND_FORMAT) {
	if ((lFlags & FIND_DIR) == FIND_NEXT && lPos > 0)
	    return -1;	// no more format changes
	else
	    return 0;

    // FIND_KEY and FIND_ANY always return the same position because
    // every frame is non-empty and a key frame
    } else
        return lPos;
}

HRESULT STDMETHODCALLTYPE AVIBallReadData     (PAVISTREAM ps, DWORD fcc, LPVOID lp, LONG FAR *lpcb)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallSetFormat    (PAVISTREAM ps, LONG lPos, LPVOID lpFormat, LONG cbFormat)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallWriteData    (PAVISTREAM ps, DWORD fcc, LPVOID lp, LONG cb)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallWrite        (PAVISTREAM ps, LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten, LONG FAR *plBytesWritten)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

HRESULT STDMETHODCALLTYPE AVIBallDelete       (PAVISTREAM ps, LONG lStart, LONG lSamples)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}
