<?php
/*
PiAnoS (Picture Annotation System), A fingerprint examiner training tool
Copyright (C) 2011-2012 University of Lausanne, Switzerland
https://ips-labs.unil.ch/pianos

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see http://www.gnu.org/licenses/.
*/


// Installs an Exception "thrower" for all PHP errors - all of them, including E_NOTICE
// which *are* damn errors, no matter what the PHP user community thinks.
function handleError($errno, $errstr, $errfile, $errline, $errcontext)
{
	throw new Exception("PHP error in ".$errfile."(".$errline."): ".$errstr." (errno=".$errno.")");
}
set_error_handler('handleError');

// Executes an arbitrary command with passhtru(). The output is put in a temporary buffer and
// returned. Any problem (invalid command, return code != 0, ...) will raise an exception - the 
// catch clause is used as a clean-up routine, but the exception will still pass through.
function execCommand($command)
{
	$content = NULL;
	try
	{
		ob_start();
		$ret = -1;
		passthru($command, $ret);
		if ($ret !== 0)
			throw new Exception('Error while executing ImageMagick command: return code '.$ret);
		$content = ob_get_clean();
		if (!$content)
			throw new Exception('Error while executing ImageMagick command: returned an empty image');
	}
	catch (Exception $e)
	{
		while (ob_get_level())
			ob_end_clean();
		throw $e;
	}
	return $content;
}

// Counts leading whitespaces the naive way - is there a standard PHP function to do that ?
function spaceCount($str)
{
	$i = 0;
	$l = strlen($str);
	while ($i < $l && $str[$i] == ' ')
		++$i;
	return $i;
}
// Removes the 'Histogram' and 'Colormap' sections of the image - the user really
// doesn't need this information. It would be *way* simpler to simply tell ImageMagick
// not to output these sections, but I couldn't find how to achieve this.
function stripImageInfo($info)
{
	$lines = explode(PHP_EOL, $info);
	$newlines = array();
	$discard = array('Histogram','Colormap');
	$discarding = false;
	foreach ($lines as $line)
	{
		$sp = spaceCount($line);
		if ($sp == 2)
		{
			$token = ltrim($line);
			$x = strpos($token, ":");
			$token = substr($token, 0, $x);
			$isKeyword = in_array($token, $discard);
			if (!$discarding && $isKeyword)
			{
				$discarding = true;
			}
			elseif ($discarding && !$isKeyword)
			{
				$discarding = false;
			}
		}
		
		if ($discarding)
			continue;
		
		$newlines[] = $line;
	}
	return implode("\n", $newlines);
}

// Use the 'identify' command to get detailed information about the image. Send it in
// a JSON object containing a status code (success) and the return value (ret), which
// is a big string. This format is a bit overkill here - a simple text/plain answer could
// have sufficed, but desirable in general as it allows the Javascript side to get 
// information about errors when required. (We can i.e. respond with an HTTP 200 and send
// a JSON object with 'success' set to false and a 'message' field describing the error).
function sendImageInfo($url)
{
	$command = 'F:\\dev\\local\\ImageMagick-6.8.7-1\\identify.exe -verbose '.escapeshellarg($url);
	$contentRaw = execCommand($command);
	$contentStripped = stripImageInfo($contentRaw);
	$contentPHP = array('success'=>true,'ret'=>$contentStripped);
	$content = json_encode($contentPHP);

	header("Content-Type: application/json; charset=utf-8");
	header("Content-Length: ".strlen($content));
	echo $content;
	exit;
}

//
function sendFilteredImage($filters)
{
	$program = 'F:\\dev\\local\\ImageMagick-6.8.7-1\\convert.exe';
	$params = array();
	
	$params[] = $filters[0]['url'];
	
	foreach ($filters as $filter)
	{
		if ($filter['type'] == 'original')
			continue;
			
		switch ($filter['type'])
		{
		case 'brightness_contrast':
			$params[] = '-brightness-contrast';
			$params[] = $filter['brightness'].'x'.$filter['contrast'];
			break;
			
		case 'invert':
			if ($filter['grayscale'])
				$params[] = '+negate';
			else
				$params[] = '-negate';
			break;
			
		default:
			throw new Exception("Invalid filter type: ".$filter['type']);
		}
	}
	
	$params[] = 'png:-';
	
	$command = $program.' ';
	foreach ($params as $p)
		$command .= escapeshellarg($p).' ';

	$content = execCommand($command);
	
	header("Content-Type: image/png");
	header("Content-Length: ".strlen($content));
	echo $content;
	exit;
}


// main() function, all enclosed in a try-catch block.
try
{	
	if (!isset($_GET['action']))
		throw new Exception("Invalid parameters");

	switch ($_GET['action'])
	{
	case 'get':
		if (!isset($_GET['filters']))
			throw new Exception("Invalid parameters");
			
		$filters = json_decode($_GET['filters'], true);
		if (count($filters) < 1 || $filters[0]['type'] != 'original')
			throw new Exception("Invalid parameters");
		
		sendFilteredImage($filters);
		
		break;
	
	case 'info':
		if (!isset($_GET['url']) || strlen($_GET['url']) == 0)
			throw new Exception("Invalid parameters");
		
		sendImageInfo($_GET['url']);
		
		break;
		
	default:
		throw new Exception("Invalid parameters");
	}
}
catch (Exception $e)
{
	// Unhandled exceptions raise an HTTP 500 error, showing a stack trace. If you think this is
	// a security risk, you are right - and you should fix the error. We need the HTTP 500 and not
	// the HTTP 200 with a JSON object with 'success' set to false, because in the general case
	// we don't know what data type the client expects (it may be an image, or a JSON object, or
	// text, or whatever).
	header("HTTP/1.1 500 Internal Server Error");
	header("Content-Type: text/plain; charset=utf-8");
	echo "The server encountered an unexpected error. We are sorry for the inconvenience.\nDetails about the error follow.\n\n";
	echo "Message: ".$e->getMessage()."\n";
	echo "File: ".$e->getFile()."(".$e->getLine().")\n";
	echo "Stack trace:\n";
	echo $e->getTraceAsString();
	echo "\n\n";
	
	echo "GET parameters:\n";
	var_dump($_GET);
	if (isset($_GET['filters']))
	{
		echo "\nDecoded filter:\n";
		var_dump(json_decode($_GET['filters'], true));
	}
}

?>
