'production', // 'production', 'development', 'strict'
//'image_path' => __DIR__ . '/img/',
//'cache_path' => __DIR__ . '/../cache/',
//'alias_path' => __DIR__ . '/img/alias/',
//'remote_allow' => true,
//'password' => false, // "secret-password",
);
/**
* Get a image from a remote server using HTTP GET and If-Modified-Since.
*
*/
class CHttpGet
{
private $request = array();
private $response = array();
/**
* Constructor
*
*/
public function __construct()
{
$this->request['header'] = array();
}
/**
* Set the url for the request.
*
* @param string $url
*
* @return $this
*/
public function setUrl($url)
{
$this->request['url'] = $url;
return $this;
}
/**
* Set custom header field for the request.
*
* @param string $field
* @param string $value
*
* @return $this
*/
public function setHeader($field, $value)
{
$this->request['header'][] = "$field: $value";
return $this;
}
/**
* Set header fields for the request.
*
* @param string $field
* @param string $value
*
* @return $this
*/
public function parseHeader()
{
$header = explode("\r\n", rtrim($this->response['headerRaw'], "\r\n"));
$output = array();
if ('HTTP' === substr($header[0], 0, 4)) {
list($output['version'], $output['status']) = explode(' ', $header[0]);
unset($header[0]);
}
foreach ($header as $entry) {
$pos = strpos($entry, ':');
$output[trim(substr($entry, 0, $pos))] = trim(substr($entry, $pos + 1));
}
$this->response['header'] = $output;
return $this;
}
/**
* Perform the request.
*
* @param boolean $debug set to true to dump headers.
*
* @return boolean
*/
public function doGet($debug = false)
{
$options = array(
CURLOPT_URL => $this->request['url'],
CURLOPT_HEADER => 1,
CURLOPT_HTTPHEADER => $this->request['header'],
CURLOPT_AUTOREFERER => true,
CURLOPT_RETURNTRANSFER => true,
CURLINFO_HEADER_OUT => $debug,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 5,
);
$ch = curl_init();
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if (!$response) {
return false;
}
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$this->response['headerRaw'] = substr($response, 0, $headerSize);
$this->response['body'] = substr($response, $headerSize);
$this->parseHeader();
if ($debug) {
$info = curl_getinfo($ch);
echo "Request header
", var_dump($info['request_header']), ""; echo "Response header (raw)
", var_dump($this->response['headerRaw']), ""; echo "Response header (parsed)
", var_dump($this->response['header']), ""; } curl_close($ch); return true; } /** * Get HTTP code of response. * * @return integer as HTTP status code or null if not available. */ public function getStatus() { return isset($this->response['header']['status']) ? (int) $this->response['header']['status'] : null; } /** * Get file modification time of response. * * @return int as timestamp. */ public function getLastModified() { return isset($this->response['header']['Last-Modified']) ? strtotime($this->response['header']['Last-Modified']) : null; } /** * Get content type. * * @return string as the content type or null if not existing or invalid. */ public function getContentType() { $type = isset($this->response['header']['Content-Type']) ? $this->response['header']['Content-Type'] : null; return preg_match('#[a-z]+/[a-z]+#', $type) ? $type : null; } /** * Get file modification time of response. * * @param mixed $default as default value (int seconds) if date is * missing in response header. * * @return int as timestamp or $default if Date is missing in * response header. */ public function getDate($default = false) { return isset($this->response['header']['Date']) ? strtotime($this->response['header']['Date']) : $default; } /** * Get max age of cachable item. * * @param mixed $default as default value if date is missing in response * header. * * @return int as timestamp or false if not available. */ public function getMaxAge($default = false) { $cacheControl = isset($this->response['header']['Cache-Control']) ? $this->response['header']['Cache-Control'] : null; $maxAge = null; if ($cacheControl) { // max-age=2592000 $part = explode('=', $cacheControl); $maxAge = ($part[0] == "max-age") ? (int) $part[1] : null; } if ($maxAge) { return $maxAge; } $expire = isset($this->response['header']['Expires']) ? strtotime($this->response['header']['Expires']) : null; return $expire ? $expire : $default; } /** * Get body of response. * * @return string as body. */ public function getBody() { return $this->response['body']; } } /** * Get a image from a remote server using HTTP GET and If-Modified-Since. * */ class CRemoteImage { /** * Path to cache files. */ private $saveFolder = null; /** * Use cache or not. */ private $useCache = true; /** * HTTP object to aid in download file. */ private $http; /** * Status of the HTTP request. */ private $status; /** * Defalt age for cached items 60*60*24*7. */ private $defaultMaxAge = 604800; /** * Url of downloaded item. */ private $url; /** * Base name of cache file for downloaded item. */ private $fileName; /** * Filename for json-file with details of cached item. */ private $fileJson; /** * Filename for image-file. */ private $fileImage; /** * Cache details loaded from file. */ private $cache; /** * Constructor * */ public function __construct() { ; } /** * Get status of last HTTP request. * * @return int as status */ public function getStatus() { return $this->status; } /** * Get JSON details for cache item. * * @return array with json details on cache. */ public function getDetails() { return $this->cache; } /** * Set the path to the cache directory. * * @param boolean $use true to use the cache and false to ignore cache. * * @return $this */ public function setCache($path) { $this->saveFolder = $path; return $this; } /** * Check if cache is writable or throw exception. * * @return $this * * @throws Exception if cahce folder is not writable. */ public function isCacheWritable() { if (!is_writable($this->saveFolder)) { throw new Exception("Cache folder is not writable for downloaded files."); } return $this; } /** * Decide if the cache should be used or not before trying to download * a remote file. * * @param boolean $use true to use the cache and false to ignore cache. * * @return $this */ public function useCache($use = true) { $this->useCache = $use; return $this; } /** * Translate a content type to a file extension. * * @param string $type a valid content type. * * @return string as file extension or false if no match. */ function contentTypeToFileExtension($type) { $extension = array( 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', ); return isset($extension[$type]) ? $extension[$type] : false; } /** * Set header fields. * * @return $this */ function setHeaderFields() { $this->http->setHeader("User-Agent", "CImage/0.6 (PHP/". phpversion() . " cURL)"); $this->http->setHeader("Accept", "image/jpeg,image/png,image/gif"); if ($this->useCache) { $this->http->setHeader("Cache-Control", "max-age=0"); } else { $this->http->setHeader("Cache-Control", "no-cache"); $this->http->setHeader("Pragma", "no-cache"); } } /** * Save downloaded resource to cache. * * @return string as path to saved file or false if not saved. */ function save() { $this->cache = array(); $date = $this->http->getDate(time()); $maxAge = $this->http->getMaxAge($this->defaultMaxAge); $lastModified = $this->http->getLastModified(); $type = $this->http->getContentType(); $extension = $this->contentTypeToFileExtension($type); $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); $this->cache['Max-Age'] = $maxAge; $this->cache['Content-Type'] = $type; $this->cache['File-Extension'] = $extension; if ($lastModified) { $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); } if ($extension) { $this->fileImage = $this->fileName . "." . $extension; // Save only if body is a valid image $body = $this->http->getBody(); $img = imagecreatefromstring($body); if ($img !== false) { file_put_contents($this->fileImage, $body); file_put_contents($this->fileJson, json_encode($this->cache)); return $this->fileImage; } } return false; } /** * Got a 304 and updates cache with new age. * * @return string as path to cached file. */ function updateCacheDetails() { $date = $this->http->getDate(time()); $maxAge = $this->http->getMaxAge($this->defaultMaxAge); $lastModified = $this->http->getLastModified(); $this->cache['Date'] = gmdate("D, d M Y H:i:s T", $date); $this->cache['Max-Age'] = $maxAge; if ($lastModified) { $this->cache['Last-Modified'] = gmdate("D, d M Y H:i:s T", $lastModified); } file_put_contents($this->fileJson, json_encode($this->cache)); return $this->fileImage; } /** * Download a remote file and keep a cache of downloaded files. * * @param string $url a remote url. * * @return string as path to downloaded file or false if failed. */ function download($url) { $this->http = new CHttpGet(); $this->url = $url; // First check if the cache is valid and can be used $this->loadCacheDetails(); if ($this->useCache) { $src = $this->getCachedSource(); if ($src) { $this->status = 1; return $src; } } // Do a HTTP request to download item $this->setHeaderFields(); $this->http->setUrl($this->url); $this->http->doGet(); $this->status = $this->http->getStatus(); if ($this->status === 200) { $this->isCacheWritable(); return $this->save(); } else if ($this->status === 304) { $this->isCacheWritable(); return $this->updateCacheDetails(); } return false; } /** * Get the path to the cached image file if the cache is valid. * * @return $this */ public function loadCacheDetails() { $cacheFile = str_replace(array("/", ":", "#", ".", "?"), "-", $this->url); $this->fileName = $this->saveFolder . $cacheFile; $this->fileJson = $this->fileName . ".json"; if (is_readable($this->fileJson)) { $this->cache = json_decode(file_get_contents($this->fileJson), true); } } /** * Get the path to the cached image file if the cache is valid. * * @return string as the path ot the image file or false if no cache. */ public function getCachedSource() { $this->fileImage = $this->fileName . "." . $this->cache['File-Extension']; $imageExists = is_readable($this->fileImage); // Is cache valid? $date = strtotime($this->cache['Date']); $maxAge = $this->cache['Max-Age']; $now = time(); if ($imageExists && $date + $maxAge > $now) { return $this->fileImage; } // Prepare for a 304 if available if ($imageExists && isset($this->cache['Last-Modified'])) { $this->http->setHeader("If-Modified-Since", $this->cache['Last-Modified']); } return false; } } /** * Resize and crop images on the fly, store generated images in a cache. * * @author Mikael Roos mos@dbwebb.se * @example http://dbwebb.se/opensource/cimage * @link https://github.com/mosbth/cimage */ class CImage { /** * Constants type of PNG image */ const PNG_GREYSCALE = 0; const PNG_RGB = 2; const PNG_RGB_PALETTE = 3; const PNG_GREYSCALE_ALPHA = 4; const PNG_RGB_ALPHA = 6; /** * Constant for default image quality when not set */ const JPEG_QUALITY_DEFAULT = 60; /** * Quality level for JPEG images. */ private $quality; /** * Is the quality level set from external use (true) or is it default (false)? */ private $useQuality = false; /** * Constant for default image quality when not set */ const PNG_COMPRESSION_DEFAULT = -1; /** * Compression level for PNG images. */ private $compress; /** * Is the compress level set from external use (true) or is it default (false)? */ private $useCompress = false; /** * Default background color, red, green, blue, alpha. * * @todo remake when upgrading to PHP 5.5 */ /* const BACKGROUND_COLOR = array( 'red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => null, );*/ /** * Default background color to use. * * @todo remake when upgrading to PHP 5.5 */ //private $bgColorDefault = self::BACKGROUND_COLOR; private $bgColorDefault = array( 'red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => null, ); /** * Background color to use, specified as part of options. */ private $bgColor; /** * Where to save the target file. */ private $saveFolder; /** * The working image object. */ private $image; /** * The root folder of images (only used in constructor to create $pathToImage?). */ private $imageFolder; /** * Image filename, may include subdirectory, relative from $imageFolder */ private $imageSrc; /** * Actual path to the image, $imageFolder . '/' . $imageSrc */ private $pathToImage; /** * Original file extension */ private $fileExtension; /** * File extension to use when saving image. */ private $extension; /** * Output format, supports null (image) or json. */ private $outputFormat = null; /** * Verbose mode to print out a trace and display the created image */ private $verbose = false; /** * Keep a log/trace on what happens */ private $log = array(); /** * Handle image as palette image */ private $palette; /** * Target filename, with path, to save resulting image in. */ private $cacheFileName; /** * Set a format to save image as, or null to use original format. */ private $saveAs; /** * Path to command for filter optimize, for example optipng or null. */ private $pngFilter; /** * Path to command for deflate optimize, for example pngout or null. */ private $pngDeflate; /** * Path to command to optimize jpeg images, for example jpegtran or null. */ private $jpegOptimize; /** * Image dimensions, calculated from loaded image. */ private $width; // Calculated from source image private $height; // Calculated from source image /** * New image dimensions, incoming as argument or calculated. */ private $newWidth; private $newWidthOrig; // Save original value private $newHeight; private $newHeightOrig; // Save original value /** * Change target height & width when different dpr, dpr 2 means double image dimensions. */ private $dpr = 1; /** * Always upscale images, even if they are smaller than target image. */ const UPSCALE_DEFAULT = true; private $upscale = self::UPSCALE_DEFAULT; /** * Array with details on how to crop, incoming as argument and calculated. */ public $crop; public $cropOrig; // Save original value /** * String with details on how to do image convolution. String * should map a key in the $convolvs array or be a string of * 11 float values separated by comma. The first nine builds * up the matrix, then divisor and last offset. */ private $convolve; /** * Custom convolution expressions, matrix 3x3, divisor and offset. */ private $convolves = array( 'lighten' => '0,0,0, 0,12,0, 0,0,0, 9, 0', 'darken' => '0,0,0, 0,6,0, 0,0,0, 9, 0', 'sharpen' => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0', 'sharpen-alt' => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0', 'emboss' => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0', 'emboss-alt' => '-2,-1,0, -1,1,1, 0,1,2, 1, 0', 'blur' => '1,1,1, 1,15,1, 1,1,1, 23, 0', 'gblur' => '1,2,1, 2,4,2, 1,2,1, 16, 0', 'edge' => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0', 'edge-alt' => '0,1,0, 1,-4,1, 0,1,0, 1, 0', 'draw' => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0', 'mean' => '1,1,1, 1,1,1, 1,1,1, 9, 0', 'motion' => '1,0,0, 0,1,0, 0,0,1, 3, 0', ); /** * Resize strategy to fill extra area with background color. * True or false. */ private $fillToFit; /** * Used with option area to set which parts of the image to use. */ private $offset; /** * Calculate target dimension for image when using fill-to-fit resize strategy. */ private $fillWidth; private $fillHeight; /** * Allow remote file download, default is to disallow remote file download. */ private $allowRemote = false; /** * Pattern to recognize a remote file. */ //private $remotePattern = '#^[http|https]://#'; private $remotePattern = '#^https?://#'; /** * Use the cache if true, set to false to ignore the cached file. */ private $useCache = true; /** * Properties, the class is mutable and the method setOptions() * decides (partly) what properties are created. * * @todo Clean up these and check if and how they are used */ public $keepRatio; public $cropToFit; private $cropWidth; private $cropHeight; public $crop_x; public $crop_y; public $filters; private $type; // Calculated from source image private $attr; // Calculated from source image private $useOriginal; // Use original image if possible /** * Constructor, can take arguments to init the object. * * @param string $imageSrc filename which may contain subdirectory. * @param string $imageFolder path to root folder for images. * @param string $saveFolder path to folder where to save the new file or null to skip saving. * @param string $saveName name of target file when saveing. */ public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null) { $this->setSource($imageSrc, $imageFolder); $this->setTarget($saveFolder, $saveName); } /** * Set verbose mode. * * @param boolean $mode true or false to enable and disable verbose mode, * default is true. * * @return $this */ public function setVerbose($mode = true) { $this->verbose = $mode; return $this; } /** * Set save folder, base folder for saving cache files. * * @todo clean up how $this->saveFolder is used in other methods. * * @param string $path where to store cached files. * * @return $this */ public function setSaveFolder($path) { $this->saveFolder = $path; return $this; } /** * Use cache or not. * * @todo clean up how $this->noCache is used in other methods. * * @param string $use true or false to use cache. * * @return $this */ public function useCache($use = true) { $this->useCache = $use; return $this; } /** * Allow or disallow remote image download. * * @param boolean $allow true or false to enable and disable. * @param string $pattern to use to detect if its a remote file. * * @return $this */ public function setRemoteDownload($allow, $pattern = null) { $this->allowRemote = $allow; $this->remotePattern = $pattern ? $pattern : $this->remotePattern; $this->log("Set remote download to: " . ($this->allowRemote ? "true" : "false") . " using pattern " . $this->remotePattern); return $this; } /** * Check if the image resource is a remote file or not. * * @param string $src check if src is remote. * * @return boolean true if $src is a remote file, else false. */ public function isRemoteSource($src) { $remote = preg_match($this->remotePattern, $src); $this->log("Detected remote image: " . ($remote ? "true" : "false")); return $remote; } /** * Check if file extension is valid as a file extension. * * @param string $extension of image file. * * @return $this */ private function checkFileExtension($extension) { $valid = array('jpg', 'jpeg', 'png', 'gif'); in_array(strtolower($extension), $valid) or $this->raiseError('Not a valid file extension.'); return $this; } /** * Download a remote image and return path to its local copy. * * @param string $src remote path to image. * * @return string as path to downloaded remote source. */ public function downloadRemoteSource($src) { $remote = new CRemoteImage(); $cache = $this->saveFolder . "/remote/"; if (!is_dir($cache)) { if (!is_writable($this->saveFolder)) { throw new Exception("Can not create remote cache, cachefolder not writable."); } mkdir($cache); $this->log("The remote cache does not exists, creating it."); } if (!is_writable($cache)) { $this->log("The remote cache is not writable."); } $remote->setCache($cache); $remote->useCache($this->useCache); $src = $remote->download($src); $this->log("Remote HTTP status: " . $remote->getStatus()); $this->log("Remote item has local cached file: $src"); $this->log("Remote details on cache:" . print_r($remote->getDetails(), true)); return $src; } /** * Set src file. * * @param string $src of image. * @param string $dir as base directory where images are. * * @return $this */ public function setSource($src, $dir = null) { if (!isset($src)) { return $this; } if ($this->allowRemote && $this->isRemoteSource($src)) { $src = $this->downloadRemoteSource($src); $dir = null; } if (!isset($dir)) { $dir = dirname($src); $src = basename($src); } $this->imageSrc = ltrim($src, '/'); $this->imageFolder = rtrim($dir, '/'); $this->pathToImage = $this->imageFolder . '/' . $this->imageSrc; $this->fileExtension = strtolower(pathinfo($this->pathToImage, PATHINFO_EXTENSION)); //$this->extension = $this->fileExtension; $this->checkFileExtension($this->fileExtension); return $this; } /** * Set target file. * * @param string $src of target image. * @param string $dir as base directory where images are stored. * * @return $this */ public function setTarget($src = null, $dir = null) { if (!(isset($src) && isset($dir))) { return $this; } $this->saveFolder = $dir; $this->cacheFileName = $dir . '/' . $src; /* Allow readonly cache is_writable($this->saveFolder) or $this->raiseError('Target directory is not writable.'); */ // Sanitize filename $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName); $this->log("The cache file name is: " . $this->cacheFileName); return $this; } /** * Set options to use when processing image. * * @param array $args used when processing image. * * @return $this */ public function setOptions($args) { $this->log("Set new options for processing image."); $defaults = array( // Options for calculate dimensions 'newWidth' => null, 'newHeight' => null, 'aspectRatio' => null, 'keepRatio' => true, 'cropToFit' => false, 'fillToFit' => null, 'crop' => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0), 'area' => null, //'0,0,0,0', 'upscale' => self::UPSCALE_DEFAULT, // Options for caching or using original 'useCache' => true, 'useOriginal' => true, // Pre-processing, before resizing is done 'scale' => null, 'rotateBefore' => null, 'autoRotate' => false, // General options 'bgColor' => null, // Post-processing, after resizing is done 'palette' => null, 'filters' => null, 'sharpen' => null, 'emboss' => null, 'blur' => null, 'convolve' => null, 'rotateAfter' => null, // Output format 'outputFormat' => null, 'dpr' => 1, // Options for saving //'quality' => null, //'compress' => null, //'saveAs' => null, ); // Convert crop settings from string to array if (isset($args['crop']) && !is_array($args['crop'])) { $pices = explode(',', $args['crop']); $args['crop'] = array( 'width' => $pices[0], 'height' => $pices[1], 'start_x' => $pices[2], 'start_y' => $pices[3], ); } // Convert area settings from string to array if (isset($args['area']) && !is_array($args['area'])) { $pices = explode(',', $args['area']); $args['area'] = array( 'top' => $pices[0], 'right' => $pices[1], 'bottom' => $pices[2], 'left' => $pices[3], ); } // Convert filter settings from array of string to array of array if (isset($args['filters']) && is_array($args['filters'])) { foreach ($args['filters'] as $key => $filterStr) { $parts = explode(',', $filterStr); $filter = $this->mapFilter($parts[0]); $filter['str'] = $filterStr; for ($i=1; $i<=$filter['argc']; $i++) { if (isset($parts[$i])) { $filter["arg{$i}"] = $parts[$i]; } else { throw new Exception( 'Missing arg to filter, review how many arguments are needed at http://php.net/manual/en/function.imagefilter.php' ); } } $args['filters'][$key] = $filter; } } // Merge default arguments with incoming and set properties. //$args = array_merge_recursive($defaults, $args); $args = array_merge($defaults, $args); foreach ($defaults as $key => $val) { $this->{$key} = $args[$key]; } if ($this->bgColor) { $this->setDefaultBackgroundColor($this->bgColor); } // Save original values to enable re-calculating $this->newWidthOrig = $this->newWidth; $this->newHeightOrig = $this->newHeight; $this->cropOrig = $this->crop; return $this; } /** * Map filter name to PHP filter and id. * * @param string $name the name of the filter. * * @return array with filter settings * @throws Exception */ private function mapFilter($name) { $map = array( 'negate' => array('id'=>0, 'argc'=>0, 'type'=>IMG_FILTER_NEGATE), 'grayscale' => array('id'=>1, 'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE), 'brightness' => array('id'=>2, 'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS), 'contrast' => array('id'=>3, 'argc'=>1, 'type'=>IMG_FILTER_CONTRAST), 'colorize' => array('id'=>4, 'argc'=>4, 'type'=>IMG_FILTER_COLORIZE), 'edgedetect' => array('id'=>5, 'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT), 'emboss' => array('id'=>6, 'argc'=>0, 'type'=>IMG_FILTER_EMBOSS), 'gaussian_blur' => array('id'=>7, 'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR), 'selective_blur' => array('id'=>8, 'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR), 'mean_removal' => array('id'=>9, 'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL), 'smooth' => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH), 'pixelate' => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE), ); if (isset($map[$name])) { return $map[$name]; } else { throw new Exception('No such filter.'); } } /** * Load image details from original image file. * * @param string $file the file to load or null to use $this->pathToImage. * * @return $this * @throws Exception */ public function loadImageDetails($file = null) { $file = $file ? $file : $this->pathToImage; is_readable($file) or $this->raiseError('Image file does not exist.'); // Get details on image $info = list($this->width, $this->height, $this->type, $this->attr) = getimagesize($file); !empty($info) or $this->raiseError("The file doesn't seem to be an image."); if ($this->verbose) { $this->log("Image file: {$file}"); $this->log("Image width x height (type): {$this->width} x {$this->height} ({$this->type})."); $this->log("Image filesize: " . filesize($file) . " bytes."); } return $this; } /** * Init new width and height and do some sanity checks on constraints, before any * processing can be done. * * @return $this * @throws Exception */ public function initDimensions() { $this->log("Init dimension (before) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); // width as % if ($this->newWidth[strlen($this->newWidth)-1] == '%') { $this->newWidth = $this->width * substr($this->newWidth, 0, -1) / 100; $this->log("Setting new width based on % to {$this->newWidth}"); } // height as % if ($this->newHeight[strlen($this->newHeight)-1] == '%') { $this->newHeight = $this->height * substr($this->newHeight, 0, -1) / 100; $this->log("Setting new height based on % to {$this->newHeight}"); } is_null($this->aspectRatio) or is_numeric($this->aspectRatio) or $this->raiseError('Aspect ratio out of range'); // width & height from aspect ratio if ($this->aspectRatio && is_null($this->newWidth) && is_null($this->newHeight)) { if ($this->aspectRatio >= 1) { $this->newWidth = $this->width; $this->newHeight = $this->width / $this->aspectRatio; $this->log("Setting new width & height based on width & aspect ratio (>=1) to (w x h) {$this->newWidth} x {$this->newHeight}"); } else { $this->newHeight = $this->height; $this->newWidth = $this->height * $this->aspectRatio; $this->log("Setting new width & height based on width & aspect ratio (<1) to (w x h) {$this->newWidth} x {$this->newHeight}"); } } elseif ($this->aspectRatio && is_null($this->newWidth)) { $this->newWidth = $this->newHeight * $this->aspectRatio; $this->log("Setting new width based on aspect ratio to {$this->newWidth}"); } elseif ($this->aspectRatio && is_null($this->newHeight)) { $this->newHeight = $this->newWidth / $this->aspectRatio; $this->log("Setting new height based on aspect ratio to {$this->newHeight}"); } // Change width & height based on dpr if ($this->dpr != 1) { if (!is_null($this->newWidth)) { $this->newWidth = round($this->newWidth * $this->dpr); $this->log("Setting new width based on dpr={$this->dpr} - w={$this->newWidth}"); } if (!is_null($this->newHeight)) { $this->newHeight = round($this->newHeight * $this->dpr); $this->log("Setting new height based on dpr={$this->dpr} - h={$this->newHeight}"); } } // Check values to be within domain is_null($this->newWidth) or is_numeric($this->newWidth) or $this->raiseError('Width not numeric'); is_null($this->newHeight) or is_numeric($this->newHeight) or $this->raiseError('Height not numeric'); $this->log("Init dimension (after) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); return $this; } /** * Calculate new width and height of image, based on settings. * * @return $this */ public function calculateNewWidthAndHeight() { // Crop, use cropped width and height as base for calulations $this->log("Calculate new width and height."); $this->log("Original width x height is {$this->width} x {$this->height}."); $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}."); // Check if there is an area to crop off if (isset($this->area)) { $this->offset['top'] = round($this->area['top'] / 100 * $this->height); $this->offset['right'] = round($this->area['right'] / 100 * $this->width); $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height); $this->offset['left'] = round($this->area['left'] / 100 * $this->width); $this->offset['width'] = $this->width - $this->offset['left'] - $this->offset['right']; $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom']; $this->width = $this->offset['width']; $this->height = $this->offset['height']; $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%."); $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px."); } $width = $this->width; $height = $this->height; // Check if crop is set if ($this->crop) { $width = $this->crop['width'] = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width']; $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height']; if ($this->crop['start_x'] == 'left') { $this->crop['start_x'] = 0; } elseif ($this->crop['start_x'] == 'right') { $this->crop['start_x'] = $this->width - $width; } elseif ($this->crop['start_x'] == 'center') { $this->crop['start_x'] = round($this->width / 2) - round($width / 2); } if ($this->crop['start_y'] == 'top') { $this->crop['start_y'] = 0; } elseif ($this->crop['start_y'] == 'bottom') { $this->crop['start_y'] = $this->height - $height; } elseif ($this->crop['start_y'] == 'center') { $this->crop['start_y'] = round($this->height / 2) - round($height / 2); } $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px."); } // Calculate new width and height if keeping aspect-ratio. if ($this->keepRatio) { $this->log("Keep aspect ratio."); // Crop-to-fit and both new width and height are set. if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) { // Use newWidth and newHeigh as width/height, image should fit in box. $this->log("Use newWidth and newHeigh as width/height, image should fit in box."); } elseif (isset($this->newWidth) && isset($this->newHeight)) { // Both new width and height are set. // Use newWidth and newHeigh as max width/height, image should not be larger. $ratioWidth = $width / $this->newWidth; $ratioHeight = $height / $this->newHeight; $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight; $this->newWidth = round($width / $ratio); $this->newHeight = round($height / $ratio); $this->log("New width and height was set."); } elseif (isset($this->newWidth)) { // Use new width as max-width $factor = (float)$this->newWidth / (float)$width; $this->newHeight = round($factor * $height); $this->log("New width was set."); } elseif (isset($this->newHeight)) { // Use new height as max-hight $factor = (float)$this->newHeight / (float)$height; $this->newWidth = round($factor * $width); $this->log("New height was set."); } // Get image dimensions for pre-resize image. if ($this->cropToFit || $this->fillToFit) { // Get relations of original & target image $ratioWidth = $width / $this->newWidth; $ratioHeight = $height / $this->newHeight; if ($this->cropToFit) { // Use newWidth and newHeigh as defined width/height, // image should fit the area. $this->log("Crop to fit."); $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; $this->cropWidth = round($width / $ratio); $this->cropHeight = round($height / $ratio); $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio)."); } else if ($this->fillToFit) { // Use newWidth and newHeigh as defined width/height, // image should fit the area. $this->log("Fill to fit."); $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth; $this->fillWidth = round($width / $ratio); $this->fillHeight = round($height / $ratio); $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio)."); } } } // Crop, ensure to set new width and height if ($this->crop) { $this->log("Crop."); $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); } // Fill to fit, ensure to set new width and height /*if ($this->fillToFit) { $this->log("FillToFit."); $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']); $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']); }*/ // No new height or width is set, use existing measures. $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->width); $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height); $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}."); return $this; } /** * Re-calculate image dimensions when original image dimension has changed. * * @return $this */ public function reCalculateDimensions() { $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight); $this->newWidth = $this->newWidthOrig; $this->newHeight = $this->newHeightOrig; $this->crop = $this->cropOrig; $this->initDimensions() ->calculateNewWidthAndHeight(); return $this; } /** * Set extension for filename to save as. * * @param string $saveas extension to save image as * * @return $this */ public function setSaveAsExtension($saveAs = null) { if (isset($saveAs)) { $saveAs = strtolower($saveAs); $this->checkFileExtension($saveAs); $this->saveAs = $saveAs; $this->extension = $saveAs; } $this->log("Prepare to save image using as: " . $this->extension); return $this; } /** * Set JPEG quality to use when saving image * * @param int $quality as the quality to set. * * @return $this */ public function setJpegQuality($quality = null) { if ($quality) { $this->useQuality = true; } $this->quality = isset($quality) ? $quality : self::JPEG_QUALITY_DEFAULT; (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100) or $this->raiseError('Quality not in range.'); $this->log("Setting JPEG quality to {$this->quality}."); return $this; } /** * Set PNG compressen algorithm to use when saving image * * @param int $compress as the algorithm to use. * * @return $this */ public function setPngCompression($compress = null) { if ($compress) { $this->useCompress = true; } $this->compress = isset($compress) ? $compress : self::PNG_COMPRESSION_DEFAULT; (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9) or $this->raiseError('Quality not in range.'); $this->log("Setting PNG compression level to {$this->compress}."); return $this; } /** * Use original image if possible, check options which affects image processing. * * @param boolean $useOrig default is to use original if possible, else set to false. * * @return $this */ public function useOriginalIfPossible($useOrig = true) { if ($useOrig && ($this->newWidth == $this->width) && ($this->newHeight == $this->height) && !$this->area && !$this->crop && !$this->cropToFit && !$this->fillToFit && !$this->filters && !$this->sharpen && !$this->emboss && !$this->blur && !$this->convolve && !$this->palette && !$this->useQuality && !$this->useCompress && !$this->saveAs && !$this->rotateBefore && !$this->rotateAfter && !$this->autoRotate && !$this->bgColor && ($this->upscale === self::UPSCALE_DEFAULT) ) { $this->log("Using original image."); $this->output($this->pathToImage); } return $this; } /** * Generate filename to save file in cache. * * @param string $base as basepath for storing file. * * @return $this */ public function generateFilename($base) { $parts = pathinfo($this->pathToImage); $cropToFit = $this->cropToFit ? '_cf' : null; $fillToFit = $this->fillToFit ? '_ff' : null; $crop_x = $this->crop_x ? "_x{$this->crop_x}" : null; $crop_y = $this->crop_y ? "_y{$this->crop_y}" : null; $scale = $this->scale ? "_s{$this->scale}" : null; $bgColor = $this->bgColor ? "_bgc{$this->bgColor}" : null; $quality = $this->quality ? "_q{$this->quality}" : null; $compress = $this->compress ? "_co{$this->compress}" : null; $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null; $rotateAfter = $this->rotateAfter ? "_ra{$this->rotateAfter}" : null; $width = $this->newWidth; $height = $this->newHeight; $offset = isset($this->offset) ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left'] : null; $crop = $this->crop ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y'] : null; $filters = null; if (isset($this->filters)) { foreach ($this->filters as $filter) { if (is_array($filter)) { $filters .= "_f{$filter['id']}"; for ($i=1; $i<=$filter['argc']; $i++) { $filters .= ":".$filter["arg{$i}"]; } } } } $sharpen = $this->sharpen ? 's' : null; $emboss = $this->emboss ? 'e' : null; $blur = $this->blur ? 'b' : null; $palette = $this->palette ? 'p' : null; $autoRotate = $this->autoRotate ? 'ar' : null; $this->extension = isset($this->extension) ? $this->extension : $parts['extension']; $optimize = null; if ($this->extension == 'jpeg' || $this->extension == 'jpg') { $optimize = $this->jpegOptimize ? 'o' : null; } elseif ($this->extension == 'png') { $optimize .= $this->pngFilter ? 'f' : null; $optimize .= $this->pngDeflate ? 'd' : null; } $convolve = null; if ($this->convolve) { $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve); } $upscale = null; if ($this->upscale !== self::UPSCALE_DEFAULT) { $upscale = '_nu'; } $subdir = str_replace('/', '-', dirname($this->imageSrc)); $subdir = ($subdir == '.') ? '_.' : $subdir; $file = $subdir . '_' . $parts['filename'] . '_' . $width . '_' . $height . $offset . $crop . $cropToFit . $fillToFit . $crop_x . $crop_y . $upscale . $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor . $convolve . '.' . $this->extension; return $this->setTarget($file, $base); } /** * Use cached version of image, if possible. * * @param boolean $useCache is default true, set to false to avoid using cached object. * * @return $this */ public function useCacheIfPossible($useCache = true) { if ($useCache && is_readable($this->cacheFileName)) { $fileTime = filemtime($this->pathToImage); $cacheTime = filemtime($this->cacheFileName); if ($fileTime <= $cacheTime) { if ($this->useCache) { if ($this->verbose) { $this->log("Use cached file."); $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); } $this->output($this->cacheFileName, $this->outputFormat); } else { $this->log("Cache is valid but ignoring it by intention."); } } else { $this->log("Original file is modified, ignoring cache."); } } else { $this->log("Cachefile does not exists or ignoring it."); } return $this; } /** * Error message when failing to load somehow corrupt image. * * @return void * */ public function failedToLoad() { header("HTTP/1.0 404 Not Found"); echo("CImage.php says 404: Fatal error when opening image.
{$log}EOD; } /** * Raise error, enables to implement a selection of error methods. * * @param string $message the error message to display. * * @return void * @throws Exception */ private function raiseError($message) { throw new Exception($message); } } /** * Resize and crop images on the fly, store generated images in a cache. * * @author Mikael Roos mos@dbwebb.se * @example http://dbwebb.se/opensource/cimage * @link https://github.com/mosbth/cimage * */ $version = "v0.7.0 (2015-02-10)"; /** * Default configuration options, can be overridden in own config-file. * * @param string $msg to display. * * @return void */ function errorPage($msg) { global $mode; header("HTTP/1.0 500 Internal Server Error"); if ($mode == 'development') { die("[img.php] $msg"); } else { error_log("[img.php] $msg"); die("HTTP/1.0 500 Internal Server Error"); } } /** * Custom exception handler. */ set_exception_handler(function ($exception) { errorPage("
img.php: Uncaught exception:
" . $exception->getMessage() . "
" . $exception->getTraceAsString(), ""); }); /** * Get input from query string or return default value if not set. * * @param mixed $key as string or array of string values to look for in $_GET. * @param mixed $default value to return when $key is not set in $_GET. * * @return mixed value from $_GET or default value. */ function get($key, $default = null) { if (is_array($key)) { foreach ($key as $val) { if (isset($_GET[$val])) { return $_GET[$val]; } } } elseif (isset($_GET[$key])) { return $_GET[$key]; } return $default; } /** * Get input from query string and set to $defined if defined or else $undefined. * * @param mixed $key as string or array of string values to look for in $_GET. * @param mixed $defined value to return when $key is set in $_GET. * @param mixed $undefined value to return when $key is not set in $_GET. * * @return mixed value as $defined or $undefined. */ function getDefined($key, $defined, $undefined) { return get($key) === null ? $undefined : $defined; } /** * Get value from config array or default if key is not set in config array. * * @param string $key the key in the config array. * @param mixed $default value to be default if $key is not set in config. * * @return mixed value as $config[$key] or $default. */ function getConfig($key, $default) { global $config; return isset($config[$key]) ? $config[$key] : $default; } /** * Log when verbose mode, when used without argument it returns the result. * * @param string $msg to log. * * @return void or array. */ function verbose($msg = null) { global $verbose; static $log = array(); if (!$verbose) { return; } if (is_null($msg)) { return $log; } $log[] = $msg; } /** * Get configuration options from file, if the file exists, else use $config * if its defined or create an empty $config. */ $configFile = __DIR__.'/'.basename(__FILE__, '.php').'_config.php'; if (is_file($configFile)) { $config = require $configFile; } else if (!isset($config)) { $config = array(); } /** * verbose, v - do a verbose dump of what happens */ $verbose = getDefined(array('verbose', 'v'), true, false); verbose("img.php version = $version"); /** * Set mode as strict, production or development. * Default is production environment. */ $mode = getConfig('mode', 'production'); // Settings for any mode set_time_limit(20); ini_set('gd.jpeg_ignore_warning', 1); if (!extension_loaded('gd')) { errorPage("Extension gd is nod loaded."); } // Specific settings for each mode if ($mode == 'strict') { error_reporting(0); ini_set('display_errors', 0); ini_set('log_errors', 1); $verbose = false; } else if ($mode == 'production') { error_reporting(-1); ini_set('display_errors', 0); ini_set('log_errors', 1); $verbose = false; } else if ($mode == 'development') { error_reporting(-1); ini_set('display_errors', 1); ini_set('log_errors', 0); } else { errorPage("Unknown mode: $mode"); } verbose("mode = $mode"); verbose("error log = " . ini_get('error_log')); /** * Set default timezone if not set or if its set in the config-file. */ $defaultTimezone = getConfig('default_timezone', null); if ($defaultTimezone) { date_default_timezone_set($defaultTimezone); } else if (!ini_get('default_timezone')) { date_default_timezone_set('UTC'); } /** * Check if passwords are configured, used and match. * Options decide themself if they require passwords to be used. */ $pwdConfig = getConfig('password', false); $pwdAlways = getConfig('password_always', false); $pwd = get(array('password', 'pwd'), null); // Check if passwords match, if configured to use passwords $passwordMatch = null; if ($pwdAlways) { $passwordMatch = ($pwdConfig === $pwd); if (!$passwordMatch) { errorPage("Password required and does not match or exists."); } } elseif ($pwdConfig && $pwd) { $passwordMatch = ($pwdConfig === $pwd); } verbose("password match = $passwordMatch"); /** * Prevent hotlinking, leeching, of images by controlling who access them * from where. * */ $allowHotlinking = getConfig('allow_hotlinking', true); $hotlinkingWhitelist = getConfig('hotlinking_whitelist', array()); $serverName = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null; $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; $refererHost = parse_url($referer, PHP_URL_HOST); if (!$allowHotlinking) { if ($passwordMatch) { ; // Always allow when password match } else if ($passwordMatch === false) { errorPage("Hotlinking/leeching not allowed when password missmatch."); } else if (!$referer) { errorPage("Hotlinking/leeching not allowed and referer is missing."); } else if (strcmp($serverName, $refererHost) == 0) { ; // Allow when serverName matches refererHost } else if (!empty($hotlinkingWhitelist)) { $allowedByWhitelist = false; foreach ($hotlinkingWhitelist as $val) { if (preg_match($val, $refererHost)) { $allowedByWhitelist = true; } } if (!$allowedByWhitelist) { errorPage("Hotlinking/leeching not allowed by whitelist."); } } else { errorPage("Hotlinking/leeching not allowed."); } } verbose("allow_hotlinking = $allowHotlinking"); verbose("referer = $referer"); verbose("referer host = $refererHost"); /** * Get the source files. */ $autoloader = getConfig('autoloader', false); $cimageClass = getConfig('cimage_class', false); if ($autoloader) { require $autoloader; } else if ($cimageClass) { require $cimageClass; } /** * Create the class for the image. */ $img = new CImage(); $img->setVerbose($verbose); /** * Allow or disallow remote download of images from other servers. * Passwords apply if used. * */ $allowRemote = getConfig('remote_allow', false); if ($allowRemote && $passwordMatch !== false) { $pattern = getConfig('remote_pattern', null); $img->setRemoteDownload($allowRemote, $pattern); } /** * shortcut, sc - extend arguments with a constant value, defined * in config-file. */ $shortcut = get(array('shortcut', 'sc'), null); $shortcutConfig = getConfig('shortcut', array( 'sepia' => "&f=grayscale&f0=brightness,-10&f1=contrast,-20&f2=colorize,120,60,0,0&sharpen", )); verbose("shortcut = $shortcut"); if (isset($shortcut) && isset($shortcutConfig[$shortcut])) { parse_str($shortcutConfig[$shortcut], $get); verbose("shortcut-constant = {$shortcutConfig[$shortcut]}"); $_GET = array_merge($_GET, $get); } /** * src - the source image file. */ $srcImage = get('src') or errorPage('Must set src-attribute.'); // Check for valid/invalid characters $imagePath = getConfig('image_path', __DIR__ . '/img/'); $imagePathConstraint = getConfig('image_path_constraint', true); $validFilename = getConfig('valid_filename', '#^[a-z0-9A-Z-/_\.:]+$#'); preg_match($validFilename, $srcImage) or errorPage('Filename contains invalid characters.'); if ($allowRemote && $img->isRemoteSource($srcImage)) { // If source is a remote file, ignore local file checks. } else if ($imagePathConstraint) { // Check that the image is a file below the directory 'image_path'. $pathToImage = realpath($imagePath . $srcImage); $imageDir = realpath($imagePath); is_file($pathToImage) or errorPage( 'Source image is not a valid file, check the filename and that a matching file exists on the filesystem.' ); substr_compare($imageDir, $pathToImage, 0, strlen($imageDir)) == 0 or errorPage( 'Security constraint: Source image is not below the directory "image_path" as specified in the config file img_config.php.' ); } verbose("src = $srcImage"); /** * Manage size constants from config file, use constants to replace values * for width and height. */ $sizeConstant = getConfig('size_constant', function () { // Set sizes to map constant to value, easier to use with width or height $sizes = array( 'w1' => 613, 'w2' => 630, ); // Add grid column width, useful for use as predefined size for width (or height). $gridColumnWidth = 30; $gridGutterWidth = 10; $gridColumns = 24; for ($i = 1; $i <= $gridColumns; $i++) { $sizes['c' . $i] = ($gridColumnWidth + $gridGutterWidth) * $i - $gridGutterWidth; } return $sizes; }); $sizes = call_user_func($sizeConstant); /** * width, w - set target width, affecting the resulting image width, height and resize options */ $newWidth = get(array('width', 'w')); $maxWidth = getConfig('max_width', 2000); // Check to replace predefined size if (isset($sizes[$newWidth])) { $newWidth = $sizes[$newWidth]; } // Support width as % of original width if ($newWidth[strlen($newWidth)-1] == '%') { is_numeric(substr($newWidth, 0, -1)) or errorPage('Width % not numeric.'); } else { is_null($newWidth) or ($newWidth > 10 && $newWidth <= $maxWidth) or errorPage('Width out of range.'); } verbose("new width = $newWidth"); /** * height, h - set target height, affecting the resulting image width, height and resize options */ $newHeight = get(array('height', 'h')); $maxHeight = getConfig('max_height', 2000); // Check to replace predefined size if (isset($sizes[$newHeight])) { $newHeight = $sizes[$newHeight]; } // height if ($newHeight[strlen($newHeight)-1] == '%') { is_numeric(substr($newHeight, 0, -1)) or errorPage('Height % out of range.'); } else { is_null($newHeight) or ($newHeight > 10 && $newHeight <= $maxHeight) or errorPage('Hight out of range.'); } verbose("new height = $newHeight"); /** * aspect-ratio, ar - affecting the resulting image width, height and resize options */ $aspectRatio = get(array('aspect-ratio', 'ar')); $aspectRatioConstant = getConfig('aspect_ratio_constant', function () { return array( '3:1' => 3/1, '3:2' => 3/2, '4:3' => 4/3, '8:5' => 8/5, '16:10' => 16/10, '16:9' => 16/9, 'golden' => 1.618, ); }); // Check to replace predefined aspect ratio $aspectRatios = call_user_func($aspectRatioConstant); $negateAspectRatio = ($aspectRatio[0] == '!') ? true : false; $aspectRatio = $negateAspectRatio ? substr($aspectRatio, 1) : $aspectRatio; if (isset($aspectRatios[$aspectRatio])) { $aspectRatio = $aspectRatios[$aspectRatio]; } if ($negateAspectRatio) { $aspectRatio = 1 / $aspectRatio; } is_null($aspectRatio) or is_numeric($aspectRatio) or errorPage('Aspect ratio out of range'); verbose("aspect ratio = $aspectRatio"); /** * crop-to-fit, cf - affecting the resulting image width, height and resize options */ $cropToFit = getDefined(array('crop-to-fit', 'cf'), true, false); verbose("crop to fit = $cropToFit"); /** * Set default background color from config file. */ $backgroundColor = getConfig('background_color', null); if ($backgroundColor) { $img->setDefaultBackgroundColor($backgroundColor); verbose("Using default background_color = $backgroundColor"); } /** * bgColor - Default background color to use */ $bgColor = get(array('bgColor', 'bg-color', 'bgc'), null); verbose("bgColor = $bgColor"); /** * fill-to-fit, ff - affecting the resulting image width, height and resize options */ $fillToFit = get(array('fill-to-fit', 'ff'), null); verbose("fill-to-fit = $fillToFit"); if ($fillToFit !== null) { if (!empty($fillToFit)) { $bgColor = $fillToFit; verbose("fillToFit changed bgColor to = $bgColor"); } $fillToFit = true; verbose("fill-to-fit (fixed) = $fillToFit"); } /** * no-ratio, nr, stretch - affecting the resulting image width, height and resize options */ $keepRatio = getDefined(array('no-ratio', 'nr', 'stretch'), false, true); verbose("keep ratio = $keepRatio"); /** * crop, c - affecting the resulting image width, height and resize options */ $crop = get(array('crop', 'c')); verbose("crop = $crop"); /** * area, a - affecting the resulting image width, height and resize options */ $area = get(array('area', 'a')); verbose("area = $area"); /** * skip-original, so - skip the original image and always process a new image */ $useOriginal = getDefined(array('skip-original', 'so'), false, true); verbose("use original = $useOriginal"); /** * no-cache, nc - skip the cached version and process and create a new version in cache. */ $useCache = getDefined(array('no-cache', 'nc'), false, true); verbose("use cache = $useCache"); /** * quality, q - set level of quality for jpeg images */ $quality = get(array('quality', 'q')); is_null($quality) or ($quality > 0 and $quality <= 100) or errorPage('Quality out of range'); verbose("quality = $quality"); /** * compress, co - what strategy to use when compressing png images */ $compress = get(array('compress', 'co')); is_null($compress) or ($compress > 0 and $compress <= 9) or errorPage('Compress out of range'); verbose("compress = $compress"); /** * save-as, sa - what type of image to save */ $saveAs = get(array('save-as', 'sa')); verbose("save as = $saveAs"); /** * scale, s - Processing option, scale up or down the image prior actual resize */ $scale = get(array('scale', 's')); is_null($scale) or ($scale >= 0 and $scale <= 400) or errorPage('Scale out of range'); verbose("scale = $scale"); /** * palette, p - Processing option, create a palette version of the image */ $palette = getDefined(array('palette', 'p'), true, false); verbose("palette = $palette"); /** * sharpen - Processing option, post filter for sharpen effect */ $sharpen = getDefined('sharpen', true, null); verbose("sharpen = $sharpen"); /** * emboss - Processing option, post filter for emboss effect */ $emboss = getDefined('emboss', true, null); verbose("emboss = $emboss"); /** * blur - Processing option, post filter for blur effect */ $blur = getDefined('blur', true, null); verbose("blur = $blur"); /** * rotateBefore - Rotate the image with an angle, before processing */ $rotateBefore = get(array('rotateBefore', 'rotate-before', 'rb')); is_null($rotateBefore) or ($rotateBefore >= -360 and $rotateBefore <= 360) or errorPage('RotateBefore out of range'); verbose("rotateBefore = $rotateBefore"); /** * rotateAfter - Rotate the image with an angle, before processing */ $rotateAfter = get(array('rotateAfter', 'rotate-after', 'ra', 'rotate', 'r')); is_null($rotateAfter) or ($rotateAfter >= -360 and $rotateAfter <= 360) or errorPage('RotateBefore out of range'); verbose("rotateAfter = $rotateAfter"); /** * autoRotate - Auto rotate based on EXIF information */ $autoRotate = getDefined(array('autoRotate', 'auto-rotate', 'aro'), true, false); verbose("autoRotate = $autoRotate"); /** * filter, f, f0-f9 - Processing option, post filter for various effects using imagefilter() */ $filters = array(); $filter = get(array('filter', 'f')); if ($filter) { $filters[] = $filter; } for ($i = 0; $i < 10; $i++) { $filter = get(array("filter{$i}", "f{$i}")); if ($filter) { $filters[] = $filter; } } verbose("filters = " . print_r($filters, 1)); /** * json - output the image as a JSON object with details on the image. */ $outputFormat = getDefined('json', 'json', null); verbose("json = $outputFormat"); /** * dpr - change to get larger image to easier support larger dpr, such as retina. */ $dpr = get(array('ppi', 'dpr', 'device-pixel-ratio'), 1); verbose("dpr = $dpr"); /** * convolve - image convolution as in http://php.net/manual/en/function.imageconvolution.php */ $convolve = get('convolve', null); $convolutionConstant = getConfig('convolution_constant', array()); // Check if the convolve is matching an existing constant if ($convolve && isset($convolutionConstant)) { $img->addConvolveExpressions($convolutionConstant); verbose("convolve constant = " . print_r($convolutionConstant, 1)); } verbose("convolve = " . print_r($convolve, 1)); /** * no-upscale, nu - Do not upscale smaller image to larger dimension. */ $upscale = getDefined(array('no-upscale', 'nu'), false, true); verbose("upscale = $upscale"); /** * Get details for post processing */ $postProcessing = getConfig('postprocessing', array( 'png_filter' => false, 'png_filter_cmd' => '/usr/local/bin/optipng -q', 'png_deflate' => false, 'png_deflate_cmd' => '/usr/local/bin/pngout -q', 'jpeg_optimize' => false, 'jpeg_optimize_cmd' => '/usr/local/bin/jpegtran -copy none -optimize', )); /** * alias - Save resulting image to another alias name. * Password always apply, must be defined. */ $alias = get('alias', null); $aliasPath = getConfig('alias_path', null); $validAliasname = getConfig('valid_aliasname', '#^[a-z0-9A-Z-_]+$#'); $aliasTarget = null; if ($alias && $aliasPath && $passwordMatch) { $aliasTarget = $aliasPath . $alias; $useCache = false; is_writable($aliasPath) or errorPage("Directory for alias is not writable."); preg_match($validAliasname, $alias) or errorPage('Filename for alias contains invalid characters. Do not add extension.'); } else if ($alias) { errorPage('Alias is not enabled in the config file or password not matching.'); } verbose("alias = $alias"); /** * Display image if verbose mode */ if ($verbose) { $query = array(); parse_str($_SERVER['QUERY_STRING'], $query); unset($query['verbose']); unset($query['v']); unset($query['nocache']); unset($query['nc']); unset($query['json']); $url1 = '?' . htmlentities(urldecode(http_build_query($query))); $url2 = '?' . urldecode(http_build_query($query)); echo <<
$url1