//	VirtualDub - Video processing and capture application
//	Copyright (C) 1998-2001 Avery Lee
//
//	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.

#include <stdio.h>

#include "filter.h"
#include "filters.h"
#include "f_convolute.h"
#include "ScriptInterpreter.h"
#include "ScriptValue.h"
#include "ScriptError.h"

#define USE_ASM

extern "C" void asm_sharpen_run(
		void *dst,
		void *src,
		unsigned long width,
		unsigned long height,
		unsigned long srcstride,
		unsigned long dststride,
		long a_mult,
		long b_mult);

#define C_TOPOK		(1)
#define C_BOTTOMOK	(2)
#define C_LEFTOK	(4)
#define C_RIGHTOK	(8)

void inline conv_add(long& rt, long& gt, long& bt, unsigned long dv, long m) {
	bt += m*(0xFF & (dv));
	gt += m*(0xFF & (dv>>8));
	rt += m*(0xFF & (dv>>16));
}

void inline conv_add2(long& rt, long& gt, long& bt, unsigned long dv) {
	bt += 0xFF & (dv);
	gt += 0xFF & (dv>>8);
	rt += 0xFF & (dv>>16);
}

static unsigned long __fastcall do_conv(unsigned long *data, long *m, long sflags, long pit) {
	long rt=0, gt=0, bt=0;

	if (sflags & C_TOPOK) {
		if (sflags & (C_LEFTOK))		conv_add2(rt, gt, bt, data[        -1]);
		else							conv_add2(rt, gt, bt, data[         0]);
										conv_add2(rt, gt, bt, data[         0]);
		if (sflags & (C_RIGHTOK))		conv_add2(rt, gt, bt, data[        +1]);
		else							conv_add2(rt, gt, bt, data[         0]);
	} else {
		if (sflags & (C_LEFTOK))		conv_add2(rt, gt, bt, data[(pit>>2)-1]);
		else							conv_add2(rt, gt, bt, data[(pit>>2)  ]);
										conv_add2(rt, gt, bt, data[(pit>>2)  ]);
		if (sflags & (C_RIGHTOK))		conv_add2(rt, gt, bt, data[(pit>>2)+1]);
		else							conv_add2(rt, gt, bt, data[(pit>>2)  ]);
	}
	if (sflags & (C_LEFTOK))			conv_add2(rt, gt, bt, data[(pit>>2)-1]);
	else								conv_add2(rt, gt, bt, data[(pit>>2)  ]);
	if (sflags & (C_RIGHTOK))			conv_add2(rt, gt, bt, data[(pit>>2)+1]);
	else								conv_add2(rt, gt, bt, data[(pit>>2)  ]);
	if (sflags & C_BOTTOMOK) {
		if (sflags & (C_LEFTOK))		conv_add2(rt, gt, bt, data[(pit>>1)-1]);
		else							conv_add2(rt, gt, bt, data[(pit>>1)  ]);
										conv_add2(rt, gt, bt, data[(pit>>1)  ]);
		if (sflags & (C_RIGHTOK))		conv_add2(rt, gt, bt, data[(pit>>1)+1]);
		else							conv_add2(rt, gt, bt, data[(pit>>1)  ]);
	} else {
		if (sflags & (C_LEFTOK))		conv_add2(rt, gt, bt, data[(pit>>2)-1]);
		else							conv_add2(rt, gt, bt, data[(pit>>2)  ]);
										conv_add2(rt, gt, bt, data[(pit>>2)  ]);
		if (sflags & (C_RIGHTOK))		conv_add2(rt, gt, bt, data[(pit>>2)+1]);
		else							conv_add2(rt, gt, bt, data[(pit>>2)  ]);
	}

	rt = rt*m[0]+m[9];
	gt = gt*m[0]+m[9];
	bt = bt*m[0]+m[9];

	conv_add(rt, gt, bt, data[(pit>>2)  ],m[4]);

	rt>>=8;	if (rt<0) rt=0; else if (rt>255) rt=255;
	gt>>=8;	if (gt<0) gt=0; else if (gt>255) gt=255;
	bt>>=8;	if (bt<0) bt=0; else if (bt>255) bt=255;

	return (unsigned long)((rt<<16) | (gt<<8) | (bt));
}

#ifndef USE_ASM
static inline unsigned long do_conv2(unsigned long *data, long *m, long pit) {
	long rt=0, gt=0, bt=0;

	conv_add2(rt, gt, bt, data[        -1]);
	conv_add2(rt, gt, bt, data[         0]);
	conv_add2(rt, gt, bt, data[        +1]);
	conv_add2(rt, gt, bt, data[(pit>>2)-1]);
	conv_add2(rt, gt, bt, data[(pit>>2)+1]);
	conv_add2(rt, gt, bt, data[(pit>>1)-1]);
	conv_add2(rt, gt, bt, data[(pit>>1)  ]);
	conv_add2(rt, gt, bt, data[(pit>>1)+1]);
	rt = rt*m[0]+m[9];
	gt = gt*m[0]+m[9];
	bt = bt*m[0]+m[9];

	conv_add(rt, gt, bt, data[(pit>>2)  ], m[4]);

	rt>>=8;	if (rt<0) rt=0; else if (rt>255) rt=255;
	gt>>=8;	if (gt<0) gt=0; else if (gt>255) gt=255;
	bt>>=8;	if (bt<0) bt=0; else if (bt>255) bt=255;

	return (unsigned long)((rt<<16) | (gt<<8) | (bt));
}
#endif

