%PDF- %PDF-
Direktori : /home/lightco1/www/plugins/content/sigplus/core/ |
Current File : /home/lightco1/www/plugins/content/sigplus/core/imagegenerator.php |
<?php /** * @file * @brief sigplus Image Gallery Plus image generation * @author Levente Hunyadi * @version 1.5.0 * @remarks Copyright (C) 2009-2017 Levente Hunyadi * @remarks Licensed under GNU/GPLv3, see http://www.gnu.org/licenses/gpl-3.0.html * @see http://hunyadi.info.hu/sigplus */ /* * sigplus Image Gallery Plus plug-in for Joomla * Copyright 2009-2017 Levente Hunyadi * * sigplus is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * sigplus is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ // no direct access defined( '_JEXEC' ) or die( 'Restricted access' ); require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'filesystem.php'; require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'librarian.php'; /** * Rotates an image in-place. * @param $image The image resource to rotate. * @param $angle The angle in degrees to rotate. * @return True on success, false on failure. */ function imagerotateinplace(&$image, $angle) { if (($rotated = imagerotate($image, $angle, 0)) !== false) { // rotation successful, we no longer need the original image imagedestroy($image); $image = $rotated; return true; } else { // rotation fails return false; } } /** * Computes the scaled target image size when an input image does not match desired dimensions. */ function imagescaledimensions($input_width, $input_height, $desired_width, $desired_height) { $scale_factor = 1; if ($input_width > 0 && $input_height > 0 && $desired_width > 0 && $desired_height > 0) { $scale_factor = min((float)$desired_width / (float)$input_width, (float)$desired_height / (float)$input_height); } else if ($input_width > 0 && $desired_width > 0) { $scale_factor = (float)$desired_width / (float)$input_width; } else if ($input_height > 0 && $desired_height > 0) { $scale_factor = (float)$desired_height / (float)$input_height; } $computed_width = (int)round($scale_factor * $input_width); $computed_height = (int)round($scale_factor * $input_height); return array($computed_width, $computed_height); } function imagefitdimensions($input_width, $input_height, $desired_width, $desired_height, $crop = false) { if ($crop || $desired_width > 0 && $desired_height > 0) { return array($desired_width, $desired_height); } else { return imagescaledimensions($input_width, $input_height, $desired_width, $desired_height); } } /** * Exposes an iterator interface to produce several thumbnail images from a single source. */ class SigPlusNovoThumbnailIterator implements Iterator { private $image; private $thumbnail; private $params; private $position = 0; public function __construct($image, array $params) { $this->image = $image; $this->params = $params; } public function rewind() { // included for completeness but this iterator does not support rewinding $this->position = 0; } public function current() { if ($this->position < count($this->params) - 1) { // make a copy because resampling in libraries ImageMagick and GraphicsMagick destroys the original image $this->thumbnail = clone $this->image; } else { // no need to make a copy because there will be no more operations $this->thumbnail = $this->image; } $item = clone $this->params[$this->position]; $item->image = $this->thumbnail; return $item; } public function key() { return $this->position; } public function next() { ++$this->position; // depending on iteration position, destroys the original or a copy $this->thumbnail->destroy(); } public function valid() { return $this->position < count($this->params); } } class SigPlusNovoImageLibrary { /** * Available memory computed from total memory and memory usage. */ protected static function memory_get_available() { static $limit = null; // value of php.ini configuration directive memory_limit in bytes if (!isset($limit)) { $inilimit = trim(ini_get('memory_limit')); if (empty($inilimit)) { // no limit set $limit = false; } elseif (is_numeric($inilimit)) { $limit = (int) $inilimit; } else { $limit = (int) substr($inilimit, 0, -1); switch (strtolower(substr($inilimit, -1))) { case 'g': $limit *= 1024; case 'm': $limit *= 1024; case 'k': $limit *= 1024; } } } if ($limit !== false) { if ($limit < 0) { return false; // no memory upper limit set in php.ini } else { return $limit - memory_get_usage(true); } } else { return false; } } /** * Generates a thumbnail image for an original image. * @param $output_params An array of objects with keys `path`, `width`, `height`, `crop` and `quality`. */ public function createRealThumbnail($image_path, array $output_params) { throw new SigPlusNovoImageLibraryUnavailableException(); } /** * Generates a watermarked image for an original image. * @param string $image_path The full path to the image to place a watermark into. * @param string $watermark_path The full path to the image to use as a watermark. * @param string $watermarked_image_path The full path where the watermarked image should be written. * @param $params An object with keys `position`, `x`, `y` and optionally `quality`. */ public function createRealWatermarked($image_path, $watermark_path, $watermarked_image_path, $params) { throw new SigPlusNovoImageLibraryUnavailableException(); } /** * Generates a thumbnail image for an original image. * @param $output_params An array of objects with keys `path`, `width`, `height`, `crop` and `quality`. */ public function createThumbnail($image_path, array $output_params) { if ('svg' != strtolower(pathinfo($image_path, PATHINFO_EXTENSION))) { return $this->createRealThumbnail($image_path, $output_params); } else { foreach ($output_params as $item) { copy($image_path, $item->path); } return true; } } /** * Generates a watermarked image for an original image. * @param string $image_path The full path to the image to place a watermark into. * @param string $watermark_path The full path to the image to use as a watermark. * @param string $watermarked_image_path The full path where the watermarked image should be written. * @param $params An object with keys `position`, `x`, `y` and optionally `quality`. */ public function createWatermarked($image_path, $watermark_path, $watermarked_image_path, $params) { if ('svg' != strtolower(pathinfo($image_path, PATHINFO_EXTENSION))) { return $this->createRealWatermarked($image_path, $watermark_path, $watermarked_image_path, $params); } else { copy($image_path, $watermarked_image_path); return true; } } public static function instantiate($library) { if ($library == 'default') { if (is_imagick_supported()) { $library = 'imagick'; } elseif (is_gmagick_supported()) { $library = 'gmagick'; } elseif (is_gd_supported()) { $library = 'gd'; } else { $library = 'none'; } } switch ($library) { case 'gd': if (is_gd_supported()) { return new SigPlusNovoImageLibraryGD(); } break; case 'gmagick': if (is_gmagick_supported()) { return new SigPlusNovoImageLibraryGmagick(); } break; case 'imagick': if (is_imagick_supported()) { return new SigPlusNovoImageLibraryImagick(); } break; } return new SigPlusNovoImageLibrary(); // all operations will throw an image library unavailable exception } /** * Checks whether sufficient memory is available to load and process an image. */ protected function checkMemory($imagepath) { $memory_available = self::memory_get_available(); if ($memory_available !== false) { $imagedata = fsx::getimagesize($imagepath); if ($imagedata === false) { return; } if (!isset($imagedata['channels'])) { // assume RGB (i.e. 3 channels) $imagedata['channels'] = 3; } if (!isset($imagedata['bits'])) { // assume 8 bits per channel $imagedata['bits'] = 8; } $memory_required = (int)ceil($imagedata[0] * $imagedata[1] * $imagedata['channels'] * $imagedata['bits'] / 8); $safety_factor = 1.8; // not all available memory can be consumed in order to ensure safe operations, safety factor is an empirical value if ($safety_factor * $memory_required >= $memory_available) { throw new SigPlusNovoOutOfMemoryException($memory_required, $memory_available, $imagepath); } } } /** * Computes the coordinates where a watermark image is to be placed within a target image. */ protected function computeCoordinates($params, $width, $height, $w, $h) { $position = isset($params->position) ? $params->position : false; $x = isset($params->x) ? $params->x : 0; $y = isset($params->y) ? $params->y : 0; $centerx = floor(($width - $w) / 2); $centery = floor(($height - $h) / 2); switch ($position) { case 'nw': break; case 'n': $x = $centerx; break; case 'ne': $x = $width - $w - $x; break; case 'w': $y = $centery; break; case 'c': $x = $centerx; $y = $centery; break; case 'e': $y = $centery; $x = $width - $w - $x; break; case 'sw': $y = $height - $h - $y; break; case 's': $x = $centerx; $y = $height - $h - $y; break; case 'se': $x = $width - $w - $x; $y = $height - $h - $y; break; default: $y = $height - $h - $y; break; } return array($x, $y); } } class SigPlusNovoImageLibraryGD extends SigPlusNovoImageLibrary { /** * Creates an in-memory image from a local or remote image. * @param string $imagepath The absolute path to a local image or the URL to a remote image. */ private static function imageFromFile($imagepath, &$orientation = null) { $ext = strtolower(pathinfo($imagepath, PATHINFO_EXTENSION)); switch ($ext) { case 'jpg': case 'jpeg': $image = fsx::imagecreatefromjpeg($imagepath); break; case 'gif': $image = fsx::imagecreatefromgif($imagepath); break; case 'png': $image = fsx::imagecreatefrompng($imagepath); break; default: $image = false; // missing or unrecognized extension break; } if ($image !== false && function_exists('exif_read_data') && func_num_args() > 1) { $exif = @exif_read_data($imagepath, 'IFD0'); if (!empty($exif['Orientation'])) { $orientation = $exif['Orientation']; } } return $image; } /** * Exports an in-memory image to a local image file. * @param string $imagepath The absolute path to a local image. * @param image $image In-memory image to export. * @param int $quality Quality measure between 0 and 100 for JPEG compression. */ private static function imageToFile($imagepath, $image, $quality) { $ext = strtolower(pathinfo($imagepath, PATHINFO_EXTENSION)); switch ($ext) { case 'jpg': case 'jpeg': return fsx::imagejpeg($image, $imagepath, $quality); case 'gif': return fsx::imagegif($image, $imagepath); case 'png': return fsx::imagepng($image, $imagepath, 9); default: return false; // missing or unrecognized extension } } /** * Gets orientation-aware image dimensions. * @param $orientation Orientation of the image resource with values 1 to 8 corresponding to the EXIF "Orientation tag"; 0 denotes unknown. * @param $image_w Image width, as per pixel data. * @param $image_h Image height, as per pixel data. * @return A two-element array of orientation-aware width and height. */ private static function getOrientationDimensions($orientation, $image_w, $image_h) { $orientation_w = $image_w; $orientation_h = $image_h; switch ($orientation) { case 5: case 6: case 7: case 8: // image is transposed $orientation_w = $image_h; $orientation_h = $image_w; break; default: // image is not transposed } return array($orientation_w, $orientation_h); } /** * Flips and/or rotates an image to match the original camera orientation. * @param $image A GD image resource. * @param $orientation Orientation of the image resource with values 1 to 8 corresponding to the EXIF "Orientation tag"; 0 denotes unknown. * @return True on success. */ private static function normalizeOrientation(&$image, $orientation) { $result = true; switch ($orientation) { case 1: // do nothing break; case 2: // flip horizontally $result = $result && imageflip($image, IMG_FLIP_HORIZONTAL); break; case 3: // rotate by 180 degrees $result = $result && imageflip($image, IMG_FLIP_BOTH); break; case 4: // flip vertically $result = $result && imageflip($image, IMG_FLIP_VERTICAL); break; case 5: // transpose (flip vertically and rotate clockwise by 90 degrees) $result = $result && imageflip($image, IMG_FLIP_VERTICAL); $result = $result && imagerotateinplace($image, 270); break; case 6: // rotate clockwise by 90 degrees $result = $result && imagerotateinplace($image, 270); break; case 7: // transverse (flip vertically and rotate counter-clockwise by 90 degrees) $result = $result && imageflip($image, IMG_FLIP_VERTICAL); $result = $result && imagerotateinplace($image, 90); break; case 8: // rotate counter-clockwise by 90 degrees $result = $result && imagerotateinplace($image, 90); break; } return $result; } /** * Determines whether an image is an animated GIF image. */ private static function isAnimated($imagepath) { if ('gif' != strtolower(pathinfo($imagepath, PATHINFO_EXTENSION))) { return false; // only GIF format supports animation } else { return (bool)preg_match('/\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)/s', file_get_contents($imagepath)); } } public function createRealThumbnail($image_path, array $output_params) { // check memory requirement for operation $this->checkMemory($image_path); if (self::isAnimated($image_path)) { // get GIF animation sequence $gifDecoder = new SigPlusNovoGifDecoder(fsx::file_get_contents($image_path)); $delays_between_frames = $gifDecoder->GIFGetDelays(); $loop_count = $gifDecoder->GIFGetLoop(); $disposal = 0; // $gifDecoder->GIFGetDisposal()[0] $transparent_red = $gifDecoder->GIFGetTransparentR(); $transparent_green = $gifDecoder->GIFGetTransparentG(); $transparent_blue = $gifDecoder->GIFGetTransparentB(); foreach ($output_params as $item) { // re-scale each frame of the animated image $target_frames = array(); foreach ($gifDecoder->GIFGetFrames() as $source_frame) { // convert string data into an image resource $source_image = imagecreatefromstring($source_frame); // re-scale a single frame $target_image = $this->getThumbnailFromResource($source_image, null, $item->width, $item->height, $item->crop, $item->quality); if ($target_image) { // convert image resource into a string ob_start(); imagegif($target_image); $target_frames[] = ob_get_clean(); // release image resource imagedestroy($target_image); } // release image resource imagedestroy($source_image); } // build an animated frames array from separate frames $gifEncoder = new SigPlusNovoGifEncoder( $target_frames, $delays_between_frames, $loop_count, $disposal, $transparent_red, $transparent_green, $transparent_blue ); // save the animation in a single file fsx::file_put_contents($item->path, $gifEncoder->GetAnimation()); } unset($gifDecoder); return true; } else { // load image from file $source_img = self::imageFromFile($image_path, $orientation); if (!$source_img) { return false; // could not create image from file } $result = true; foreach ($output_params as $item) { $result = $result && $this->createThumbnailFromResource($source_img, $orientation, $item->path, $item->width, $item->height, $item->crop, $item->quality); } imagedestroy($source_img); return $result; } } public function createThumbnailFromResource($source_img, $orientation, $thumbpath, $thumb_w, $thumb_h, $crop = true, $quality = 85) { // process image $thumb_img = $this->getThumbnailFromResource($source_img, $orientation, $thumb_w, $thumb_h, $crop, $quality); if (!$thumb_img) { return false; } // save image to file $result = self::imageToFile($thumbpath, $thumb_img, $quality); imagedestroy($thumb_img); return $result; } /** * Creates a thumbnail image from a source image. * * @param $source_img The image resource to serve as source. * @param $orientation Orientation of the image resource with values 1 to 8 corresponding to the EXIF "Orientation tag"; 0 denotes unknown. * @param $expected_w The desired thumbnail width. * @param $expected_h The desired thumbnail height. * @param $crop Whether to crop images or re-scale them. */ private function getThumbnailFromResource($source_img, $orientation, $expected_w, $expected_h, $crop = true, $quality = 85) { // set intermediate dimensions for target image, taking into account original image orientation list($thumb_w, $thumb_h) = self::getOrientationDimensions($orientation, $expected_w, $expected_h); // get dimensions for cropping and resizing $orig_w = imagesx($source_img); $orig_h = imagesy($source_img); if (false && $thumb_w >= $orig_w && $thumb_h >= $orig_h) { // nothing to do $thumb_img = $source_img; } else { $ratio_orig = $orig_w/$orig_h; // width-to-height ratio of original image if ($crop) { // resize with automatic centering, crop image if necessary if ($thumb_w == 0 || $thumb_h == 0) { throw new SigPlusNovoImageProcessingException('Both width and height must be specified when images are rescaled with cropping enabled.'); } $ratio_thumb = $thumb_w/$thumb_h; // width-to-height ratio of thumbnail image if ($ratio_thumb > $ratio_orig) { // crop top and bottom $zoom = $orig_w / $thumb_w; // zoom factor of original image w.r.t. thumbnail $crop_h = floor($zoom * $thumb_h); $crop_w = $orig_w; $crop_x = 0; $crop_y = floor(0.5 * ($orig_h - $crop_h)); } else { // crop left and right $zoom = $orig_h / $thumb_h; // zoom factor of original image w.r.t. thumbnail $crop_h = $orig_h; $crop_w = floor($zoom * $thumb_w); $crop_x = floor(0.5 * ($orig_w - $crop_w)); $crop_y = 0; } } else { // resize with fitting larger dimension, do not crop image $crop_w = $orig_w; $crop_h = $orig_h; $crop_x = 0; $crop_y = 0; if ($thumb_w == 0) { // width unspecified $zoom = $orig_h / $thumb_h; $thumb_w = floor($orig_w / $zoom); } elseif ($thumb_h == 0) { // height unspecified $zoom = $orig_w / $thumb_w; $thumb_h = floor($orig_h / $zoom); } elseif ($thumb_w/$thumb_h > $ratio_orig) { // fit height $zoom = $orig_h / $thumb_h; $thumb_w = floor($orig_w / $zoom); } else { // fit width $zoom = $orig_w / $thumb_w; $thumb_h = floor($orig_h / $zoom); } // formula above may produce zero width or height for extremely narrow and extremely elongated images if ($thumb_w < 1) { $thumb_w = 1; // any image must be at least 1px wide } if ($thumb_h < 1) { $thumb_h = 1; // any image must be at least 1px tall } } // create resource for thumbnail image $thumb_img = imagecreatetruecolor($thumb_w, $thumb_h); // set transparency for a palette image if (!imageistruecolor($source_img) && ($transparentindex = imagecolortransparent($source_img)) >= 0) { // convert index transparency to color (alpha channel) transparency if (imagecolorstotal($source_img) > $transparentindex) { // transparent color is in palette $transparentrgba = imagecolorsforindex($source_img, $transparentindex); } else { // use white as transparent background color $transparentrgba = array('red' => 255, 'green' => 255, 'blue' => 255); } // fill image with transparent color $transparentcolor = imagecolorallocate($thumb_img, $transparentrgba['red'], $transparentrgba['green'], $transparentrgba['blue']); imagecolortransparent($thumb_img, $transparentcolor); imagefilledrectangle($thumb_img, 0, 0, $thumb_w, $thumb_h, $transparentcolor); imagecolordeallocate($thumb_img, $transparentcolor); } // set alpha blending mode $result = true; if (imageistruecolor($source_img)) { $result = $result && imagealphablending($thumb_img, false); $result = $result && imagesavealpha($thumb_img, true); } // re-sample image into thumbnail size if (imageistruecolor($source_img)) { // use re-sample for true color images (e.g. PNG, JPEG) $result = $result && imagecopyresampled($thumb_img, $source_img, 0, 0, $crop_x, $crop_y, $thumb_w, $thumb_h, $crop_w, $crop_h); } else { // use re-size for palette images (e.g. GIF) $result = $result && imagecopyresized($thumb_img, $source_img, 0, 0, $crop_x, $crop_y, $thumb_w, $thumb_h, $crop_w, $crop_h); // convert true color thumbnail image to match palette source image $result = $result && imagetruecolortopalette($thumb_img, false, imagecolorstotal($source_img)); } // flip and/or rotate target image to match original image orientation $result = $result && self::normalizeOrientation($thumb_img, $orientation); if ($result === false) { imagedestroy($thumb_img); return false; } } return $thumb_img; } public function createRealWatermarked($image_path, $watermark_path, $watermarked_image_path, $params) { if (self::isAnimated($image_path)) { throw new SigPlusNovoImageProcessingException("A watermark cannot be applied to animated images."); } if (!isset($params->quality)) { $params->quality = 85; } // check memory requirement for operation $this->checkMemory($image_path); // load watermark image $watermark_img = self::imageFromFile($watermark_path); if (!$watermark_img) { return false; // could not create image from file } // load image $source_img = self::imageFromFile($image_path, $orientation); if (!$source_img) { return false; // could not create image from file } // get image dimensions, taking into account camera orientation list($width, $height) = self::getOrientationDimensions($orientation, imagesx($source_img), imagesy($source_img)); // flip and/or rotate target image to match original image orientation $result = self::normalizeOrientation($source_img, $orientation); // get target location for watermark image $w = imagesx($watermark_img); $h = imagesy($watermark_img); list($x, $y) = $this->computeCoordinates($params, $width, $height, $w, $h); // super-impose watermark $result = $result && imagecopy($source_img, $watermark_img, $x, $y, 0, 0, $w, $h); imagedestroy($watermark_img); $result = $result && self::imageToFile($watermarked_image_path, $source_img, $params->quality); imagedestroy($source_img); return $result; } } class SigPlusNovoImageLibraryImagick extends SigPlusNovoImageLibrary { private static function isAnimated($image) { $frames = 0; foreach ($image as $i) { $frames++; if ($frames > 1) { return true; } } return false; } private static function getImageOrientation($image) { if (method_exists($image, 'getImageOrientation')) { return $image->getImageOrientation(); } elseif (function_exists('exif_read_data')) { $filepath = $image->getImageFilename(); if (empty($filepath)) { $filepath = 'data:image/jpeg;base64,' . base64_encode($image->getImageBlob()); } $exif = @exif_read_data($filepath); return isset($exif['Orientation']) ? $exif['Orientation'] : null; } else { return 0; } } /** * Flips and/or rotates an image to match the original camera orientation. * @param $image An ImageMagick image resource. * @param $orientation Orientation of the image resource with values 1 to 8 corresponding to the EXIF "Orientation tag"; 0 denotes unknown. * @return True on success. */ private static function normalizeOrientation($image, $orientation) { $result = true; if (!empty($orientation)) { switch ($orientation) { case 1: // do nothing break; case 2: // flip horizontally $result = $result && $image->flopImage(); break; case 3: // rotate by 180 degrees $result = $result && $image->flipImage(); $result = $result && $image->flopImage(); break; case 4: // flip vertically $result = $result && $image->flipImage(); break; case 5: // transpose (flip vertically and rotate clockwise by 90 degrees) $result = $result && $image->transposeImage(); break; case 6: // rotate clockwise by 90 degrees $result = $result && $image->rotateImage(new ImagickPixel(), 90); break; case 7: // transverse (flip vertically and rotate counter-clockwise by 90 degrees) $result = $result && $image->transverseImage(); break; case 8: // rotate counter-clockwise by 90 degrees $result = $result && $image->rotateImage(new ImagickPixel(), -90); break; } if (method_exists($image, 'getImageOrientation') && method_exists($image, 'setImageOrientation')) { if ($image->getImageOrientation()) { $result = $result && $image->setImageOrientation(1); } } } return $result; } public function createRealThumbnail($image_path, array $output_params) { $result = true; try { $original_image = new Imagick($image_path); // remove all EXIF data but keep ICC profile (which causes richer colors) $profiles = $original_image->getImageProfiles('icc', true); $original_image->stripImage(); if (!empty($profiles)) { $original_image->profileImage('icc', $profiles['icc']); } // get iterator over output parameters that clones source image on demand $iterator = new SigPlusNovoThumbnailIterator($original_image, $output_params); if (self::isAnimated($original_image)) { foreach ($iterator as $item) { // loop through the frames foreach ($item->image as $frame) { if ($crop) { // resize with automatic centering, crop frame if necessary $frame->cropThumbnailImage($item->width, $item->height); } else { // resize with fitting larger dimension, do not crop frame $frame->thumbnailImage($item->width, $item->height, true); } $frame->setImagePage($item->width, $item->height, 0, 0); } // write animated image to disk $result = $result && $item->image->writeImages($item->path, true); } } else { // get image orientation $orientation = self::getImageOrientation($original_image); // set compression target foreach ($iterator as $item) { $item->image->setImageCompressionQuality($item->quality); // resize standard (non-animated) image if ($item->crop) { // resize with automatic centering, crop image if necessary $result = $result && $item->image->cropThumbnailImage($item->width, $item->height); } else { // resize with fitting larger dimension, do not crop image $result = $result && $item->image->thumbnailImage($item->width, $item->height, true); } // flip and/or rotate target image to match original image orientation $result = $result && self::normalizeOrientation($item->image, $orientation); // write standard image to disk $result = $result && $item->image->writeImage($item->path); } } } catch (ImagickException $exception) { throw new SigPlusNovoImageProcessingException($exception->getMessage()); } return $result; } public function createRealWatermarked($image_path, $watermark_path, $watermarked_image_path, $params) { $result = true; try { // load target image $image = new Imagick($image_path); // flip and/or rotate target image to match original image orientation $orientation = self::getImageOrientation($image); $result = self::normalizeOrientation($image, $orientation); // load watermark image $watermark = new Imagick($watermark_path); $geometry = $watermark->getImageGeometry(); $w = $geometry['width']; $h = $geometry['height']; // calculate coordinates of watermark within target image bounds $geometry = $image->getImageGeometry(); $width = $geometry['width']; $height = $geometry['height']; list($x, $y) = $this->computeCoordinates($params, $width, $height, $w, $h); // super-impose watermark on target image $result = $result && $image->compositeImage($watermark, Imagick::COMPOSITE_DEFAULT, $x, $y); $watermark->destroy(); // write target image to disk $result = $result && $image->writeImage($watermarked_image_path); $image->destroy(); } catch (ImagickException $exception) { throw new SigPlusNovoImageProcessingException($exception->getMessage()); } return $result; } } class SigPlusNovoImageLibraryGmagick extends SigPlusNovoImageLibrary { /** * Returns image EXIF orientation. * @param $image A Gmagick image resource. * @return Orientation of the image resource with values 1 to 8 corresponding to the EXIF "Orientation tag"; 0 denotes unknown. */ private static function getImageOrientation($image) { if (function_exists('exif_read_data')) { $filepath = $image->getFilename(); if (empty($filepath)) { $filepath = 'data:image/jpeg;base64,' . base64_encode($image->getImageBlob()); } $exif = @exif_read_data($filepath); return isset($exif['Orientation']) ? $exif['Orientation'] : null; } else { return 0; } } /** * Flips and/or rotates an image to match the original camera orientation. * @param $image A Gmagick image resource. * @param $orientation Orientation of the image resource with values 1 to 8 corresponding to the EXIF "Orientation tag"; 0 denotes unknown. */ private static function normalizeOrientation($image, $orientation) { if (!empty($orientation)) { switch ($orientation) { case 1: // do nothing break; case 2: // flip horizontally $image->flopImage(); break; case 3: // rotate by 180 degrees $image->flipImage(); $image->flopImage(); break; case 4: // flip vertically $image->flipImage(); break; case 5: // transpose (flip vertically and rotate clockwise by 90 degrees) $image->flipImage(); $image->rotateImage(new GmagickPixel(), -90); break; case 6: // rotate clockwise by 90 degrees $image->rotateImage(new GmagickPixel(), 90); break; case 7: // transverse (flip vertically and rotate counter-clockwise by 90 degrees) $image->flipImage(); $image->rotateImage(new GmagickPixel(), 90); break; case 8: // rotate counter-clockwise by 90 degrees $image->rotateImage(new GmagickPixel(), -90); break; } // reset orientation to default $image->stripImage(); } } public function createRealThumbnail($image_path, array $output_params) { try { // read source image from disk $original_image = new Gmagick(); $original_image->readImage($image_path); // get image orientation $orientation = self::getImageOrientation($original_image); // get iterator over output parameters that clones source image on demand $iterator = new SigPlusNovoThumbnailIterator($original_image, $output_params); foreach ($iterator as $item) { $item->image->setCompressionQuality($item->quality); if ($item->crop) { // resize with automatic centering, crop image if necessary $item->image->cropThumbnailImage($item->width, $item->height); } else { // resize with fitting larger dimension, do not crop image $item->image->thumbnailImage($item->width, $item->height, true); } // flip and/or rotate target image to match original image orientation self::normalizeOrientation($item->image, $orientation); // write target image to disk $item->image->writeImage($item->path); } } catch (GmagickException $exception) { throw new SigPlusNovoImageProcessingException($exception->getMessage()); } return true; } public function createRealWatermarked($image_path, $watermark_path, $watermarked_image_path, $params) { try { // load target image $image = new Gmagick(); $image->readImage($image_path); // flip and/or rotate target image to match original image orientation $orientation = self::getImageOrientation($image); self::normalizeOrientation($image, $orientation); // load watermark image $watermark = new Gmagick(); $watermark->readImage($watermark_path); $width = $watermark->getImageWidth(); $height = $watermark->getImageHeight(); // calculate coordinates of watermark within target image bounds list($x, $y) = $this->computeCoordinates($params, $image->getImageWidth(), $image->getImageHeight(), $width, $height); // super-impose watermark on target image $image->compositeImage($watermark, Gmagick::COMPOSITE_DEFAULT, $x, $y); $watermark->destroy(); // write target image to disk $image->writeImage($watermarked_image_path); $image->destroy(); } catch (GmagickException $exception) { throw new SigPlusNovoImageProcessingException($exception->getMessage()); } return true; } } class SigPlusNovoGifDecoder { private $GIF_TransparentR = -1; private $GIF_TransparentG = -1; private $GIF_TransparentB = -1; private $GIF_TransparentI = 0; private $GIF_buffer = array(); private $GIF_arrays = array(); private $GIF_delays = array(); private $GIF_dispos = array(); private $GIF_stream = ""; private $GIF_string = ""; private $GIF_bfseek = 0; private $GIF_anloop = 0; private $GIF_screen = array(); private $GIF_global = array(); private $GIF_sorted; private $GIF_colorS; private $GIF_colorC; private $GIF_colorF; /** * Decodes an animated GIF image into a sequence of frames. * * @param $GIF_pointer Binary data of an animated GIF image. */ public function __construct($GIF_pointer) { $this->GIF_stream = $GIF_pointer; self::GIFGetByte(6); self::GIFGetByte(7); $this->GIF_screen = $this->GIF_buffer; $this->GIF_colorF = ($this->GIF_buffer[4] & 0x80) ? 1 : 0; $this->GIF_sorted = ($this->GIF_buffer[4] & 0x08) ? 1 : 0; $this->GIF_colorC = $this->GIF_buffer[4] & 0x07; $this->GIF_colorS = 2 << $this->GIF_colorC; if ($this->GIF_colorF == 1) { self::GIFGetByte(3 * $this->GIF_colorS); $this->GIF_global = $this->GIF_buffer; } for ($cycle = 1; $cycle; ) { if (self::GIFGetByte(1)) { switch ($this->GIF_buffer[0]) { case 0x21: // character "!" indicates an extension block self::GIFReadExtensions(); break; case 0x2C: // character "," indicates an image self::GIFReadDescriptor(); break; case 0x3B: // character ";" should be the last byte of file $cycle = 0; break; } } else { $cycle = 0; } } } private function GIFReadExtensions() { self::GIFGetByte(1); // Graphic Control Label if ($this->GIF_buffer[0] == 0xff) { for (;;) { self::GIFGetByte(1); if (($u = $this->GIF_buffer[0]) == 0x00) { break; } self::GIFGetByte($u); if ($u == 0x03) { $this->GIF_anloop = ($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8); } } } else { for (;;) { self::GIFGetByte(1); // Block Size if (($u = $this->GIF_buffer[0]) == 0x00) { // block size of zero marks the end of Graphic Control Extension break; } self::GIFGetByte($u); // read as many bytes as size of block if ($u == 0x04) { // +---------------+ // 0 | | Block Size Byte // +---------------+ // 1 | | | | | <Packed Fields> See below // +---------------+ // 2 | | Delay Time Unsigned // +- -+ // 3 | | // +---------------+ // 4 | | Transparent Color Index Byte // +---------------+ // // <Packed Fields> = Reserved 3 Bits // Disposal Method 3 Bits // User Input Flag 1 Bit // Transparent Color Flag 1 Bit $this->GIF_dispos[] = ($this->GIF_buffer[0] >> 2) & 0x07; $this->GIF_delays[] = ($this->GIF_buffer[1] | $this->GIF_buffer[2] << 8); if ($this->GIF_buffer[0] & 0x01) { $this->GIF_TransparentI = $this->GIF_buffer[3]; } } } } } private function GIFReadDescriptor() { $GIF_screen = array(); self::GIFGetByte(9); $GIF_screen = $this->GIF_buffer; $GIF_colorF = ($this->GIF_buffer[8] & 0x80) ? 1 : 0; if ($GIF_colorF) { $GIF_code = $this->GIF_buffer[8] & 0x07; $GIF_sort = ($this->GIF_buffer[8] & 0x20) ? 1 : 0; } else { $GIF_code = $this->GIF_colorC; $GIF_sort = $this->GIF_sorted; } $GIF_size = 2 << $GIF_code; $this->GIF_screen[4] &= 0x70; $this->GIF_screen[4] |= 0x80; $this->GIF_screen[4] |= $GIF_code; if ($GIF_sort) { $this->GIF_screen[4] |= 0x08; } if ($this->GIF_TransparentI) { $this->GIF_string = 'GIF89a'; } else { $this->GIF_string = 'GIF87a'; } self::GIFPutByte($this->GIF_screen); if ($GIF_colorF == 1) { self::GIFGetByte(3 * $GIF_size); if($this->GIF_TransparentI) { $this->GIF_TransparentR = $this->GIF_buffer[3 * $this->GIF_TransparentI + 0]; $this->GIF_TransparentG = $this->GIF_buffer[3 * $this->GIF_TransparentI + 1]; $this->GIF_TransparentB = $this->GIF_buffer[3 * $this->GIF_TransparentI + 2]; } self::GIFPutByte($this->GIF_buffer); } else { if ($this->GIF_TransparentI) { $this->GIF_TransparentR = $this->GIF_global[3 * $this->GIF_TransparentI + 0]; $this->GIF_TransparentG = $this->GIF_global[3 * $this->GIF_TransparentI + 1]; $this->GIF_TransparentB = $this->GIF_global[3 * $this->GIF_TransparentI + 2]; } self::GIFPutByte($this->GIF_global); } if ($this->GIF_TransparentI) { $this->GIF_string .= "!\xF9\x04\x1\x0\x0".chr($this->GIF_TransparentI)."\x0"; } $this->GIF_string .= chr(0x2C); $GIF_screen[8] &= 0x40; self::GIFPutByte($GIF_screen); self::GIFGetByte(1); self::GIFPutByte($this->GIF_buffer); for (;;) { self::GIFGetByte(1); self::GIFPutByte($this->GIF_buffer); if (($u = $this->GIF_buffer[0]) == 0x00) { break; } self::GIFGetByte($u); self::GIFPutByte($this->GIF_buffer); } $this->GIF_string .= chr(0x3B); $this->GIF_arrays[] = $this->GIF_string; } private function GIFGetByte($len) { $this->GIF_buffer = array(); for ($i = 0; $i < $len; $i++) { if ($this->GIF_bfseek > strlen($this->GIF_stream)) { return 0; } $this->GIF_buffer[] = ord($this->GIF_stream{$this->GIF_bfseek++}); // { and } stand for string indexing } return 1; } private function GIFPutByte($bytes) { foreach ($bytes as $byte) { $this->GIF_string .= chr($byte); } } public function GIFGetFrames() { return $this->GIF_arrays; } public function GIFGetDelays() { return $this->GIF_delays; } public function GIFGetLoop() { return $this->GIF_anloop; } public function GIFGetDisposal() { return $this->GIF_dispos; } public function GIFGetTransparentR() { return $this->GIF_TransparentR; } public function GIFGetTransparentG() { return $this->GIF_TransparentG; } public function GIFGetTransparentB() { return $this->GIF_TransparentB; } } class SigPlusNovoGifEncoder { private $GIF = 'GIF89a'; private $BUF = array(); /** The number of times the animation is to be repeated, or 0 to repeat indefinitely. */ private $LOP = 0; /** Disposal. */ private $DIS = 2; /** Transparent color, or -1 for no transparent color. */ private $COL = -1; private $IMG = -1; /** * Encodes a sequence of frames into an animated GIF image. * * @param $GIF_src Binary data of image frames, each array element corresponding to a frame. * @param $GIF_dly Delay time. * @param $GIF_lop The number of times the animation is to be repeated, or 0 to repeat indefinitely. * @param $GIF_dis Disposal. * @param $GIF_red Red component of transparent color, or -1 for no transparent color. * @param $GIF_grn Green component of transparent color, or -1 for no transparent color. * @param $GIF_blu Blue component of transparent color, or -1 for no transparent color. */ public function __construct(array $GIF_src, array $GIF_dly, $GIF_lop, $GIF_dis, $GIF_red, $GIF_grn, $GIF_blu) { $this->LOP = ($GIF_lop > -1) ? $GIF_lop : 0; $this->DIS = ($GIF_dis > -1) ? ($GIF_dis < 3 ? $GIF_dis : 3) : 2; $this->COL = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ? ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1; for ($i = 0; $i < count($GIF_src); $i++) { $this->BUF[] = $GIF_src[$i]; if (substr($this->BUF[$i], 0, 6) != 'GIF87a' && substr($this->BUF[$i], 0, 6) != 'GIF89a') { // invalid image format (not a GIF image) throw new SigPlusNovoImageFormatException(); } for ($j = (13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))), $k = true; $k; $j++) { switch ($this->BUF[$i]{$j}) { // { and } stand for string indexing case '!': if ((substr($this->BUF[$i], ($j + 3), 8)) == 'NETSCAPE') { // already an animated image throw new SigPlusNovoImageFormatException(); } break; case ';': $k = false; break; } } } self::GIFAddHeader(); for ($i = 0; $i < count($this->BUF); $i++) { self::GIFAddFrames($i, $GIF_dly[$i]); } self::GIFAddFooter(); } private function GIFAddHeader() { $cmap = 0; if (ord($this->BUF[0]{10}) & 0x80) { $cmap = 3 * (2 << (ord($this->BUF[0]{10}) & 0x07)); // { and } stand for string indexing $this->GIF .= substr($this->BUF[0], 6, 7); $this->GIF .= substr($this->BUF[0], 13, $cmap); $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . self::GIFWord($this->LOP) . "\0"; } } private function GIFAddFrames($i, $d) { $Locals_str = 13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07)); $Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1; $Locals_tmp = substr($this->BUF[$i], $Locals_str, $Locals_end); $Global_len = 2 << (ord($this->BUF[0]{10}) & 0x07); $Locals_len = 2 << (ord($this->BUF[$i]{10}) & 0x07); $Global_rgb = substr($this->BUF[0], 13, 3 * (2 << (ord($this->BUF[0]{10}) & 0x07))); $Locals_rgb = substr($this->BUF[$i], 13, 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))); $Locals_ext = "!\xF9\x04".chr(($this->DIS << 2) + 0).chr(($d >> 0) & 0xFF).chr(($d >> 8) & 0xFF)."\x0\x0"; if ($this->COL > -1 && (ord($this->BUF[$i]{10}) & 0x80)) { for ($j = 0; $j < (2 << (ord($this->BUF[$i]{10}) & 0x07)); $j++) { if (ord($Locals_rgb{3 * $j + 0}) == (($this->COL >> 16) & 0xFF) && ord($Locals_rgb{3 * $j + 1}) == (($this->COL >> 8) & 0xFF) && ord($Locals_rgb{3 * $j + 2}) == (($this->COL >> 0) & 0xFF)) { $Locals_ext = "!\xF9\x04".chr(($this->DIS << 2) + 1).chr(($d >> 0) & 0xFF).chr(($d >> 8) & 0xFF).chr($j)."\x0"; break; } } } switch($Locals_tmp{0}) { case '!' : $Locals_img = substr($Locals_tmp, 8, 10); $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18); break; case ',' : $Locals_img = substr($Locals_tmp, 0, 10); $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10); break; } if ((ord($this->BUF[$i]{10}) & 0x80) && $this->IMG > -1) { if ($Global_len == $Locals_len) { if (self::GIFBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) { $this->GIF .= $Locals_ext.$Locals_img.$Locals_tmp; } else { $byte = ord($Locals_img{9}); $byte |= 0x80; $byte &= 0xF8; $byte |= (ord($this->BUF[0]{10}) & 0x07); $Locals_img{9} = chr($byte); $this->GIF .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; } } else { $byte = ord($Locals_img{9}); $byte |= 0x80; $byte &= 0xF8; $byte |= (ord($this->BUF[$i]{10}) & 0x07); $Locals_img{9} = chr($byte); $this->GIF .= $Locals_ext.$Locals_img.$Locals_rgb.$Locals_tmp; } } else { $this->GIF .= $Locals_ext.$Locals_img.$Locals_tmp; } $this->IMG = 1; } private function GIFAddFooter() { $this->GIF .= ';'; } private function GIFBlockCompare($GlobalBlock, $LocalBlock, $Len) { for ($i = 0; $i < $Len; $i++) { if($GlobalBlock{3 * $i + 0} != $LocalBlock{3 * $i + 0} || $GlobalBlock{3 * $i + 1} != $LocalBlock{3 * $i + 1} || $GlobalBlock{3 * $i + 2} != $LocalBlock{3 * $i + 2}) { return 0; } } return 1; } private function GIFWord($int) { return chr($int & 0xFF).chr(($int >> 8) & 0xFF); } public function GetAnimation() { return $this->GIF; } } /** * Extracts a frame from an MPEG movie into an image file. */ class SigPlusNovoMPEGPosterExtractor { /** The GD image processing library wrapper. */ private $imagelibrary; public static function instantiate() { if (is_gd_supported() && extension_loaded('ffmpeg')) { return new SigPlusNovoMPEGPosterExtractor(new SigPlusNovoImageLibraryGD()); } else { return false; } } private function __construct(SigPlusNovoImageLibraryGD $imagelibrary) { $this->imagelibrary = $imagelibrary; } public function createPosterImage($moviepath, $thumbpath, $thumb_w, $thumb_h, $crop = true, $quality = 85) { $movie = new ffmpeg_movie($moviepath); // extract frame from the video $thumbindex = (int) round($movie->getFrameCount() / 2.5); $frame = $movie->getFrame($thumbindex); $poster_img = $frame->toGDImage(); // process and save image return $this->imagelibrary->createThumbnailFromResource($poster_img, null, $thumbpath, $thumb_w, $thumb_h, $crop, $quality); } }