FBALL.CPP

//==========================================================================; 
//
// 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 - 1997 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;

#include <streams.h>
#include <olectl.h>
#include <initguid.h>
#include "balluids.h"
#include "ball.h"
#include "fball.h"


// Setup data

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{
&MEDIATYPE_Video, // Major type
&MEDIASUBTYPE_NULL // Minor type
};

const AMOVIESETUP_PIN sudOpPin =
{
L"Output", // Pin string name
FALSE, // Is it rendered
TRUE, // Is it an output
FALSE, // Can we have none
FALSE, // Can we have many
&CLSID_NULL, // Connects to filter
NULL, // Connects to pin
1, // Number of types
&sudOpPinTypes }; // Pin details

const AMOVIESETUP_FILTER sudBallax =
{
&CLSID_BouncingBall, // Filter CLSID
L"Bouncing Ball", // String name
MERIT_DO_NOT_USE, // Filter merit
1, // Number pins
&sudOpPin // Pin details
};


// COM global table of objects in this dll

CFactoryTemplate g_Templates[] = {
{ L"Bouncing Ball"
, &CLSID_BouncingBall
, CBouncingBall::CreateInstance
, NULL
, &sudBallax }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);


//
// DllRegisterServer
//
// Exported entry points for registration and unregistration
//
STDAPI DllRegisterServer()
{
return AMovieDllRegisterServer2( TRUE );

} // DllRegisterServer


//
// DllUnregisterServer
//
STDAPI DllUnregisterServer()
{
return AMovieDllRegisterServer2( FALSE );

} // DllUnregisterServer


//
// CreateInstance
//
// The only allowed way to create Bouncing balls!
//
CUnknown * WINAPI CBouncingBall::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{
CUnknown *punk = new CBouncingBall(lpunk, phr);
if (punk == NULL) {
*phr = E_OUTOFMEMORY;
}
return punk;

} // CreateInstance


//
// Constructor
//
// Initialise a CBallStream object so that we have a pin.
//
CBouncingBall::CBouncingBall(LPUNKNOWN lpunk, HRESULT *phr) :
CSource(NAME("Bouncing ball"),
lpunk,
CLSID_BouncingBall)
{
CAutoLock cAutoLock(&m_cStateLock);

m_paStreams = (CSourceStream **) new CBallStream*[1];
if (m_paStreams == NULL) {
*phr = E_OUTOFMEMORY;
return;
}

m_paStreams[0] = new CBallStream(phr, this, L"A Bouncing Ball!");
if (m_paStreams[0] == NULL) {
*phr = E_OUTOFMEMORY;
return;
}

} // (Constructor)


//
// Constructor
//
CBallStream::CBallStream(HRESULT *phr,
CBouncingBall *pParent,
LPCWSTR pPinName) :
CSourceStream(NAME("Bouncing Ball"),phr, pParent, pPinName),
m_iImageWidth(320),
m_iImageHeight(240),
m_iDefaultRepeatTime(20)
{
CAutoLock cAutoLock(&m_cSharedState);

m_Ball = new CBall(m_iImageWidth, m_iImageHeight);
if (m_Ball == NULL) {
*phr = E_OUTOFMEMORY;
}

} // (Constructor)


//
// Destructor
//
CBallStream::~CBallStream()
{
CAutoLock cAutoLock(&m_cSharedState);
if( m_Ball )
delete m_Ball;

} // (Destructor)


//
// FillBuffer
//
// Plots a ball into the supplied video buffer
//
HRESULT CBallStream::FillBuffer(IMediaSample *pms)
{
BYTE *pData;
long lDataLen;

pms->GetPointer(&pData);
lDataLen = pms->GetSize();

// If true then we clear the output buffer and don't attempt to
// erase a previous drawing of the ball - this will be the case
// when we start running as the buffer will be full of rubbish
if( m_bZeroMemory ) {
ZeroMemory( pData, lDataLen );
}

{
CAutoLock cAutoLockShared(&m_cSharedState);

// If we haven't just cleared the buffer delete the old
// ball and move the ball on

if( !m_bZeroMemory ){
BYTE aZeroes[ 4 ] = { 0, 0, 0, 0 };
m_Ball->PlotBall(pData, aZeroes, m_iPixelSize);
m_Ball->MoveBall(m_rtSampleTime - (LONG) m_iRepeatTime);
}

m_Ball->PlotBall(pData, m_BallPixel, m_iPixelSize);

// The current time is the sample's start
CRefTime rtStart = m_rtSampleTime;

// Increment to find the finish time
m_rtSampleTime += (LONG)m_iRepeatTime;

pms->SetTime((REFERENCE_TIME *) &rtStart,(REFERENCE_TIME *) &m_rtSampleTime);
}

m_bZeroMemory = FALSE;
pms->SetSyncPoint(TRUE);
return NOERROR;

} // FillBuffer


