forked from leftypol/leftypol
LibMagickMediaHandler.php: add support for animated webp thumbnails
This commit is contained in:
parent
37fbb35f8b
commit
096a6f04f1
1 changed files with 126 additions and 62 deletions
|
@ -8,7 +8,7 @@ use Vichan\Functions\{Fs, Metadata};
|
||||||
class LibMagickMediaHandler implements MediaHandler {
|
class LibMagickMediaHandler implements MediaHandler {
|
||||||
use MediaHandlerTrait;
|
use MediaHandlerTrait;
|
||||||
|
|
||||||
// getImageAlphaChannel requires Imagick >= 2.3.0 and ImageMagick >= 6.4.0
|
// getImageAlphaChannel and writeImages requires Imagick >= 2.3.0 and ImageMagick >= 6.4.0
|
||||||
private const MIN_IMAGICK_VERSION = '2.3.0';
|
private const MIN_IMAGICK_VERSION = '2.3.0';
|
||||||
private const MIN_IMAGEMAGICK_VERSION = '6.4.0';
|
private const MIN_IMAGEMAGICK_VERSION = '6.4.0';
|
||||||
|
|
||||||
|
@ -17,10 +17,11 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
|
|
||||||
|
|
||||||
private bool $strip_metadata;
|
private bool $strip_metadata;
|
||||||
private bool $frames_for_gif_thumbs;
|
private bool $frames_for_animated_thumbs;
|
||||||
private int $image_max_width;
|
private int $image_max_width;
|
||||||
private int $image_max_height;
|
private int $image_max_height;
|
||||||
private string $static_thumb_mime;
|
private string $static_thumb_mime;
|
||||||
|
private ?string $animated_thumb_mime;
|
||||||
|
|
||||||
|
|
||||||
private static function degreesFromOrientation(int $orientation): int {
|
private static function degreesFromOrientation(int $orientation): int {
|
||||||
|
@ -65,6 +66,13 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the mime type supports animations.
|
||||||
|
*/
|
||||||
|
private static function mimeSupportAnimation(string $mime) {
|
||||||
|
return $mime === 'image/gif' || $mime === 'image/webp';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool Returns if width and height were swapped.
|
* @return bool Returns if width and height were swapped.
|
||||||
*/
|
*/
|
||||||
|
@ -83,6 +91,40 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateThumbImplSingleFrame(
|
||||||
|
\Imagick $imagick,
|
||||||
|
string $preferred_out_file_basepath,
|
||||||
|
int $width,
|
||||||
|
int $height,
|
||||||
|
int $max_width,
|
||||||
|
int $max_height
|
||||||
|
) {
|
||||||
|
if ($width > $max_width || $height > $max_height) {
|
||||||
|
$thumb_width = $max_width;
|
||||||
|
$thumb_height = $max_height;
|
||||||
|
|
||||||
|
// Unreliable behavior on some versions if the target width/height are under the limit?
|
||||||
|
$imagick->thumbnailImage($max_width, $max_height, true);
|
||||||
|
} else {
|
||||||
|
$thumb_width = $width;
|
||||||
|
$thumb_height = $height;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out_ext = Metadata\mime_to_ext($this->static_thumb_mime);
|
||||||
|
$out_path = "$preferred_out_file_basepath.$out_ext";
|
||||||
|
|
||||||
|
$imagick->stripImage();
|
||||||
|
$imagick->setImageCompressionQuality(70);
|
||||||
|
$imagick->writeImage("$out_ext:$out_path");
|
||||||
|
|
||||||
|
return new ThumbGenerationResult(
|
||||||
|
$out_path,
|
||||||
|
$this->static_thumb_mime,
|
||||||
|
$thumb_width,
|
||||||
|
$thumb_height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function generateThumbImpl(
|
private function generateThumbImpl(
|
||||||
\Imagick $imagick,
|
\Imagick $imagick,
|
||||||
string $source_file_mime,
|
string $source_file_mime,
|
||||||
|
@ -92,13 +134,12 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
int $max_width,
|
int $max_width,
|
||||||
int $max_height
|
int $max_height
|
||||||
) {
|
) {
|
||||||
// Special handling for gifs with multiple frames.
|
$source_is_animated = self::mimeSupportAnimation($source_file_mime) && $imagick->getNumberImages() > 1;
|
||||||
if (
|
|
||||||
$source_file_mime === 'image/gif'
|
// Special handling for animated images with multiple frames.
|
||||||
&& $this->frames_for_gif_thumbs !== self::THUMB_KEEP_FRAMES_NO
|
if ($this->frames_for_animated_thumbs !== self::THUMB_KEEP_FRAMES_NO && $source_is_animated) {
|
||||||
&& $imagick->getNumberImages() > 1
|
$animated_thumb_ext = Metadata\mime_to_ext($this->animated_thumb_mime);
|
||||||
) {
|
$out_path = "$preferred_out_file_basepath.$animated_thumb_ext";
|
||||||
$out_path = $preferred_out_file_basepath . '.gif';
|
|
||||||
|
|
||||||
if ($width > $max_width || $height > $max_height) {
|
if ($width > $max_width || $height > $max_height) {
|
||||||
$thumb_width = $max_width;
|
$thumb_width = $max_width;
|
||||||
|
@ -109,14 +150,15 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// By now $this->frames_for_gif_thumbs !== 0.
|
// By now $this->frames_for_gif_thumbs !== 0.
|
||||||
$step = \floor($imagick->getNumberImages() / $this->frames_for_gif_thumbs);
|
$step = \floor($imagick->getNumberImages() / $this->frames_for_animated_thumbs);
|
||||||
|
|
||||||
if ($this->frames_for_gif_thumbs !== self::THUMB_KEEP_FRAMES_ALL && $step > 1) {
|
if ($this->frames_for_animated_thumbs !== self::THUMB_KEEP_FRAMES_ALL) {
|
||||||
|
if ($step > 1) {
|
||||||
// Reduce the number of frames.
|
// Reduce the number of frames.
|
||||||
|
|
||||||
$other = new \Imagick();
|
$other = new \Imagick();
|
||||||
try {
|
try {
|
||||||
$other->setFormat('gif');
|
$other->setFormat($animated_thumb_ext);
|
||||||
|
|
||||||
for ($i = 0, $j = 0; $i < $imagick->getNumberImages(); $i += $step, $j++) {
|
for ($i = 0, $j = 0; $i < $imagick->getNumberImages(); $i += $step, $j++) {
|
||||||
$imagick->setIteratorIndex($i);
|
$imagick->setIteratorIndex($i);
|
||||||
|
@ -132,49 +174,47 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
$other->optimizeImageLayers();
|
$other->optimizeImageLayers();
|
||||||
|
|
||||||
$other->setImageCompressionQuality(70);
|
$other->setImageCompressionQuality(70);
|
||||||
$other->writeImage("gif:$out_path");
|
$other->writeImages("$animated_thumb_ext:$out_path", true);
|
||||||
} finally {
|
} finally {
|
||||||
$other->clear();
|
$other->clear();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Only a single frame would be left, save it as a single image.
|
||||||
|
$imagick->setIteratorIndex(0);
|
||||||
|
|
||||||
|
return $this->generateThumbImplSingleFrame(
|
||||||
|
$imagick,
|
||||||
|
$preferred_out_file_basepath,
|
||||||
|
$width,
|
||||||
|
$height,
|
||||||
|
$max_width,
|
||||||
|
$max_height
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Just try to optimize it a little.
|
// Just try to optimize it a little.
|
||||||
$imagick->stripImage();
|
$imagick->stripImage();
|
||||||
$imagick->optimizeImageLayers();
|
$imagick->optimizeImageLayers();
|
||||||
|
|
||||||
|
$imagick->setFormat($animated_thumb_ext);
|
||||||
$imagick->setImageCompressionQuality(70);
|
$imagick->setImageCompressionQuality(70);
|
||||||
$imagick->writeImage("gif:$out_path");
|
$imagick->writeImages("$animated_thumb_ext:$out_path", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ThumbGenerationResult(
|
return new ThumbGenerationResult(
|
||||||
$out_path,
|
$out_path,
|
||||||
'image/gif',
|
$this->animated_thumb_mime,
|
||||||
$thumb_width,
|
$thumb_width,
|
||||||
$thumb_height
|
$thumb_height
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if ($width > $max_width || $height > $max_height) {
|
return $this->generateThumbImplSingleFrame(
|
||||||
$thumb_width = $max_width;
|
$imagick,
|
||||||
$thumb_height = $max_height;
|
$preferred_out_file_basepath,
|
||||||
|
$width,
|
||||||
// Unreliable behavior on some versions if the target width/height are under the limit?
|
$height,
|
||||||
$imagick->thumbnailImage($max_width, $max_height, true);
|
$max_width,
|
||||||
} else {
|
$max_height
|
||||||
$thumb_width = $width;
|
|
||||||
$thumb_height = $height;
|
|
||||||
}
|
|
||||||
|
|
||||||
$out_ext = Metadata\mime_to_ext($this->static_thumb_mime);
|
|
||||||
$out_path = $preferred_out_file_basepath . '.' . $out_ext;
|
|
||||||
|
|
||||||
$imagick->stripImage();
|
|
||||||
$imagick->setImageCompressionQuality(70);
|
|
||||||
$imagick->writeImage("$out_ext:$out_path");
|
|
||||||
|
|
||||||
return new ThumbGenerationResult(
|
|
||||||
$out_path,
|
|
||||||
$this->static_thumb_mime,
|
|
||||||
$thumb_width,
|
|
||||||
$thumb_height
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,22 +224,46 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public static function checkImagickVersion(): bool {
|
public static function checkImagickVersion(): bool {
|
||||||
|
static $version_ok = null;
|
||||||
|
|
||||||
|
if ($version_ok === null) {
|
||||||
|
$version_ok = false;
|
||||||
$imagick_ver = \phpversion('imagick');
|
$imagick_ver = \phpversion('imagick');
|
||||||
if ($imagick_ver !== false && \version_compare($imagick_ver, self::MIN_IMAGICK_VERSION, '>=')) {
|
if ($imagick_ver !== false && \version_compare($imagick_ver, self::MIN_IMAGICK_VERSION, '>=')) {
|
||||||
$str = \Imagick::getVersion()['versionString'];
|
$str = \Imagick::getVersion()['versionString'];
|
||||||
if (\preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $str, $matches)) {
|
if (\preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $str, $matches)) {
|
||||||
return \version_compare($matches[1], self::MIN_IMAGEMAGICK_VERSION, '>=');
|
$version_ok = \version_compare($matches[1], self::MIN_IMAGEMAGICK_VERSION, '>=');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(bool $strip_metadata, int $frames_for_gif_thumbs, int $max_width, int $max_height, string $static_thumb_mime) {
|
return $version_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
bool $strip_metadata,
|
||||||
|
int $frames_for_animated_thumbs,
|
||||||
|
int $max_width,
|
||||||
|
int $max_height,
|
||||||
|
string $static_thumb_mime,
|
||||||
|
?string $animated_thumb_mime
|
||||||
|
) {
|
||||||
|
if (!self::checkImagickVersion()) {
|
||||||
|
throw new MediaException('Imagick extension or ImageMagick are not available or too old', MediaException::ERR_BAD_HANDLER);
|
||||||
|
}
|
||||||
|
if ($frames_for_animated_thumbs !== self::THUMB_KEEP_FRAMES_NO && $animated_thumb_mime === null) {
|
||||||
|
throw new MediaException('Missing mime type for animated thumbnails', MediaException::ERR_BAD_MEDIA_TYPE);
|
||||||
|
}
|
||||||
|
if ($animated_thumb_mime !== null && !self::mimeSupportAnimation($animated_thumb_mime)) {
|
||||||
|
throw new MediaException("$animated_thumb_mime does not support animations", MediaException::ERR_BAD_MEDIA_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
$this->strip_metadata = $strip_metadata;
|
$this->strip_metadata = $strip_metadata;
|
||||||
$this->frames_for_gif_thumbs = $frames_for_gif_thumbs;
|
$this->frames_for_animated_thumbs = $frames_for_animated_thumbs;
|
||||||
$this->image_max_width = $max_width;
|
$this->image_max_width = $max_width;
|
||||||
$this->image_max_height = $max_height;
|
$this->image_max_height = $max_height;
|
||||||
$this->static_thumb_mime = $static_thumb_mime;
|
$this->static_thumb_mime = $static_thumb_mime;
|
||||||
|
$this->animated_thumb_mime = $animated_thumb_mime;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function supportsMime(string $mime): bool {
|
public function supportsMime(string $mime): bool {
|
||||||
|
@ -324,7 +388,7 @@ class LibMagickMediaHandler implements MediaHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
$out_ext = Metadata\mime_to_ext($media_file_mime);
|
$out_ext = Metadata\mime_to_ext($media_file_mime);
|
||||||
$out_path = $media_preferred_out_file_basepath . '.' . $out_ext;
|
$out_path = "$media_preferred_out_file_basepath.$out_ext";
|
||||||
$imagick->writeImage("$out_ext:$out_path");
|
$imagick->writeImage("$out_ext:$out_path");
|
||||||
|
|
||||||
$thumb = self::generateThumbImpl(
|
$thumb = self::generateThumbImpl(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue