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
|
||||
namespace Misuzu;
|
||||
|
||||
use Imagick;
|
||||
use Misuzu\Imaging\Image;
|
||||
|
||||
$userAssetsMode = !empty($_GET['m']) && is_string($_GET['m']) ? (string)$_GET['m'] : '';
|
||||
$misuzuBypassLockdown = $userAssetsMode === 'avatar';
|
||||
|
@ -56,45 +56,9 @@ switch($userAssetsMode) {
|
|||
try {
|
||||
mkdirs($avatarStorage, true);
|
||||
|
||||
$avatarImage = new Imagick($avatarOriginal);
|
||||
$avatarImage->setImageFormat($avatarImage->getNumberImages() > 1 ? 'gif' : 'png');
|
||||
$avatarImage = $avatarImage->coalesceImages();
|
||||
|
||||
$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);
|
||||
$avatarImage = Image::create($avatarOriginal);
|
||||
$avatarImage->squareCrop($dimensions);
|
||||
$avatarImage->save($filename = $avatarCropped);
|
||||
} 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