Improved image resizing code and added GD fallback.
This commit is contained in:
parent
e50cb31a6e
commit
d4e4b17a42
4 changed files with 245 additions and 40 deletions
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Misuzu;
|
namespace Misuzu;
|
||||||
|
|
||||||
use Imagick;
|
use Misuzu\Imaging\Image;
|
||||||
|
|
||||||
$userAssetsMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
$userAssetsMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||||
$misuzuBypassLockdown = $userAssetsMode === 'avatar';
|
$misuzuBypassLockdown = $userAssetsMode === 'avatar';
|
||||||
|
@ -56,45 +56,9 @@ switch($userAssetsMode) {
|
||||||
try {
|
try {
|
||||||
mkdirs($avatarStorage, true);
|
mkdirs($avatarStorage, true);
|
||||||
|
|
||||||
$avatarImage = new Imagick($avatarOriginal);
|
$avatarImage = Image::create($avatarOriginal);
|
||||||
$avatarImage->setImageFormat($avatarImage->getNumberImages() > 1 ? 'gif' : 'png');
|
$avatarImage->squareCrop($dimensions);
|
||||||
$avatarImage = $avatarImage->coalesceImages();
|
$avatarImage->save($filename = $avatarCropped);
|
||||||
|
|
||||||
$avatarOriginalWidth = $avatarImage->getImageWidth();
|
|
||||||
$avatarOriginalHeight = $avatarImage->getImageHeight();
|
|
||||||
|
|
||||||
if($avatarOriginalWidth > $avatarOriginalHeight) {
|
|
||||||
$avatarWidth = $avatarOriginalWidth * $dimensions / $avatarOriginalHeight;
|
|
||||||
$avatarHeight = $dimensions;
|
|
||||||
} else {
|
|
||||||
$avatarWidth = $dimensions;
|
|
||||||
$avatarHeight = $avatarOriginalHeight * $dimensions / $avatarOriginalWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
$avatarImage->resizeImage(
|
|
||||||
$avatarWidth,
|
|
||||||
$avatarHeight,
|
|
||||||
Imagick::FILTER_LANCZOS,
|
|
||||||
0.9
|
|
||||||
);
|
|
||||||
|
|
||||||
$avatarImage->cropImage(
|
|
||||||
$dimensions,
|
|
||||||
$dimensions,
|
|
||||||
($avatarWidth - $dimensions) / 2,
|
|
||||||
($avatarHeight - $dimensions) / 2
|
|
||||||
);
|
|
||||||
|
|
||||||
$avatarImage->setImagePage(
|
|
||||||
$dimensions,
|
|
||||||
$dimensions,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
} while($avatarImage->nextImage());
|
|
||||||
|
|
||||||
$avatarImage->deconstructImages()->writeImages($filename = $avatarCropped, true);
|
|
||||||
} catch(Exception $ex) {}
|
} catch(Exception $ex) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
112
src/Imaging/GdImage.php
Normal file
112
src/Imaging/GdImage.php
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\Imaging;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class GdImage extends Image {
|
||||||
|
private $gd;
|
||||||
|
private int $type;
|
||||||
|
|
||||||
|
private const CONSTRUCTORS = [
|
||||||
|
IMAGETYPE_GIF => 'imagecreatefromgif',
|
||||||
|
IMAGETYPE_JPEG => 'imagecreatefromjpeg',
|
||||||
|
IMAGETYPE_PNG => 'imagecreatefrompng',
|
||||||
|
IMAGETYPE_BMP => 'imagecreatefrombmp',
|
||||||
|
IMAGETYPE_WBMP => 'imagecreatefromwbmp',
|
||||||
|
IMAGETYPE_WEBP => 'imagecreatefromwebp',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const SAVERS = [
|
||||||
|
IMAGETYPE_GIF => 'imagegif',
|
||||||
|
IMAGETYPE_JPEG => 'imagejpeg',
|
||||||
|
IMAGETYPE_PNG => 'imagepng',
|
||||||
|
IMAGETYPE_BMP => 'imagebmp',
|
||||||
|
IMAGETYPE_WBMP => 'imagewbmp',
|
||||||
|
IMAGETYPE_WEBP => 'imagewebp',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct($pathOrWidth, int $height = -1) {
|
||||||
|
parent::__construct($pathOrWidth, $height);
|
||||||
|
|
||||||
|
if(is_int($pathOrWidth)) {
|
||||||
|
$this->gd = imagecreatetruecolor($pathOrWidth, $height < 1 ? $pathOrWidth : $height);
|
||||||
|
$this->type = IMAGETYPE_PNG;
|
||||||
|
} elseif(is_string($pathOrWidth)) {
|
||||||
|
$imageInfo = getimagesize($pathOrWidth);
|
||||||
|
|
||||||
|
if($imageInfo !== false) {
|
||||||
|
$this->type = $imageInfo[2];
|
||||||
|
|
||||||
|
if(isset(self::CONSTRUCTORS[$this->type]))
|
||||||
|
$this->gd = self::CONSTRUCTORS[$this->type]($pathOrWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isset($this->gd)) {
|
||||||
|
throw new InvalidArgumentException('Unsupported image format.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
if(isset($this->gd))
|
||||||
|
$this->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWidth(): int {
|
||||||
|
return imagesx($this->gd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeight(): int {
|
||||||
|
return imagesy($this->gd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasFrames(): bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next(): bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resize(int $width, int $height): bool {
|
||||||
|
$resized = imagescale($this->gd, $width, $height, IMG_BICUBIC_FIXED);
|
||||||
|
|
||||||
|
if($resized === false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
imagedestroy($this->gd);
|
||||||
|
$this->gd = $resized;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function crop(int $width, int $height, int $x, int $y): bool {
|
||||||
|
$cropped = imagecrop($this->gd, compact('width', 'height', 'x', 'y'));
|
||||||
|
|
||||||
|
if($cropped === false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
imagedestroy($this->gd);
|
||||||
|
$this->gd = $cropped;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPage(int $width, int $height, int $x, int $y): bool {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(string $path): bool {
|
||||||
|
if(isset(self::SAVERS[$this->type]))
|
||||||
|
return self::SAVERS[$this->type]($this->gd, $path);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(): void {
|
||||||
|
if(imagedestroy($this->gd)) {
|
||||||
|
$this->gd = null;
|
||||||
|
$this->type = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/Imaging/Image.php
Normal file
53
src/Imaging/Image.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\Imaging;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
|
||||||
|
abstract class Image {
|
||||||
|
public function __construct($pathOrWidth, int $height = -1) {
|
||||||
|
if(!is_int($pathOrWidth) && !is_string($pathOrWidth))
|
||||||
|
throw new InvalidArgumentException('The first argument must be or type string to open an image file, or int to set a width for a new image.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function create($pathOrWidth, int $height = -1): Image {
|
||||||
|
if(extension_loaded('imagick'))
|
||||||
|
return new ImagickImage($pathOrWidth, $height);
|
||||||
|
if(extension_loaded('gd'))
|
||||||
|
return new GdImage($pathOrWidth, $height);
|
||||||
|
throw new UnexpectedValueException('No image manipulation extensions are available.');
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function getWidth(): int;
|
||||||
|
abstract public function getHeight(): int;
|
||||||
|
abstract public function hasFrames(): bool;
|
||||||
|
abstract public function next(): bool;
|
||||||
|
abstract public function resize(int $width, int $height): bool;
|
||||||
|
abstract public function crop(int $width, int $height, int $x, int $y): bool;
|
||||||
|
abstract public function setPage(int $width, int $height, int $x, int $y): bool;
|
||||||
|
abstract public function save(string $path): bool;
|
||||||
|
abstract public function destroy(): void;
|
||||||
|
|
||||||
|
public function squareCrop(int $dimensions): void {
|
||||||
|
$originalWidth = $this->getWidth();
|
||||||
|
$originalHeight = $this->getHeight();
|
||||||
|
|
||||||
|
if($originalWidth > $originalHeight) {
|
||||||
|
$targetWidth = $originalWidth * $dimensions / $originalHeight;
|
||||||
|
$targetHeight = $dimensions;
|
||||||
|
} else {
|
||||||
|
$targetWidth = $dimensions;
|
||||||
|
$targetHeight = $originalHeight * $dimensions / $originalWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
$this->resize($targetWidth, $targetHeight);
|
||||||
|
$this->crop(
|
||||||
|
$dimensions, $dimensions,
|
||||||
|
($targetWidth - $dimensions) / 2,
|
||||||
|
($targetHeight - $dimensions) / 2
|
||||||
|
);
|
||||||
|
$this->setPage($dimensions, $dimensions, 0, 0);
|
||||||
|
} while($this->next());
|
||||||
|
}
|
||||||
|
}
|
76
src/Imaging/ImagickImage.php
Normal file
76
src/Imaging/ImagickImage.php
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
namespace Misuzu\Imaging;
|
||||||
|
|
||||||
|
use Imagick;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
final class ImagickImage extends Image {
|
||||||
|
private ?Imagick $imagick = null;
|
||||||
|
|
||||||
|
public function __construct($pathOrWidth, int $height = -1) {
|
||||||
|
parent::__construct($pathOrWidth, $height);
|
||||||
|
|
||||||
|
if(is_int($pathOrWidth)) {
|
||||||
|
$this->imagick = new Imagick();
|
||||||
|
$this->newImage($pathOrWidth, $height < 1 ? $pathOrWidth : $height, 'none');
|
||||||
|
$this->setImageFormat('png');
|
||||||
|
} elseif(is_string($pathOrWidth)) {
|
||||||
|
$imagick = new Imagick($pathOrWidth);
|
||||||
|
$imagick->setImageFormat($imagick->getNumberImages() > 1 ? 'gif' : 'png');
|
||||||
|
$this->imagick = $imagick->coalesceImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!isset($this->imagick))
|
||||||
|
throw new InvalidArgumentException('Unsupported image format.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
if(isset($this->imagick))
|
||||||
|
$this->destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImagick(): Imagick {
|
||||||
|
return $this->imagick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWidth(): int {
|
||||||
|
return $this->imagick->getImageWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeight(): int {
|
||||||
|
return $this->imagick->getImageHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasFrames(): bool {
|
||||||
|
return $this->imagick->getNumberImages() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next(): bool {
|
||||||
|
return $this->imagick->nextImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function resize(int $width, int $height): bool {
|
||||||
|
return $this->imagick->resizeImage(
|
||||||
|
$width, $height, Imagick::FILTER_LANCZOS, 0.9
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function crop(int $width, int $height, int $x, int $y): bool {
|
||||||
|
return $this->imagick->cropImage($width, $height, $x, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPage(int $width, int $height, int $x, int $y): bool {
|
||||||
|
return $this->imagick->setImagePage($width, $height, $x, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(string $path): bool {
|
||||||
|
return $this->imagick
|
||||||
|
->deconstructImages()
|
||||||
|
->writeImages($path, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(): void {
|
||||||
|
if($this->imagick->destroy())
|
||||||
|
$this->imagick = null;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue