www.pudn.com > VirtualVCR-src-v2.6.9.zip > VideoCrop.cpp


/* 
	Virtual VCR 
    Copyright (C) 2002  Shaun Faulds 
 
    This program is free software; you can redistribute it and/or modify 
    it under the terms of the GNU General Public License as published by 
    the Free Software Foundation; either version 2 of the License, or 
    (at your option) any later version. 
 
    This program is distributed in the hope that it will be useful, 
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    GNU General Public License for more details. 
 
    You should have received a copy of the GNU General Public License 
    along with this program; if not, write to the Free Software 
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 
	Acknowledgments: 
	This application and associated filters are based on the examples 
	from the Microsoft DirectX DirectShow SDK. 
*/ 
 
#include  
#include  
#include  
 
#if (1100 > _MSC_VER) 
#include  
#else 
#include  
#endif 
 
#include "VideoCropUIDs.h" 
#include "iVideoCrop.h" 
#include "VideoCropProp.h" 
#include "VideoCropPropData.h" 
#include "VideoCrop.h" 
#include "resource.h" 
 
#include "VideoCrop.version" 
 
#include "../Common/Filter/iFilterProperties.h" 
 
/* 
// not defined (currently) in the directshow headers 
// remove this structure if it clashes in the future 
typedef struct tagVIDEOINFOHEADER2 { 
    RECT                rcSource; 
    RECT                rcTarget; 
    DWORD               dwBitRate; 
    DWORD               dwBitErrorRate; 
    REFERENCE_TIME      AvgTimePerFrame; 
    DWORD               dwInterlaceFlags; 
    DWORD               dwCopyProtectFlags; 
    DWORD               dwPictAspectRatioX;  
    DWORD               dwPictAspectRatioY;  
    DWORD               dwReserved1;         
    DWORD               dwReserved2;         
    BITMAPINFOHEADER    bmiHeader; 
} VIDEOINFOHEADER2; 
*/ 
// End of define. 
 
// 
// Setup information 
// 
const AMOVIESETUP_MEDIATYPE sudPinTypes = 
{ 
    &MEDIATYPE_Video,       // Major type 
    &MEDIASUBTYPE_NULL      // Minor type 
}; 
 
const AMOVIESETUP_PIN sudpPins[] = 
{ 
    { L"Input",             // Pins string name 
      FALSE,                // Is it rendered 
      FALSE,                // Is it an output 
      FALSE,                // Are we allowed none 
      FALSE,                // And allowed many 
      &CLSID_NULL,          // Connects to filter 
      NULL,                 // Connects to pin 
      1,                    // Number of types 
      &sudPinTypes          // Pin information 
    }, 
    { L"Output",            // Pins string name 
      FALSE,                // Is it rendered 
      TRUE,                 // Is it an output 
      FALSE,                // Are we allowed none 
      FALSE,                // And allowed many 
      &CLSID_NULL,          // Connects to filter 
      NULL,                 // Connects to pin 
      1,                    // Number of types 
      &sudPinTypes          // Pin information 
    } 
}; 
 
const AMOVIESETUP_FILTER sudHistogram = 
{ 
    &CLSID_VideoCrop,         // Filter CLSID 
    L"Video Crop Filter",       // String name 
    MERIT_DO_NOT_USE,       // Filter merit 
    2,                      // Number of pins 
    sudpPins                // Pin information 
}; 
 
 
// List of class IDs and creator functions for the class factory. This 
// provides the link between the OLE entry point in the DLL and an object 
// being created. The class factory will call the static CreateInstance 
 
CFactoryTemplate g_Templates[] = { 
    { L"Video Crop" 
    , &CLSID_VideoCrop 
    , CVideoCrop::CreateInstance 
    , NULL 
    , &sudHistogram } 
  , 
    { L"Video Crop" 
    , &CLSID_VideoCropPropertyPage 
    , CVideoCropProperties::CreateInstance } 
}; 
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); 
 
