PHP » Quellcode betrachten

Download

<?php
/* (C) 2006 - 2008 by Julian von Mendel (prog@derjulian.net)
 * License: LGPL3
 * $LastChangedBy: jvm $
 * $LastChangedDate: 2010-03-28 16:25:11 +0200 (Sun, 28 Mar 2010) $
 * $Revision: 37 $
 */

error_reporting(E_ALL E_STRICT);

class 
ImageException extends Exception {}
class 
AlgorithmException extends Exception {}

/** implement this interface to create an image processing algorithm which
 * can be called by image::algorithm and used via
 *   $img = new Image;
 *   $img->createFromFile(...);
 *   $img->algorithm(new yourclass());
 *   $img->save();
 */
interface imagealgorithm {
    public function 
setoriginalimage(image $image);
    public function 
option($get); /* return true or false */
    
public function algorithm($x$y);
    
/* optional:
     *   public function setnewimage(image $image);
     *   public function return();
     * possible options are:
     *   needspixel (you need to know the colors of each pixel)
     *   newimg (you want to replace the image by your own one but need
     *           the old one for information purposes)
     *   return (for own return value)
    */
}

class 
imagealgorithmmask implements imagealgorithm {
    protected 
$image false;

    protected 
$mask;
    protected 
$shrink;
    protected 
$offsetx;
    protected 
$offsety;
    protected 
$threshold 0;
    
    public function 
option($get) {
        if (
$get == "newimg")
            return 
true;
        return 
false;
    }
    
    public function 
setoriginalimage(image $image) {
        
$image->initpixelcache();
        
$this->image $image;
    }
    
    public function 
setparameter($mask$shrink) {
        
$this->mask $mask;
        
$this->shrink $shrink;
        
$this->offsetx = (count($mask) - 1) / 2;
        
$this->offsety = (count($mask[0]) - 1) / 2;
    }

    public function 
setthreshold($threshold) {
        
$this->threshold $threshold;
    }

    public function 
algorithm($x$y) {
        if (!(
$this->image instanceof image)) 
            throw new 
AlgorithmException("No valid image set.");

        if (
$y $this->offsety ||
                
$y >= $this->image->height() - $this->offsety ||
                
$x $this->offsetx ||
                
$x >= $this->image->width() - $this->offsetx)
            return 
0;

        
$sum = array("red" => 0"green" => 0"blue" => 0);

        for(
$i = -$this->offsetx$i <= $this->offsetx$i++) {
        for(
$j = -$this->offsety$j <= $this->offsety$j++) {
            foreach (
$this->image->getpixel($x $i$y $jfalse) as
                    
$key => $val) {
                if (
$key == "alpha")
                    continue;

                
$sum[$key] += $val $this->mask[$i +
                        
$this->offsetx][$j $this->offsety];
            }
        }
        }

        foreach (
$sum as $key => $val) {
            
$sum[$key] = $this->shrink $val;

            if (
$sum[$key] < $this->threshold) {
                
$sum 0;
                break;
            }
        }

        return 
$sum;
    }
}

/** if you want to use Image::algorithm to iterate over every pixel of
 * your image, but you don't want to write a whole class for this purpose,
 * you can use this class as an interface for your function. example:
 *   function makeeverythingblue($image, $pixel, $new, $additionalparam) {
 *       # pixel and additionalparam will be false, because we deactivate both
 *      return array("red" => 0, "green" => 0, "blue" => 255)
 *   }
 *   
 *   $img = new Image;
 *   $img->createFromFile(...)
 *       ->algorithm(new imagealgorithm_function("makeeverythingblue",
 *           false, true, false))
 *       ->save();
 * 
 * the array you return defines the new color for this pixel. ("newimg"
 * option neccessary)
 * if you return only one number (0..255), it will be used
 * as value for red, green and blue (so 255 is white, 0 is black and
 * 128 is gray)
 */
class imagealgorithm_function implements imagealgorithm {
    protected 
$image false;
    protected 
$callback;
    protected 
$newimg;
    protected 
$additionalparam;
    protected 
$needspixel;
    
    function 
__construct($callback$additionalparam false,
            
$newimg true) {
        if (!
is_callable($callback))
            throw new 
ImageException("No valid callback given.");
        
        
$this->callback $callback;
        
$this->additionalparam $additionalparam;
        
$this->newimg $newimg;
    }
    
    public function 
option($get) {
        if (
$get == "newimg")
            return 
$this->newimg;
        return 
false;
    }
    
