'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 $actions if(!function_exists('gd_info')) return false; // and if it passes the mandatory requirements, we check particularly aspects here switch($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 first if(is_array($this->cropExtra) && 4 == count($this->cropExtra)) { // crop before resize list($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"); } } }