I hacked this together a while back because animated avatars were and are are a staple of my board. Figured I'd mention it since every upgrade to XF has involved me changing the abstract class to point back to my Imagick library.
It's a bit of a hack and for some operations relies on shell calls, just because I wanted it done quickly.
That said, maybe with a bit of fixing to not use shell calls but instead 100% imagick bindings this could end up in XF as an option for admins who want animated avatars?
If you want to use this currently, at your own risk of course, you can just drop it in library/XenForo/Image/Imagick.php and change Abstract.php to point to Xenforo_Image_Imagick instead of Xenforo_Image_Gd. You'd also need to fix the paths to binaries, /tmp if applicable, etc. Been using this for 3 months now with no issues at all. Thoughts? I'd imagine it could boil down to a simple config.php setting for Gd or Imagick.
It's a bit of a hack and for some operations relies on shell calls, just because I wanted it done quickly.
That said, maybe with a bit of fixing to not use shell calls but instead 100% imagick bindings this could end up in XF as an option for admins who want animated avatars?
If you want to use this currently, at your own risk of course, you can just drop it in library/XenForo/Image/Imagick.php and change Abstract.php to point to Xenforo_Image_Imagick instead of Xenforo_Image_Gd. You'd also need to fix the paths to binaries, /tmp if applicable, etc. Been using this for 3 months now with no issues at all. Thoughts? I'd imagine it could boil down to a simple config.php setting for Gd or Imagick.
Code:
<?php
/**
* Image processor using GD.
*
* @package XenForo_Image
*/
class XenForo_Image_Imagick extends XenForo_Image_Abstract
{
/**
* The GD image resource.
*
* @var resource
*/
protected $_image;
/**
* Constructor.
*
* @param resource $image GD image resource
*/
protected function __construct($image)
{
$this->_setImage($image);
}
/**
* Creates a blank image.
*
* @param integer $width
* @param integer $height
*
* @return XenForo_Image_Gd
*/
public static function createImageDirect($width, $height)
{
$image = new Imagick();
$image->newImage($width, $height, new ImagickPixel('white'));
$image->setImageFormat('png');
return new self($image);
}
/**
* Creates an image from an existing file.
*
* @param string $fileName
* @param integer $inputType IMAGETYPE_XYZ constant representing image type
*
* @return XenForo_Image_Gd
*/
public static function createFromFileDirect($fileName, $inputType)
{
$invalidType = false;
try
{
$image = new Imagick();
$image->readImage($fileName);
}
catch (Exception $e)
{
return false;
}
if ($invalidType)
{
throw new XenForo_Exception('Invalid image type given. Expects IMAGETYPE_XXX constant.');
}
return new self($image);
}
/**
* Thumbnails the image.
*
* @see XenForo_Image_Abstract::thumbnail()
*/
public function thumbnail($maxWidth, $maxHeight = 0)
{
if ($maxWidth < 10)
{
$maxWidth = 10;
}
if ($maxHeight < 10)
{
$maxHeight = $maxWidth;
}
if ($this->_width < $maxWidth && $this->_height < $maxHeight)
{
return false;
}
$ratio = $this->_width / $this->_height;
$maxRatio = ($maxWidth / $maxHeight);
if ($maxRatio > $ratio)
{
$width = max(1, $maxHeight * $ratio);
$height = $maxHeight;
}
else
{
$width = $maxWidth;
$height = max(1, $maxWidth / $ratio);
}
$a = tempnam("/tmp","avi_");
$b = tempnam("/tmp","avo_");
$this->_image->writeImages($a, true);
exec('/usr/bin/convert '.$a.' -coalesce -bordercolor White -border 0 -resize '.$width.'x'.$height.' '.$b);
$this->_image->clear();
$this->_image->readImage($b);
unlink($a);
unlink($b);
$this->_setImage($this->_image);
}
/**
* Produces a thumbnail of the current image whose shorter side is the specified length
*
* @see XenForo_Image_Abstract::thumbnailFixedShorterSide
*/
public function thumbnailFixedShorterSide($shortSideLength)
{
if ($shortSideLength < 10)
{
$shortSideLength = 10;
}
$ratio = $this->_width / $this->_height;
if ($ratio > 1) // landscape
{
$width = $shortSideLength * $ratio;
$height = $shortSideLength;
}
else
{
$width = $shortSideLength;
$height = max(1, $shortSideLength / $ratio);
}
$a = tempnam("/tmp","avi_");
$b = tempnam("/tmp","avo_");
$this->_image->writeImages($a, true);
exec('/usr/bin/convert '.$a.' -coalesce -bordercolor White -border 0 -resize '.$width.'x'.$height.' '.$b);
$this->_image->clear();
$this->_image->readImage($b);
unlink($a);
unlink($b);
$this->_setImage($this->_image);
}
/**
* Crops the image.
*
* @see XenForo_Image_Abstract::crop()
*/
public function crop($x, $y, $width, $height)
{
error_log("$x $y $width $height");
$a = tempnam("/tmp","avi_");
$b = tempnam("/tmp","avo_");
$this->_image->writeImages($a, true);
exec('/usr/bin/convert '.$a.' -crop '.$width.'x'.$height.'+'.$x.'+'.$y.' +repage '.$b);
$this->_image->clear();
$this->_image->readImage($b);
unlink($a);
unlink($b);
$this->_setImage($this->_image);
}
/**
* Outputs the image.
*
* @see XenForo_Image_Abstract::output()
*/
public function output($outputType, $outputFile = null, $quality = 85)
{
switch ($outputType)
{
case IMAGETYPE_GIF: $this->_image->setImageFormat('gif'); break;
case IMAGETYPE_JPEG: $this->_image->setImageFormat('jpeg'); break;
case IMAGETYPE_PNG: $this->_image->setImageFormat('png'); break;
default:
throw new XenForo_Exception('Invalid output type given. Expects IMAGETYPE_XXX constant.');
}
return $this->_image->writeImages($outputFile, true);
}
protected function _preallocateBackground($image)
{
// Not necessary
}
/**
* Sets the internal GD image resource.
*
* @param resource $image
*/
protected function _setImage($image)
{
$this->_image = $image;
$this->_width = $image->getImageWidth();
$this->_height = $image->getImageHeight();
}
}