<?php
/* Functions for doing image processing.
*
* Written by: Chris Studholme
* Copyright: GPL (http://www.fsf.org/copyleft/gpl.html)
* $Id: ImageProcessing.php,v 1.17 2003/06/17 03:46:06 cstudhol Exp $
*/
// this could go somewhere else
if ($path_binaries)
putenv("PATH=".getenv("PATH").":".$path_binaries);
// check if the given binary is executable
function check_command($command) {
$command_line = "which ".escapeshellarg($command);
$messages = array();
exec($command_line." 2>&1",$messages,$ret);
return ($ret==0);
}
/* Image manipulation functions exist within a class and have the following
* prototype:
*
* function process($dest_filename, $dest_type,
* $src_filename, $src_type,
* $image_parameters);
*
* The $image_parameters argument is an associative array. The following are
* standard image manipulation parameters:
*
* "orientation" => R - rotate R degrees
* "rotate" => R - rotate R degrees
* "flip" => 1 or 2 - 1=horizontal, 2=vertical
* "bound" => (W,H) - maximum width and height of photo
* "crop" => (W,H,X,Y) - crop: width,height,xoffset,yoffset
* "norm" => (B,W) - normalize intensity: black %, white %
* "gamma" => G - gamma correction (additive)
* "gamma_net" => G - net gamma correction
* "jpeg_qual" => Q - jpeg quality value in [0,100]
* "jpeg_prog" => true - output a progressive jpeg file
* "comment" => "string" - include comment in output file
* "fast" => true - sacrifice quality to make processing fast
*
* The $dest_type and $src_type arguments are strings are mime-types.
*
* The function should return true if it was successful, false if is was
* unable to process the request, or an associate array if the request was
* attempted but failed. The array contains keys:
* command - command line attempted
* retval - return value
* messages - output messages
*/
class ImageProcessing_convert {
function process($dest_filename, $dest_type, $src_filename, $src_type,
$image_parameters) {
if (!check_command("convert"))
return false;
$options=array();
reset($image_parameters);
while (list($key,$value)=each($image_parameters)) {
switch ($key) {
case "rotate":
case "orientation":
$options[] = "-rotate ".(int)$value;
break;
case "flip":
// need to fill in code here!
break;
case "bound":
$options[] = "-sample ".((int)$value[0])."x".((int)$value[1]);
break;
case "crop":
$options[] = "-crop ".((int)$value[0])."x".((int)$value[1]).
"+".((int)$value[2])."+".((int)$value[3]);
break;
case "norm":
if ($value)
$options[] = "-normalize";
break;
case "gamma_net":
$options[] = "-gamma ".(float)$value;
break;
case "comment":
if ($value)
$options[] = "-comment ".escapeshellarg($value);
break;
case "jpeg_qual":
$options[] = "-quality ".(int)$value;
break;
//case "jpeg_prog":
//if ($value)
// $options .= "";
//break;
//case "fast":
//if ($value)
// $options .= "";
//break;
}
}
$command_line = "convert";
reset($options);
while(list($key,$op)=each($options)) {
$command_line = " ".$op;
}
$command_line .= " ".escapeshellarg($src_filename);
$command_line .= " ".escapeshellarg($dest_filename);
$messages = array();
exec($command_line." 2>&1",$messages,$ret);
if ($ret==0)
return true;
return array("command"=>$command_line,
"messages"=>$messages,
"retval"=>$ret);
}
}
class ImageProcessing_netpbm {
function process($dest_filename, $dest_type, $src_filename, $src_type,
$image_parameters) {
$fast = $image_parameters["fast"];
$operations = array(); // key is order of operations
reset($image_parameters);
while (list($key,$value)=each($image_parameters)) {
switch ($key) {
case "orientation":
if (!check_command("pnmflip"))
return false;
switch ($value) {
case 90:
$operations[0] = "pnmflip -r270";
break;
case 180:
$operations[0] = "pnmflip -r180";
break;
case 270:
$operations[0] = "pnmflip -r90";
break;
}
break;
case "rotate":
if (!check_command("pnmrotate"))
return false;
$operations[0] = "pnmrotate ".(int)$value;
break;
case "flip":
if (!check_command("pnmflip"))
return false;
switch ($value) {
case 1:
$operations[5] = "pnmflip -lr";
break;
case 2:
$operations[5] = "pnmflip -tb";
break;
}
break;
case "crop":
if (!check_command("pnmcut"))
return false;
$operations[10] = "pnmcut ".((int)$value[2])." ".((int)$value[3]).
" ".((int)$value[0])." ".((int)$value[1]);
break;
case "norm":
if (!check_command("ppmnorm"))
return false;
$command = "ppmnorm";
if ($value[0]>0)
$command .= " -bpercent ".(int)$value[0];
else if ($value[0]==0)
$command .= " -bvalue 0";
if ($value[1]>0)
$command .= " -wpercent ".(int)$value[1];
else if ($value[1]==0)
$command .= " -wvalue 0";
$operations[50] = $command;
break;
case "gamma_net":
if (!check_command("pnmgamma"))
return false;
if ($value>0)
$operations[60] = "pnmgamma ".(float)$value;
break;
case "bound":
if (!check_command("pnmscale"))
return false;
$operations[99] = "pnmscale -xysize ".
((int)$value[0])." ".((int)$value[1]);
break;
}
}
// read input file
switch ($src_type) {
case "image/jpeg":
//if (!check_command("jpegtopnm")) return false;
$command_line = "jpegtopnm";
if ($fast)
$command_line .= " -nosmooth -dct fast";
break;
case "image/tiff":
if (!check_command("tifftopnm"))
return false;
$command_line = "tifftopnm";
break;
default:
// input type not supported
return false;
}
$command_line .= " ".escapeshellarg($src_filename);
// sort operations
ksort($operations);
// append operations
reset($operations);
while (list($key,$op)=each($operations)) {
$command_line .= " |".$op;
}
// write output file
switch ($dest_type) {
case "image/jpeg":
if (!check_command("jpegtopnm"))
return false;
$command_line .= " |pnmtojpeg";
if ($image_parameters["jpeg_qual"])
$command_line .= " --quality=".(int)$image_parameters["jpeg_qual"];
if ($image_parameters["jpeg_prog"])
$command_line .= " --progressive";
if ($image_parameters["comment"])
$command_line .= " --comment=".
escapeshellarg(trim($image_parameters["comment"]));
if ($fast)
$command_line .= " --dct=fast";
break;
default:
// output type not supported
return false;
}
$command_line .= " >".escapeshellarg($dest_filename);
$messages = array();
exec("sh -c \"".$command_line."\" 2>&1",$messages,$ret);
if ($ret==0)
return true;
return array("command"=>$command_line,
"messages"=>$messages,
"retval"=>$ret);
}
}
class ImageProcessing_kdc2jpeg {
function process($dest_filename, $dest_type, $src_filename, $src_type,
$image_parameters) {
if (!check_command("kdc2jpeg"))
return false;
$options=array();
reset($image_parameters);
while (list($key,$value)=each($image_parameters)) {
switch ($key) {
case "orientation":
case "rotate":
switch ($value) {
case 90:
$options[] = "+R";
break;
case 270:
$options[] = "+B";
break;
}
break;
case "flip":
break;
case "bound":
$options[] = "-p";
$options[] = "-w ".((int)$value[0]);
$options[] = "-h ".((int)$value[1]);
break;
//case "crop":
//$options[] = "-crop ".((int)$value[0])."x".((int)$value[1]).
//"+".((int)$value[2])."+".((int)$value[3]);
//break;
case "norm":
if ($value)
$options[] = "+e";
break;
case "gamma_net":
$options[] = "-g ".(float)$value;
break;
case "comment":
if ($value)
$options[] = "-C ".escapeshellarg($value);
break;
case "jpeg_qual":
$options[] = "-q ".(int)$value;
break;
case "jpeg_prog":
if ($value)
$options[] = "+P";
break;
case "fast":
if ($value)
$options[] = "+f";
break;
}
}
if (!$image_parameters["norm"])
$options[] = "-e";
$command_line = "kdc2jpeg";
reset($options);
while(list($key,$op)=each($options)) {
$command_line .= " ".$op;
}
$command_line .= " ".escapeshellarg($src_filename);
$command_line .= " ".escapeshellarg($dest_filename);
$messages = array();
exec("sh -c \"".$command_line."\" 2>&1",$messages,$ret);
if ($ret==0)
return true;
return array("command"=>$command_line,
"messages"=>$messages,
"retval"=>$ret);
}
}
/* This array lists the various classes available for doing image processing
* and the types of input and output they can handle. Both mimetype_src and
* mimetype_dest must be arrays and they indicated that the class can process
* input of any mimetype_src to produce output of any mimetype_dest.
*/
$image_manipulations =
array(
array("class"=>"ImageProcessing_netpbm",
"mimetype_src"=>array("image/tiff","image/jpeg"),
"mimetype_dest"=>array("image/jpeg")),
array("class"=>"ImageProcessing_convert",
"mimetype_src"=>array("image/tiff","image/jpeg"),
"mimetype_dest"=>array("image/jpeg")),
array("class"=>"ImageProcessing_kdc2jpeg",
"mimetype_src"=>array("image/x-kdc","image/tiff","image/jpeg"),
"mimetype_dest"=>array("image/jpeg")),
);
function process_image($dest_filename,$mimetype_dest,
$src_filename,$mimetype_src,
$image_parameters) {
global $image_manipulations;
reset($image_manipulations);
while (list($index,$process)=each($image_manipulations)) {
if (in_array($mimetype_dest,$process["mimetype_dest"])&&
in_array($mimetype_src,$process["mimetype_src"])) {
$class = $process["class"];
$instance = new $class;
$result = $instance->process($dest_filename,$mimetype_dest,
$src_filename,$mimetype_src,
$image_parameters);
if ($result)
return $result;
}
}
return false;
}
/* Specifying a data type:
* associative array with "type"=>type, "name"=>name,
* "description"=>description, "units"=>units,
* and possible other parameters
*
* Data types:
* boolean - no parameters
* int - optional: min, max (bound)
* float - optional: min, max (bound)
* text - optional: min, max (length)
* enum - required: options
* list - required: elements
* array - required: length, element
*/
$image_options =
array(
"orientation"=>array("type"=>"enum",
"options"=>array(0=>"none",
90=>"clockwise",
270=>"counter-clockwise",
180=>"upsidedown"),
"default"=>0,
"name"=>"Orientation",
"description"=>"Rotate image by a multiple of 90 degrees."),
"rotate"=>array("type"=>"int",
"min"=>-359,
"max"=>359,
"default"=>0,
"units"=>"degrees",
"name"=>"Precise rotate",
"description"=>"Rotate image by a specific angle."),
"flip"=>array("type"=>"enum",
"options"=>array(0=>"none",
1=>"horizontal",
2=>"vertical"),
"default"=>0,
"name"=>"Flip",
"description"=>"Flip image horizontally or vertically."),
"scale"=>array("type"=>"float",
"min"=>0.01,
"max"=>100,
"default"=>1,
"units"=>"<1 for smaller, >1 for larger",
"name"=>"Scale",
"description"=>"Scale image by specified factor."),
"bound"=>array("type"=>"list",
"elements"=>array(array("type"=>"int",
"min"=>1,
"default"=>1,
"units"=>"pixels",
"description"=>"width"),
array("type"=>"int",
"min"=>1,
"default"=>1,
"units"=>"pixels",
"description"=>"height")),
"name"=>"Size bound",
"description"=>"Maximum width and height of image."),
"crop"=>array("type"=>"list",
"elements"=>array(array("type"=>"int",
"min"=>1,
"default"=>1,
"units"=>"pixels",
"description"=>"width"),
array("type"=>"int",
"min"=>1,
"default"=>1,
"units"=>"pixels",
"description"=>"height"),
array("type"=>"int",
"min"=>0,
"default"=>0,
"units"=>"pixels",
"description"=>"x-offset"),
array("type"=>"int",
"min"=>0,
"default"=>0,
"units"=>"pixels",
"description"=>"y-offset")),
"name"=>"Crop",
"description"=>"Crop image."),
"norm"=>array("type"=>"list",
"elements"=>array(array("type"=>"int",
"min"=>-1,
"max"=>100,
"default"=>-1,
"units"=>"percent, -1 for default",
"description"=>"black"),
array("type"=>"int",
"min"=>-1,
"max"=>100,
"default"=>-1,
"units"=>"percent, -1 for default",
"description"=>"white")),
"name"=>"Normalize intensity",
"description"=>"Normalize image intensity."),
"gamma"=>array("type"=>"float",
"min"=>0.01,
"max"=>100,
"default"=>1,
"units"=>"<1 to darken, >1 to make brighter",
"name"=>"Gamma correction",
"description"=>"Gamma correction factor (additive)."),
"jpeg_qual"=>array("type"=>"int",
"min"=>1,
"max"=>100,
"default"=>90,
"units"=>"maximum is 100",
"name"=>"JPEG quality",
"description"=>"JPEG quality setting."),
"jpeg_prog"=>array("type"=>"boolean",
"name"=>"JPEG progressive",
"description"=>"Output a progressive JPEG file."),
"comment"=>array("type"=>"text",
"name"=>"Comment/copyright",
"description"=>"Comment to include in output file."),
);
/* Merge option arrays.
*/
function options_merge($op1, $op2) {
if (!$op1["gamma_net"]&&$op1["gamma"]>0)
$op1["gamma_net"] = (float)$op1["gamma"];
$result = array_merge($op1,$op2);
if ($op2["gamma"]>0) {
$result["gamma_net"] = $result["gamma_net"]>0 ?
$result["gamma_net"]*$op2["gamma"] : (float)$op2["gamma"];
}
return $result;
}
function get_type_default($type) {
if (isset($type["default"]))
return $type["default"];
switch ($type["type"]) {
case "int":
case "float":
return 0;
case "boolean":
return false;
case "text":
return "";
case "enum":
reset($type["options"]);
return key($type["options"]);
case "array":
$result = array();
for ($i=0; $i<$type["length"]; ++$i)
$result[] = get_type_default($type["element"]);
return $result;
case "list":
$result = array();
for ($i=0; $i<count($type["elements"]); ++$i)
$result[] = get_type_default($type["elements"][$i]);
return $result;
}
return false;
}
// processes value and if it's valid returns true,
// otherwise an error message (string) is returned
function verify_value(&$value,$type) {
switch ($type["type"]) {
case "boolean":
$value = ($value ? true : false);
break;
case "int":
$value = (int)$value;
if (isset($type["min"])&&($value<$type["min"]))
return "minimum value is ".$type["min"];
if (isset($type["max"])&&($value>$type["max"]))
return "maximum value is ".$type["max"];
break;
case "float":
$value = (float)$value;
if (isset($type["min"])&&($value<$type["min"]))
return "minimum value is ".$type["min"];
if (isset($type["max"])&&($value>$type["max"]))
return "maximum value is ".$type["max"];
break;
case "text":
$value = strval($value);
if (isset($type["min"])&&(strlen($value)<$type["min"]))
return "minimum length is ".$type["min"];
if (isset($type["max"])&&(strlen($value)>$type["max"]))
return "maximum length is ".$type["max"];
break;
case "enum":
$value = strval($value);
reset($type["options"]);
while (list($v,$a)=each($type["options"])) {
if ($value==strval($v)) {
$value = $v;
return true;
}
}
return "invalid option";
default:
return "unknown type";
}
return true;
}
// input options (ignores unknown options)
function input_options($vars) {
global $image_options;
$options = array();
reset($vars);
while (list($key,$value)=each($vars)) {
if (ereg("^option_(.*)$",$key,$regs)) {
$key = $regs[1];
if ($image_options[$key]) {
switch ($image_options[$key]["type"]) {
case "int":
case "float":
case "boolean":
case "text":
case "enum":
$err = verify_value($value,$image_options[$key]);
break;
default:
fatal_error("bad_parameter");
}
if (is_bool($err))
$options[$key] = $value;
else {
echo tag("p",$err);
fatal_error("bad_parameter");
}
}
else if (ereg("^(.*)_([0-9]+)$",$key,$regs)) {
$key = $regs[1];
$index = $regs[2];
if ($image_options[$key]) {
switch ($image_options[$key]["type"]) {
case "list":
$err =
verify_value($value,$image_options[$key]["elements"][$index]);
break;
case "array":
$err = verify_value($value,$image_options[$key]["element"]);
break;
default:
fatal_error("bad_parameter");
}
if (is_bool($err))
$options[$key][$index] = $value;
else {
echo tag("p",$err);
fatal_error("bad_parameter");
}
}
}
}
}
return $options;
}
/*
function read_tiff_info($path) {
$handle = fopen($path,"r");
if (!$handle)
return false;
$sig = fread($handle,4);
fclose($handle);
$end = substr($sig,0,2);
$result = array();
if ($end=="MM") {
$check = ord($sig{2})*256+ord($sig{3});
if ($check!=42)
return false;
$result["ByteOrder"] = "Motorola";
}
else if ($end=="II") {
$check = ord($sig{3})*256+ord($sig{2});
if ($check!=42)
return false;
$result["ByteOrder"] = "Intel";
}
else
return false; // not a tiff file
function unquote($str) {
if (ereg("^\"(.*)\"$",$str,$regs))
return $regs[1];
return $str;
}
$tag_map = array("Samples/Pixel"=>"Channels",
"Bits/Sample"=>"BitsPerChannel",
"Rows/Strip"=>"RowsPerStrip",
"Date & Time"=>"DateTime",
"Make"=>"CameraMake",
"Model"=>"CameraModel",
"Compression Scheme"=>"CompressionScheme",
"Document Name"=>"DocumentName",
"FillOrder"=>"FillOrder",
"Image Description"=>"ImageDescription",
"Orientation"=>"Orientation",
"Photometric Interpretation"=>"PhotometricInterpretation",
"Planar Configuration"=>"PlanarConfiguration",
"Predictor"=>"Predictor",
"Primary Chromaticities"=>"PrimaryChromaticities",
"Resolution"=>"Resolution",
"Software"=>"Software",
"Subfile Type"=>"SubfileType",
"SubIFD Offsets"=>"SubIFDOffsets",
"White Point"=>"WhitePoint",
);
$info = shell_exec("sh -c \"tifftopnm --headerdump ".escapeshellarg($path).
" >/dev/null\" 2>&1");
$info = explode("\n",$info);
for ($i=0; $i<count($info); ++$i) {
if (ereg("^([^:]*):(.*)$",$info[$i],$regs)) {
$key = trim($regs[1]);
$value = trim($regs[2]);
switch ($key) {
case "Image Width":
ereg("^([0-9]*)[^0-9]*([0-9]*)$",$value,$regs);
$result["Width"] = (int)$regs[1];
$result["Height"] = (int)$regs[2];
break;
case "tifftopnm":
case $path:
break;
default:
if ($tag_map[$key])
$result[$tag_map[$key]]=unquote($value);
else
$result["TiffOtherTags"][$key]=$value;
}
}
}
if ($result["SubfileType"]=="reduced-resolution image (1 = 0x1)") {
if ($result["Width"]) {
$result["ThumbnailWidth"]=$result["Width"];
unset($result["Width"]);
}
if ($result["Height"]) {
$result["ThumbnailHeight"]=$result["Height"];
unset($result["Height"]);
}
}
return $result;
}
function GetFileInfo($path) {
if (!is_file($path)||!is_readable($path))
return false;
$result = array();
$result["FilePath"] = $path;
$result["FileName"] = basename($path);
$result["FileSize"] = filesize($path);
$result["FileDateTime"] = filemtime($path);
// GetImageSize() data
$type_mapping = array("1"=>"image/gif",
"2"=>"image/jpeg",
"3"=>"image/png",
"4"=>"application/x-shockwave-flash",
//"5"=>"PSD",
"6"=>"image/bmp");
@$size = GetImageSize($path,&$extrainfo);
if ($size[0]>0)
$result["Width"]=$size[0];
if ($size[1]>0)
$result["Height"]=$size[1];
if ($type_mapping[$size[2]])
$result["MimeType"]=$type_mapping[$size[2]];
if ($size["bits"]>0)
$result["BitsPerChannel"]=$size["bits"];
if ($size["channels"]>0)
$result["Channels"]=$size["channels"];
//if ($extrainfo&&is_array($extrainfo))
// $result["ExtraInfo"] = $extrainfo;
// EXIF data
@$exif = read_exif_data($path);
if ($exif&&is_array($exif))
$result = array_merge($result,$exif);
// TIFF data
@$tiff = read_tiff_info($path);
if ($tiff&&is_array($tiff)) {
$result["MimeType"]="image/tiff";
$result = array_merge($result,$tiff);
}
return $result;
}
*/
?>