//
// Notify
//
// Alter the repeat rate according to quality management messages sent from
// the downstream filter (often the renderer). Wind it up or down according
// to the flooding level - also skip forward if we are notified of Late-ness
//
STDMETHODIMP CBallStream::Notify(IBaseFilter * pSender, Quality q)
{
// Adjust the repeat rate.
if (q.Proportion<=0) {
m_iRepeatTime = 1000; // We don't go slower than 1 per second
} else {
m_iRepeatTime = m_iRepeatTime*1000/q.Proportion;
if (m_iRepeatTime>1000) {
m_iRepeatTime = 1000; // We don't go slower than 1 per second
} else if (m_iRepeatTime<10) {
m_iRepeatTime = 10; // We don't go faster than 100/sec
}
}

// skip forwards
if (q.Late > 0) {
m_rtSampleTime += q.Late;
}
return NOERROR;

} // Notify


//
// GetMediaType
//
// I _prefer_ 5 formats - 8, 16 (*2), 24 or 32 bits per pixel and
// I will suggest these with an image size of 320x240. However
// I can accept any image size which gives me some space to bounce.
//
// A bit of fun:
// 8 bit displays get red balls
// 16 bit displays get blue
// 24 bit see green
// And 32 bit see yellow
//
// Prefered types should be ordered by quality, zero as highest quality
// Therefore iPosition =
// 0return a 32bit mediatype
// 1return a 24bit mediatype
// 2return 16bit RGB565
// 3return a 16bit mediatype (rgb555)
// 4return 8 bit palettised format
// (iPosition > 4 is invalid)
//
HRESULT CBallStream::GetMediaType(int iPosition, CMediaType *pmt)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());
if (iPosition < 0) {
return E_INVALIDARG;
}

// Have we run off the end of types

if (iPosition > 4) {
return VFW_S_NO_MORE_ITEMS;
}

VIDEOINFO *pvi = (VIDEOINFO *) pmt->AllocFormatBuffer(sizeof(VIDEOINFO));
if (NULL == pvi) {
return(E_OUTOFMEMORY);
}
ZeroMemory(pvi, sizeof(VIDEOINFO));

switch (iPosition) {
case 0: {// Return our highest quality 32bit format

// Place the RGB masks as the first 3 doublewords in the palette area
for (int i = 0; i < 3; i++)
pvi->TrueColorInfo.dwBitMasks[i] = bits888[i];

SetPaletteEntries(Yellow);
pvi->bmiHeader.biCompression = BI_BITFIELDS;
pvi->bmiHeader.biBitCount = 32;
}
break;

case 1: {// Return our 24bit format

SetPaletteEntries(Green);
pvi->bmiHeader.biCompression = BI_RGB;
pvi->bmiHeader.biBitCount = 24;
}
break;

case 2: { // 16 bit per pixel RGB565

// Place the RGB masks as the first 3 doublewords in the palette area
for (int i = 0; i < 3; i++)
pvi->TrueColorInfo.dwBitMasks[i] = bits565[i];

SetPaletteEntries(Blue);
pvi->bmiHeader.biCompression = BI_BITFIELDS;
pvi->bmiHeader.biBitCount = 16;
}
break;

case 3: {// 16 bits per pixel RGB555

// Place the RGB masks as the first 3 doublewords in the palette area
for (int i = 0; i < 3; i++)
pvi->TrueColorInfo.dwBitMasks[i] = bits555[i];

SetPaletteEntries(Blue);
pvi->bmiHeader.biCompression = BI_BITFIELDS;
pvi->bmiHeader.biBitCount = 16;

}
break;

case 4: {// 8 bit palettised

SetPaletteEntries(Red);
pvi->bmiHeader.biCompression = BI_RGB;
pvi->bmiHeader.biBitCount = 8;
pvi->bmiHeader.biClrUsed= iPALETTE_COLORS;
}
break;
}