static int sharpen_run(const FilterActivation *fa, const FilterFunctions *ff) {
	unsigned long w,h;
	unsigned long *src = (unsigned long *)fa->src.data, *dst = (unsigned long *)fa->dst.data;
	unsigned long pitch = fa->src.pitch;
	long *m = ((ConvoluteFilterData *)(fa->filter_data))->m;

	src -= pitch>>2;

	*dst++ = do_conv(src++, m, C_BOTTOMOK | C_RIGHTOK, pitch);
	w = fa->src.w-2;
	do { *dst++ = do_conv(src++, m, C_BOTTOMOK | C_LEFTOK | C_RIGHTOK, pitch); } while(--w);
	*dst++ = do_conv(src++, m, C_BOTTOMOK | C_LEFTOK, pitch);

	src += fa->src.modulo>>2;
	dst += fa->dst.modulo>>2;

#ifdef USE_ASM
	asm_sharpen_run(
			dst+1,
			src+1,
			fa->src.w-2,
			fa->src.h-2,
			fa->src.pitch,
			fa->dst.pitch,
			m[0],
			m[4]);
#endif

	h = fa->src.h-2;
	do {
		*dst++ = do_conv(src++, m, C_TOPOK | C_BOTTOMOK | C_RIGHTOK, pitch);
#ifdef USE_ASM
		src += fa->src.w-2;
		dst += fa->src.w-2;
#else
		w = fa->src.w-2;
		do { *dst++ = do_conv2(src++, m, pitch); } while(--w);
#endif
		*dst++ = do_conv(src++, m, C_TOPOK | C_BOTTOMOK | C_LEFTOK, pitch);

		src += fa->src.modulo>>2;
		dst += fa->dst.modulo>>2;
	} while(--h);

	*dst++ = do_conv(src++, m, C_TOPOK | C_RIGHTOK, pitch);
	w = fa->src.w-2;
	do { *dst++ = do_conv(src++, m, C_TOPOK | C_LEFTOK | C_RIGHTOK, pitch); } while(--w);
	*dst++ = do_conv(src++, m, C_TOPOK | C_LEFTOK, pitch);

	return 0;
}

static long sharpen_param(FilterActivation *fa, const FilterFunctions *ff) {
	return FILTERPARAM_SWAP_BUFFERS;
}

static int sharpen_config(FilterActivation *fa, const FilterFunctions *ff, HWND hWnd) {
	ConvoluteFilterData *cfd;
	LONG lv;
	int i;

	if (!fa->filter_data) {
		if (!(fa->filter_data = (void *)new ConvoluteFilterData)) return 0;
		memset(fa->filter_data, 0, sizeof ConvoluteFilterData);
		((ConvoluteFilterData *)fa->filter_data)->m[4] = 256;
	}
	cfd = (ConvoluteFilterData *)fa->filter_data;

	lv = FilterGetSingleValue(hWnd, -cfd->m[0], 0, 64, "sharpen");

	for(i=0; i<9; i++)
		if (i==4) cfd->m[4] = 256+8*lv; else cfd->m[i]=-lv;

	cfd->bias = -lv*4;

	return 0;
}

static void sharpen_string(const FilterActivation *fa, const FilterFunctions *ff, char *buf) {
	ConvoluteFilterData *cfd = (ConvoluteFilterData *)fa->filter_data;

	wsprintf(buf, " (by %ld)", -cfd->bias/4);
}

static void sharpen_script_config(IScriptInterpreter *isi, void *lpVoid, CScriptValue *argv, int argc) {
	FilterActivation *fa = (FilterActivation *)lpVoid;
	int lv = argv[0].asInt();

	ConvoluteFilterData *cfd = (ConvoluteFilterData *)fa->filter_data;

	for(int i=0; i<9; i++)
		if (i==4) cfd->m[4] = 256+8*lv; else cfd->m[i]=-lv;

	cfd->bias = -lv*4;
}

static ScriptFunctionDef sharpen_func_defs[]={
	{ (ScriptFunctionPtr)sharpen_script_config, "Config", "0i" },
	{ NULL },
};

static CScriptObject sharpen_obj={
	NULL, sharpen_func_defs
};

static bool sharpen_script_line(FilterActivation *fa, const FilterFunctions *ff, char *buf, int buflen) {
	ConvoluteFilterData *cfd = (ConvoluteFilterData *)fa->filter_data;

	_snprintf(buf, buflen, "Config(%d)", -cfd->bias/4);

	return true;
}

FilterDefinition filterDef_sharpen={
	0,0,NULL,
	"sharpen",
	"Enhances contrast between adjacent elements in an image.\n\n[Assembly optimized] [MMX optimized]",
	NULL,NULL,
	sizeof(ConvoluteFilterData),
	NULL,NULL,
	sharpen_run,
	sharpen_param,
	sharpen_config,
	sharpen_string,
	NULL,
	NULL,

	&sharpen_obj,
	sharpen_script_line,
};