/*
    Smart Deinterlacing Filter for VirtualDub -- performs deinterlacing only
    in moving picture areas, allowing full resolution in static areas.
    Copyright (C) 1999-2001 Donald A. Graft
    Miscellaneous suggestions and optimizations by Avery Lee.
    Useful suggestions by Hans Zimmer, Jim Casaburi, Ondrej Kavka, 
	and Gunnar Thalin. Field-only differencing based on algorithm by
	Gunnar Thalin.

    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.

    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.

    The author can be contacted at:
    Donald Graft
    neuron2@home.com.
*/

#include <windows.h>
#include <math.h>
#include "dvdfilters.h"


// fixme
#define memcopy memcpy

#define FRAME_ONLY 0
#define FIELD_ONLY 1
#define FRAME_AND_FIELD 2

class Deinterlace
{
	int m_width;
	int m_height;
	int m_pitch;

	unsigned char		*m_prevFrame;
	unsigned char		*m_saveFrame;
	unsigned char		*m_moving;
	unsigned char		*m_fmoving;

	int					m_motionOnly;
	int 				m_Blend;
	int 				m_threshold;
	int					m_scenethreshold;
	int					m_fieldShift;
	int					m_inswap;
	int					m_outswap;
	int					m_highq;
	int					m_diffmode;
	int					m_noMotion;

public:
	Deinterlace(int width, int height, int motionOnly, int Blend, int threshold=15, int scenethreshold=100, int	fieldShift=0, int	inswap=0,	int	outswap=0, int highq=0, int diffmode=FRAME_ONLY, int noMotion=0);
	~Deinterlace();
	
	int ProcessFrame(unsigned char* srcp, unsigned char* dstp, unsigned long frame_count);
};

Deinterlace::Deinterlace(int width, int height, int motionOnly, int Blend, int threshold, int scenethreshold, int	fieldShift,	int	inswap,	int	outswap, int highq,	int	diffmode, int noMotion)
{
	m_width = width;
	m_height = height;
	m_pitch = width * 4;
	m_motionOnly		= motionOnly;
	m_Blend			= Blend;
	m_threshold		= threshold;
	m_scenethreshold	= scenethreshold;
	m_fieldShift		= fieldShift;
	m_inswap			= inswap;
	m_outswap		= outswap;
	m_noMotion		= noMotion;
	m_highq			= highq;
	m_diffmode		= diffmode;
	
	if (m_diffmode == FRAME_ONLY || m_diffmode == FRAME_AND_FIELD)
	{
		m_prevFrame = new unsigned char[m_width*m_height*3];
		memset(m_prevFrame, 0, m_width*m_height*3);
	}
	
	if (m_fieldShift ||	(m_inswap && !m_outswap) || (!m_inswap && m_outswap))
	{
		m_saveFrame = new unsigned char[m_width*m_height*3];
	}
	
	if (!m_noMotion)
	{
		m_moving = new unsigned char[m_width*m_height];
		memset(m_moving, 0, m_width*m_height);
	}
	
	if (m_highq)
	{
		m_fmoving = new unsigned char[m_width*m_height];
	}
}

Deinterlace::~Deinterlace()
{
	if (m_diffmode == FRAME_ONLY || m_diffmode == FRAME_AND_FIELD)
	{
		delete[] m_prevFrame;
		m_prevFrame = NULL;
	}
	
	if (m_fieldShift ||
		(m_inswap && !m_outswap) || (!m_inswap && m_outswap))
	{
		delete[] m_saveFrame;
		m_saveFrame = NULL;
	}
	
	if (!m_noMotion)
	{
		delete[] m_moving;
		m_moving = NULL;
	}
	
	if (m_highq)
	{
		delete[] m_fmoving;
		m_fmoving = NULL;
	}
}

///////////////////////////////////////////////////////////////////////////

