artabro/wire/modules/Image/ImageSizerEngineAnimatedGif/ImageSizerEngineAnimatedGif.module

377 lines
14 KiB
Text
Raw Normal View History

2024-08-27 11:35:37 +02:00
<?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 $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");
}
}
}