// 
// DllRegisterServer 
// 
// Handles sample registry and unregistry 
// 
STDAPI DllRegisterServer() 
{ 
    return AMovieDllRegisterServer2( TRUE ); 
 
} // DllRegisterServer 
 
 
// 
// DllUnregisterServer 
// 
STDAPI DllUnregisterServer() 
{ 
    return AMovieDllRegisterServer2( FALSE ); 
 
} // DllUnregisterServer 
 
 
// 
// Constructor 
// 
CVideoCrop::CVideoCrop(TCHAR *tszName, 
                   LPUNKNOWN punk, 
                   HRESULT *phr) : 
    CTransformFilter(tszName, punk, CLSID_VideoCrop) 
{ 
 
	input_width = 0; 
	input_height = 0; 
	output_width = 0; 
	output_height = 0; 
 
	crop_left = 0; 
	crop_right = 0; 
	crop_top = 0; 
	crop_bottom = 0; 
 
	pixel_length_bytes = 0; 
	bottom_up = true; 
 
} // (Constructor) 
 
 
// 
// CreateInstance 
// 
// Provide the way for COM to create a EZrgb24 object 
// 
CUnknown *CVideoCrop::CreateInstance(LPUNKNOWN punk, HRESULT *phr) 
{ 
    CVideoCrop *pNewObject = new CVideoCrop(NAME("Video Crop"), punk, phr); 
    if (pNewObject == NULL) 
	{ 
        *phr = E_OUTOFMEMORY; 
    } 
    return pNewObject; 
 
} // CreateInstance 
 
 
// 
// NonDelegatingQueryInterface 
// 
// Reveals IIPEffect and ISpecifyPropertyPages 
// 
STDMETHODIMP CVideoCrop::NonDelegatingQueryInterface(REFIID riid, void **ppv) 
{ 
    CheckPointer(ppv,E_POINTER); 
 
    if (riid == IID_IVideoCrop) 
	{ 
        return GetInterface((IVideoCrop *) this, ppv); 
    } 
	else if (riid == IID_IFilterProperties) 
	{ 
		return GetInterface((IFilterProperties *) this, ppv); 
	} 
	else if (riid == IID_ISpecifyPropertyPages) 
	{ 
        return GetInterface((ISpecifyPropertyPages *) this, ppv); 
    } 
	else 
	{ 
        return CTransformFilter::NonDelegatingQueryInterface(riid, ppv); 
    } 
 
} // NonDelegatingQueryInterface 
 
// 
// On Join Filter Graph Check to see if this is the only instance of this 
// filter. 
// 
/* 
int FindMyself(IFilterGraph* pGB) 
{ 
 
    HRESULT hr; 
 
    IEnumFilters* pEnum = NULL; 
    IBaseFilter* pFilter = NULL; 
	IVideoCrop *pDVF = NULL; 
 
    hr = pGB->EnumFilters(&pEnum); 
    if(FAILED(hr)) return(0); 
 
	int nInstances = 0; 
 
    while(pEnum->Next(1, &pFilter, NULL) == S_OK) 
    { 
        hr = pFilter->QueryInterface(CLSID_VideoCrop, (LPVOID*)&pDVF); 
 
        pFilter->Release(); 
 
        if(FAILED(hr)) continue; 
 
		nInstances++; 
 
		pDVF->Release(); 
    } 
 
    pEnum->Release(); 
 
    return(nInstances); 
 
} 
 
// 
// On Join Filter Graph Check to see if there is less then 10 instances 
// of this filter int he graph. 
// This is just an example of what can be done. 
// 
HRESULT CVideoCrop::JoinFilterGraph(IFilterGraph* pGraph, LPCWSTR pName) 
{ 
 
	if(pGraph) 
	{ 
		//MsgBOX(TEXT("Added To FilterGraph")); 
		if(FindMyself(pGraph) > 10)  
		{ 
			return E_FAIL; 
		} 
	} 
 
	return CTransformFilter::JoinFilterGraph(pGraph, pName); 
} 
*/ 
// 
// Verifies that the input pin supports the media type. 
// 
HRESULT CVideoCrop::CheckInputType(const CMediaType* mediaType) 
{ 
	// assertion 
	CheckPointer (mediaType, E_POINTER); 
 
	// major type supported? 
	if (!IsEqualGUID (*mediaType->Type (), MEDIATYPE_Video)) 
	{ 
		return E_FAIL; 
	} 
 
	// format type supported 
	if ((!IsEqualGUID (*mediaType->FormatType (), FORMAT_VideoInfo)) && 
			(!IsEqualGUID (*mediaType->FormatType (), FORMAT_VideoInfo2))) 
	{ 
		return E_FAIL; 
	} 
 
	// we can support this connection 
	return S_OK; 
} 
 
