/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): Morten Mikkelsen.
 *
 * ***** END GPL LICENSE BLOCK *****
 * filter.c
 *
 */

/** \file blender/imbuf/intern/filter.c
 *  \ingroup imbuf
 */

#include "MEM_guardedalloc.h"

#include "BLI_utildefines.h"

#include "IMB_imbuf_types.h"
#include "IMB_imbuf.h"
#include "IMB_filter.h"

#include "imbuf.h"

/************************************************************************/
/*				FILTERS					*/
/************************************************************************/

static void filtrow(unsigned char *point, int x)
{
	unsigned int c1, c2, c3, error;

	if (x > 1) {
		c1 = c2 = *point;
		error = 2;
		for (x--; x > 0; x--) {
			c3 = point[4];
			c1 += (c2 << 1) + c3 + error;
			error = c1 & 3;
			*point = c1 >> 2;
			point += 4;
			c1 = c2;
			c2 = c3;
		}
		*point = (c1 + (c2 << 1) + c2 + error) >> 2;
	}
}

static void filtrowf(float *point, int x)
{
	float c1, c2, c3;
	
	if (x > 1) {
		c1 = c2 = *point;
		for (x--; x > 0; x--) {
			c3 = point[4];
			c1 += (c2 * 2) + c3;
			*point = 0.25f * c1;
			point += 4;
			c1 = c2;
			c2 = c3;
		}
		*point = 0.25f * (c1 + (c2 * 2) + c2);
	}
}



static void filtcolum(unsigned char *point, int y, int skip)
{
	unsigned int c1, c2, c3, error;
	unsigned char *point2;

	if (y > 1) {
		c1 = c2 = *point;
		point2 = point;
		error = 2;
		for (y--; y > 0; y--) {
			point2 += skip;
			c3 = *point2;
			c1 += (c2 << 1) + c3 + error;
			error = c1 & 3;
			*point = c1 >> 2;
			point = point2;
			c1 = c2;
			c2 = c3;
		}
		*point = (c1 + (c2 << 1) + c2 + error) >> 2;
	}
}

static void filtcolumf(float *point, int y, int skip)
{
	float c1, c2, c3, *point2;
	
	if (y > 1) {
		c1 = c2 = *point;
		point2 = point;
		for (y--; y > 0; y--) {
			point2 += skip;
			c3 = *point2;
			c1 += (c2 * 2) + c3;
			*point = 0.25f * c1;
			point = point2;
			c1 = c2;
			c2 = c3;
		}
		*point = 0.25f * (c1 + (c2 * 2) + c2);
	}
}

void IMB_filtery(struct ImBuf *ibuf)
{
	unsigned char *point;
	float *pointf;
	int x, y, skip;

	point = (unsigned char *)ibuf->rect;
	pointf = ibuf->rect_float;

	x = ibuf->x;
	y = ibuf->y;
	skip = x << 2;

	for (; x > 0; x--) {
		if (point) {
			if (ibuf->planes > 24) filtcolum(point, y, skip);
			point++;
			filtcolum(point, y, skip);
			point++;
			filtcolum(point, y, skip);
			point++;
			filtcolum(point, y, skip);
			point++;
		}
		if (pointf) {
			if (ibuf->planes > 24) filtcolumf(pointf, y, skip);
			pointf++;
			filtcolumf(pointf, y, skip);
			pointf++;
			filtcolumf(pointf, y, skip);
			pointf++;
			filtcolumf(pointf, y, skip);
			pointf++;
		}
	}
}


void imb_filterx(struct ImBuf *ibuf)
{
	unsigned char *point;
	float *pointf;
	int x, y, skip;

	point = (unsigned char *)ibuf->rect;
	pointf = ibuf->rect_float;

	x = ibuf->x;
	y = ibuf->y;
	skip = (x << 2) - 3;

	for (; y > 0; y--) {
		if (point) {
			if (ibuf->planes > 24) filtrow(point, x);
			point++;
			filtrow(point, x);
			point++;
			filtrow(point, x);
			point++;
			filtrow(point, x);
			point += skip;
		}
		if (pointf) {
			if (ibuf->planes > 24) filtrowf(pointf, x);
			pointf++;
			filtrowf(pointf, x);
			pointf++;
			filtrowf(pointf, x);
			pointf++;
			filtrowf(pointf, x);
			pointf += skip;
		}
	}
}

