Blame | Last modification | View Log | Download
<?php namespace ProcessWire;/*** ImageSizer Engine Animated GIF by Horst** This module supports resizing and cropping of animated GIFs when using GD-Library* (The GD-Library does not support this)** This module is based upon the work of** * László Zsidi (initial classes)* http://www.gifs.hu/* http://www.phpclasses.org/gifsplit* http://www.phpclasses.org/gifmerge* (License: Apache 2.0)** * xurei (enhanced classes)* https://github.com/xurei/GIFDecoder_optimized* (License: Apache 2.0)** Ported first to ProcessWire module & then to ImageSizerEngine module by Horst Nogajski** https://processwire.com/talk/topic/8386-image-animated-gif/*/class ImageSizerEngineAnimatedGif extends ImageSizerEngine {public static function getModuleInfo() {return array('title' => 'Animated GIF Image Sizer','version' => 1,'summary' => "Upgrades image manipulations for animated GIFs.",'author' => 'Horst Nogajski',);}/*** Class constructor**/public function __construct() {parent::__construct();$this->set('enginePriority', 9); // use a late priority so that optional other installed engines get the job (first) if they support animated gifs}/*** Get valid image source formats** @return array**/protected function validSourceImageFormats() {return array('GIF');}/*** Get valid target image formats** @return array**/protected function validTargetImageFormats() {return $this->validSourceImageFormats();}/*** Get library version string** @return string Returns version string or blank string if not applicable/available* @since 3.0.138**/public function getLibraryVersion() {$gd = gd_info();return isset($gd['GD Version']) ? $gd['GD Version'] : '';}/*** Is GD supported?** @param string $action* @return bool**/public function supported($action = 'imageformat') {// first we check parts that are mandatory for all $actionsif(!function_exists('gd_info')) return false;// and if it passes the mandatory requirements, we check particularly aspects hereswitch($action) {case 'imageformat':// compare current imagefile infos fetched from ImageInspector$requested = $this->getImageInfo(false);switch($requested) {case 'gif-anim':case 'gif-trans-anim':return true;default:return false;}break;case 'install':return true;default:return false;}}/*** Process the image resize** Processing is as follows:* 1. first do a check if the given image(type) can be processed, if not do an early return false* 2. than (try) to process all required steps, if one failes, return false* 3. if all is successful, finally return true** @param string $srcFilename Source file* @param string $dstFilename Destination file* @param int $fullWidth Current width* @param int $fullHeight Current height* @param int $finalWidth Requested final width* @param int $finalHeight Requested final height* @return bool True if successful, false if not* @throws WireException**/protected function processResize($srcFilename, $dstFilename, $fullWidth, $fullHeight, $finalWidth, $finalHeight) {$this->modified = false;if(isset($this->info['bits'])) $this->imageDepth = $this->info['bits'];$this->imageFormat = strtoupper(str_replace('image/', '', $this->info['mime']));if(!in_array($this->imageFormat, $this->validSourceImageFormats())) {throw new WireException(sprintf($this->_("loaded file '%s' is not in the list of valid images"), basename($dstFilename)));}require_once(__DIR__ . '/gif_encoder.php');require_once(__DIR__ . '/gif_decoder.php');$this->setTimeLimit(120);$zoom = $this->getFocusZoomPercent();if($zoom > 1) {// we need to configure a cropExtra call to respect the zoom factor$this->cropExtra = $this->getFocusZoomCropDimensions($zoom, $fullWidth, $fullHeight, $finalWidth, $finalHeight);$this->cropping = false;}// if extra crop manipulation is requested, it is processed firstif(is_array($this->cropExtra) && 4 == count($this->cropExtra)) { // crop before resizelist($cropX, $cropY, $cropWidth, $cropHeight) = $this->cropExtra;$bg = null;$gif = new ISEAG_GIFDecoder(file_get_contents($srcFilename));$originalFramesMeta = $gif->GIFGetFramesMeta();if(count($originalFramesMeta) <= 0) return false;$this->meta = array('delays' => $gif->GIFGetDelays(),'loops' => $gif->GIFGetLoop(),'disposal' => $gif->GIFGetDisposal(),'tr' => $gif->GIFGetTransparentR(),'tg' => $gif->GIFGetTransparentG(),'tb' => $gif->GIFGetTransparentB(),'trans' => (0 == $gif->GIFGetTransparentI() ? false : true));$originalFrames = $gif->GIFGetFrames();$newFrames = array();foreach($originalFrames as $k => $v) {$frame = @imagecreatefromstring($v);if(!is_resource($frame)) continue;if(!is_resource($bg)) {$bg = imagecreatetruecolor($fullWidth, $fullHeight);$this->prepareGDimage($bg);}$srcX = 0;$srcY = 0;$srcW = imagesx($frame);$srcH = imagesy($frame);$dstX = $originalFramesMeta[$k]['left'];$dstY = $originalFramesMeta[$k]['top'];$dstW = $originalFramesMeta[$k]['width'];$dstH = $originalFramesMeta[$k]['height'];imagecopy($bg, $frame, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH);$newimg = imagecreatetruecolor($cropWidth, $cropHeight);$this->prepareGDimage($newimg);imagecopy($newimg, $bg, 0, 0, $cropX, $cropY, $cropWidth, $cropHeight);array_push($newFrames, $newimg);$originalFrames[$k] = null;}if(count($newFrames) > 0) {$frames = array();foreach($newFrames as $nf) {if(!is_resource($nf)) continue;ob_start();imagegif($nf);$gifdata = ob_get_clean();array_push($frames, $gifdata);@imagedestroy($nf);}$gifmerge = new ISEAG_GIFEncoder($frames,$this->meta['delays'],$this->meta['loops'],$this->meta['disposal'],$this->meta['tr'], $this->meta['tg'], $this->meta['tb'],'bin');$result = false === fwrite(fopen($srcFilename, 'wb'), $gifmerge->GetAnimation()) ? false : true;if($result) {$fullWidth = $cropWidth;$fullHeight = $cropHeight;$this->image = array('width' => $fullWidth, 'height' => $fullHeight);}} else {// $result = false;}if(isset($bg) && is_resource($bg)) @imagedestroy($bg);if(isset($frame) && is_resource($frame)) @imagedestroy($frame);if(isset($newimg) && is_resource($newimg)) @imagedestroy($newimg);unset($gif, $gifmerge, $originalFrames, $originalFramesMeta, $newFrames, $cropHeight, $cropWidth, $cropX, $cropY, $dstH, $dstW, $dstX, $dstY, $frames, $nf, $srcH, $srcW, $srcX, $srcY);$this->meta = null;}// regular resize / crop manipulation starts here$bgX = $bgY = 0;$bgWidth = $fullWidth;$bgHeight = $fullHeight;$resizemethod = $this->getResizeMethod($bgWidth, $bgHeight, $finalWidth, $finalHeight, $bgX, $bgY);if(0 == $resizemethod) return true; // if same size or disallowed greater size is requested, we stop here and leave the original copy as is$gif = new ISEAG_GIFdecoder(file_get_contents($srcFilename));$originalFramesMeta = $gif->GIFGetFramesMeta();if(count($originalFramesMeta) <= 0) return false;$this->meta = array('delays' => $gif->GIFGetDelays(),'loops' => $gif->GIFGetLoop(),'disposal' => $gif->GIFGetDisposal(),'tr' => $gif->GIFGetTransparentR(),'tg' => $gif->GIFGetTransparentG(),'tb' => $gif->GIFGetTransparentB(),'trans' => (0 == $gif->GIFGetTransparentI() ? false : true));$originalFrames = $gif->GIFGetFrames();$newFrames = array();if(2 == $resizemethod) { // 2 = resize with aspect ratio$bg = null;// $ratio = 1.0;$ratio_w = $fullWidth / $finalWidth;$ratio_h = $fullHeight / $finalHeight;$ratio = ($ratio_h > $ratio_w ? $ratio_h : $ratio_w);foreach($originalFrames as $k => $v) {$frame = @imagecreatefromstring($v);if(!is_resource($frame)) continue;$newimg = imagecreatetruecolor($finalWidth, $finalHeight);$this->prepareGDimage($newimg);if(is_resource($bg)) {imagecopy($newimg, $bg, 0, 0, 0, 0, $finalWidth, $finalHeight);}$srcX = 0;$srcY = 0;$srcW = imagesx($frame);$srcH = imagesy($frame);$dstX = floor($originalFramesMeta[$k]['left'] / $ratio);$dstY = floor($originalFramesMeta[$k]['top'] / $ratio);$dstW = ceil($originalFramesMeta[$k]['width'] / $ratio);$dstH = ceil($originalFramesMeta[$k]['height'] / $ratio);imagecopyresampled($newimg, $frame, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);array_push($newFrames, $newimg);if(!is_resource($bg)) {$bg = imagecreatetruecolor($finalWidth, $finalHeight);$this->prepareGDimage($bg);}imagecopy($bg, $newimg, 0, 0, 0, 0, $finalWidth, $finalHeight);$originalFrames[$k] = null;}}if(4 == $resizemethod) { // 4 = resize and crop from center with aspect ratio$bg = null;// $ratio = 1.0;$ratio_w = $fullWidth / $bgWidth;$ratio_h = $fullHeight / $bgHeight;$ratio = ($ratio_h > $ratio_w ? $ratio_h : $ratio_w);foreach($originalFrames as $k => $v) {$frame = @imagecreatefromstring($v);if(!is_resource($frame)) continue;$newimg = imagecreatetruecolor($bgWidth, $bgHeight);$this->prepareGDimage($newimg);if(is_resource($bg)) {imagecopy($newimg, $bg, 0, 0, 0, 0, $bgWidth, $bgHeight);}$srcX = 0;$srcY = 0;$srcW = imagesx($frame);$srcH = imagesy($frame);$dstX = floor($originalFramesMeta[$k]['left'] / $ratio);$dstY = floor($originalFramesMeta[$k]['top'] / $ratio);$dstW = ceil($originalFramesMeta[$k]['width'] / $ratio);$dstH = ceil($originalFramesMeta[$k]['height'] / $ratio);imagecopyresampled($newimg, $frame, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);if(!is_resource($bg)) {$bg = imagecreatetruecolor($bgWidth, $bgHeight);$this->prepareGDimage($bg);}imagecopy($bg, $newimg, 0, 0, 0, 0, $bgWidth, $bgHeight);$newimg = imagecreatetruecolor($finalWidth, $finalHeight);$this->prepareGDimage($newimg);imagecopy($newimg, $bg, 0, 0, $bgX, $bgY, $finalWidth, $finalHeight);array_push($newFrames, $newimg);$originalFrames[$k] = null;}}if(count($newFrames) > 0) {$frames = array();foreach($newFrames as $nf) {if(!is_resource($nf)) continue;ob_start();imagegif($nf);$gifdata = ob_get_clean();array_push($frames, $gifdata);@imagedestroy($nf);}$gifmerge = new ISEAG_GIFEncoder($frames,$this->meta['delays'],$this->meta['loops'],$this->meta['disposal'],$this->meta['tr'], $this->meta['tg'], $this->meta['tb'],'bin');$result = false === fwrite(fopen($dstFilename, 'wb'), $gifmerge->GetAnimation()) ? false : true;} else {$result = false;}if(isset($bg) && is_resource($bg)) @imagedestroy($bg);if(isset($frame) && is_resource($frame)) @imagedestroy($frame);if(isset($newimg) && is_resource($newimg)) @imagedestroy($newimg);if(!$result) {return false;}$this->modified = true;return true;}/*** Helper for transparent background preparation** @param resource $gdimage by reference $gdimage* @return void**/protected function prepareGDimage(&$gdimage) {if(!$this->meta['trans']) return;$transparentNew = imagecolorallocate($gdimage, $this->meta['tr'], $this->meta['tg'], $this->meta['tb']);$transparentNewIndex = imagecolortransparent($gdimage, $transparentNew);imagefill($gdimage, 0, 0, $transparentNewIndex);}/*** Module install** @throws WireException**/public function ___install() {if(!$this->supported('install')) {throw new WireException("This module requires that you have PHP's GD image library bundled or installed");}}}