// 
// Verifies that the output pin supports the media type. 
// 
HRESULT CVideoCrop::CheckOutputType(const CMediaType* mediaType) 
{ 
	// assertion 
	CheckPointer (mediaType, E_POINTER); 
 
	// major type supported? 
	if (!IsEqualGUID (*mediaType->Type (), MEDIATYPE_Video)) 
	{ 
		return E_FAIL; 
	} 
 
	// format type supported 
	if ((!IsEqualGUID (*mediaType->FormatType (), FORMAT_VideoInfo)) && 
			(!IsEqualGUID (*mediaType->FormatType (), FORMAT_VideoInfo2))) 
	{ 
		return E_FAIL; 
	} 
 
	// we can support this connection 
	return S_OK; 
} 
 
// 
// Verifies that the input and output pins support the media type.  
// Calls CheckOutputType and CheckInputType depending on 
// the pin direction. 
// 
HRESULT CVideoCrop::CheckTransform (const CMediaType* inputMediaType, const CMediaType* outputMediaType) 
{ 
	HRESULT result = S_OK; 
 
	// check input media 
	if (inputMediaType != 0) 
	{ 
		result = this->CheckInputType (inputMediaType); 
		if (result != S_OK) 
		{ 
			return result; 
		} 
	} 
 
	// check output media 
	if (outputMediaType != 0) 
	{ 
		result = this->CheckOutputType (outputMediaType); 
		if (result != S_OK) 
		{ 
			return result; 
		} 
	} 
 
	// Found a vaild match 
	return S_OK; 
} 
 
// 
// Sets the number and size of buffers required for the transfer. 
// Works out the buffer size by multiplying the width by the height 
// of the output video frame by the colour format bit byte size. 
// BufferSize = Width * Height * (Colour Bit Depth in bytes) 
// 
HRESULT CVideoCrop::DecideBufferSize ( IMemAllocator* allocator, ALLOCATOR_PROPERTIES* properties ) 
{ 
	// make sure the input pin is connected 
	if (this->m_pInput->IsConnected () == FALSE) 
	{ 
		return E_UNEXPECTED; 
	} 
 
	// assertions 
	CheckPointer (allocator, E_POINTER); 
	CheckPointer (properties, E_POINTER); 
 
	properties->cBuffers = 1; 
 
	properties->cbBuffer = input_width * input_height * pixel_length_bytes; 
	//properties->cbBuffer = output_width * output_height * pixel_length_bytes; 
 
	// set properties 
	ALLOCATOR_PROPERTIES actualProperties; 
	HRESULT result = allocator->SetProperties (properties, &actualProperties); 
	if (result != S_OK) 
	{ 
		return result; 
	} 
 
	// verify we got enough memory 
	if ((actualProperties.cBuffers < properties->cBuffers) || 
			(actualProperties.cbBuffer < properties->cbBuffer)) 
	{ 
		return E_FAIL; 
	} 
 
	return S_OK; 
} 
 
// 
// Returns one of the media types that the output pin supports 
// 
HRESULT CVideoCrop::GetMediaType(int position, CMediaType* mediaType) 
{ 
	// make sure the input pin is connected 
	if (this->m_pInput->IsConnected () == FALSE) 
	{ 
		return E_UNEXPECTED; 
	} 
 
	// assertion 
	if (mediaType == 0) 
	{ 
		return E_POINTER; 
	} 
	if (position < 0) 
	{ 
		return E_INVALIDARG; 
	} 
	if (position > 1) 
	{ 
		return VFW_S_NO_MORE_ITEMS; 
	} 
 
 
	// Alter the size of the width and height for the ouput 
	// also alter the sample size. 
	*mediaType = m_pInput->CurrentMediaType(); 
	VIDEOINFOHEADER* header = reinterpret_cast (mediaType->Format ()); 
 
	int pixSize = header->bmiHeader.biBitCount / 8; 
 
	header->bmiHeader.biWidth = output_width; 
	header->bmiHeader.biHeight = output_height; 
	header->bmiHeader.biSizeImage = output_width * output_height * pixSize; 
	mediaType->SetSampleSize(header->bmiHeader.biSizeImage); 
 
	return S_OK; 
} 
 