void IMB_filterN(ImBuf *out, ImBuf *in)
{
	register char *row1, *row2, *row3;
	register char *cp, *r11, *r13, *r21, *r23, *r31, *r33;
	int rowlen, x, y;
	
	rowlen = in->x;
	
	/* generate 32-bit version for float images if it is not already generated by other space */
	if (in->rect == NULL)
		IMB_rect_from_float(in);
	
	for (y = 0; y < in->y; y++) {
		/* setup rows */
		row2 = (char *)(in->rect + y * rowlen);
		row1 = (y == 0) ? row2 : row2 - 4 * rowlen;
		row3 = (y == in->y - 1) ? row2 : row2 + 4 * rowlen;
		
		cp = (char *)(out->rect + y * rowlen);
		
		for (x = 0; x < rowlen; x++) {
			if (x == 0) {
				r11 = row1;
				r21 = row2;
				r31 = row3;
			}
			else {
				r11 = row1 - 4;
				r21 = row2 - 4;
				r31 = row3 - 4;
			}

			if (x == rowlen - 1) {
				r13 = row1;
				r23 = row2;
				r33 = row3;
			}
			else {
				r13 = row1 + 4;
				r23 = row2 + 4;
				r33 = row3 + 4;
			}

			cp[0] = (r11[0] + 2 * row1[0] + r13[0] + 2 * r21[0] + 4 * row2[0] + 2 * r23[0] + r31[0] + 2 * row3[0] + r33[0]) >> 4;
			cp[1] = (r11[1] + 2 * row1[1] + r13[1] + 2 * r21[1] + 4 * row2[1] + 2 * r23[1] + r31[1] + 2 * row3[1] + r33[1]) >> 4;
			cp[2] = (r11[2] + 2 * row1[2] + r13[2] + 2 * r21[2] + 4 * row2[2] + 2 * r23[2] + r31[2] + 2 * row3[2] + r33[2]) >> 4;
			cp[3] = (r11[3] + 2 * row1[3] + r13[3] + 2 * r21[3] + 4 * row2[3] + 2 * r23[3] + r31[3] + 2 * row3[3] + r33[3]) >> 4;
			cp += 4; row1 += 4; row2 += 4; row3 += 4;
		}
	}
}

void IMB_filter(struct ImBuf *ibuf)
{
	IMB_filtery(ibuf);
	imb_filterx(ibuf);
}

void IMB_mask_filter_extend(char *mask, int width, int height)
{
	char *row1, *row2, *row3;
	int rowlen, x, y;
	char *temprect;

	rowlen = width;

	/* make a copy, to prevent flooding */
	temprect = MEM_dupallocN(mask);

	for (y = 1; y <= height; y++) {
		/* setup rows */
		row1 = (char *)(temprect + (y - 2) * rowlen);
		row2 = row1 + rowlen;
		row3 = row2 + rowlen;
		if (y == 1)
			row1 = row2;
		else if (y == height)
			row3 = row2;

		for (x = 0; x < rowlen; x++) {
			if (mask[((y - 1) * rowlen) + x] == 0) {
				if (*row1 || *row2 || *row3 || *(row1 + 1) || *(row3 + 1) ) {
					mask[((y - 1) * rowlen) + x] = FILTER_MASK_MARGIN;
				}
				else if ((x != rowlen - 1) && (*(row1 + 2) || *(row2 + 2) || *(row3 + 2)) ) {
					mask[((y - 1) * rowlen) + x] = FILTER_MASK_MARGIN;
				}
			}

			if (x != 0) {
				row1++; row2++; row3++;
			}
		}
	}

	MEM_freeN(temprect);
}

