<?
// ***********************************************************************************
// * ImageBooth by Abbey Hawk Sparrow
// ***********************************************************************************
// * This class abstracts image manipulations functions into an Object Oriented
// * abstraction layer. The purpose of this is to make working with images in code
// * equivilent to using your favorite image editor. You can expect, for the near term
// * my priority will be to implement most of what older releases of photoshop provide.
// *
// * This started as a preprocessor to optimize images for OCR, but is expanding in
// * scope. I will listen to feature requests.
// ***********************************************************************************
// * KEY FEATURES *
// ****************
// * 1) Layers
// * 2) Sizing: Image and Canvas
// * 3) Filters: blur, threshold, greyscale, emboss, lines and edges
// ***********************************************************************************
// * EXAMPLE *
// ***********
// * Let's say I want a greyscale thumbnail with a watermark:
// * $image = ImageBooth::newImage(imagecreatefromjpeg('./image.jpg'));
// * $image->filter('greyscale');
// * $image->newLayer(imagecreatefrompng('./watermark_overlay.png'));
// * $image->resize(50, 50);
// * $image->dumpToBrowser();
// ***********************************************************************************
// pre PHP 5.1 convolver function credit to: mgcclx at gmail dot com
if(!function_exists('imageconvolution')){ function imageconvolution($src, $filter, $filter_div, $offset){
if ($src==NULL) return 0; $sx = imagesx($src); $sy = imagesy($src); $srcback = ImageCreateTrueColor ($sx, $sy); ImageCopy($srcback, $src,0,0,0,0,$sx,$sy); if($srcback==NULL) return 0; $pxl = array(1,1); for ($y=0; $y<$sy; ++$y){ for($x=0; $x<$sx; ++$x){ $new_r = $new_g = $new_b = 0; $alpha = imagecolorat($srcback, $pxl[0], $pxl[1]); $new_a = $alpha >> 24; for ($j=0; $j<3; ++$j) { $yv = min(max($y - 1 + $j, 0), $sy - 1); for ($i=0; $i<3; ++$i) { $pxl = array(min(max($x - 1 + $i, 0), $sx - 1), $yv); $rgb = imagecolorat($srcback, $pxl[0], $pxl[1]); $new_r += (($rgb >> 16) & 0xFF) * $filter[$j][$i]; $new_g += (($rgb >> 8) & 0xFF) * $filter[$j][$i]; $new_b += ($rgb & 0xFF) * $filter[$j][$i]; } } $new_r = ($new_r/$filter_div)+$offset; $new_g = ($new_g/$filter_div)+$offset; $new_b = ($new_b/$filter_div)+$offset; $new_r = ($new_r > 255)? 255 : (($new_r < 0)? 0:$new_r); $new_g = ($new_g > 255)? 255 : (($new_g < 0)? 0:$new_g); $new_b = ($new_b > 255)? 255 : (($new_b < 0)? 0:$new_b); $new_pxl = ImageColorAllocateAlpha($src, (int)$new_r, (int)$new_g, (int)$new_b, $new_a); if ($new_pxl == -1) $new_pxl = ImageColorClosestAlpha($src, (int)$new_r, (int)$new_g, (int)$new_b, $new_a); if (($y >= 0) && ($y < $sy)) imagesetpixel($src, $x, $y, $new_pxl); } } imagedestroy($srcback); return 1; }
}
class ImageBooth{
private static $initialized = false;
private static $filters = false;
public static $color = '000000';
public static $background_color = 'FFFFFF';
public static function newImage($var1 = null, $var2 = null, $var3 = null){
ImageBooth::checkInit();
return new ImBo_Image($var1, $var2, $var3);
}
public static function filter($name, $image, $controls = null){
ImageBooth::checkInit();
if(isset(ImageBooth::$filters[$name])) return ImageBooth::$filters[$name]->filter($image, $controls);
else throw new Exception('Filter '.$name.' unsupported!');
}
protected static function checkInit(){
if(!ImageBooth::$initialized){
ImageBooth::$filters['blur'] = new ImBo_Gaussian_Blur_Filter();
ImageBooth::$filters['threshold'] = new ImBo_Threshold_Filter();
ImageBooth::$filters['greyscale'] = new ImBo_Greyscale_Filter();
ImageBooth::$filters['emboss'] = new ImBo_Gradient_Detection_Filter();
ImageBooth::$filters['lines'] = new ImBo_Line_Detection_Filter();
ImageBooth::$filters['edges'] = new ImBo_Sobel_Filter();
ImageBooth::$initialized = true;
}
}
public static function threshold(&$original_image, $new_image, $threshold){
//TODO: thresholding function (original vs modified)
}
public static function convolve(&$image, $matrix){
return imageconvolution($image, $matrix, 16, 0);
}
private static function load_filters($directory){
//TODO: externally loadable filters
}
}
class ImBo_Image{
private $layers = array();
private $height = null;
private $width = null;
function __construct($var1 = null, $var2 = null, $var3 = null){ //(image), (x, y), (x, y, bit_depth), (x, y, color)
if(is_numeric($var1) && is_numeric($var2)){
// we have x & y
$this->width = $var1;
$this->height = $var2;
}else if($var1 != null){
// we have an image
$image = $var1;
$this->width = imagesy($image);
$this->height = imagesx($image);
$this->newLayer($image);
}
}
public function filter($name, $controls = null){
$layer = current($this->layers);
return $layer->filter($name, $controls);
}
public function dumpToBrowser(){
$image = $this->image();
header("Content-type: image/png");
imagepng($image);
imagedestroy($image);
}
public function newLayer($image = null){
imagealphablending($image, false);
imagesavealpha($image, true);
$this->layers[] = new ImBo_Layer($image);
return $this->layers[count($this->layers)-1];
}
public function selectLayer($name){
//todo
}
public function composite($dst_image, $src_image){
imagecopyresampled($dst_image,$src_image, 0,0,0,0, imagesx($src_image),imagesy($src_image),imagesx($dst_image),imagesy($dst_image));
return $dst_image;
}
public function image(){
if(count($this->layers) == 1){
$arr = array_keys($this->layers);
return $this->layers[$arr[0]]->getImage();
}else{
//composite the layers
$composite_image = imagecreatetruecolor( $this->height, $this->width );
$transparent_color = imagecolorallocate($composite_image, 255, 0, 0);
imagecolortransparent($composite_image, $transparent_color);
imagefilledrectangle($composite_image,0,0,imagesx($composite_image),imagesy($composite_image),$transparent_color);
foreach($this->layers as $layer){
$layer_image = $layer->getImage();
$composite_image = $this->composite($composite_image, $layer_image);
}
return $composite_image;
}
}
public function resize($height, $width){
$this->height = $height;
$this->width = $width;
foreach($this->layers as $layer) $layer->resize($height, $width);
}
public function resizeCanvas($height, $width, $horizontalAnchor='middle', $verticalAnchor='center'){
$this->height = $height;
$this->width = $width;
foreach($this->layers as $layer) $layer->resizeCanvas($height, $width, $horizontalAnchor, $verticalAnchor);
}
public function rgbAt($x, $y){
$image = $this->image();
$rgb = imagecolorat($image, $x, $y);
return array(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
}
//public function crop($x, $y, $width, $height){ }
//public function rotate($degrees){ }
//public function scale($vertical, $horizontal){ }
//public function skew($top_offset, $bottom_offset){ }
//public function brightnessAndContrast($brightness, $contrast){ }
//public function freeTransform($p1_x_drift, $p1_y_drift, $p2_x_drift, $p2_y_drift, $p3_x_drift, $p3_y_drift, $p4_x_drift, $p4_y_drift){ }
}
class ImBo_Layer{
private $image;
//private $mask = null;
//private $shapes = array();
function __construct($image, $name = null){
$this->image = $image;
}
public function getImage(){
return $this->image;
}
public function resize($height, $width){
$image_resized = imagecreatetruecolor( $height, $width );
imagealphablending($image_resized, false);
imagesavealpha($image_resized, true);
imagecopyresampled($image_resized, $this->image, 0,0,0,0,$height, $width, imagesx($this->image),imagesy($this->image));
$this->image = $image_resized;
}
public function resizeCanvas($height, $width, $horizontalAnchor='middle', $verticalAnchor='center'){
//calc x & y based on alignment
$current_width = imagesx($this->image);
$current_height = imagesy($this->image);
switch($horizontalAnchor){
case 'left':
$x = 0;
break;
case 'middle':
$current_middle = ($current_width/2);
$new_middle = ($width/2);
$x = $new_middle - $current_middle;
break;
case 'right':
$x = $width - $current_width;
break;
}
switch($verticalAnchor){
case 'top':
$y = 0;
break;
case 'center':
$current_center = ($current_height/2);
$new_center = ($height/2);
$y = $new_center - $current_center;
break;
case 'bottom':
$y = $height - $current_height;
break;
}
$image_resized = imagecreatetruecolor( $height, $width );
imagealphablending($image_resized, false);
imagesavealpha($image_resized, true);
$transparent_color = imagecolorallocate($image_resized, 255, 0, 0);
imagecolortransparent($image_resized, $transparent_color);
imagefilledrectangle($image_resized,0,0,imagesx($image_resized),imagesy($image_resized),$transparent_color);
imagecopyresampled($image_resized, $this->image, $x,$y,0,0, $current_width, $current_height, $current_width, $current_height);
$this->image = $image_resized;
}
public function move(){
}
public function filter($name, $controls = null){
$this->image = ImageBooth::filter($name, $this->image, $controls);
}
public function rgbAt($x, $y){
$rgb = imagecolorat($this->image, $x, $y);
return array(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
}
}
abstract class ImBo_Filter{
public abstract function name();
public abstract function filter(&$image, $controls=null);
public function getControls(){
return array();
}
}
// DEFAULT FILTERS
class ImBo_Gaussian_Blur_Filter extends ImBo_Filter{
public function name(){ return 'blur';}
private $gaussian_blur_matrix = array(
array( 1.0, 2.0, 1.0 ),
array( 2.0, 4.0, 2.0 ),
array( 1.0, 2.0, 1.0 )
);
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
//set up control values
$amount = $controls['amount'];
$radius = $controls['radius'];
$threshold = $controls['threshold'];
if ($amount > 500) $amount = 500;
$amount = ((float)$amount) * 0.016;
if ($radius > 50) $radius = 50;
$radius = $radius * 2;
if ($threshold > 255) $threshold = 255;
$radius = abs(round($radius)); // Only integers make sense.
if ($radius == 0) { return $image; imagedestroy($image); break; }
// make a copy to blur
$blurred_image = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($blurred_image, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
//blur
ImageBooth::convolve($blurred_image, $this->gaussian_blur_matrix);
return $blurred_image;
}
public function getControls(){
// attempts to mimic PS settings
return array(
'amount' => array(
'value' => '500',
'default' => '500',
'upper_bound' => '500',
'lower_bound' => '0'
),
'radius' => array(
'value' => '50',
'default' => '50',
'upper_bound' => '50',
'lower_bound' => '0'
),
'threshold' => array(
'value' => '255',
'default' => '255',
'upper_bound' => '255',
'lower_bound' => '0'
),
);
}
}
class ImBo_Threshold_Filter extends ImBo_Filter{
public function name(){ return 'threshold';}
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
// make a copy to blur
$im = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($im, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
$level = $controls['level'];
if ($level > 255) $level = 255;
// from class.image.php : Andrew Collington, 2005
// hide@address.com, http://php.amnuts.com/
$sx = imagesx($im);
$sy = imagesy($im);
$black = imagecolorallocate($im, 0, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
for ($x = 0; $x < $sx; $x++) {
for ($y = 0; $y < $sy; $y++) {
$rgb = imagecolorat($image, $x, $y);
$rgb = array(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
$intensity = ($rgb[0] + $rgb[1] + $rgb[2]) / 3;
imagesetpixel($im, $x, $y, ($intensity < $level ? $black : $white));
}
}
return $im;
}
public function getControls(){
// attempts to mimic PS settings
return array(
'level' => array(
'value' => '128',
'default' => '128',
'upper_bound' => '255',
'lower_bound' => '0'
)
);
}
}
class ImBo_Greyscale_Filter extends ImBo_Filter{
public function name(){ return 'greyscale';}
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
// make a copy to blur
$im = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($im, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
$level = $controls['level'];
if ($level > 255) $level = 255;
// from class.image.php : Andrew Collington, 2005
// hide@address.com, http://php.amnuts.com/
$sx = imagesx($im);
$sy = imagesy($im);
for ($x = 0; $x < $sx; $x++) {
for ($y = 0; $y < $sy; $y++) {
//$rgb = Image::getPixelRGB($im, $x, $y);
$rgb = imagecolorat($image, $x, $y);
$rgb = array(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
$colour = ($rgb[0] + $rgb[1] + $rgb[2]) / 3;
imagesetpixel($im, $x, $y, imagecolorallocate($im, $colour, $colour, $colour));
}
}
return $im;
}
}
class ImBo_Line_Detection_Filter extends ImBo_Filter{
public function name(){ return 'line_detect';}
private $horizontal_gradient_matrix = array(
array( -1.0, -1.0, -1.0 ),
array( 2.0, 2.0, 2.0 ),
array( -1.0, -1.0, -1.0 )
);
private $vertical_gradient_matrix = array(
array( -1.0, 2.0, -1.0 ),
array( -1.0, 2.0, -1.0 ),
array( -1.0, 2.0, -1.0 )
);
private $left_diagonal_gradient_matrix = array(
array( 2.0, -1.0, -1.0 ),
array( -1.0, 2.0, -1.0 ),
array( -1.0, -1.0, 2.0 )
);
private $right_diagonal_gradient_matrix = array(
array( -1.0, -1.0, 2.0 ),
array( -1.0, 2.0, -1.0 ),
array( 2.0, -1.0, -1.0 )
);
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
// make a copy to detect
$convolved_image = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($convolved_image, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
//detect
if($controls['direction'] == 'horizontal'){
ImageBooth::convolve($convolved_image, $this->horizontal_gradient_matrix);
}else if($controls['direction'] == 'vertical'){
ImageBooth::convolve($convolved_image, $this->vertical_gradient_matrix);
}else if($controls['direction'] == 'left-diagonal'){
ImageBooth::convolve($convolved_image, $this->left_diagonal_gradient_matrix);
}else if($controls['direction'] == 'right-diagonal'){
ImageBooth::convolve($convolved_image, $this->right_diagonal_gradient_matrix);
}
return $convolved_image;
}
public function getControls(){
// attempts to mimic PS settings
return array(
'direction' => array(
'value' => 'left-diagonal',
'default' => 'left-diagonal',
'enumeration' => 'horizontal,vertical,left-diagonal,right-diagonal'
),
);
}
}
class ImBo_Gradient_Detection_Filter extends ImBo_Filter{
public function name(){ return 'gradient_detect';}
private $north_matrix = array(
array( -1.0, -2.0, -1.0 ),
array( 0.0, 0.0, 0.0 ),
array( 1.0, 2.0, 1.0 )
);
private $west_matrix = array(
array( -1.0, 0.0, 1.0 ),
array( -2.0, 0.0, 2.0 ),
array( -1.0, 0.0, 1.0 )
);
private $east_matrix = array(
array( 1.0, 0.0, -1.0 ),
array( 2.0, 0.0, -2.0 ),
array( 1.0, 0.0, -1.0 )
);
private $south_matrix = array(
array( 1.0, 2.0, 1.0 ),
array( 0.0, 0.0, 0.0 ),
array( -1.0, -2.0, -1.0 )
);
private $north_east_matrix = array(
array( 0.0, -1.0, -2.0 ),
array( 1.0, 0.0, -1.0 ),
array( 2.0, 1.0, 0.0 )
);
private $south_west_matrix = array(
array( 0.0, 1.0, 2.0 ),
array( -1.0, 0.0, 1.0 ),
array( -2.0, -1.0, 0.0 )
);
private $north_west_matrix = array(
array( -2.0, -1.0, 0.0 ),
array( -1.0, 0.0, 1.0 ),
array( 0.0, 1.0, 2.0 )
);
private $south_east_matrix = array(
array( 2.0, 1.0, 0.0 ),
array( 1.0, 0.0, -1.0 ),
array( 0.0, -1.0, -2.0 )
);
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
// make a copy to detect
$convolved_image = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($convolved_image, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
//detect
switch($controls['direction']){
case 'north':
ImageBooth::convolve($convolved_image, $this->north_matrix);
break;
case 'north-east':
ImageBooth::convolve($convolved_image, $this->north_east_matrix);
break;
case 'east':
ImageBooth::convolve($convolved_image, $this->east_matrix);
break;
case 'south-east':
ImageBooth::convolve($convolved_image, $this->south_east_matrix);
break;
case 'south':
ImageBooth::convolve($convolved_image, $this->south_matrix);
break;
case 'south-west':
ImageBooth::convolve($convolved_image, $this->south_west_matrix);
break;
case 'west':
ImageBooth::convolve($convolved_image, $this->west_matrix);
break;
case 'north-west':
ImageBooth::convolve($convolved_image, $this->north_west_matrix);
break;
}
return $convolved_image;
}
public function getControls(){
// attempts to mimic PS settings
return array(
'direction' => array(
'value' => 'left-diagonal',
'default' => 'left-diagonal',
'enumeration' => 'north,north-east,east,south-east,south,south-west,west,north-west'
),
);
}
}
class ImBo_Laplacian_Filter extends ImBo_Filter{
public function name(){ return 'gradient_detect';}
private $laplacian_matrix = array(
array( 0.0, -1.0, 0.0 ),
array( -1.0, 4.0, -1.0 ),
array( 0.0, -1.0, 0.0 )
);
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
// make a copy to detect
$convolved_image = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($convolved_image, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
//detect
ImageBooth::convolve($convolved_image, $this->laplacian_matrix);
return $convolved_image;
}
public function getControls(){
// attempts to mimic PS settings
return array(
);
}
}
class ImBo_Sobel_Filter extends ImBo_Filter{
public function name(){ return 'edge_detect';}
private $sobel_gx_matrix = array(
array( -1.0, 0.0, 1.0 ),
array( -2.0, 0.0, 2.0 ),
array( -1.0, 0.0, 1.0 )
);
private $sobel_gy_matrix = array(
array( 1.0, 2.0, 1.0 ),
array( 0.0, 0.0, 0.0 ),
array( -1.0, -2.0, -1.0 )
);
public function filter(&$image, $incoming_controls=null){
if($controls == null ) $controls = $this->getControls();
// let's cleanup the controls so it's just name = value
foreach($controls as $name => $values) if(is_array($values)) $controls[$name] = $values['value'];
if($incoming_controls != null) foreach($incoming_controls as $name => $value) if( isset($value) && $value != null ) $controls[$name] = $value;
// make a copy to detect
$convolved_image = imagecreatetruecolor(imagesx($image), imagesy($image));
imagecopy($convolved_image, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
//detect
ImageBooth::convolve($convolved_image, $this->sobel_gx_matrix);
ImageBooth::convolve($convolved_image, $this->sobel_gy_matrix);
return $convolved_image;
}
public function getControls(){
// attempts to mimic PS settings
return array(
);
}
}
?>