/******************************************************************************
Peep filter plugin for Avisynth by V.C.Mohan
Specified rectangular or elliptical window is filled/ blendd with an equal sized window from
 a frame from another clip. The window, %age of blend, %age of taper can vary linearly along clip.  

  Author V.C.Mohan
  2 Jan 2008, 18 Dec 2008

  Copyright (C) <2008>  <V.C.Mohan>

    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, version 3 of the License.

    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.

    A copy of the GNU General Public License is at
    see <http://www.gnu.org/licenses/>.
	For details of how to contact author see <http://www.avisynth.org/vcmohan> 
********************************************************************************/  
#include "windows.h"
#include "avisynth.h"
#include "math.h"
class Peep : public GenericVideoFilter {
		PClip mclip;			// clip to be used as mask for coloring
			
		int sxcoord;		// window left x coord
		int sycoord;		// window top y coord
		int swidth;			// window width
		int sheight;		// window height
		int staper;			// at start blend taper value %age within window
		int sblend;			// at start blend %age value
		bool ellipse;		// window ellipse (true) or rectangle false
		int excoord;		// end xcoord	
		int eycoord;		// end y coord	
		int ewidth;			// end width
		int eheight;		// end height
		int etaper;			// taper at end of clip
		int eblend;			// blend %age at end of clip
		
  public:
							//Definition of function
    Peep(PClip _child,PClip _mclip,
						int _sxcoord, int _sycoord,
						int _swidth, int _sheight,
						
						bool _ellipse,
						int _excoord, int _eycoord,
						int _ewidth, int _eheight,
						int _staper,  int _sblend, int _etaper, int _eblend,
				   
						IScriptEnvironment* env) ;	
			
	virtual ~Peep(){}				//destructor

	PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
	
// This is the function that AviSynth calls to get a given frame.
// So when this functions gets called, the filter is supposed to return frame n.
};	
/*************************************************
 * The following is the implementation 
 * of the defined functions.
 *************************************************/
//Here is the acutal constructor code used

Peep::Peep(PClip _child,PClip _mclip,
						int _sxcoord, int _sycoord,
						int _swidth, int _sheight,
						
						bool _ellipse,
						int _excoord, int _eycoord,
						int _ewidth, int _eheight,
						int _staper, int _sblend, int _etaper, int _eblend,
						IScriptEnvironment* env) :

	GenericVideoFilter(_child),mclip(_mclip),
						sxcoord(_sxcoord), sycoord(_sycoord),
						swidth(_swidth), sheight(_sheight),
						
						ellipse(_ellipse),
						excoord(_excoord), eycoord(_eycoord),
						ewidth(_ewidth), eheight(_eheight),
						staper(_staper), sblend(_sblend), 
						etaper(_etaper), eblend(_eblend)						 
			
{


	
}
	