// (Adjust the parameters common to all formats...)

// put the optimal palette in place
for (int i = 0; i < iPALETTE_COLORS; i++) {
pvi->TrueColorInfo.bmiColors[i].rgbRed = m_Palette[i].peRed;
pvi->TrueColorInfo.bmiColors[i].rgbBlue = m_Palette[i].peBlue;
pvi->TrueColorInfo.bmiColors[i].rgbGreen = m_Palette[i].peGreen;
pvi->TrueColorInfo.bmiColors[i].rgbReserved = 0;
}

pvi->bmiHeader.biSize= sizeof(BITMAPINFOHEADER);
pvi->bmiHeader.biWidth= m_iImageWidth;
pvi->bmiHeader.biHeight= m_iImageHeight;
pvi->bmiHeader.biPlanes= 1;
pvi->bmiHeader.biSizeImage= GetBitmapSize(&pvi->bmiHeader);
pvi->bmiHeader.biClrImportant= 0;

SetRectEmpty(&(pvi->rcSource));// we want the whole image area rendered.
SetRectEmpty(&(pvi->rcTarget));// no particular destination rectangle

pmt->SetType(&MEDIATYPE_Video);
pmt->SetFormatType(&FORMAT_VideoInfo);
pmt->SetTemporalCompression(FALSE);

// Work out the GUID for the subtype from the header info.
const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
pmt->SetSubtype(&SubTypeGUID);
pmt->SetSampleSize(pvi->bmiHeader.biSizeImage);

return NOERROR;

} // GetMediaType


//
// CheckMediaType
//
// We will accept 8, 16, 24 or 32 bit video formats, in any
// image size that gives room to bounce.
// Returns E_INVALIDARG if the mediatype is not acceptable
//
HRESULT CBallStream::CheckMediaType(const CMediaType *pMediaType)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());

if ((*(pMediaType->Type()) != MEDIATYPE_Video)// we only output video!
|| !(pMediaType->IsFixedSize()) ) {// ...in fixed size samples
return E_INVALIDARG;
}

// Check for the subtypes we support
const GUID *SubType = pMediaType->Subtype();
if ((*SubType != MEDIASUBTYPE_RGB8)
&& (*SubType != MEDIASUBTYPE_RGB565)
&& (*SubType != MEDIASUBTYPE_RGB555)
&& (*SubType != MEDIASUBTYPE_RGB24)
&& (*SubType != MEDIASUBTYPE_RGB32)) {
return E_INVALIDARG;
}

// Get the format area of the media type
VIDEOINFO *pvi = (VIDEOINFO *) pMediaType->Format();

if (pvi == NULL)
return E_INVALIDARG;

// Check the image size. As my default ball is 10 pixels big
// look for at least a 20x20 image. This is an arbitary size constraint,
// but it avoids balls that are bigger than the picture...

if ((pvi->bmiHeader.biWidth < 20) || (pvi->bmiHeader.biHeight < 20) ) {
return E_INVALIDARG;
}

return S_OK; // This format is acceptable.

} // CheckMediaType


//
// DecideBufferSize
//
// This will always be called after the format has been sucessfully
// negotiated. So we have a look at m_mt to see what size image we agreed.
// Then we can ask for buffers of the correct size to contain them.
//
HRESULT CBallStream::DecideBufferSize(IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());
ASSERT(pAlloc);
ASSERT(pProperties);
HRESULT hr = NOERROR;

VIDEOINFO *pvi = (VIDEOINFO *) m_mt.Format();
pProperties->cBuffers = 1;
pProperties->cbBuffer = pvi->bmiHeader.biSizeImage;

ASSERT(pProperties->cbBuffer);

// Ask the allocator to reserve us some sample memory, NOTE the function
// can succeed (that is return NOERROR) but still not have allocated the
// memory that we requested, so we must check we got whatever we wanted

ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProperties,&Actual);
if (FAILED(hr)) {
return hr;
}

// Is this allocator unsuitable