void IMB_mask_clear(ImBuf *ibuf, char *mask, int val)
{
	int x, y;
	if (ibuf->rect_float) {
		for (x = 0; x < ibuf->x; x++) {
			for (y = 0; y < ibuf->y; y++) {
				if (mask[ibuf->x * y + x] == val) {
					float *col = ibuf->rect_float + 4 * (ibuf->x * y + x);
					col[0] = col[1] = col[2] = col[3] = 0.0f;
				}
			}
		}
	}
	else {
		/* char buffer */
		for (x = 0; x < ibuf->x; x++) {
			for (y = 0; y < ibuf->y; y++) {
				if (mask[ibuf->x * y + x] == val) {
					char *col = (char *)(ibuf->rect + ibuf->x * y + x);
					col[0] = col[1] = col[2] = col[3] = 0;
				}
			}
		}
	}
}

static int filter_make_index(const int x, const int y, const int w, const int h)
{
	if (x < 0 || x >= w || y < 0 || y >= h) return -1;  /* return bad index */
	else return y * w + x;
}

static int check_pixel_assigned(const void *buffer, const char *mask, const int index, const int depth, const int is_float)
{
	int res = 0;

	if (index >= 0) {
		const int alpha_index = depth * index + (depth - 1);

		if (mask != NULL) {
			res = mask[index] != 0 ? 1 : 0;
		}
		else if ((is_float  && ((const float *) buffer)[alpha_index] != 0.0f) ||
		         (!is_float && ((const unsigned char *) buffer)[alpha_index] != 0) )
		{
			res = 1;
		}
	}

	return res;
}

/* if alpha is zero, it checks surrounding pixels and averages color. sets new alphas to 1.0
 * 
 * When a mask is given, only effect pixels with a mask value of 1, defined as BAKE_MASK_MARGIN in rendercore.c
 * */
void IMB_filter_extend(struct ImBuf *ibuf, char *mask, int filter)
{
	const int width = ibuf->x;
	const int height = ibuf->y;
	const int depth = 4;     /* always 4 channels */
	const int chsize = ibuf->rect_float ? sizeof(float) : sizeof(unsigned char);
	const int bsize = width * height * depth * chsize;
	const int is_float = ibuf->rect_float != NULL;
	void *dstbuf = (void *) MEM_dupallocN(ibuf->rect_float ? (void *) ibuf->rect_float : (void *) ibuf->rect);
	char *dstmask = mask == NULL ? NULL : (char *) MEM_dupallocN(mask);
	void *srcbuf = ibuf->rect_float ? (void *) ibuf->rect_float : (void *) ibuf->rect;
	char *srcmask = mask;
	int cannot_early_out = 1, r, n, k, i, j, c;
	float weight[25];

	/* build a weights buffer */
	n = 1;

#if 0
	k = 0;
	for (i = -n; i <= n; i++)
		for (j = -n; j <= n; j++)
			weight[k++] = sqrt((float) i * i + j * j);
#endif

	weight[0] = 1; weight[1] = 2; weight[2] = 1;
	weight[3] = 2; weight[4] = 0; weight[5] = 2;
	weight[6] = 1; weight[7] = 2; weight[8] = 1;

	/* run passes */
	for (r = 0; cannot_early_out == 1 && r < filter; r++) {
		int x, y;
		cannot_early_out = 0;

		for (y = 0; y < height; y++) {
			for (x = 0; x < width; x++) {
				const int index = filter_make_index(x, y, width, height);

				/* only update unassigned pixels */
				if (!check_pixel_assigned(srcbuf, srcmask, index, depth, is_float)) {
					float tmp[4];
					float wsum = 0;
					float acc[4] = {0, 0, 0, 0};
					k = 0;

					if (check_pixel_assigned(srcbuf, srcmask, filter_make_index(x - 1, y, width, height), depth, is_float) ||
					    check_pixel_assigned(srcbuf, srcmask, filter_make_index(x + 1, y, width, height), depth, is_float) ||
					    check_pixel_assigned(srcbuf, srcmask, filter_make_index(x, y - 1, width, height), depth, is_float) ||
					    check_pixel_assigned(srcbuf, srcmask, filter_make_index(x, y + 1, width, height), depth, is_float))
					{
						for (i = -n; i <= n; i++) {
							for (j = -n; j <= n; j++) {
								if (i != 0 || j != 0) {
									const int tmpindex = filter_make_index(x + i, y + j, width, height);

									if (check_pixel_assigned(srcbuf, srcmask, tmpindex, depth, is_float)) {
										if (is_float) {
											for (c = 0; c < depth; c++)
												tmp[c] = ((const float *) srcbuf)[depth * tmpindex + c];
										}
										else {
											for (c = 0; c < depth; c++)
												tmp[c] = (float) ((const unsigned char *) srcbuf)[depth * tmpindex + c];
										}

										wsum += weight[k];

										for (c = 0; c < depth; c++)
											acc[c] += weight[k] * tmp[c];
									}
								}
								k++;
							}
						}

						if (wsum != 0) {
							for (c = 0; c < depth; c++)
								acc[c] /= wsum;

							if (is_float) {
								for (c = 0; c < depth; c++)
									((float *) dstbuf)[depth * index + c] = acc[c];
							}
							else {
								for (c = 0; c < depth; c++) {
									((unsigned char *) dstbuf)[depth * index + c] = acc[c] > 255 ? 255 : (acc[c] < 0 ? 0 : ((unsigned char) (acc[c] + 0.5f)));
								}
							}

							if (dstmask != NULL) dstmask[index] = FILTER_MASK_MARGIN;  /* assigned */
							cannot_early_out = 1;
						}
					}
				}
			}
		}

		/* keep the original buffer up to date. */
		memcpy(srcbuf, dstbuf, bsize);
		if (dstmask != NULL) memcpy(srcmask, dstmask, width * height);
	}

	/* free memory */
	MEM_freeN(dstbuf);
	if (dstmask != NULL) MEM_freeN(dstmask);
}

