Initial commit

This commit is contained in:
isUnknown 2026-02-12 15:22:46 +01:00
commit 65e0da7e11
1397 changed files with 596542 additions and 0 deletions

View file

@ -0,0 +1,256 @@
<?php
namespace Kirby\Plugins\ImageKit\Optimizer;
use Exception;
use F;
use ReflectionClass;
/**
* Interface for the optimizer class. An optimizer is
* created for a specific thumbnail by calling the
* `create($thumb)` method.
*/
interface BaseInterface {
/**
* Should implement at least some basic checks to make
* sure, that the optimizer is working. This method should
* check for executables being in place,
* for PHP extensions, operating system etc.
*
* @return boolean Return `true`, if the optimizer is
* available, otherwise `false`.
*/
public static function available();
}
/**
* The base class for ImageKits optimizers.
*/
abstract class Base implements BaseInterface {
/**
* Defines the file types, this optimizer can handle.
*
* @var string|array Either string with a value of '*' or
* an array containing a list of mime types.
*/
public static $selector = '*';
/**
* Defines the priority of this optimizers operations.
* @var array An array of two elements, where each can be
* either a positive integer, zero or false.
* The first value is the priority of
* pre-operations, the second one of
* post-operations. Priority is not set static
* to make synamic changed possible.
*/
public $priority = [10, 10];
/**
* Kirbys instance.
*
* @var Kirby
*/
public static $kirby;
/**
* Thumbnail will be set by create method.
*
* @var Thumb
*/
protected $thumb;
/**
* Constructor of this optimizer
*
* @param Thumb An instance of the Thumb class.
*/
protected function __construct($thumb) {
$this->thumb = $thumb;
}
/**
* Returns an array of all available setting variables of
* this optimizer.
*
* @return array
*/
public static function defaults() {
return [];
}
/**
* Called after defaults have been added to the global
* Kirby instance. Can be used for further operations
* that need all options to be in place.
*/
public static function configure($kirby) {
static::$kirby = $kirby;
}
/**
* Use this method to create an instance of given
* optimizer.
*/
public static function create($thumb) {
if(!static::matches($thumb)) {
// Dont create optimizer for given thumb, if it
// cannot handle its file type.
return null;
} else {
return new static($thumb);
}
}
/**
* Operations to performed before thumbnail creation. This
* can be used to modify parameters on the passed $thumb
* object.
*
* @param Thumb $thumb
*/
public function pre() {
// Do some crazy stuff here in a subclass …
}
/**
* Operations to be performed after the thumbnai has been
* created by the thumbs driver.
*
* @param Thumb $thumb
*/
public function post() {
// Do some crazy stuff here in a subclass …
}
/**
* Returns the priority of this optimizer.
*
* @param string $which Must be either 'pre' or 'post'.
* @return int|boolean The priority of either 'pre' or
* 'post' operations or false, if this
* optimizer does not have a pre/post
* operation defined.
*/
public function priority($which) {
switch($which) {
case 'pre':
return $this->priority[0];
case 'post':
return $this->priority[1];
default:
throw new Exception('`$which` parameter must have a value of either `"pre"` or `"post"`.');
}
}
/**
* Returns true, checks the extension of a thumb
* destination file against the mime types, this optimizer
* can handle. Additional checks are not performed.
*
* @param Thumb $thumb
* @return bool `true`, if Optimizer can handle given
* thumb, otherwise `false`.
*/
public static function matches($thumb) {
$mime = f::extensionToMime(f::extension($thumb->destination->root));
return in_array($mime, static::$selector);
}
/**
* Returns the name of the optimizer class in lowercase
* without namespace.
*
* @return string The optimizers class name without
* namespace.
*/
public static function name() {
return strtolower(str_replace(__NAMESPACE__ . '\\', '', get_called_class()));
}
/* ===== Utility Functions ============================================== */
/**
* Returns a temporary filename, based on the original
* filename of the thumbnail destination.
*
* @param string $extension Optionally change the
* extension of the temporary
* file by providing this
* parameter.
* @return string The full path to the
* temporary file.
*/
protected function getTemporaryFilename($extension = null) {
$parts = pathinfo($this->thumb->destination->root);
if (!$extension) {
$extension = $parts['extension'];
}
// Add a unique suffix
$suffix = '-' . uniqid();
return $parts['dirname'] . DS . $parts['filename'] . $suffix . '.' . $extension;
}
/**
* Compares the filesize of $target and $alternative and
* only keeps the smallest of both files. If $alternative
* is smaller than $target, $target will be replaced by
* $alternative.
*
* @param string $target Full path to target file.
* @param string $alternative Full path to alternative file.
*
*/
protected function keepSmallestFile($target, $alternative) {
if(f::size($alternative) <= f::size($target)) {
f::remove($target);
f::move($alternative, $target);
} else {
f::remove($alternative);
}
}
/**
* Checks the driver of a given thumb is given driver id.
*
* @param string $driver Name of the thumbnail engine,
* (i.e. 'im' or 'gd').
* @return boolean
*/
protected function isDriver($driver) {
return (
(isset($this->thumb->options['driver']) && $this->thumb->options['driver'] === $driver) ||
(static::$kirby->option('imagekit.driver') === $driver)
);
}
/**
* Tries to get the value of an option from given Thumb
* object. If not set, returns the global value of this
* option.
*
* @param Thumb $thumb An instance of the Thumb class
* from Kirbys toolkit.
* @param string $key The option key.
* @return mixed Either local or global value of
* the option.
*/
protected function option($key, $default = null) {
if(isset($this->thumb->options[$key])) {
return $this->thumb->options[$key];
} else {
return static::$kirby->option($key, $default);
}
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Kirby\Plugins\ImageKit\Optimizer;
use F;
/**
* Lossless optimization of GIF files by using `gifsicle`.
* Supports animated GIFs.
*
* See: https://www.lcdf.org/gifsicle/
*/
class Gifsicle extends Base {
public static $selector = ['image/gif'];
public $priority = [false, 50];
protected $targetFile;
protected $tempFile;
public static function defaults() {
return [
'imagekit.gifsicle.bin' => null,
'imagekit.gifsicle.level' => 3,
'imagekit.gifsicle.colors' => false,
'imagekit.gifsicle.flags' => '',
];
}
public static function available() {
return !empty(static::$kirby->option('imagekit.gifsicle.bin'));
}
public function post() {
$tmpFile = $this->getTemporaryFilename();
$command = [];
$command[] = static::$kirby->option('imagekit.gifsicle.bin');
if($this->thumb->options['interlace']) {
$command[] = '--interlace';
}
// Set colors
$colors = $this->option('imagekit.gifsicle.colors');
if($colors !== false) {
$command[] = "--colors $colors";
}
// Set optimization level.
$command[] = '--optimize=' . $this->option('imagekit.gifsicle.level');
$flags = $this->option('imagekit.gifsicle.flags');
if(!empty($flags)) {
// Add exra flags, if defined by user.
$command[] = $flags;
}
// Set output file
$command[] = '--output "' . $tmpFile . '"';
// Set input file
$command[] = '"' . $this->thumb->destination->root . '"';
exec(implode(' ', $command));
$this->keepSmallestFile($this->thumb->destination->root, $tmpFile);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Kirby\Plugins\ImageKit\Optimizer;
use F;
/**
* Lossless optimization of JPEG files by using `jpegtran`.
*
* See: http://jpegclub.org/jpegtran/
* and: http://linux.die.net/man/1/jpegtran
*/
class JPEGTran extends Base {
public static $selector = ['image/jpeg'];
public $priority = [false, 50];
protected $targetFile;
protected $tempFile;
public static function defaults() {
return [
'imagekit.jpegtran.bin' => null,
'imagekit.jpegtran.optimize' => true,
'imagekit.jpegtran.copy' => 'none',
'imagekit.jpegtran.flags' => '',
];
}
public static function available() {
return !empty(static::$kirby->option('imagekit.jpegtran.bin'));
}
public function post() {
$tmpFile = $this->getTemporaryFilename();
$command = [];
$command[] = static::$kirby->option('imagekit.jpegtran.bin');
if($this->thumb->options['interlace']) {
$command[] = '-progressive';
}
if($this->thumb->options['grayscale']) {
$command[] = '-grayscale';
}
// Copy metadata (or not)?
if($copy = $this->option('imagekit.jpegtran.copy')) {
$command[] = "-copy $copy";
}
if($this->option('imagekit.jpegtran.optimize')) {
$command[] = '-optimize';
}
// Write to a temporary file, so we can compare filesizes
// after optimization and keep only the smaller file as
// jpegtran does not always create smaller files.
$command[] = '-outfile "' . $tmpFile . '"';
$flags = $this->option('imagekit.jpegtran.flags');
if(!empty($flags)) {
// Add exra flags, if defined by user.
$command[] = $flags;
}
$command[] = '"' . $this->thumb->destination->root . '"';
exec(implode(' ', $command));
$this->keepSmallestFile($this->thumb->destination->root, $tmpFile);
}
}

View file

@ -0,0 +1,100 @@
<?php
namespace Kirby\Plugins\ImageKit\Optimizer;
/**
* Uses `mozjpeg` for encoding JPEG files. This often creates
* much smaller JPEG files than most other encoders at a
* comparable quality.
*
* See: https://github.com/mozilla/mozjpeg
*/
class MozJPEG extends Base {
public static $selector = ['image/jpeg'];
public $priority = [100, 5];
protected $targetFile;
protected $tmpFile;
public static function defaults() {
return [
'imagekit.mozjpeg.bin' => null,
'imagekit.mozjpeg.quality' => 85,
'imagekit.mozjpeg.flags' => '',
];
}
public static function available() {
return !empty(static::$kirby->option('imagekit.mozjpeg.bin'));
}
public function pre() {
$this->targetFile = $this->thumb->destination->root;
if($this->isDriver('im')) {
// Instruct imagemagick driver to write out a temporary,
// uncrompressed TGA file, so our JPEG will not be
// compressed twice. I played around with PNM too,
// because its much faster as an intermediate format,
// but some images got corrupted by mozjpeg.
$this->tmpFile = $this->getTemporaryFilename('tga');
$this->thumb->destination->root = $this->tmpFile;
} else {
// If GD driver (or an unknown driver) is active, we
// need to encode twice, because SimpleImage can only
// save to JPEG, PNG or GIF. As saving a 24-bit
// lossless PNG as an intermediate step is too
// expensive for large images, we need to encode
// as JPEG :-(
// This also needs a temporary file, because it seems
// that mozjpeg cannot overwrite the its file.
$this->tmpFile = $this->getTemporaryFilename();
$this->thumb->destination->root = $this->tmpFile;
$this->thumb->options['quality'] = 99;
}
}
public function post() {
$command = [];
$command[] = static::$kirby->option('imagekit.mozjpeg.bin');
// Quality
$command[] = '-quality ' . $this->option('imagekit.mozjpeg.quality');
// Interlace
if($this->thumb->options['interlace']) {
$command[] = '-progressive';
}
// Grayscale
if($this->thumb->options['grayscale']) {
$command[] = '-grayscale';
}
// Set output file.
$command[] = '-outfile "' . $this->targetFile . '"';
$flags = $this->option('imagekit.mozjpeg.flags');
if(!empty($flags)) {
// Add exra flags, if defined by user.
$command[] = $flags;
}
// Use tmp file as input.
$command[] = '"' . $this->tmpFile . '"';
exec(implode(' ', $command));
// Delete temporary file and restore destination path
// on thumb object. This only needs to be done, if
// ImageMagick driver is used and the input file was
// in PNM format.
@unlink($this->tmpFile);
$this->thumb->destination->root = $this->targetFile;
}
}

View file

@ -0,0 +1,64 @@
<?php
namespace Kirby\Plugins\ImageKit\Optimizer;
/**
* Lossless optimization of PNG images using `optipng`.
*
* See: http://optipng.sourceforge.net/
* and: http://optipng.sourceforge.net/pngtech/optipng.html
*/
class OptiPNG extends Base {
public static $selector = ['image/png'];
public $priority = [false, 50];
protected $targetFile;
protected $tmpFile;
public static function defaults() {
return [
'imagekit.optipng.bin' => null,
'imagekit.optipng.level' => 2,
'imagekit.optipng.strip' => 'all',
'imagekit.optipng.flags' => '',
];
}
public static function available() {
return !empty(static::$kirby->option('imagekit.optipng.bin'));
}
public function post() {
$command = [];
$command[] = static::$kirby->option('imagekit.optipng.bin');
// Optimization Level
$level = $this->option('imagekit.optipng.level');
if($level !== false) { // Level can be 0, so strict comparison is neccessary
$command[] = "-o$level";
}
// Should we strip Metadata?
if($strip = $this->option('imagekit.optipng.strip')) {
$command[] = "-strip $strip";
}
$flags = $this->option('imagekit.optipng.flags');
if(!empty($flags)) {
// Add exra flags, if defined by user.
$command[] = $flags;
}
// Set file to optimize
$command[] = '"' . $this->thumb->destination->root . '"';
exec(implode(' ', $command));
}
}

View file

@ -0,0 +1,127 @@
<?php
namespace Kirby\Plugins\ImageKit\Optimizer;
/**
* Lossy optimization by using `pngquant` for converting
* 24-bit PNGs to an 8-bit palette while preserving the
* alpha-channel.
*
* See: https://pngquant.org/
*/
class PNGQuant extends Base {
public static $selector = ['image/png'];
public $priority = [100, 5];
protected $targetFile;
protected $tmpFile;
public static function defaults() {
return [
'imagekit.pngquant.bin' => null,
'imagekit.pngquant.quality' => null,
'imagekit.pngquant.speed' => 3,
'imagekit.pngquant.posterize' => false,
'imagekit.pngquant.colors' => false,
'imagekit.pngquant.flags' => '',
];
}
public static function available() {
return !empty(static::$kirby->option('imagekit.pngquant.bin'));
}
public function pre() {
$this->targetFile = $this->thumb->destination->root;
if($this->isDriver('im')) {
// Instruct imagemagick driver to write out a
// temporary, uncrompressed PNM file, so our PNG will
// not need to be encoded twice.
$this->tmpFile = $this->getTemporaryFilename('pnm');
$this->thumb->destination->root = $this->tmpFile;
} else {
// If driver is anything else but IM, we do not create
// a temporary file.
$this->tmpFile = null;
}
}
public function post() {
$command = [];
$command[] = static::$kirby->option('imagekit.pngquant.bin');
// Quality
$quality = $this->option('imagekit.pngquant.quality');
if($quality !== null) {
$command[] = "--quality $quality";
}
// Speed
if($speed = $this->option('imagekit.pngquant.speed')) {
$command[] = "--speed $speed";
}
// Posterize
$posterize = $this->option('imagekit.pngquant.posterize');
if($posterize !== false) { // need verbose check,
// because posterize can have
// value of 0
$command[] = "--posterize $posterize";
}
$copy = $this->option('imagekit.pngquant.copy');
if($copy !== null) {
$command[] = "--copy $copy";
}
if(is_null($this->tmpFile)) {
// Only save optimized file, if it is smaller than the
// original. This only makes sense, if input file a
// PNG image. If input is PNM, the result should
// always be saved.
$command[] = '--skip-if-larger';
}
$flags = $this->option('imagekit.pngquant.flags');
if(!empty($flags)) {
$command[] = $flags;
}
// Force pngquant to override original file, if it was used
// as input (i.e. when no temporary file was created).
$command[] = '--force --output "' . $this->targetFile . '"';
// Colors
$colors = $this->option('imagekit.pngquant.colors');
if($colors != false) {
$command[] = $colors;
}
// Separator between options and input file as
// recommended according by the pngquant docs.
$command[] = '--';
if(!is_null($this->tmpFile)) {
// Use tmp file as input.
$command[] = '"' . $this->tmpFile . '"';
} else {
// Use target file as input
$command[] = '"' . $this->targetFile . '"';
}
exec(implode(' ', $command));
if(!is_null($this->tmpFile)) {
// Delete temporary file and restore destination path on thumb object.
@unlink($this->tmpFile);
$this->thumb->destination->root = $this->targetFile;
}
}
}