if (Actual.cbBuffer < pProperties->cbBuffer) {
return E_FAIL;
}

// Make sure that we have only 1 buffer (we erase the ball in the
// old buffer to save having to zero a 200k+ buffer every time
// we draw a frame)

ASSERT( Actual.cBuffers == 1 );
return NOERROR;

} // DecideBufferSize


//
// SetMediaType
//
// Called when a media type is agreed between filters
//
HRESULT CBallStream::SetMediaType(const CMediaType *pMediaType)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());

// Pass the call up to my base class

HRESULT hr = CSourceStream::SetMediaType(pMediaType);
if (SUCCEEDED(hr)) {

VIDEOINFO * pvi = (VIDEOINFO *) m_mt.Format();
switch (pvi->bmiHeader.biBitCount) {
case 8:// Make a red pixel

m_BallPixel[0] = 10;// 0 is palette index of red
m_iPixelSize = 1;
SetPaletteEntries(Red);
break;

case 16:// Make a blue pixel

m_BallPixel[0] = 0xf8;// 00000000 00011111 is blue in rgb555 or rgb565
m_BallPixel[1] = 0x0;// don't forget the byte ordering within the mask word.
m_iPixelSize = 2;
SetPaletteEntries(Blue);
break;

case 24:// Make a green pixel

m_BallPixel[0] = 0x0;
m_BallPixel[1] = 0xff;
m_BallPixel[2] = 0x0;
m_iPixelSize = 3;
SetPaletteEntries(Green);
break;

case 32:// Make a yellow pixel

m_BallPixel[0] = 0x0;
m_BallPixel[1] = 0x0;
m_BallPixel[2] = 0xff;
m_BallPixel[3] = 0xff;
m_iPixelSize = 4;
SetPaletteEntries(Yellow);
break;

default:
// We should never agree any other pixel sizes
ASSERT("Tried to agree inappropriate format");
}

CBall *pNewBall = new CBall(pvi->bmiHeader.biWidth, pvi->bmiHeader.biHeight);

if( pNewBall ){
delete m_Ball;
m_Ball = pNewBall;
} else
hr = E_OUTOFMEMORY;

return NOERROR;
} else {
return hr;
}

} // SetMediaType


//
// OnThreadCreate
//
// As we go active reset the stream time to zero
//
HRESULT CBallStream::OnThreadCreate()
{
CAutoLock cAutoLockShared(&m_cSharedState);
m_rtSampleTime = 0;

// we need to also reset the repeat time in case the system
// clock is turned off after m_iRepeatTime gets very big
m_iRepeatTime = m_iDefaultRepeatTime;

// Zero the output buffer on the first frame.
m_bZeroMemory = TRUE;

return NOERROR;

} // OnThreadCreate


//
// SetPaletteEntries
//
// If we set our palette to the current system palette + the colours we want
// the system has the least amount of work to do whilst plotting our images,
// if this stream is rendered to the current display. The first non reserved
// palette slot is at m_Palette[10], so put our first colour there. Also
// guarantees that black is always represented by zero in the frame buffer
//
HRESULT CBallStream::SetPaletteEntries(Colour colour)
{
CAutoLock cAutoLock(m_pFilter->pStateLock());

HDC hdc = GetDC(NULL);// hdc for the current display.
UINT res = GetSystemPaletteEntries(hdc, 0, iPALETTE_COLORS, (LPPALETTEENTRY) &m_Palette);
ReleaseDC(NULL, hdc);

if (res == 0) {
return E_FAIL;
}

switch (colour) {
case Red:
m_Palette[10].peBlue = 0;
m_Palette[10].peGreen = 0;
m_Palette[10].peRed = 0xff;
break;
case Yellow:
m_Palette[10].peBlue = 0;
m_Palette[10].peGreen = 0xff;
m_Palette[10].peRed = 0xff;
break;
case Blue:
m_Palette[10].peBlue = 0xff;
m_Palette[10].peGreen = 0;
m_Palette[10].peRed = 0;
break;
case Green:
m_Palette[10].peBlue = 0;
m_Palette[10].peGreen = 0xff;
m_Palette[10].peRed = 0;
break;
}

m_Palette[10].peFlags = 0;
return NOERROR;

} // SetPaletteEntries