/* threadsafe version, only recreates existing maps */
void IMB_remakemipmap(ImBuf *ibuf, int use_filter)
{
	ImBuf *hbuf = ibuf;
	int curmap = 0;
	
	ibuf->miptot = 1;
	
	while (curmap < IB_MIPMAP_LEVELS) {
		
		if (ibuf->mipmap[curmap]) {
			
			if (use_filter) {
				ImBuf *nbuf = IMB_allocImBuf(hbuf->x, hbuf->y, 32, IB_rect);
				IMB_filterN(nbuf, hbuf);
				imb_onehalf_no_alloc(ibuf->mipmap[curmap], nbuf);
				IMB_freeImBuf(nbuf);
			}
			else
				imb_onehalf_no_alloc(ibuf->mipmap[curmap], hbuf);
		}
		
		ibuf->miptot = curmap + 2;
		hbuf = ibuf->mipmap[curmap];
		if (hbuf)
			hbuf->miplevel = curmap + 1;
		
		if (!hbuf || (hbuf->x <= 2 && hbuf->y <= 2))
			break;
		
		curmap++;
	}
}

/* frees too (if there) and recreates new data */
void IMB_makemipmap(ImBuf *ibuf, int use_filter)
{
	ImBuf *hbuf = ibuf;
	int curmap = 0;

	imb_freemipmapImBuf(ibuf);
	
	/* no mipmap for non RGBA images */
	if (ibuf->rect_float && ibuf->channels < 4)
		return;
	
	ibuf->miptot = 1;

	while (curmap < IB_MIPMAP_LEVELS) {
		if (use_filter) {
			ImBuf *nbuf = IMB_allocImBuf(hbuf->x, hbuf->y, 32, IB_rect);
			IMB_filterN(nbuf, hbuf);
			ibuf->mipmap[curmap] = IMB_onehalf(nbuf);
			IMB_freeImBuf(nbuf);
		}
		else
			ibuf->mipmap[curmap] = IMB_onehalf(hbuf);

		ibuf->miptot = curmap + 2;
		hbuf = ibuf->mipmap[curmap];
		hbuf->miplevel = curmap + 1;

		if (hbuf->x <= 2 && hbuf->y <= 2)
			break;

		curmap++;
	}
}