// 
// Sets the input media type, this is called when SetMediaType 
// is called with a pin direction of INPUT 
// 
HRESULT CVideoCrop::SetInputMediaType(const CMediaType* mediaType) 
{ 
	int width = 0; 
	int height = 0; 
	int bitsPerPixel = 0; 
 
	if (*mediaType->FormatType () == FORMAT_VideoInfo) 
	{ 
		VIDEOINFOHEADER* header = reinterpret_cast (mediaType->Format ()); 
 
		if ( header == 0 ) 
			return E_FAIL; 
 
		width = (int)(header->bmiHeader.biWidth); 
		height = (int)(header->bmiHeader.biHeight); 
		bottom_up = (height > 0); 
		bitsPerPixel = (int)(header->bmiHeader.biBitCount); 
	} 
/* 
	else if ( *mediaType->FormatType () == FORMAT_VideoInfo2) 
	{ 
		// VIDEOINFOHEADER2 
		VIDEOINFOHEADER2* header = reinterpret_cast (mediaType->Format ()); 
 
		if (header == 0) 
			return E_FAIL; 
 
		width = (int)(header->bmiHeader.biWidth); 
		height = (int)(header->bmiHeader.biHeight); 
		bottom_up = (height > 0); 
		bitsPerPixel = (int)(header->bmiHeader.biBitCount); 
	} 
*/ 
	else 
	{	 
		return E_FAIL; 
	} 
 
	input_width = width; 
	input_height = height; 
	output_width = input_width - (crop_left + crop_right); 
	output_height = input_height - (crop_top + crop_bottom); 
 
	pixel_length_bytes = bitsPerPixel / 8; 
 
	//Reconnect the output pins to pass the new size downstream 
	ReconnectOutputPins(); 
 
	//MsgBOX(TEXT("Pixel Depth %d width %d height %d"), bitsPerPixel, width, height); 
 
	return S_OK; 
} 
 
// 
// Sets the output media type, this is called when SetMediaType 
// is called with a pin direction of OUTPUT 
// 
HRESULT CVideoCrop::SetOutputMediaType (const CMediaType* mediaType) 
{ 
//	// copy media type 
//	this->outputMediaType = *mediaType; 
 
	// setup format info 
 
	if (*mediaType->FormatType () == FORMAT_VideoInfo) 
	{ 
		//MsgBOX(TEXT("Set Output Video Info VIDEOINFOHEADER")); 
		VIDEOINFOHEADER* header = reinterpret_cast (mediaType->Format ());		 
 
		int pixSize = header->bmiHeader.biBitCount / 8; 
 
		header->bmiHeader.biWidth = output_width; 
		header->bmiHeader.biHeight = output_height; 
		header->bmiHeader.biSizeImage = output_width * output_height * pixSize; 
		 
	} 
/* 
	// VIDEOINFOHEADER2 
	else if (*mediaType->FormatType () == FORMAT_VideoInfo2) 
	{ 
		//MsgBOX(TEXT("Set Output Video Info VIDEOINFOHEADER2")); 
		VIDEOINFOHEADER2* header = reinterpret_cast (mediaType->Format ());	 
			 
		int pixSize = header->bmiHeader.biBitCount / 8; 
 
		header->bmiHeader.biWidth = output_width; 
		header->bmiHeader.biHeight = output_height; 
		header->bmiHeader.biSizeImage = output_width * output_height * pixSize; 
	} 
*/ 
	// unrecognized type 
	else 
	{ 
		return E_FAIL; 
	} 
 
	return S_OK; 
} 
 
// 
// Informs when the media type is established for the connection.  
// This then calls the SetInputMediaType and SetOutputMediaType 
// for each pin direction. 
// 
HRESULT CVideoCrop::SetMediaType(PIN_DIRECTION direction, const CMediaType* mediaType) 
{ 
	// assertion 
	CheckPointer (mediaType, E_POINTER); 
 
	// Call super method, pass it what we got here. 
	HRESULT result = this->CTransformFilter::SetMediaType (direction, mediaType); 
	if (result != S_OK) 
	{ 
		return result; 
	} 
 
	// set media type for input pin? 
	if (direction == PINDIR_INPUT) 
	{ 
		this->SetInputMediaType(mediaType); 
	} 
 
	// set media type for the output pin 
	else if (direction == PINDIR_OUTPUT) 
	{ 
		this->SetOutputMediaType(mediaType); 
	} 
 
	// unrecognized pin direction 
	else 
	{ 
		return E_FAIL; 
	} 
 
	return S_OK; 
} 
 
