/*
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.0 of the PHP license,       |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_0.txt.                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
   | Authors: MORIKAWA Joe <unknown@example.org>                          |
   +----------------------------------------------------------------------+
*/

/* $ Id: $ */ 

#include "php_seam_carving.h"

#if HAVE_SEAM_CARVING

static int _get_image_energy(gdImagePtr im, int x, int y);
static void _create_m_image(gdImagePtr energy_im, gdImagePtr m_im);
static void _remove_vertical_seam(gdImagePtr im, gdImagePtr energy_im, gdImagePtr m_im, int *seam);

/* {{{ seam_carving_functions[] */
function_entry seam_carving_functions[] = {
	PHP_FE(seam_imagecreate    , seam_imagecreate_arg_info)
	{ NULL, NULL, NULL }
};
/* }}} */

/* {{{ cross-extension dependencies */

#if ZEND_EXTENSION_API_NO >= 220050617
static zend_module_dep seam_carving_deps[] = {
	ZEND_MOD_REQUIRED("gd")
	{NULL, NULL, NULL, 0}
};
#endif
/* }}} */

/* {{{ seam_carving_module_entry
 */
zend_module_entry seam_carving_module_entry = {
#if ZEND_EXTENSION_API_NO >= 220050617
		STANDARD_MODULE_HEADER_EX, NULL,
		seam_carving_deps,
#else
		STANDARD_MODULE_HEADER,
#endif

	"seam_carving",
	seam_carving_functions,
	PHP_MINIT(seam_carving),     /* Replace with NULL if there is nothing to do at php startup   */ 
	PHP_MSHUTDOWN(seam_carving), /* Replace with NULL if there is nothing to do at php shutdown  */
	PHP_RINIT(seam_carving),     /* Replace with NULL if there is nothing to do at request start */
	PHP_RSHUTDOWN(seam_carving), /* Replace with NULL if there is nothing to do at request end   */
	PHP_MINFO(seam_carving),
	"0.1.0", 
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_SEAM_CARVING
ZEND_GET_MODULE(seam_carving)
#endif

#ifndef MAX
#define MAX(x, y) (x > y ? x : y)
#endif

#ifndef MIN
#define MIN(x, y) (x > y ? x : y)
#endif

/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(seam_carving)
{

	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(seam_carving)
{

	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_RINIT_FUNCTION */
PHP_RINIT_FUNCTION(seam_carving)
{
	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_RSHUTDOWN_FUNCTION */
PHP_RSHUTDOWN_FUNCTION(seam_carving)
{
	/* add your stuff here */

	return SUCCESS;
}
/* }}} */


/* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(seam_carving)
{
	php_info_print_box_start(0);
	php_printf("<p>Seam Carving</p>\n");
	php_printf("<p>Version 0.1.0alpha (2008-01-30)</p>\n");
	php_printf("<p><b>Authors:</b></p>\n");
	php_printf("<p>MORIKAWA Joe &lt;unknown@example.org&gt; (lead)</p>\n");
	php_info_print_box_end();
	/* add your stuff here */

}
/* }}} */


/* {{{ proto bool seam_imagecreate(resource Image im, int len)
   */
PHP_FUNCTION(seam_imagecreate)
{
	zval * zim = NULL;
	int im_id = -1;
	gdImagePtr im;

	long len = 0;



	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rl", &zim, &len) == FAILURE) {
		return;
	}
	ZEND_FETCH_RESOURCE(im, gdImagePtr, &zim, im_id, "Image",  phpi_get_le_gd());



	do {
		gdImagePtr energy_im, m_im, ret_im;
		gdImagePtr energy_im_tmp, m_im_tmp, ret_im_tmp;
		int *seam;
		
		if (len < 0) {
			RETURN_FALSE;
		}
		if (!gdImageTrueColor(im)) {
		  RETURN_FALSE;
		}
		if (!im->tpixels) {
		  RETURN_FALSE;
		}
//		if (gdImageSX(im) > (len * 3 / 2)) {
//			RETURN_FALSE;
//		}

		ret_im = gdImageCreateTrueColor(gdImageSX(im), gdImageSY(im));
		if (ret_im == NULL) {
		  RETURN_FALSE;
		}
		gdImageCopy(ret_im, im, 0, 0, 0, 0, gdImageSX(im), gdImageSY(im));
		energy_im = gdImageCreateTrueColor(gdImageSX(im), gdImageSY(im));
		if (energy_im == NULL) {
		  RETURN_FALSE;
		}
		m_im = gdImageCreateTrueColor(gdImageSX(im), gdImageSY(im));
		if (m_im == NULL) {
			gdImageDestroy(energy_im);
			RETURN_FALSE;
		}
		
		int x, y;
		for (x = 0; x < gdImageSX(im); x++) {
		  for (y = 0; y < gdImageSY(im); y++) {
		    gdImageSetPixel(energy_im, x, y, _get_image_energy(ret_im, x, y));
		  }
		}
		
		_create_m_image(energy_im, m_im);
		
		seam = (int *)emalloc(sizeof(int) * gdImageSY(im));
		for (x = 0; x < len; x ++) {
			_remove_vertical_seam(ret_im, energy_im, m_im, seam);
			ret_im_tmp = gdImageCreateTrueColor(gdImageSX(ret_im) - 1, gdImageSY(ret_im));
			if (ret_im_tmp == NULL) {
				efree(seam);
				RETURN_FALSE;
			}
			gdImageCopy(ret_im_tmp, ret_im, 0, 0, 0, 0, 
					gdImageSX(ret_im_tmp), gdImageSY(ret_im_tmp));
			gdImageDestroy(ret_im);
			ret_im = ret_im_tmp;

			energy_im_tmp = gdImageCreateTrueColor(gdImageSX(energy_im) - 1, gdImageSY(energy_im));
			if (energy_im_tmp == NULL) {
				efree(seam);
				RETURN_FALSE;
			}
			gdImageCopy(energy_im_tmp, energy_im, 0, 0, 0, 0, 
					gdImageSX(energy_im_tmp), gdImageSY(energy_im_tmp));
			gdImageDestroy(energy_im);
			energy_im = energy_im_tmp;

			m_im_tmp = gdImageCreateTrueColor(gdImageSX(m_im) - 1, gdImageSY(m_im));
			if (m_im_tmp == NULL) {
				efree(seam);
				RETURN_FALSE;
			}
			gdImageCopy(m_im_tmp, m_im, 0, 0, 0, 0, 
					gdImageSX(m_im_tmp), gdImageSY(m_im_tmp));
			gdImageDestroy(m_im);
			m_im = m_im_tmp;
		}
		efree(seam);
		ZEND_REGISTER_RESOURCE(return_value, ret_im, phpi_get_le_gd()); 
	} while (0);
}
/* }}} seam_imagecreate */

/* {{{ _get_image_energy()
 Calculate Image energy */
static int _get_image_energy(gdImagePtr im, int x, int y)
{
	int dx, dy;
	int count = 0;
	int rgb, next_rgb, r, g, b;
	rgb = gdImageTrueColorPixel(im, x, y);
	if (gdImageBoundsSafe(im, x - 1, y)) {
		count ++;
		next_rgb = gdImageTrueColorPixel(im, x - 1, y);
		r = gdTrueColorGetRed(rgb) - gdTrueColorGetRed(next_rgb);
		g = gdTrueColorGetGreen(rgb) - gdTrueColorGetGreen(next_rgb);
		b = gdTrueColorGetBlue(rgb) - gdTrueColorGetBlue(next_rgb);
		dx = sqrt( r * r + g * g + b * b);
	}
	if (gdImageBoundsSafe(im, x + 1, y)) {
		count ++;
		next_rgb = gdImageTrueColorPixel(im, x + 1, y);
		r = gdTrueColorGetRed(rgb) - gdTrueColorGetRed(next_rgb);
		g = gdTrueColorGetGreen(rgb) - gdTrueColorGetGreen(next_rgb);
		b = gdTrueColorGetBlue(rgb) - gdTrueColorGetBlue(next_rgb);
		dx = (dx + sqrt( r * r + g * g + b * b)) / count;
	}
	count = 0;

	if (gdImageBoundsSafe(im, x, y - 1)) {
		count ++;
		next_rgb = gdImageTrueColorPixel(im, x, y - 1);
		r = gdTrueColorGetRed(rgb) - gdTrueColorGetRed(next_rgb);
		g = gdTrueColorGetGreen(rgb) - gdTrueColorGetGreen(next_rgb);
		b = gdTrueColorGetBlue(rgb) - gdTrueColorGetBlue(next_rgb);
		dy = sqrt( r * r + g * g + b * b);
	}
	if (gdImageBoundsSafe(im, x, y + 1)) {
		count ++;
		next_rgb = gdImageTrueColorPixel(im, x, y + 1);
		r = gdTrueColorGetRed(rgb) - gdTrueColorGetRed(next_rgb);
		g = gdTrueColorGetGreen(rgb) - gdTrueColorGetGreen(next_rgb);
		b = gdTrueColorGetBlue(rgb) - gdTrueColorGetBlue(next_rgb);
		dy = (dx + sqrt( r * r + g * g + b * b)) / count;
	}
	return dx + dy;
}
/* }}} _get_image_energy */

/** {{{ _create_m_image()
 */
static void _create_m_image(gdImagePtr energy_im, gdImagePtr m_im)
{
	int x, y;
	for (x = 0; x < gdImageSX(m_im); x++) {
		gdImageSetPixel(m_im, x, 0, gdImageTrueColorPixel(energy_im, x, 0));
	}
	for (y = 1; y < gdImageSY(m_im); y++) {
		gdImageSetPixel(m_im, 0, y, 
				gdImageTrueColorPixel(energy_im, 0, y) 
				+ MIN(gdImageTrueColorPixel(m_im, 0, y - 1), 
					gdImageTrueColorPixel(m_im, 1, y - 1)));
		for (x = 1; x < gdImageSX(m_im) - 1; x++) {
			gdImageSetPixel(m_im, x, y,
					gdImageTrueColorPixel(energy_im, x, y) + MIN(MIN(
						gdImageTrueColorPixel(m_im, x - 1, y - 1),
						gdImageTrueColorPixel(m_im, x    , y - 1)),
						gdImageTrueColorPixel(m_im, x + 1, y - 1)));
		}
		gdImageSetPixel(m_im, x, y, 
				gdImageTrueColorPixel(energy_im, x, y) + MIN(
					gdImageTrueColorPixel(m_im, x - 1, y - 1), 
					gdImageTrueColorPixel(m_im, x    , y - 1)));
	}
}
/* }}} _get_image_energy */

/** {{{ _remove_vertical_seam()
 */
static void _remove_vertical_seam(gdImagePtr im, gdImagePtr energy_im, gdImagePtr m_im, int *seam)
{
	int x, m, pre_m;
	int y = gdImageSY(im) - 1;
	int min_last_m = gdImageTrueColorPixel(m_im, 0, y);
	int start, end;
	gdImagePtr im_tmp, energy_im_tmp, m_im_tmp;

	seam[y] = 0;

	/* {{{ get seam */
	for (x = 0; x < gdImageSX(im); x++) {
		if (min_last_m > gdImageTrueColorPixel(m_im, x, y)) {
			seam[y] = x;
			min_last_m = gdImageTrueColorPixel(m_im, x, y);
		}
	}

	for (y = gdImageSY(im) - 2; y >= 0; y--) {
		x = seam[y + 1];
		m = gdImageTrueColorPixel(m_im, x, y + 1);
		pre_m = m - gdImageTrueColorPixel(energy_im, x, y + 1);
		if (pre_m == gdImageTrueColorPixel(m_im, x, y))
			seam[y] = x;
		else if (pre_m == gdImageTrueColorPixel(m_im, x - 1, y))
			seam[y] = x - 1;
		else if (pre_m == gdImageTrueColorPixel(m_im, x + 1, y))
			seam[y] = x + 1;
	}
	/* }}} end of get seam */

	for (y = 0; y < gdImageSY(im); y++) {
		for (x = seam[y]; x < gdImageSX(im) - 1; x++) {
			gdImageSetPixel(im, x, y, gdImageTrueColorPixel(im, x + 1, y));
			gdImageSetPixel(energy_im, x, y, gdImageTrueColorPixel(energy_im, x + 1, y));
			gdImageSetPixel(m_im, x, y, gdImageTrueColorPixel(m_im, x + 1, y));
		}
	}

	for (y = 0;  y < gdImageSY(im) ; y ++) {
		if (seam[y] > 0) 
			gdImageSetPixel(energy_im, seam[y] - 1, y, _get_image_energy(im, seam[y] - 1, y));
		if (seam[y] != (gdImageSX(im) - 1))
			gdImageSetPixel(energy_im, seam[y]    , y, _get_image_energy(im, seam[y]    , y));
	}
	start = MAX(0, seam[0] - 2);
	end = MIN(gdImageSX(im) - 1, seam[0] + 2);


	y = 0;
	if (seam[y] > 0) 
		gdImageSetPixel(m_im, seam[y] - 1, y, gdImageTrueColorPixel(energy_im, seam[y] - 1, y));
	if (seam[y] != (gdImageSX(im) - 1))
		gdImageSetPixel(m_im, seam[y]    , y, gdImageTrueColorPixel(energy_im, seam[y]    , y));
	for (y = 1; y < gdImageSY(im); y ++) {
		for (x = start; x < end; x++) {
			if (x == 0)
				gdImageSetPixel(m_im, x, y, gdImageTrueColorPixel(energy_im, x, y) + MIN(
							gdImageTrueColorPixel(m_im, x    , y - 1),
							gdImageTrueColorPixel(m_im, x + 1, y - 1)));
			else if (x == gdImageSX(im) - 2)
				gdImageSetPixel(m_im, x, y, gdImageTrueColorPixel(energy_im, x, y) + MIN(
							gdImageTrueColorPixel(m_im, x - 1, y - 1),
							gdImageTrueColorPixel(m_im, x    , y - 1)));
			else 
				gdImageSetPixel(m_im, x, y, gdImageTrueColorPixel(energy_im, x, y) + MIN(MIN(
							gdImageTrueColorPixel(m_im, x - 1, y - 1),
							gdImageTrueColorPixel(m_im, x + 1, y - 1)),
							gdImageTrueColorPixel(m_im, x    , y - 1)));
		}
		start = MAX(0, start - 1);
		end = MIN(gdImageSX(im) - 1, end + 1);
	}

	return;

}
/* }}} _remove_vertical_seam */

#endif /* HAVE_SEAM_CARVING */


/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