    public function 
setoriginalimage(image $image) {
        
$image->initpixelcache();
        
$this->image $image;
    }
    
    public function 
setnewimage(image $image) {
        
$this->newimg $image;
    }
    
    public function 
algorithm($x$y) {
        if (!(
$this->image instanceof image)) 
            throw new 
AlgorithmException("No valid image set.");

        return 
call_user_func($this->callback$x$y$this->image,
                
$this->newimg$this->additionalparam);
    }
}

class 
image {
    protected 
$type/* file type; "png"/"jpeg"/"gif" */
    
protected $height/* height in px */
    
protected $width/* height in px */
    
protected $handle/* image handle */
    
protected $file "output.png";
    protected 
$channel self::black;
    protected 
$autohandledestroy true;

    public 
$colornames = array("red""green""blue");

    protected 
$pixel = array();
    const 
undefined 256;

    const 
all 2;
    const 
maxval 3;
    const 
red 4;
    const 
green 5;
    const 
blue 6;
    const 
black 7;

    
/* redirect function calls */
    
function __call($method$args) {
        
/* convert everything to lower case,
         * so people can use what they prefer */
        
$method strtolower($method);

        
/* aliases */
        
switch ($method) {
            case 
"sethandler":
                
$method "sethandle";
                break;
            case 
"handler":
                
$method "handle";
                break;
        }

        
/* check if maybe a method of this class was meant
         * (and a not lowercase method was called, or an alias) */            
        
if (method_exists($this$method)) {
            return 
call_user_func_array(array($this$method), $args);
        }
        
        
/* check if php gd function exists */
        
if (!function_exists("image".$method)) {
            throw new 
ImageException("Function 'image".$method.
                    
"' doesn't exist.");
        }

        
/* php gd functions want an image handle as first parameter */
        
if ($this->handle())
            
$args array_merge(array($this->handle()), $args);

        
$ret call_user_func_array("image".$method$args);

        
/* some php functions return an image handle; we always change
         * our own image, because it is not common to edit multiple images
         * at the same time, so most times this is wanted; if not, user has
         * to create a copy of this image before */
        
if (is_resource($ret))
            
$this->sethandle($ret);

        return 
$ret;
    }

    public function 
createfromfile($path) {
        if (!
file_exists($path))
            throw new 
ImageException("File '".$path."' not found.");

        
$info self::info($path);
        switch(
$info[2]) {
            case 
1:
                
$this->sethandler($this->createfromgif($path));
                
$this->type "gif";
                break;
            case 
2:
                
$this->sethandler($this->createfromjpeg($path));
                
$this->type "jpeg";
                break;
            case 
3:
                
$this->sethandler($this->createfrompng($path));
                
$this->type "png";
                break;
            default:
                throw new 
ImageException("Unknown file type '".$info[2]."'.");
        }
        
        
$this->file $path;
        
$this->width  $this->sx();
        
$this->height $this->sy();
        
        return 
$this;
    }

    public function 
sethandle($handle) {
        if (!
is_resource($handle))
            throw new 
ImageException(
                    
"Only valid image identifiers can be set as handle.");

        if (
$this->autohandledestroy)
            
$this->close();

        
$this->type   false;
        
$this->handle $handle;
        
$this->width  $this->sx();
        
$this->height $this->sy();
        
        return 
$this;
    }

    public function 
autohandledestroy($yes) {
        
$this->autohandledestroy $yes;
        return 
$this;
    }

    public function 
setchannel($channel) {
        
$this->channel $channel;        
        return 
$this;
    }
    
    public function 
channel() {
        return 
$channel;        
    }

    public function 
handle() {
        return 
$this->handle;
    }

    public function 
width() {
        return 
$this->width;
    }

    public function 
height() {
        return 
$this->height;
    }

    public function 
filetype() {
        return 
$this->type;
    }

    public function 
mimetype() {
        return 
"image/".$this->type;
    }

    
/** save file;
     * type is determined by extension and can be "gif", "jpg" or "png" */
    
public function save($destination false$quality 25) {
        if (!
$destination)
            
$destination $this->file;
        
        
$type explode("."$destination);
        
$type strtolower(end($type));
        
        switch(
$type) {
            case 
"gif":
                return 
$this->gif($destination);
                break;
            case 
"jpg":
            case 
"jpeg":
                return 
$this->jpeg($destination$quality);
                break;
            case 
"png":
                return 
$this->png($destination);
                break;
            default:
                throw new 
ImageException("Unknown file type '".$type."'.");
        }
        
        return 
$this;
    }

    public function 
output($quality 80) {
        
header("Content-Type: ".$this->mimetype());
        return 
$this->png();
    }
    
    public function 
preparecopy($old) {
        
$this->alphablending(false);
        
$this->savealpha(true);
        
$this->palettecopy($old->handle());
        
$transparent $this->colorallocate(000);
        
$this->fill(00$transparent);
        
$this->colortransparent($transparent);
        
        return 
$this;
    }

    public function 
cut($left$top$width$height) {
        
$new = new self;
        
$new->createtruecolor($width$height);
        
$new->preparecopy($this);
        
$new->copyresampled(
            
$this->handle(),
            
00$left$top,
            
$width,
            
$height,
            
$width,
            
$height
        
);

        
$this->sethandle($new->handler());
        return 
$this;
    }

    public function 
resize($maxwidth 150$maxheight 150$scale true) {
        if (!
$scale) {
            
$width  $maxwidth ;
            
$height $maxheight;
        } else {
            if (
$this->width() < $maxwidth && $this->height() < $maxheight) {
                
$width  $this->width();
                
$height $this->height();
            } else if (
$this->width() / $this->height() <
                    
$maxwidth $maxheight) {
                
$height $maxheight;
                
$width  round($this->width() * $height $this->height());
            } else {
                
$width  $maxwidth;
                
$height round($this->height() * $width $this->width());
            }
        }

        
$new = new self;
        
$new->createtruecolor($width$height);
        
$new->preparecopy($this);
        
$new->copyresampled(
            
$this->handle(),
            
0000,
            
$width,
            
$height,
            
$this->width(),
            
$this->height()
        );

        
$this->sethandle($new->handle());
        return 
$this;
    }

    
/** extract one color channel and use it as a new image */
    
public function filter() {
        return 
$this->algorithm(new imagealgorithm_function(
                array(
$this"filter_")));
    }    
    public function 
filter_($x$y$image$new,
            
$additionalparam) {
        
/* used on every single pixel */
        
return $this->getpixel($x$yfalse);
    }

    
/** rotate image clockwise and automatically adjust height/width;
     * beware: only 90°/-90°/180° are supported */
    
public function rotate($angle) {
        switch (
$angle) {
            case 
90:
            case -
90:
                
$height $this->width;
                
$width $this->height;
                break;
            case 
180:
                
$width $this->width;
                
$height $this->height;
                break;
            default:
                throw new 
ImageException("angle must be 90, -90 or 180.");
        }

        
$new = new self;
        
$new->createtruecolor($width$height);
        
$new->preparecopy($this);

        for (
$y 0$y $this->height(); $y++) {
            for (
$x 0$x $this->width(); $x++) {
                
$color $this->colorat($x$y);
                switch (
$angle) {
                    case 
90:
                        
$new->setpixel($width $y 1$x$color);
                        break;
                    case -
90:
                        
$new->setpixel($y$height $x 1$color);
                        break;
                    case 
180:
                        
$new->setpixel($width $x 1,
                                
$height $y 1$color);
                        break;
                }
            }
        }

        
$this->sethandle($new->handle());
        return 
$this;
    }
    
    public function 
pixel($channel false) {
        for (
$y 0$y $this->height(); $y++) {
            for (
$x 0$x $this->width(); $x++) {
                
$color $this->getpixel($x$y$channel);
                
$pixel[$x][$y] = $color;
            }
        }
        
        return 
$pixel;
    }

    public function 
initpixelcache() {
        if (
$this->cacheinitialized())
            return 
$this;

        
$this->pixel = array();
        for (
$i 0$i $this->width() * $this->height() * 3$i++)
            
$this->pixel[] = self::undefined;

        return 
$this;
    }

    public function 
resetpixelcache() {
        
$this->deactivatepixelcache();
        
$this->initpixelcache();
        return 
$this;
    }

    public function 
deactivatepixelcache() {
        
$this->pixel = array();
        return 
$this;
    }

    public function 
getpixelcache() {
        return 
$this->pixel;
    }

    public function 
setpixelcache($pixel) {
        
$this->pixel $pixel;
        return 
$this;
    }

    public function 
cacheinitialized() {
        return ((
count($this->pixel) == $this->width() * $this->height() *3) ?
                
true false);
    }

    public function 
getpixel($x$y$channel self::all) {
        
/* get color out of cache */
        
if ($this->cacheinitialized()) {
            
$offset = ($y $this->width() + $x) * 3;

            
$color = array("red" => $this->pixel[$offset 0],
                    
"green" => $this->pixel[$offset 1],
                    
"blue" => $this->pixel[$offset 2]);
        }

        
/* get color out of image */
        
if (!isset($color) || $color["red"] == self::undefined)
            
$color $this->colorsforindex($this->colorat($x$y));

        
/* save color in cache */
        
if ($this->cacheinitialized())
            for (
$i 0$i 3$i++)
                
$this->pixel[$offset $i] = $color[$this->colornames[$i]];

        
/* load default channel */
        
if ($channel == false)
            
$channel $this->channel;

        
/* get channel values */
        
switch ($channel) {
            default:
                if (!
is_array($channel))
                    throw new 
ImageException(
                            
"No valid color channel was chosen.");
                
$m / ($channel[0] + $channel[1] + $channel[2]);
                
$intensity $channel[0] * $m $color["red"] +
                        
$channel[1] * $m $color["green"] +
                        
$channel[2] * $m $color["blue"];
                break;
            case 
self::all:
                return 
$color;
            case 
self::maxval:
                return 
max($color["red"], $color["green"], $color["blue"]);
            case 
self::black:
                
$intensity 0.3 $color["red"] +
                        
0.59 $color["green"] +
                        
0.11 $color["blue"];
                break;
            case 
self::red:
                
$intensity $color["red"];
                break;
            case 
self::green:
                
$intensity $color["green"];
                break;
            case 
self::blue:
                
$intensity $color["blue"];
                break;
        }

        
$intensity round($intensity);

        return array(
"red" => $intensity,
                
"green" => $intensity,
                
"blue" => $intensity);
    }

    public function 
setpixelrgb($x$y, array $color) {
        
/* save new color in image */
        
$this->setpixel($x$y$this->colorallocate($color["red"],
                
$color["green"], $color["blue"]));

        
/* save new color in cache */
        
if ($this->pixel) {
            
$this->pixel[($y $this->width() + $x) * 0] = $color["red"];
            
$this->pixel[($y $this->width() + $x) * 1] = $color["green"];
            
$this->pixel[($y $this->width() + $x) * 2] = $color["blue"];
        }
    }
    
    public function 
algorithm($callback) {
        if (!(
$callback instanceof imagealgorithm)) {
            throw new 
ImageException("Callback is no class implementing ".
                    
"interface 'imagealgorithm'.");
        }

        if (
$callback->option("newimg")) {
            
$new = new self;
            
$new->createtruecolor($this->width(), $this->height());
            
$new->preparecopy($this);
        }
        
        
$callback->setoriginalimage($this);
        
        if (
$callback->option("newimg") &&
                
method_exists($callback"setnewimage"))
            
$callback->setnewimage($new);

        for (
$x 0$x $this->width(); $x++) {
            for (
$y 0$y $this->height(); $y++) {
                
$color $callback->algorithm($x$y);
                
                if (!
$callback->option("newimg") ||
                        
$color == false || $color == self::undefined)
                    continue;

                if (
is_numeric($color)) {
                    
$color min(255max(0$color));
                    
$new->setpixelrgb($x$y, array("red" => $color,
                            
"green" => $color"blue" => $color));
                    continue;
                }
                
                foreach (
$color as $key => $val)
                    
$color[$key] = min(255max(0$val));

                
$new->setpixelrgb($x$y$color);
            }
        }

        if (
$callback->option("newimg")) {
            
$this->sethandle($new->handle());
            
$this->setpixelcache($new->getpixelcache());
            
$new->close();
        }
        
        if (
$callback->option("return"))
            return 
$callback->return();
        return 
$this;
    }

    public function 
close() {
        
/*if (is_resource($this->handle))
            imagedestroy($this->handle);*/
        
return $this;
    }

    public static function 
info($path$cache false) {
        return 
getimagesize($path);
    }

    public static function 
thumbnail($source$destination,
            
$maxwidth 150$maxheight 150$scale true) {
        if (
file_exists($destination))
            return 
true;

        
$img = new self;
        
$img->createfromfile($source);
        
$img->resize($maxwidth$maxheight$scale);
        
$img->interlace();
        
$ret $img->save($destination);
        
$img->close();
        
        return 
$ret;
    }
}

© 2009 Julian von Mendel (http://derjulian.net) | Datum: 28.04.2024