// 
// Performs transform operations of the filter. 
// Used PrepareOutputSample to copy the properties from 
// the input sample to the output sample then does 
// processes the video data by copying the input 
// data from the input sample to the output sample. 
// Only the data that is in the cropped are is copied! 
// 
HRESULT CVideoCrop::Transform(IMediaSample* inSample,  IMediaSample* outSample) 
{ 
	// 
	// Perpare output sample. 
	// Use the local method to copy sample times etc 
	// from input sample to the output sample 
	// 
	HRESULT result = this->PrepareOutputSample(inSample, outSample); 
	if (result != S_OK) return result; 
 
	// Now do the data copy 
	BYTE *srcBuffer = 0; 
	BYTE *dstBuffer = 0; 
	inSample->GetPointer (&srcBuffer); 
	outSample->GetPointer (&dstBuffer); 
 
	// Copy the image data from the input to the output 
 
	// If the image it bottom up then work out the crop value 
	// from the bottom crop 
	if(bottom_up) 
		srcBuffer += (crop_bottom * input_width) * pixel_length_bytes; 
	else 
		srcBuffer += (crop_top * input_width) * pixel_length_bytes; 
 
	srcBuffer += crop_left * pixel_length_bytes; 
	for(int x = 0;x < output_height; x++) 
	{ 
		CopyMemory((PVOID)dstBuffer,(PVOID)srcBuffer, output_width * pixel_length_bytes); 
		dstBuffer += output_width * pixel_length_bytes; 
		srcBuffer += input_width * pixel_length_bytes; 
	} 
 
	// set output datalength 
	outSample->SetActualDataLength(output_height * output_width * pixel_length_bytes); 
 
	return S_OK; 
} 
 
// 
// Prepares the output sample. 
// This copies the property from the 
// input sample to the output sample. 
// 
HRESULT CVideoCrop::PrepareOutputSample (IMediaSample* inSample, IMediaSample* outSample) 
{ 
	// assertions 
	CheckPointer(inSample, E_POINTER); 
	CheckPointer(outSample, E_POINTER);	 
 
	//.1 
	// set media type 
	// 
    // Copy the media type 
	//outSample->SetMediaType(&this->outputMediaType); 
 
	//.2 
	// copy the sample times 
	// 
	REFERENCE_TIME timeStart = 0; 
	REFERENCE_TIME timeEnd = 0; 
	if (inSample->GetTime (&timeStart, &timeEnd) == S_OK) 
	{ 
		outSample->SetTime (&timeStart, &timeEnd); 
	} 
 
	LONGLONG mediaStart = 0; 
	LONGLONG mediaEnd = 0; 
	if (inSample->GetMediaTime (&mediaStart, &mediaEnd) == S_OK) 
	{ 
			outSample->SetMediaTime (&mediaStart,&mediaEnd); 
	} 
 
	//.3 
	// copy the sync point property 
	// 
	HRESULT result = inSample->IsSyncPoint (); 
	if (result == S_OK) 
	{ 
		outSample->SetSyncPoint (TRUE); 
	} 
	else if (result == S_FALSE) 
	{ 
		outSample->SetSyncPoint (FALSE); 
	} 
	else 
	{ 
		return E_UNEXPECTED; 
	} 
 
	//.4 
	// copy the preroll property 
	// 
	result = inSample->IsPreroll (); 
	if (result == S_OK) 
	{ 
		outSample->SetPreroll (TRUE); 
	} 
	else if (result == S_FALSE) 
	{ 
		outSample->SetPreroll (FALSE); 
	} 
	else 
	{ 
		return E_UNEXPECTED; 
	} 
 
	//.5 
	// copy the discontinuity property 
	// 
	result = inSample->IsDiscontinuity (); 
	if (result == S_OK) 
	{ 
		outSample->SetDiscontinuity (TRUE); 
	} 
	else if (result == S_FALSE) 
	{ 
		outSample->SetDiscontinuity(FALSE); 
	} 
	else 
	{ 
		return E_UNEXPECTED; 
	} 
 
	return S_OK; 
} 
 
