setSource($imageSrc, $imageFolder); $this->setTarget($saveFolder, $saveName); } /** * Set verbose mode. * * @param boolean $mode true or false to enable and disable versbose mode, default is true. * * @return $this */ public function setVerbose($mode = true) { $this->verbose = $mode; return $this; } /** * 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($extension, $valid) or $this->raiseError('Not a valid file extension.'); return $this; } /** * Set src file. * * @param string $src of image. * @param string $dir as base directory where images are. * * @return $this */ public function setSource($src = null, $dir = null) { if (!(isset($src) && isset($dir))) { return $this; } $this->imageSrc = ltrim($src, '/'); $this->imageFolder = rtrim($dir, '/'); $this->pathToImage = $this->imageFolder . '/' . $this->imageSrc; $this->fileExtension = 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; 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, 'crop' => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0), 'area' => null, //'0,0,0,0', // Options for caching or using original 'useCache' => true, 'useOriginal' => true, // Pre-processing, before resizing is done 'scale' => null, // Post-processing, after resizing is done 'palette' => null, 'filters' => null, 'sharpen' => null, 'emboss' => null, 'blur' => null, // 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]; } 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.'); } } /** * 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() { is_readable($this->pathToImage) or $this->raiseError('Image file does not exist.'); // Get details on image $info = list($this->width, $this->height, $this->type, $this->attr) = getimagesize($this->pathToImage); !empty($info) or $this->raiseError("The file doesn't seem to be an image."); if ($this->verbose) { $this->log("Image file: {$this->pathToImage}"); $this->log("Image width x height (type): {$this->width} x {$this->height} ({$this->type})."); $this->log("Image filesize: " . filesize($this->pathToImage) . " bytes."); } // 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}"); } // 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'); 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}."); 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; 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) { // Crop-to-fit and both new width and height are set. if ($this->cropToFit && isset($this->newWidth) && isset($this->newHeight)) { // 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); } elseif (isset($this->newWidth)) { // Use new width as max-width $factor = (float)$this->newWidth / (float)$width; $this->newHeight = round($factor * $height); } elseif (isset($this->newHeight)) { // Use new height as max-hight $factor = (float)$this->newHeight / (float)$height; $this->newWidth = round($factor * $width); } // Use newWidth and newHeigh as defined width/height, image should fit the area. if ($this->cropToFit) { $ratioWidth = $width / $this->newWidth; $ratioHeight = $height / $this->newHeight; $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight; $this->cropWidth = round($width / $ratio); $this->cropHeight = round($height / $ratio); } } // Crop, ensure to set new width and height if ($this->crop) { $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; } /** * 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)) { $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) { $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) { $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->filters && !$this->sharpen && !$this->emboss && !$this->blur && !$this->palette && !$this->quality && !$this->compress && !$this->saveAs ) { $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; $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; $quality = $this->quality ? "_q{$this->quality}" : null; $compress = $this->compress ? "_co{$this->compress}" : null; $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; $this->extension = isset($this->extension) ? $this->extension : $parts['extension']; // Check optimizing options $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; } $subdir = str_replace('/', '-', dirname($this->imageSrc)); $subdir = ($subdir == '.') ? '_.' : $subdir; $file = $subdir . '_' . $parts['filename'] . '_' . round($this->newWidth) . '_' . round($this->newHeight) . $offset . $crop . $cropToFit . $crop_x . $crop_y . $quality . $filters . $sharpen . $emboss . $blur . $palette . $optimize . $scale . '.' . $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); } 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; } /** * Load image from disk. * * @param string $src of image. * @param string $dir as base directory where images are. * * @return $this * */ public function load($src = null, $dir = null) { if (isset($src)) { $this->setSource($src, $dir); } $this->log("Opening file as {$this->fileExtension}."); switch ($this->fileExtension) { case 'jpg': case 'jpeg': $this->image = @imagecreatefromjpeg($this->pathToImage); break; case 'gif': $this->image = @imagecreatefromgif($this->pathToImage); break; case 'png': $this->image = @imagecreatefrompng($this->pathToImage); $type = $this->getPngType(); $hasFewColors = imagecolorstotal($this->image); if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) { if ($this->verbose) { $this->log("Handle this image as a palette image."); } $this->palette = true; } break; default: $this->image = false; throw new Exception('No support for this file extension.'); } if ($this->verbose) { $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); $this->log("Number of colors in image = " . $this->colorsTotal($this->image)); } return $this; } /** * Get the type of PNG image. * * @return int as the type of the png-image * */ private function getPngType() { $pngType = ord (file_get_contents ($this->pathToImage, false, null, 25, 1)); switch ($pngType) { case self::PNG_GREYSCALE: $this->log("PNG is type 0, Greyscale."); break; case self::PNG_RGB: $this->log("PNG is type 2, RGB"); break; case self::PNG_RGB_PALETTE: $this->log("PNG is type 3, RGB with palette"); break; case self::PNG_GREYSCALE_ALPHA: $this->Log("PNG is type 4, Greyscale with alpha channel"); break; case self::PNG_RGB_ALPHA: $this->Log("PNG is type 6, RGB with alpha channel (PNG 32-bit)"); break; default: $this->Log("PNG is UNKNOWN type, is it really a PNG image?"); } return $pngType; } /** * Calculate number of colors in an image. * * @param resource $im the image. * * @return int */ private function colorsTotal($im) { if (imageistruecolor($im)) { $h = imagesy($im); $w = imagesx($im); $c = array(); for ($x=0; $x < $w; $x++) { for ($y=0; $y < $h; $y++) { @$c['c'.imagecolorat($im, $x, $y)]++; } } return count($c); } else { return imagecolorstotal($im); } } /** * Preprocess image before rezising it. * * @return $this */ public function preResize() { $this->log("Pre-process before resizing"); // Scale the original image before starting if (isset($this->scale)) { $this->log("Scale by {$this->scale}%"); $newWidth = $this->width * $this->scale / 100; $newHeight = $this->height * $this->scale / 100; $img = $this->CreateImageKeepTransparency($newWidth, $newHeight); imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height); $this->image = $img; $this->width = $newWidth; $this->height = $newHeight; } return $this; } /** * Resize and or crop the image. * * @return $this */ public function resize() { $this->log("Starting to Resize()"); // Only use a specified area of the image, $this->offset is defining the area to use if (isset($this->offset)) { $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}"); $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']); imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']); $this->image = $img; $this->width = $this->offset['width']; $this->height = $this->offset['height']; } // SaveAs need to copy image to remove transparency, if any if ($this->saveAs) { $this->log("Copying image before saving as another format, loosing transparency, width={$this->width}, height={$this->height}."); $img = imagecreatetruecolor($this->width, $this->height); $bg = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $bg); imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height); $this->image = $img; } // Do as crop, take only part of image if ($this->crop) { $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}"); $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']); imagecopyresampled($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height'], $this->crop['width'], $this->crop['height']); $this->image = $img; $this->width = $this->crop['width']; $this->height = $this->crop['height']; } // Resize by crop to fit if ($this->cropToFit) { $this->log("Crop to fit"); $cropX = round(($this->cropWidth/2) - ($this->newWidth/2)); $cropY = round(($this->cropHeight/2) - ($this->newHeight/2)); $imgPreCrop = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight); $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); imagecopyresampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height); imagecopyresampled($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight, $this->newWidth, $this->newHeight); $this->image = $imageResized; $this->width = $this->newWidth; $this->height = $this->newHeight; } else if (!($this->newWidth == $this->width && $this->newHeight == $this->height)) { // Resize it $this->log("Resizing, new height and/or width"); $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight); imagecopyresampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); //imagecopyresized($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height); $this->image = $imageResized; $this->width = $this->newWidth; $this->height = $this->newHeight; } return $this; } /** * Postprocess image after rezising image. * * @return $this */ public function postResize() { $this->log("Post-process after resizing"); // Apply filters if (isset($this->filters) && is_array($this->filters)) { foreach ($this->filters as $filter) { $this->log("Applying filter $filter."); switch ($filter['argc']) { case 0: imagefilter($this->image, $filter['type']); break; case 1: imagefilter($this->image, $filter['type'], $filter['arg1']); break; case 2: imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']); break; case 3: imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']); break; case 4: imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']); break; } } } // Convert to palette image if($this->palette) { $this->log("Converting to palette image."); $this->trueColorToPalette(); } // Blur the image if($this->blur) { $this->log("Blur."); $this->blurImage(); } // Emboss the image if($this->emboss) { $this->log("Emboss."); $this->embossImage(); } // Sharpen the image if($this->sharpen) { $this->log("Sharpen."); $this->sharpenImage(); } return $this; } /** * Convert true color image to palette image, keeping alpha. * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library * * @return void */ public function trueColorToPalette() { $img = imagecreatetruecolor($this->width, $this->height); $bga = imagecolorallocatealpha($img, 0, 0, 0, 127); imagecolortransparent($img, $bga); imagefill($img, 0, 0, $bga); imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height); imagetruecolortopalette($img, false, 255); imagesavealpha($img, true); if (imageistruecolor($this->image)) { $this->log("Matching colors with true color image."); imagecolormatch($this->image, $img); } $this->image = $img; } /** * Sharpen image as http://php.net/manual/en/ref.image.php#56144 * http://loriweb.pair.com/8udf-sharpen.html * * @return $this */ public function sharpenImage() { $matrix = array( array(-1,-1,-1,), array(-1,16,-1,), array(-1,-1,-1,), ); $divisor = 8; $offset = 0; imageconvolution($this->image, $matrix, $divisor, $offset); return $this; } /** * Emboss image as http://loriweb.pair.com/8udf-emboss.html * * @return $this */ public function embossImage() { $matrix = array( array( 1, 1,-1,), array( 1, 3,-1,), array( 1,-1,-1,), ); $divisor = 3; $offset = 0; imageconvolution($this->image, $matrix, $divisor, $offset); return $this; } /** * Blur image as http://loriweb.pair.com/8udf-basics.html * * @return $this */ public function blurImage() { $matrix = array( array( 1, 1, 1,), array( 1,15, 1,), array( 1, 1, 1,), ); $divisor = 23; $offset = 0; imageconvolution($this->image, $matrix, $divisor, $offset); return $this; } /** * Create a image and keep transparency for png and gifs. * * @param int $width of the new image. * @param int $height of the new image. * @return image resource. */ private function createImageKeepTransparency($width, $height) { $this->log("Creating a new working image width={$width}px, height={$height}px."); $img = imagecreatetruecolor($width, $height); imagealphablending($img, false); imagesavealpha($img, true); /* $this->Log("Filling image with background color."); $bg = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $bg); */ return $img; } /** * Set optimizing and post-processing options. CHANGE FROM DEFINE TO INJECT INTO CLASS, TO BE ABLE TO SET OFF POSTPROCESSING * * @param array $options with config for postprocessing with external tools. * * @return $this */ public function setPostProcessingOptions($options) { if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) { $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd']; } else { $this->jpegOptimizeCmd = null; } if (isset($options['png_filter']) && $options['png_filter']) { $this->pngFilterCmd = $options['png_filter_cmd']; } else { $this->pngFilterCmd = null; } if (isset($options['png_deflate']) && $options['png_deflate']) { $this->pngDeflateCmd = $options['png_deflate_cmd']; } else { $this->pngDeflateCmd = null; } return $this; } /** * Save image. * * @param string $src as target filename. * @param string $base as base directory where to store images. * * @return $this or false if no folder is set. */ public function save($src = null, $base = null) { if (isset($src)) { $this->setTarget($src, $base); } switch($this->extension) { case 'jpeg': case 'jpg': $this->Log("Saving image as JPEG to cache using quality = {$this->quality}."); imagejpeg($this->image, $this->cacheFileName, $this->quality); // Use JPEG optimize if defined if ($this->jpegOptimizeCmd) { if ($this->verbose) { clearstatcache(); $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes."); } $res = array(); $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName"; exec($cmd, $res); $this->log($cmd); $this->log($res); } break; case 'gif': if ($this->saveFolder) { $this->Log("Saving image as GIF to cache."); imagegif($this->image, $this->cacheFileName); } break; case 'png': $this->Log("Saving image as PNG to cache using compression = {$this->compress}."); // Turn off alpha blending and set alpha flag imagealphablending($this->image, false); imagesavealpha($this->image, true); imagepng($this->image, $this->cacheFileName, $this->compress); // Use external program to filter PNG, if defined if ($this->pngFilterCmd) { if ($this->verbose) { clearstatcache(); $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes."); } $res = array(); $cmd = $this->pngFilterCmd . " $this->cacheFileName"; exec($cmd, $res); $this->Log($cmd); $this->Log($res); } // Use external program to deflate PNG, if defined if ($this->pngDeflateCmd) { if ($this->verbose) { clearstatcache(); $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes."); } $res = array(); $cmd = $this->pngDeflateCmd . " $this->cacheFileName"; exec($cmd, $res); $this->Log($cmd); $this->Log($res); } break; default: $this->RaiseError('No support for this file extension.'); break; } if ($this->verbose) { clearstatcache(); $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes."); $this->log("imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false')); $this->log("imagecolorstotal() : " . imagecolorstotal($this->image)); $this->log("Number of colors in image = " . $this->ColorsTotal($this->image)); } return $this; } /** * Output image to browser using caching. * * @param string $file to read and output, default is to use $this->cacheFileName * * @return void */ public function output($file = null) { if (is_null($file)) { $file = $this->cacheFileName; } $this->log("Outputting image: $file"); // Get details on image $info = list($width, $height, $type, $attr) = getimagesize($file); !empty($info) or $this->raiseError("The file doesn't seem to be an image."); $mime = $info['mime']; $lastModified = filemtime($file); $gmdate = gmdate("D, d M Y H:i:s", $lastModified); if (!$this->verbose) { header('Last-Modified: ' . $gmdate . " GMT"); } if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) { if ($this->verbose) { $this->log("304 not modified"); $this->verboseOutput(); exit; } header("HTTP/1.0 304 Not Modified"); } else { if ($this->verbose) { $this->log("Last modified: " . $gmdate . " GMT"); $this->verboseOutput(); exit; } header('Content-type: ' . $mime); readfile($file); } exit; } /** * Log an event if verbose mode. * * @param string $message to log. * * @return this */ public function log($message) { if ($this->verbose) { $this->log[] = $message; } return $this; } /** * Do verbose output and print out the log and the actual images. * * @return void */ private function verboseOutput() { $log = null; $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M"); $this->log("Memory limit: " . ini_get('memory_limit')); $included = get_included_files(); $this->log("Included files: " . count($included)); foreach ($this->log as $val) { if (is_array($val)) { foreach ($val as $val1) { $log .= htmlentities($val1) . '
'; } } else { $log .= htmlentities($val) . '
'; } } echo << CImage verbose output

CImage Verbose Output

{$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); } }