/***************************************************************/	
	PVideoFrame __stdcall Peep::GetFrame(int n, IScriptEnvironment* env)
	{
		PVideoFrame Frame = child->GetFrame(n, env);
		
		const VideoInfo& mvi = mclip->GetVideoInfo();
		const	int bwd = vi.width;
        const	int bht = Frame->GetHeight();		
		const	int lpitch = Frame->GetPitch();
		int kb=vi.BitsPerPixel()/8;
		
		const unsigned char *lp= Frame->GetReadPtr();
		PVideoFrame MFrame = mclip->GetFrame(n, env);
		
		const unsigned char *mp= MFrame->GetReadPtr();
		
		
		const int mpitch = MFrame->GetPitch();
		
		int nf = vi.num_frames == 1? 1 : vi.num_frames - 1;
	
		// compute current frame parameters
		int xcoord = (sxcoord+((excoord-sxcoord)*n)/nf);
		int ycoord = (sycoord+((eycoord-sycoord)*n)/nf);
		int width = (swidth+((ewidth-swidth)*n)/nf);
		int height = (sheight+((eheight-sheight)*n)/nf);
		float mfactor = (sblend+((eblend-sblend)*n)/nf)/100.0;
		float taper = 1.0 - (staper+((etaper-staper)*n)/nf)/100.0;

		PVideoFrame work = env->NewVideoFrame(vi);
		
		unsigned char *wp= work->GetWritePtr();

		const	int wpitch = work->GetPitch();

			// copy main input onto work

		env->BitBlt(wp, wpitch, lp, lpitch, bwd * kb, bht);
		
		if(vi.IsRGB() )		
		
			ycoord = bht -1 - ycoord;

		int lx = (xcoord - width < 0 ? 0 : xcoord - width) & 0xfffffffe;

		int rx = (xcoord + width > bwd ? bwd  : xcoord + width) ;

		int ty = (ycoord - height < 0 ? 0 : ycoord - height) & 0xfffffffe;

		int by = (ycoord + height > bht ? bht  : ycoord + height) ;


		if(vi.IsRGB() || vi.IsYUY2())
		{
			if(ellipse)
			{
					// chck within superscribed rectangle	
				for(int h= ty; h < by; h++)
							
					for(int w = lx; w < rx; w ++)
					{
						float factor = (1.0*(h - ycoord) * (h - ycoord))/(height*height) + (1.0 * (xcoord - w) * (xcoord - w))/( width * width);
						if(factor <= 1)	// within ellipse
						{


							if( factor > taper && taper < 0.9999)
							{

								// within taper zone

								for( int k = 0; k < kb; k ++)
									// blend with taper
									wp[h * wpitch + kb * w + k ]
									
										= mp[h * mpitch + kb * w + k ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lp[h * lpitch + kb * w + k ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor); 
							}

							else
							{

								// no tapering in this zone
								for( int k = 0; k < kb; k ++)
									// blend
									wp[h * wpitch + kb * w + k ]
									
										= mp[h * mpitch + kb * w + k ] * mfactor
										
										+ lp[h * lpitch + kb * w + k ] * (1.0 - mfactor); 
							}
						}
					}
			
			}

			else
			{

				float onebyw = 1.0 / width;

				float onebyh = 1.0 / height;

					// rectangle, blend with taper

				for(int h= ty; h < by; h++)
							
					for(int w = lx; w < rx; w ++)
					{
						float factor = onebyw * abs(w - xcoord) > onebyh * abs ( h - ycoord) ?
							abs(w- xcoord)  * onebyw  
							: abs(h - ycoord )  * onebyh;


						if ( factor > taper && taper < 0.9999)
						{
							// within taper zone
							
							for( int k = 0; k < kb; k ++)
									
								wp[h * wpitch + kb * w + k]
									
								= mp[h * mpitch + kb * w + k] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lp[h * lpitch + kb * w + k] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);
						}

						// rectangle. No taper. only blend

						else
						{

							for( int k = 0; k < kb; k ++)
									// blend
								wp[h * wpitch + kb * w + k ]
									
									= mp[h * mpitch + kb * w + k ] * mfactor
										
										+ lp[h * lpitch + kb * w + k ] * (1.0 - mfactor);
						}
					}
			}
			

			
		}

		else if( vi.IsYV12() )
		{

			const unsigned char *lpU = Frame->GetReadPtr(PLANAR_U);
			const unsigned char *lpV = Frame->GetReadPtr(PLANAR_V);
			const unsigned char *mpU = MFrame->GetReadPtr(PLANAR_U);
			const unsigned char *mpV = MFrame->GetReadPtr(PLANAR_V);
			unsigned char *wpU = work->GetWritePtr(PLANAR_U);
			unsigned char *wpV = work->GetWritePtr(PLANAR_V);
			const int lpitchUV = Frame->GetPitch(PLANAR_U);
			const int mpitchUV = MFrame->GetPitch(PLANAR_U);
			const int wpitchUV = work->GetPitch(PLANAR_U);

			env->BitBlt(wpU, wpitchUV, lpU, lpitchUV, bwd/2, bht/2);
			env->BitBlt(wpV, wpitchUV, lpV, lpitchUV, bwd/2, bht/2);
	
			if(ellipse)
			{
				
				for(int h= ty; h < by; h++)
							
					for(int w = lx; w < rx; w ++)
					{
						float factor = (1.0*(h - ycoord) * (h - ycoord))/(height*height) + (1.0 * (xcoord - w) * (xcoord - w))/( width * width);
						if(factor <= 1)	// within ellipse
						{

							if( factor > taper && taper < 0.9999)
							{

								// within taper zone								
									
									wp[h * wpitch + w ]
									
										= mp[h * mpitch + w ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lp[h * lpitch + w ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);
									

									if( ( h & 1 ) == 0 && ( w & 1) == 0)
									{

										wpU[h/2 * wpitchUV + w/2 ]
									
										= mpU[h/2 * mpitchUV + w/2 ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lpU[h/2 * lpitchUV + w/2 ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);

										wpV[h/2 * wpitchUV + w/2 ]
									
										= mpV[h/2 * mpitchUV + w/2 ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lpV[h/2 * lpitchUV + w/2 ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);
									}
									
							}

							else
							{

									
									wp[h * wpitch + w ]
									
										= mp[h * mpitch + w ] * mfactor
										
										+ lp[h * lpitch + w ] * (1.0 - mfactor);
									
									if( ( h & 1 ) == 0 && ( w & 1) == 0)
									{

										wpU[h/2 * wpitchUV + w/2 ]
									
										= mpU[h/2 * mpitchUV + w/2 ] * (mfactor) 
										
										+ lpU[h/2 * lpitchUV + w/2 ] * ( 1.0 -  mfactor);

										wpV[h/2 * wpitchUV + w/2 ]
									
										= mpV[h/2 * mpitchUV + w/2 ] * mfactor
										
										+ lpV[h/2 * lpitchUV + w/2 ] * ( 1.0 - mfactor);
									}
							} 
						}
					}
			
			}

			else
			{

				float onebyw = 1.0 / width;

				float onebyh = 1.0 / height;
				
					// rectangle, blend with taper

				for(int h= ty; h < by; h++)
							
					for(int w = lx; w < rx; w ++)
					{
						float factor = onebyw * abs(w - xcoord) > onebyh * abs ( h - ycoord) ?
							abs(w- xcoord)  * onebyw  
							: abs(h - ycoord )  * onebyh;
					
						if ( factor > taper && taper < 0.9999)
						{
							// within taper zone								
									
							wp[h * wpitch + w ]
									
								= mp[h * mpitch + w ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lp[h * lpitch + w ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);
									

							if( ( h & 1 ) == 0 && ( w & 1) == 0)
							{

								wpU[h/2 * wpitchUV + w/2 ]
									
									= mpU[h/2 * mpitchUV + w/2 ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lpU[h/2 * lpitchUV + w/2 ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);

								wpV[h/2 * wpitchUV + w/2 ]
									
									= mpV[h/2 * mpitchUV + w/2 ] * (1.0 - factor)/(1.0 -  taper) * mfactor
										
										+ lpV[h/2 * lpitchUV + w/2 ] * ( 1.0 - (1.0 - factor)/(1.0 -  taper) * mfactor);
							}
									
						}

						else
						{

						// rectangle. No taper. only blend

				
						
									// blend
							wp[h * wpitch + w ]
									
								= mp[h * mpitch + w ] * mfactor
										
									+ lp[h * lpitch + w ] * (1.0 - mfactor);

							if( ( h & 1 ) == 0 && ( w & 1) == 0)
							{

								wpU[h/2 * wpitchUV + w/2 ]
									
									= mpU[h/2 * mpitchUV + w/2 ] * (mfactor) 
										
										+ lpU[h/2 * lpitchUV + w/2 ] * ( 1.0 -  mfactor);

								wpV[h/2 * wpitchUV + w/2 ]
									
									= mpV[h/2 * mpitchUV + w/2 ] * mfactor
										
										+ lpV[h/2 * lpitchUV + w/2 ] * ( 1.0 - mfactor);
							}

						}
					}

			}
						
		}
			
				
				
			
	return work;		
	
}

 


/***************************************************************/
// This is the function that created the filter, when the filter has been called.
// This can be used for simple parameter checking, so it is possible to create different filters,

AVSValue __cdecl Create_Peep(AVSValue args, void* user_data, IScriptEnvironment* env) 
{
	const VideoInfo& vi=args[0].AsClip()->GetVideoInfo();
	
	if(  !vi.IsYUY2() && !vi.IsYV12() && !vi.IsRGB())
		env->ThrowError("Peep: This color format is not supported here");

	const VideoInfo& mvi = args[1].AsClip()->GetVideoInfo();

	if(mvi.num_frames< vi.num_frames)
		env->ThrowError("Peep:the blend clip has fewer frames than main clip");
	if(vi.pixel_type != mvi.pixel_type || vi.width != mvi.width || vi.height != mvi.height)

		env->ThrowError("Peep: The blend clip differs in colorspace or dimensions");
	
	
	if( !args[2].Defined() || !args[3].Defined() || !args[4].Defined() || !args[5].Defined() )

		env->ThrowError("Peep:  x, y, w and h must be defined");

	if (args[4].AsInt() <=0 || args[5].AsInt() <= 0 
		|| args[9].AsInt(args[4].AsInt())<= 0 || args[10].AsInt(args[5].AsInt()) <= 0)
		
		env->ThrowError("Peep: w, h, ew, eh must neither be negative nor  zero");

	if(args[2].AsInt() + args[4].AsInt()<0 ||args[3].AsInt() + args[5].AsInt() <0
		|| args[2].AsInt() - args[4].AsInt()>= vi.width ||args[3].AsInt() - args[5].AsInt() >= vi.height)		
		env->ThrowError("Peep: at least a part of rectangle of window must lie within frame");

	
	if(args[7].AsInt(args[2].AsInt()) + args[9].AsInt(args[4].AsInt())<0 
		|| args[8].AsInt(args[3].AsInt()) + args[10].AsInt(args[5].AsInt()) < 0
		|| args[7].AsInt(args[2].AsInt()) - args[9].AsInt(args[4].AsInt()) >= vi.width
		|| args[8].AsInt(args[3].AsInt()) - args[10].AsInt(args[5].AsInt()) >= vi.height)		
		env->ThrowError("Peep: at end frame even a part of rectangle of window is not in frame ");

	
	if( args[11].AsInt(0) < 0 || args[11].AsInt(0) > 100 ||
		args[12].AsInt(0) < 0 || args[12].AsInt(0) > 100 ||
		args[13].AsInt(0) < 0 || args[13].AsInt(0) > 100 ||
		args[14].AsInt(0) < 0 || args[14].AsInt(0) > 100 )

		env->ThrowError("Peep:taper and blend should be between 0 and 100");

		return  new Peep(args[0].AsClip(),	// clip on which areas are to be filled
								args[1].AsClip(),	// blend  clip
								args[2].AsInt(),	// window center x coord
								args[3].AsInt(),	// window center y coord
								args[4].AsInt(),	// window width
								args[5].AsInt(),	// window height
								
								args[6].AsBool(true),	// is window rectangle or ellipse?
								args[7].AsInt(args[2].AsInt()),	// end x coordinate	
								args[8].AsInt(args[3].AsInt()),	// end y coordinate
								args[9].AsInt(args[4].AsInt()),	// end width
								args[10].AsInt(args[5].AsInt()),// end height

								args[11].AsInt(10),	// taper %age
								args[12].AsInt(80),	// blend %age
								args[13].AsInt(args[11].AsInt(10)),	// end taper %age
								args[14].AsInt(args[12].AsInt(80)),	// end blend %age
								env);
	



	
}
// The following function is the function that actually registers the filter in AviSynth
// It is called automatically, when the plugin is loaded to see which functions this filter contains.

extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env)
{
    env->AddFunction("Peep", "cc[x]i[y]i[w]i[h]i[ellipse]b[ex]i[ey]i[ew]i[eh]i[taper]i[blend]i[etaper]i[eblend]i", Create_Peep, 0);
					
    return "Peep  Plugin";
}