int Deinterlace::ProcessFrame(unsigned char *srcp, unsigned char* dstp, unsigned long frame_count)
{
	const int		pitchtimes2 = 2 * m_pitch;
	const int	w = m_width;
	const int		wminus1 = w - 1;
//	const int		wtimes2 = w * 2;
	const int		wtimes4 = w * 4;
	const int	h = m_height;
	const int		hminus1 = h - 1;
	const int		hover2 = h / 2;
#define Pixel32 unsigned char
#define Pixel unsigned char
	Pixel32			*src, *dst, *srcminus, *srcplus;
	unsigned char	*moving, *movingminus, *movingplus;
	unsigned char	*fmoving;
	unsigned char	*saved = 0, *sv;
	Pixel32 		*src1 = 0, *src2 = 0, *s1, *s2;
	Pixel32 		*dst1 = 0, *dst2 = 0, *d1, *d2;
	unsigned char				*prev;
	int				scenechange;
	long			count;
	int				x, y;
	long			prevValue, nextValue, luma, lumap, luman;
	Pixel32			p0, p1, p2;
	long			r, g, b, rp, gp, bp, rn, gn, bn, T;
	unsigned char	frMotion, fiMotion;
	int				copyback;

	/* If we are performing Advanced Processing... */
	if (m_inswap || m_outswap || m_fieldShift)
	{
		/* Advanced Processing is used typically to clean up PAL video
		   which has erroneously been digitized with the field phase off by
		   one field. The result is that the frames look interlaced,
		   but really if we can phase shift by one field, we'll get back
		   the original progressive frames. Also supported are field swaps
		   before and/or after the phase shift to accommodate different
		   capture cards and telecining methods, as explained in the
		   help file. Finally, the user can optionally disable full
		   motion processing after this processing. */
		copyback = 1;
		if (!m_fieldShift)
		{
			/* No phase shift enabled, but we have swap(s) enabled. */
			if (m_inswap && m_outswap)
			{
				if (m_noMotion)
				{
					/* Swapping twice is a null operation. */
					memcopy(dstp, srcp, m_height*m_pitch);
					return 0;
				}
				else
				{
					copyback = 0;
				}
			}
			else
			{
				/* Swap fields. */
				src1 = srcp + m_pitch;
				saved = m_saveFrame + m_pitch;
				for (y = 0; y < hover2; y++)
				{					
					memcopy(saved, src1, m_pitch);
					src1 += pitchtimes2;
					saved += pitchtimes2;
				}
				src1 = srcp;
				dst1 = dstp + m_pitch;
				for (y = 0; y < hover2; y++)
				{					
					memcopy(dst1, src1, m_pitch);
					src1 += pitchtimes2;
					dst1 += pitchtimes2;
				}
				dst1 = dstp;
				saved = m_saveFrame + m_pitch;
				for (y = 0; y < hover2; y++)
				{					
					memcopy(dst1, saved, m_pitch);
					dst1 += pitchtimes2;
					saved += pitchtimes2;
				}
			}
		}
		/* If we reach here, then phase shift has been enabled. */
		else
		{
			switch (m_inswap | (m_outswap << 1))
			{
			case 0:
				/* No inswap, no outswap. */
				src1 = srcp;
				src2 = srcp + m_pitch;
				dst1 = dstp + m_pitch;
				dst2 = dstp;
				saved = m_saveFrame + m_pitch;
				break;
			case 1:
				/* Inswap, no outswap. */
				src1 = srcp + m_pitch;
				src2 = srcp;
				dst1 = dstp + m_pitch;
				dst2 = dstp;
				saved = m_saveFrame;
				break;
			case 2:
				/* No inswap, outswap. */
				src1 = srcp;
				src2 = srcp + m_pitch;
				dst1 = dstp;
				dst2 = dstp + m_pitch;
				saved = m_saveFrame + m_pitch;
				break;
			case 3:
				/* Inswap, outswap. */
				src1 = srcp + m_pitch;
				src2 = srcp;
				dst1 = dstp;
				dst2 = dstp + m_pitch;
				saved = m_saveFrame;
				break;
			}

			s1 = src1;
			d1 = dst1;
			for (y = 0; y < hover2; y++)
			{
				memcopy(d1, s1, m_pitch);
				s1 += pitchtimes2;
				d1 += pitchtimes2;
			}

			/* If this is not the first frame, copy the buffered field
			   of the last frame to the output. This creates a correct progressive
			   output frame. If this is the first frame, a buffered field is not
			   available, so interpolate the field from the current field. */
			if (frame_count == 0)
			{
				s1 = src1;
				d2 = dst2;
				for (y = 0; y < hover2; y++)
				{
					memcopy(d2, s1, m_pitch);
					s1 += pitchtimes2;
					d2 += pitchtimes2;
				}
			}
			else
			{
				d2 = dst2;
				sv = saved;
				for (y = 0; y < hover2; y++)
				{
					memcopy(d2, sv, m_pitch);
					sv += pitchtimes2;
					d2 += pitchtimes2;
				}
			}
			/* Finally, save the unused field of the current frame in the buffer.
			   It will be used to create the next frame. */
			s2 = src2;
			sv = saved;
			for (y = 0; y < hover2; y++)
			{
				memcopy(sv, s2, m_pitch);
				sv += pitchtimes2;
				s2 += pitchtimes2;
			}
		}
		if (m_noMotion) return 0;

		if (copyback)
		{
			/* We're going to do motion processing also, so copy
			   the result back into the src bitmap. */
			memcopy(srcp, dstp, m_height*m_pitch);
		}
	}
	else if (m_noMotion)
	{
		/* Well, I suppose somebody might select no advanced processing options
		   but tick disable motion processing. This covers that. */
		memcopy(dstp, srcp, m_height*m_pitch);
		return 0;
	}

	/* End advanced processing mode code. Now do full motion-adaptive deinterlacing. */

	/* Not much deinterlacing to do if there aren't at least 2 lines. */
	if (h < 2) return 0;

	count = 0;
	if (m_diffmode == FRAME_ONLY || m_diffmode == FRAME_AND_FIELD)
	{
		/* Skip first and last lines, they'll get a free ride. */
		src = srcp + m_pitch;
		srcminus = src - m_pitch;
		prev = m_prevFrame + m_pitch;
		moving = m_moving + m_pitch;
		for (y = 1; y < hminus1; y++)
		{
			x = 0;
			do
			{
				// First check frame motion.
				// Set the moving flag if the diff exceeds the configured
				// threshold.
				moving[x] = 0;
				frMotion = 0;
				prevValue = *prev;
					r = (src[x] >> 16) & 0xff;
					g = (src[x] >> 8) & 0xff;
					b = src[x] & 0xff;
					luma = (76 * r + 30 * b + 150 * g) >> 8;
					if (abs(luma - prevValue) > m_threshold) frMotion = 1;

				// Now check field motion if applicable.
				if (m_diffmode == FRAME_ONLY) moving[x] = frMotion;
				else
				{
					fiMotion = 0;
					if (y & 1)
						prevValue = srcminus[x];
					else
						prevValue = *(prev + w);
						r = (src[x] >> 16) & 0xff;
						g = (src[x] >> 8) & 0xff;
						b = src[x] & 0xff;
						luma = (76 * r + 30 * b + 150 * g) >> 8;
						if (abs(luma - prevValue) > m_threshold) fiMotion = 1;
					moving[x] = (fiMotion && frMotion);
				}
					*prev++ = (unsigned char)luma;
				/* Keep a count of the number of moving pixels for the
				   scene change detection. */
				if (moving[x]) count++;
			} while(++x < w);
			src = (Pixel *)((char *)src + m_pitch);
			srcminus = (Pixel *)((char *)srcminus + m_pitch);
			moving += w;
		}

		/* Determine whether a scene change has occurred. */
		if ((100L * count) / (h * w) >= m_scenethreshold) scenechange = 1;
		else scenechange = 0;

		/* Perform a denoising of the motion map if enabled. */
		if (!scenechange && m_highq)
		{
			int xlo, xhi, ylo, yhi;
			int u, v;
			int N = 5;
			int Nover2 = N/2;
			int sum;
			unsigned char *m;

			// Erode.
			fmoving = m_fmoving;
			for (y = 0; y < h; y++)
			{
				for (x = 0; x < w; x++)
				{
					if (!((m_moving + y * w)[x]))
					{
						fmoving[x] = 0;	
						continue;
					}
					xlo = x - Nover2; if (xlo < 0) xlo = 0;
					xhi = x + Nover2; if (xhi >= w) xhi = wminus1;
					ylo = y - Nover2; if (ylo < 0) ylo = 0;
					yhi = y + Nover2; if (yhi >= h) yhi = hminus1;
					m = m_moving + ylo * w;
					sum = 0;
					for (u = ylo; u <= yhi; u++)
					{
						for (v = xlo; v <= xhi; v++)
						{
							sum += m[v];
						}
						m += w;
					}
					if (sum > 9)
						fmoving[x] = 1;
					else
						fmoving[x] = 0;
				}
				fmoving += w;
			}
			// Dilate.
			N = 5;
			Nover2 = N/2;
			moving = m_moving;
			for (y = 0; y < h; y++)
			{
				for (x = 0; x < w; x++)
				{
					if (!((m_fmoving + y * w)[x]))
					{
						moving[x] = 0;	
						continue;
					}
					xlo = x - Nover2; if (xlo < 0) xlo = 0;
					xhi = x + Nover2; if (xhi >= w) xhi = wminus1;
					ylo = y - Nover2; if (ylo < 0) ylo = 0;
					yhi = y + Nover2; if (yhi >= h) yhi = hminus1;
					m = m_moving + ylo * w;
					for (u = ylo; u <= yhi; u++)
					{
						for (v = xlo; v <= xhi; v++)
						{
							m[v] = 1;
						}
						m += w;
					}
				}
				moving += w;
			}
		}
	}
	else
	{
		/* Field differencing only mode. */
		T = m_threshold * m_threshold;
		src = (Pixel *)((char *)srcp + m_pitch);
		srcminus = (Pixel *)((char *)src - m_pitch);
		srcplus = (Pixel *)((char *)src + m_pitch);
		moving = m_moving + w;
		for (y = 1; y < hminus1; y++)
		{
			x = 0;
			do
			{
				// Set the moving flag if the diff exceeds the configured
				// threshold.
				moving[x] = 0;
				if (y & 1)
				{
					// Now check field motion.
					fiMotion = 0;
					nextValue = srcplus[x];
					prevValue = srcminus[x];
						r = (src[x] >> 16) & 0xff;
						rp = (prevValue >> 16) & 0xff;
						rn = (nextValue >> 16) & 0xff;
						g = (src[x] >> 8) & 0xff;
						gp = (prevValue >> 8) & 0xff;
						gn = (nextValue >> 8) & 0xff;
						b = src[x] & 0xff;
						bp = prevValue & 0xff;
						bn = nextValue & 0xff;
						luma = (76 * r + 30 * b + 150 * g) >> 8;
						lumap = (76 * rp + 30 * bp + 150 * gp) >> 8;
						luman = (76 * rn + 30 * bn + 150 * gn) >> 8;
						if ((lumap - luma) * (luman - luma) > T)
							moving[x] = 1;
				}
				/* Keep a count of the number of moving pixels for the
				   scene change detection. */
				if (moving[x]) count++;
			} while(++x < w);
			src = (Pixel *)((char *)src + m_pitch);
			srcminus = (Pixel *)((char *)srcminus + m_pitch);
			srcplus = (Pixel *)((char *)srcplus + m_pitch);
			moving += w;
		}

		/* Determine whether a scene change has occurred. */
		if ((100L * count) / (h * w) >= m_scenethreshold) scenechange = 1;
		else scenechange = 0;

		/* Perform a denoising of the motion map if enabled. */
		if (!scenechange && m_highq)
		{
			int xlo, xhi, ylo, yhi;
			int u, v;
			int N = 5;
			int Nover2 = N/2;
			int sum;
			unsigned char *m;

			// Erode.
			fmoving = m_fmoving;
			for (y = 0; y < h; y++)
			{
				for (x = 0; x < w; x++)
				{
					if (!((m_moving + y * w)[x]))
					{
						fmoving[x] = 0;	
						continue;
					}
					xlo = x - Nover2; if (xlo < 0) xlo = 0;
					xhi = x + Nover2; if (xhi >= w) xhi = wminus1;
					ylo = y - Nover2; if (ylo < 0) ylo = 0;
					yhi = y + Nover2; if (yhi >= h) yhi = hminus1;
					m = m_moving + ylo * w;
					sum = 0;
					for (u = ylo; u <= yhi; u++)
					{
						for (v = xlo; v <= xhi; v++)
						{
							sum += m[v];
						}
						m += w;
					}
					if (sum > 9)
						fmoving[x] = 1;
					else
						fmoving[x] = 0;
				}
				fmoving += w;
			}

			// Dilate.
			N = 5;
			Nover2 = N/2;
			moving = m_moving;
			for (y = 0; y < h; y++)
			{
				for (x = 0; x < w; x++)
				{
					if (!((m_fmoving + y * w)[x]))
					{
						moving[x] = 0;	
						continue;
					}
					xlo = x - Nover2; if (xlo < 0) xlo = 0;
					xhi = x + Nover2; if (xhi >= w) xhi = wminus1;
					ylo = y - Nover2; if (ylo < 0) ylo = 0;
					yhi = y + Nover2; if (yhi >= h) yhi = hminus1;
					m = m_moving + ylo * w;
					for (u = ylo; u <= yhi; u++)
					{
						for (v = xlo; v <= xhi; v++)
						{
							m[v] = 1;
						}
						m += w;
					}
				}
				moving += w;
			}		
		}
	}

	// Render.
    // The first line gets a free ride.
	src = srcp;
	dst = dstp;
	memcopy(dst, src, wtimes4);
	src = (Pixel *)((char *)srcp + m_pitch);
	srcminus = (Pixel *)((char *)src - m_pitch);
	srcplus = (Pixel *)((char *)src + m_pitch);
	dst = (Pixel *)((char *)dstp + m_pitch);
	moving = m_moving + w;
	movingminus = moving - w;
	movingplus = moving + w;
	for (y = 1; y < hminus1; y++)
	{
		if (m_motionOnly)
		{
			if (m_Blend)
			{
				x = 0;
				do {
					if (!(movingminus[x] | moving[x] | movingplus[x]) && !scenechange)
						dst[x] = (unsigned char)0x7f;
					else
					{	
						/* Blend fields. */
						p0 = src[x];
						p0 &= 0x00fefefe;

						p1 = srcminus[x];
						p1 &= 0x00fcfcfc;

						p2 = srcplus[x];
						p2 &= 0x00fcfcfc;

						dst[x] = (unsigned char)((p0>>1) + (p1>>2) + (p2>>2));
					}
				} while(++x < w);
			}
			else
			{
				x = 0;
				do {
					if (!(movingminus[x] | moving[x] | movingplus[x]) && !scenechange)
						dst[x] = (unsigned char)0x7f;
					else if (y&1)
					{
						p1 = srcminus[x];
						p1 &= 0x00fefefe;

						p2 = srcplus[x];
						p2 &= 0x00fefefe;
						dst[x] = (unsigned char)((p1>>1) + (p2>>1));
					}
					else
						dst[x] = src[x];
				} while(++x < w);
			}
		} else {
			if (m_Blend)
			{
				x = 0;
				do {
					if (!(movingminus[x] | moving[x] | movingplus[x]) && !scenechange)
						dst[x] = src[x];
					else
					{
						/* Blend fields. */
						p0 = src[x];
						p0 &= 0x00fefefe;

						p1 = srcminus[x];
						p1 &= 0x00fcfcfc;

						p2 = srcplus[x];
						p2 &= 0x00fcfcfc;

						dst[x] = (unsigned char)((p0>>1) + (p1>>2) + (p2>>2));
					}
				} while(++x < w);
			}
			else
			{
				// Doing line interpolate. Thus, even lines are going through
				// for moving and non-moving mode. Odd line pixels will be subject
				// to the motion test.
				if (y&1)
				{
					x = 0;
					do {
						if (!(movingminus[x] | moving[x] | movingplus[x]) && !scenechange)
							dst[x] = src[x];
						else
						{
							p1 = srcminus[x];
							p1 &= 0x00fefefe;

							p2 = srcplus[x];
							p2 &= 0x00fefefe;

							dst[x] = (unsigned char)((p1>>1) + (p2>>1));
						}
					} while(++x < w);
				}
				else
				{
					// Even line; pass it through.
					memcopy(dst, src, m_pitch);
				}
			}
		}
		src += m_pitch;
		srcminus += m_pitch;
		srcplus += m_pitch;
		dst += m_pitch;
		moving += m_pitch;
		movingminus += m_pitch;
		movingplus += m_pitch;
	}
	
	// The last line gets a free ride.
	memcopy(dst, src, m_pitch);

	return 0;
}