ImBuf *IMB_getmipmap(ImBuf *ibuf, int level)
{
	CLAMP(level, 0, ibuf->miptot - 1);
	return (level == 0) ? ibuf : ibuf->mipmap[level - 1];
}

void IMB_premultiply_rect(unsigned int *rect, char planes, int w, int h)
{
	char *cp;
	int x, y, val;

	if (planes == 24) { /* put alpha at 255 */
		cp = (char *)(rect);

		for (y = 0; y < h; y++)
			for (x = 0; x < w; x++, cp += 4)
				cp[3] = 255;
	}
	else {
		cp = (char *)(rect);

		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++, cp += 4) {
				val = cp[3];
				cp[0] = (cp[0] * val) >> 8;
				cp[1] = (cp[1] * val) >> 8;
				cp[2] = (cp[2] * val) >> 8;
			}
		}
	}
}

void IMB_premultiply_rect_float(float *rect_float, char planes, int w, int h)
{
	float val, *cp;
	int x, y;

	if (planes == 24) {   /* put alpha at 1.0 */
		cp = rect_float;

		for (y = 0; y < h; y++)
			for (x = 0; x < w; x++, cp += 4)
				cp[3] = 1.0;
	}
	else {
		cp = rect_float;
		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++, cp += 4) {
				val = cp[3];
				cp[0] = cp[0] * val;
				cp[1] = cp[1] * val;
				cp[2] = cp[2] * val;
			}
		}
	}

}

void IMB_premultiply_alpha(ImBuf *ibuf)
{
	if (ibuf == NULL)
		return;

	if (ibuf->rect)
		IMB_premultiply_rect(ibuf->rect, ibuf->planes, ibuf->x, ibuf->y);

	if (ibuf->rect_float)
		IMB_premultiply_rect_float(ibuf->rect_float, ibuf->planes, ibuf->x, ibuf->y);
}

void IMB_unpremultiply_rect(unsigned int *rect, char planes, int w, int h)
{
	char *cp;
	int x, y;
	float val;

	if (planes == 24) { /* put alpha at 255 */
		cp = (char *)(rect);

		for (y = 0; y < h; y++)
			for (x = 0; x < w; x++, cp += 4)
				cp[3] = 255;
	}
	else {
		cp = (char *)(rect);

		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++, cp += 4) {
				val = cp[3] != 0 ? 1.0f / (float)cp[3] : 1.0f;
				cp[0] = FTOCHAR(cp[0] * val);
				cp[1] = FTOCHAR(cp[1] * val);
				cp[2] = FTOCHAR(cp[2] * val);
			}
		}
	}
}

void IMB_unpremultiply_rect_float(float *rect_float, char planes, int w, int h)
{
	float val, *fp;
	int x, y;

	if (planes == 24) {   /* put alpha at 1.0 */
		fp = rect_float;

		for (y = 0; y < h; y++)
			for (x = 0; x < w; x++, fp += 4)
				fp[3] = 1.0;
	}
	else {
		fp = rect_float;
		for (y = 0; y < h; y++) {
			for (x = 0; x < w; x++, fp += 4) {
				val = fp[3] != 0.0f ? 1.0f / fp[3] : 1.0f;
				fp[0] = fp[0] * val;
				fp[1] = fp[1] * val;
				fp[2] = fp[2] * val;
			}
		}
	}

}

void IMB_unpremultiply_alpha(ImBuf *ibuf)
{
	if (ibuf == NULL)
		return;

	if (ibuf->rect)
		IMB_unpremultiply_rect(ibuf->rect, ibuf->planes, ibuf->x, ibuf->y);

	if (ibuf->rect_float)
		IMB_unpremultiply_rect_float(ibuf->rect_float, ibuf->planes, ibuf->x, ibuf->y);
}