// 
// GetPages 
// 
// Returns the clsid's of the property pages we support 
// 
STDMETHODIMP CVideoCrop::GetPages(CAUUID *pPages) 
{ 
    pPages->cElems = 1; 
    pPages->pElems = (GUID *) CoTaskMemAlloc(sizeof(GUID)); 
    if (pPages->pElems == NULL) 
	{ 
        return E_OUTOFMEMORY; 
    } 
 
    *(pPages->pElems) = CLSID_VideoCropPropertyPage; 
 
    return NOERROR; 
 
} // GetPages 
 
// 
// Interface Methods to get and set the crop data 
// 
 
// 
// Gets the data for the crop 
// 
STDMETHODIMP CVideoCrop::get_Data(VideoCropPropData*& data) 
{ 
	data = new VideoCropPropData(crop_left, crop_right, crop_top, crop_bottom, input_width, input_height); 
 
	return NOERROR; 
} 
 
// 
// Sets the Data for the crop 
// 
STDMETHODIMP CVideoCrop::set_Data(VideoCropPropData*& data) 
{ 
 
	crop_left = data->getLeft(); 
	crop_right = data->getRight(); 
	crop_top = data->getTop(); 
	crop_bottom = data->getBottom(); 
 
	output_width = input_width - (crop_left + crop_right); 
	output_height = input_height - (crop_top + crop_bottom); 
 
	//Reconnect the output pins to pass the new size downstream 
	ReconnectOutputPins(); 
 
	return NOERROR; 
} 
 
// 
// Method to display a message box to show data 
// 
int CVideoCrop::MsgBOX(LPTSTR sz,...)  
{ 
    static TCHAR tach[2000]; 
    va_list va; 
 
    va_start(va, sz); 
    wvsprintf(tach, sz, va); 
    va_end(va); 
    MessageBox(HWND_DESKTOP, tach, "Filter Message Box", MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL); 
    return FALSE; 
} 
 
// 
//Reconnect the output pins to pass the new size down the stream 
// 
BOOL CVideoCrop::ReconnectOutputPins() 
{ 
 
	FILTER_INFO filerInfo; 
	HRESULT hr = this->QueryFilterInfo(&filerInfo); 
 
	if(filerInfo.pGraph) 
	{ 
		IPin *pP; 
		ULONG u; 
		IEnumPins *pins = NULL; 
 
		PIN_INFO pininfo; 
		hr = this->EnumPins(&pins); 
		pins->Reset(); 
 
		while(hr == NOERROR) 
		{ 
	 
			hr = pins->Next(1, &pP, &u);				 
 
			if(hr == S_OK && pP) 
			{ 
				hr = pP->QueryPinInfo(&pininfo); 
 
				if(hr == NOERROR) 
				{ 
					if(pininfo.dir == PINDIR_OUTPUT) 
					{ 
						filerInfo.pGraph->Reconnect(pP); 
					} 
 
					if(pininfo.pFilter) 
						pininfo.pFilter->Release(); 
				} 
 
				pP->Release(); 
			} 
		} 
 
		if(pins) 
			pins->Release(); 
	} 
	 
	if(filerInfo.pGraph) 
		filerInfo.pGraph->Release(); 
	filerInfo.pGraph = NULL; 
 
	return (hr == S_OK); 
} 
 
//This is for the common interface IFilterProperties 
// 
// Gets the data properties 
// 
STDMETHODIMP CVideoCrop::get_Prop(char*& data) 
{ 
	char filterData[1024]; 
	wsprintf(filterData, "%d, %d, %d ,%d", crop_left, crop_right, crop_top, crop_bottom); 
 
	data = filterData; 
	return NOERROR; 
} 
 
// 
// Sets the Data for properties 
// 
STDMETHODIMP CVideoCrop::set_Prop(char* data) 
{ 
	int value = 0; 
	int destination = 0; 
	char *pch; 
	pch = strtok (data,","); 
    while (pch != NULL) 
    { 
		value = atol(pch); 
		if(destination == 0) 
			crop_left = value; 
 
		if(destination == 1) 
			crop_right = value; 
 
		if(destination == 2) 
			crop_top = value; 
 
		if(destination == 3) 
			crop_bottom = value; 
 
		destination++; 
 
		pch = strtok (NULL, ","); 
    } 
		 
	return NOERROR; 
} 
 
STDMETHODIMP CVideoCrop::get_Version(char *data) 
{ 
	wsprintf(data, "%s", MAIN_VERSION); 
 
	return NOERROR; 
}