Version: current

      1 <?php
      2 /**
      3  * A wrapper class for GD-based images, with lots of manipulation functions.
      4  * @package sapphire
      5  * @subpackage filesystem
      6  */
      7 class GD extends Object {
      8     protected $gd, $width, $height;
      9     protected $quality;
     10
     11     protected static $default_quality = 75;
     12
     13     function __construct($filename = null) {
     14         // If we're working with image resampling, things could take a while.  Bump up the time-limit
     15         increase_time_limit_to(300);
     16
     17         if($filename) {
     18             // We use getimagesize instead of extension checking, because sometimes extensions are wrong.
     19             @list($width, $height, $type, $attr) = getimagesize($filename);
     20             switch($type) {
     21                 case 1: if(function_exists('imagecreatefromgif')) $this->setGD(@imagecreatefromgif($filename)); break;
     22                 case 2: if(function_exists('imagecreatefromjpeg')) $this->setGD(@imagecreatefromjpeg($filename)); break;
     23                 case 3: if(function_exists('imagecreatefrompng')) $this->setGD(@imagecreatefrompng($filename)); break;
     24             }
     25         }
     26
     27         $this->quality = self::$default_quality;
     28         parent::__construct();
     29     }
     30     public function setGD($gd) {
     31         $this->gd = $gd;
     32         $this->width = imagesx($gd);
     33         $this->height = imagesy($gd);
     34     }
     35
     36     public function getGD() {
     37         return $this->gd;
     38     }
     39
     40
     41     /**
     42      * Set the default image quality.
     43      * @param quality int A number from 0 to 100, 100 being the best quality.
     44      */
     45     static function set_default_quality($quality) {
     46         if(is_numeric($quality) && (int) $quality >= 0 && (int) $quality <= 100) {
     47             self::$default_quality = (int) $quality;
     48         }
     49     }
     50
     51     /**
     52      * Set the image quality, used when saving JPEGs.
     53      */
     54     function setQuality($quality) {
     55         $this->quality = $quality;
     56     }
     57
     58     /**
     59      * Resize an image to cover the given width/height completely, and crop off any overhanging edges.
     60      */
     61     function croppedResize($width, $height) {
     62         if(!$this->gd) return;
     63
     64         $width = round($width);
     65         $height = round($height);
     66
     67         $newGD = imagecreatetruecolor($width, $height);
     68
     69         // Preserves transparency between images
     70         imagealphablending($newGD, false);
     71         imagesavealpha($newGD, true);
     72
     73         $destAR = $width / $height;
     74         if ($this->width > 0 && $this->height > 0 ){
     75             // We can't divide by zero theres something wrong.
     76
     77             $srcAR = $this->width / $this->height;
     78
     79             // Destination narrower than the source
     80             if($destAR < $srcAR) {
     81                 $srcY = 0;
     82                 $srcHeight = $this->height;
     83
     84                 $srcWidth = $this->height * $destAR;
     85                 $srcX = ($this->width - $srcWidth) / 2;
     86
     87             // Destination shorter than the source
     88             } else {
     89                 $srcX = 0;
     90                 $srcWidth = $this->width;
     91
     92                 $srcHeight = $this->width / $destAR;
     93                 $srcY = ($this->height - $srcHeight) / 2;
     94             }
     95
     96             imagecopyresampled($newGD, $this->gd, 0,0, $srcX, $srcY, $width, $height, $srcWidth, $srcHeight);
     97         }
     98         $output = clone $this;
     99         $output->setGD($newGD);
    100         return $output;
    101     }
    102
    103     /**
    104      * Resizes the image to fit within the given region.
    105      * Behaves similarly to paddedResize but without the padding.
    106      * @todo This method isn't very efficent
    107      */
    108     function fittedResize($width, $height) {
    109         $gd = $this->resizeByHeight($height);
    110         if($gd->width > $width) $gd = $gd->resizeByWidth($width);
    111         return $gd;
    112     }
    113
    114     function hasGD() {
    115         return $this->gd ? true : false;
    116     }
    117
    118
    119     /**
    120      * Resize an image, skewing it as necessary.
    121      */
    122     function resize($width, $height) {
    123         if(!$this->gd) return;
    124
    125         $width = round($width);
    126         $height = round($height);
    127         if(!$width && !$height) user_error("No dimensions given", E_USER_ERROR);
    128         if(!$width) user_error("Width not given", E_USER_ERROR);
    129         if(!$height) user_error("Height not given", E_USER_ERROR);
    130
    131         if ($this->width == $width && $this->height == $height)
    132             return clone $this;
    133
    134         $newGD = imagecreatetruecolor($width, $height);
    135
    136         // Preserves transparency between images
    137         imagealphablending($newGD, false);
    138         imagesavealpha($newGD, true);
    139
    140         imagecopyresampled($newGD, $this->gd, 0,0, 0, 0, $width, $height, $this->width, $this->height);
    141
    142         $output = clone $this;
    143         $output->setGD($newGD);
    144         return $output;
    145     }
    146
    147     /**
    148      * Rotates image by given angle.
    149      *
    150      * @param angle
    151      *
    152      * @return GD
    153     */
    154
    155     function rotate($angle) {
    156         if(!$this->gd) return;
    157
    158         if(function_exists("imagerotate")) {
    159             $newGD = imagerotate($this->gd, $angle,0);
    160         } else {
    161             //imagerotate is not included in PHP included in Ubuntu
    162             $newGD = $this->rotatePixelByPixel($angle);
    163         }
    164         $output = clone $this;
    165         $output->setGD($newGD);
    166         return $output;
    167     }
    168
    169     /**
    170      * Rotates image by given angle. It's slow because makes it pixel by pixel rather than
    171      * using built-in function. Used when imagerotate function is not available(i.e. Ubuntu)
    172      *
    173      * @param angle
    174      *
    175      * @return GD
    176     */
    177
    178     function rotatePixelByPixel($angle) {
    179         $sourceWidth = imagesx($this->gd);
    180         $sourceHeight = imagesy($this->gd);
    181         if ($angle == 180) {
    182             $destWidth = $sourceWidth;
    183             $destHeight = $sourceHeight;
    184         } else {
    185             $destWidth = $sourceHeight;
    186             $destHeight = $sourceWidth;
    187         }
    188         $rotate=imagecreatetruecolor($destWidth,$destHeight);
    189         imagealphablending($rotate, false);
    190         for ($x = 0; $x < ($sourceWidth); $x++) {
    191             for ($y = 0; $y < ($sourceHeight); $y++) {
    192                 $color = imagecolorat($this->gd, $x, $y);
    193                 switch ($angle) {
    194                     case 90:
    195                         imagesetpixel($rotate, $y, $destHeight - $x - 1, $color);
    196                     break;
    197                     case 180:
    198                         imagesetpixel($rotate, $destWidth - $x - 1, $destHeight - $y - 1, $color);
    199                     break;
    200                     case 270:
    201                         imagesetpixel($rotate, $destWidth - $y - 1, $x, $color);
    202                     break;
    203                     default: $rotate = $this->gd;
    204                 };
    205             }
    206         }
    207         return $rotate;
    208     }
    209
    210
    211     /**
    212      * Crop's part of image.
    213      *
    214      * @param top y position of left upper corner of crop rectangle
    215      * @param left x position of left upper corner of crop rectangle
    216      * @param width rectangle width
    217      * @param height rectangle height
    218      *
    219      * @return GD
    220     */
    221
    222     function crop($top, $left, $width, $height) {
    223         $newGD = imagecreatetruecolor($width, $height);
    224         imagecopyresampled($newGD, $this->gd, 0, 0, $left, $top, $width, $height, $width, $height);
    225
    226         $output = clone $this;
    227         $output->setGD($newGD);
    228         return $output;
    229     }
    230
    231     /**
    232      * Method return width of image.
    233      *
    234      * @return integer width.
    235     */
    236     function getWidth() {
    237         return $this->width;
    238     }
    239
    240     /**
    241      * Method return height of image.
    242      *
    243      * @return integer height
    244     */
    245
    246     function getHeight() {
    247         return $this->height;
    248     }
    249
    250     /**
    251      * Resize an image by width. Preserves aspect ratio.
    252      */
    253     function resizeByWidth( $width ) {
    254         if (round($width) == $this->width)
    255             return clone $this;
    256         $heightScale = $width / $this->width;
    257         return $this->resize( $width, $heightScale * $this->height );
    258     }
    259
    260     /**
    261      * Resize an image by height. Preserves aspect ratio
    262      */
    263     function resizeByHeight( $height ) {
    264         if (round($height) == $this->height)
    265             return clone $this;
    266         $scale = $height / $this->height;
    267         return $this->resize( $scale * $this->width, $height );
    268     }
    269
    270     /**
    271      * Resize the image by preserving aspect ratio. By default, it will keep the image inside the maxWidth and maxHeight
    272      * Passing useAsMinimum will make the smaller dimension equal to the maximum corresponding dimension
    273      */
    274     function resizeRatio( $maxWidth, $maxHeight, $useAsMinimum = false ) {
    275
    276         $widthRatio = $maxWidth / $this->width;
    277         $heightRatio = $maxHeight / $this->height;
    278
    279         if( $widthRatio < $heightRatio )
    280             return $useAsMinimum ? $this->resizeByHeight( $maxHeight ) : $this->resizeByWidth( $maxWidth );
    281         else
    282             return $useAsMinimum ? $this->resizeByWidth( $maxWidth ) : $this->resizeByHeight( $maxHeight );
    283     }
    284
    285     static function color_web2gd($image, $webColor) {
    286         if(substr($webColor,0,1) == "#") $webColor = substr($webColor,1);
    287         $r = hexdec(substr($webColor,0,2));
    288         $g = hexdec(substr($webColor,2,2));
    289         $b = hexdec(substr($webColor,4,2));
    290
    291         return imagecolorallocate($image, $r, $g, $b);
    292
    293     }
    294
    295     /**
    296      * Resize to fit fully within the given box, without resizing.  Extra space left around
    297      * the image will be padded with the background color.
    298      * @param width
    299      * @param height
    300      * @param backgroundColour
    301      */
    302     function paddedResize($width, $height, $backgroundColor = "FFFFFF") {
    303         if(!$this->gd) return;
    304
    305         $width = round($width);
    306         $height = round($height);
    307
    308
    309         $newGD = imagecreatetruecolor($width, $height);
    310
    311         // Preserves transparency between images
    312         imagealphablending($newGD, false);
    313         imagesavealpha($newGD, true);
    314
    315         $bg = GD::color_web2gd($newGD, $backgroundColor);
    316         imagefilledrectangle($newGD, 0, 0, $width, $height, $bg);
    317
    318         $destAR = $width / $height;
    319         if ($this->width > 0 && $this->height > 0) {
    320             // We can't divide by zero theres something wrong.
    321
    322             $srcAR = $this->width / $this->height;
    323
    324             // Destination narrower than the source
    325             if($destAR > $srcAR) {
    326                 $destY = 0;
    327                 $destHeight = $height;
    328
    329                 $destWidth = $height * $srcAR;
    330                 $destX = ($width - $destWidth) / 2;
    331
    332             // Destination shorter than the source
    333             } else {
    334                 $destX = 0;
    335                 $destWidth = $width;
    336
    337                 $destHeight = $width / $srcAR;
    338                 $destY = ($height - $destHeight) / 2;
    339             }
    340
    341             imagecopyresampled($newGD, $this->gd, $destX, $destY, 0, 0, $destWidth, $destHeight, $this->width, $this->height);
    342         }
    343         $output = clone $this;
    344         $output->setGD($newGD);
    345         return $output;
    346     }
    347
    348     /**
    349      * Make the image greyscale
    350      * $rv = red value, defaults to 38
    351      * $gv = green value, defaults to 36
    352      * $bv = blue value, defaults to 26
    353      * Based (more or less entirely, with changes for readability) on code from http://www.teckis.com/scriptix/thumbnails/teck.html
    354      */
    355     function greyscale($rv=38, $gv=36, $bv=26) {
    356         $width = $this->width;
    357         $height = $this->height;
    358         $newGD = imagecreatetruecolor($this->width, $this->height);
    359
    360         $rt = $rv + $bv + $gv;
    361         $rr = ($rv == 0) ? 0 : 1/($rt/$rv);
    362         $br = ($bv == 0) ? 0 : 1/($rt/$bv);
    363         $gr = ($gv == 0) ? 0 : 1/($rt/$gv);
    364         for($dy = 0; $dy < $height; $dy++) {
    365             for($dx = 0; $dx < $width; $dx++) {
    366                 $pxrgb = imagecolorat($this->gd, $dx, $dy);
    367                 $heightgb = ImageColorsforIndex($this->gd, $pxrgb);
    368                 $newcol = ($rr*$heightgb['red']) + ($br*$heightgb['blue']) + ($gr*$heightgb['green']);
    369                 $setcol = ImageColorAllocate($newGD, $newcol, $newcol, $newcol);
    370                 imagesetpixel($newGD, $dx, $dy, $setcol);
    371             }
    372         }
    373
    374         $output = clone $this;
    375         $output->setGD($newGD);
    376         return $output;
    377     }
    378
    379     function makeDir($dirname) {
    380         if(!file_exists(dirname($dirname))) $this->makeDir(dirname($dirname));
    381         if(!file_exists($dirname)) mkdir($dirname, Filesystem::$folder_create_mask);
    382     }
    383
    384     function writeTo($filename) {
    385         $this->makeDir(dirname($filename));
    386
    387         if($filename) {
    388             if(file_exists($filename)) list($width, $height, $type, $attr) = getimagesize($filename);
    389
    390             if(file_exists($filename)) unlink($filename);
    391
    392             $ext = strtolower(substr($filename, strrpos($filename,'.')+1));
    393             if(!isset($type)) switch($ext) {
    394                 case "gif": $type = 1; break;
    395                 case "jpeg": case "jpg": case "jpe": $type = 2; break;
    396                 default: $type = 3; break;
    397             }
    398
    399             // if the extension does not exist, the file will not be created!
    400
    401             switch($type) {
    402                 case 1: imagegif($this->gd, $filename); break;
    403                 case 2: imagejpeg($this->gd, $filename, $this->quality); break;
    404
    405                 // case 3, and everything else
    406                 default:
    407                     // Save them as 8-bit images
    408                     // imagetruecolortopalette($this->gd, false, 256);
    409                     imagepng($this->gd, $filename); break;
    410             }
    411             if(file_exists($filename)) @chmod($filename,0664);
    412         }
    413     }
    414
    415 }
    416
    417 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -