sterzycom/kirby/toolkit/lib/thumb.php

358 lines
9.3 KiB
PHP

<?php
/**
* Thumb
*
* @package Kirby Toolkit
* @author Bastian Allgeier <bastian@getkirby.com>
* @link http://getkirby.com
* @copyright Bastian Allgeier
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
class Thumb extends Obj {
const ERROR_INVALID_IMAGE = 0;
const ERROR_INVALID_DRIVER = 1;
public static $drivers = array();
public static $defaults = array(
'destination' => false,
'filename' => '{safeName}-{hash}.{extension}',
'url' => '/thumbs',
'root' => '/thumbs',
'driver' => 'im',
'memory' => '128M',
'quality' => 90,
'blur' => false,
'blurpx' => 10,
'width' => null,
'height' => null,
'upscale' => false,
'crop' => false,
'grayscale' => false,
'overwrite' => false,
'autoOrient' => false,
'interlace' => false
);
public $source = null;
public $result = null;
public $destination = null;
public $options = array();
public $error = null;
/**
* Constructor
*
* @param mixed $source
* @param array $params
*/
public function __construct($source, $params = array()) {
$this->source = $this->result = is_a($source, 'Media') ? $source : new Media($source);
$this->options = array_merge(static::$defaults, $this->params($params));
$this->destination = $this->destination();
// don't create the thumbnail if it's not necessary
if($this->isObsolete()) return;
// don't create the thumbnail if it exists
if(!$this->isThere()) {
// try to create the thumb folder if it is not there yet
dir::make(dirname($this->destination->root));
// check for a valid image
if(!$this->source->exists() || $this->source->type() != 'image') {
throw new Error('The given image is invalid', static::ERROR_INVALID_IMAGE);
}
// check for a valid driver
if(!array_key_exists($this->options['driver'], static::$drivers)) {
throw new Error('Invalid thumbnail driver', static::ERROR_INVALID_DRIVER);
}
// create the thumbnail
$this->create();
// check if creating the thumbnail failed
if(!file_exists($this->destination->root)) return;
}
// create the result object
$this->result = new Media($this->destination->root, $this->destination->url);
}
/**
* Build the destination object
*
* @return Obj
*/
public function destination() {
if(is_callable($this->options['destination'])) {
return call($this->options['destination'], $this);
} else {
$destination = new Obj();
$safeName = f::safeName($this->source->name());
$destination->filename = str::template($this->options['filename'], array(
'extension' => $this->source->extension(),
'name' => $this->source->name(),
'filename' => $this->source->filename(),
'safeName' => $safeName,
'safeFilename' => $safeName . '.' . $this->extension(),
'width' => $this->options['width'],
'height' => $this->options['height'],
'hash' => md5($this->source->root() . $this->settingsIdentifier()),
));
$destination->url = $this->options['url'] . '/' . $destination->filename;
$destination->root = $this->options['root'] . DS . $destination->filename;
return $destination;
}
}
/**
* Returns the source media object
*
* @return Media
*/
public function source() {
return $this->source;
}
/**
* Returns the exception if available
*
* @return Exception
*/
public function error() {
return $this->error;
}
/**
* Makes it possible to pass a string of params
* which is shorter and more convenient than
* passing a full array of keys and values:
* width:300|height:200|crop:true
*
* @param array $params
* @return array
*/
public function params($params) {
if(is_array($params)) return $params;
$result = array();
foreach(explode('|', $params) as $param) {
$pos = strpos($param, ':');
$result[trim(substr($param, 0, $pos))] = trim(substr($param, $pos+1));
}
return $result;
}
/**
* Builds a hash for all relevant settings
*
* @return string
*/
public function settingsIdentifier() {
// build the settings string
return implode('-', array(
($this->options['width']) ? $this->options['width'] : 0,
($this->options['height']) ? $this->options['height'] : 0,
($this->options['upscale']) ? $this->options['upscale'] : 0,
($this->options['crop']) ? $this->options['crop'] : 0,
$this->options['blur'],
$this->options['grayscale'],
$this->options['quality']
));
}
/**
* Checks if the thumbnail already exists
* and is newer than the original file
*
* @return boolean
*/
public function isThere() {
if($this->options['overwrite'] === true) return false;
// if the thumb already exists and the source hasn't been updated
// we don't need to generate a new thumbnail
if(file_exists($this->destination->root) && f::modified($this->destination->root) >= $this->source->modified()) return true;
return false;
}
/**
* Checks if the thumbnail is not needed
* because the original image is small enough
*
* @return boolean
*/
public function isObsolete() {
if($this->options['overwrite'] === true) return false;
// try to use the original if resizing is not necessary
if($this->options['width'] >= $this->source->width() &&
$this->options['height'] >= $this->source->height() &&
$this->options['crop'] == false &&
$this->options['blur'] == false &&
$this->options['upscale'] == false) return true;
return false;
}
/**
* Calls the driver function and
* creates the thumbnail
*/
protected function create() {
return call_user_func_array(static::$drivers[$this->options['driver']], array($this));
}
/**
* Makes all public methods of the result object
* available to the thumb class
*
* @param string $method
* @param mixed $arguments
* @return mixed
*/
public function __call($method, $arguments) {
if(method_exists($this->result, $method)) {
return call_user_func_array(array($this->result, $method), $arguments);
}
}
/**
* Generates and returns the full html tag for the thumbnail
*
* @param array $attr An optional array of attributes, which should be added to the image tag
* @return string
*/
public function tag($attr = array()) {
// don't return the tag if the url is not available
if(!$this->result->url()) return false;
return html::img($this->result->url(), array_merge(array(
'alt' => isset($this->options['alt']) ? $this->options['alt'] : ' ',
'class' => isset($this->options['class']) ? $this->options['class'] : null,
), $attr));
}
/**
* Makes it possible to echo the entire object
*/
public function __toString() {
return $this->tag();
}
}
/**
* ImageMagick Driver
*/
thumb::$drivers['im'] = function($thumb) {
$command = array();
$command[] = isset($thumb->options['bin']) ? $thumb->options['bin'] : 'convert';
$command[] = '"' . $thumb->source->root() . '"';
$command[] = '-strip';
if($thumb->options['interlace']) {
$command[] = '-interlace line';
}
if($thumb->source->extension() === 'gif') {
$command[] = '-coalesce';
}
if($thumb->options['grayscale']) {
$command[] = '-colorspace gray';
}
if($thumb->options['autoOrient']) {
$command[] = '-auto-orient';
}
$command[] = '-resize';
if($thumb->options['crop']) {
$command[] = $thumb->options['width'] . 'x' . $thumb->options['height'] . '^';
$command[] = '-gravity Center -crop ' . $thumb->options['width'] . 'x' . $thumb->options['height'] . '+0+0';
} else {
$dimensions = clone $thumb->source->dimensions();
$dimensions->fitWidthAndHeight($thumb->options['width'], $thumb->options['height'], $thumb->options['upscale']);
$command[] = $dimensions->width() . 'x' . $dimensions->height() . '!';
}
$command[] = '-quality ' . $thumb->options['quality'];
if($thumb->options['blur']) {
$command[] = '-blur 0x' . $thumb->options['blurpx'];
}
$command[] = '-limit thread 1';
$command[] = '"' . $thumb->destination->root . '"';
exec(implode(' ', $command));
};
/**
* GDLib Driver
*/
thumb::$drivers['gd'] = function($thumb) {
try {
$img = new abeautifulsite\SimpleImage($thumb->root());
$img->quality = $thumb->options['quality'];
if($thumb->options['crop']) {
@$img->thumbnail($thumb->options['width'], $thumb->options['height']);
} else {
$dimensions = clone $thumb->source->dimensions();
$dimensions->fitWidthAndHeight($thumb->options['width'], $thumb->options['height'], $thumb->options['upscale']);
@$img->resize($dimensions->width(), $dimensions->height());
}
if($thumb->options['grayscale']) {
$img->desaturate();
}
if($thumb->options['blur']) {
$img->blur('gaussian', $thumb->options['blurpx']);
}
if($thumb->options['autoOrient']) {
$img->auto_orient();
}
@$img->save($thumb->destination->root);
} catch(Exception $e) {
$thumb->error = $e;
}
};