ver. 0.283: Legacy class cleanup — S, Html, Email, Image, Log, Mobile_Detect → Shared namespace
- Migrate class.S → Shared\Helpers\Helpers (140+ files), remove 12 unused methods - Migrate class.Html → Shared\Html\Html - Migrate class.Email → Shared\Email\Email - Migrate class.Image → Shared\Image\ImageManipulator - Delete class.Log (unused), class.Mobile_Detect (outdated UA detection) - Remove grid library loading from admin (index.php, ajax.php) - Replace gridEdit usage in 10 admin templates with grid-edit-replacement.php - Fix grid-edit-replacement.php AJAX to send values as JSON (grid.js compat) - Remove mobile layout conditionals (m_html/m_css/m_js) from Site + LayoutsRepository - Remove \Log::save_log() calls from OrderAdminService, ShopOrder, Order Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
442
autoload/Shared/Image/ImageManipulator.php
Normal file
442
autoload/Shared/Image/ImageManipulator.php
Normal file
@@ -0,0 +1,442 @@
|
||||
<?php
|
||||
namespace Shared\Image;
|
||||
|
||||
class ImageManipulator
|
||||
{
|
||||
protected int $width;
|
||||
protected int $height;
|
||||
/** @var resource|\GdImage */
|
||||
protected $image;
|
||||
protected ?string $file = null;
|
||||
|
||||
/**
|
||||
* Image manipulator constructor
|
||||
*
|
||||
* @param string|null $file Path to image file or image data as string
|
||||
*/
|
||||
public function __construct(?string $file = null)
|
||||
{
|
||||
if ($file !== null) {
|
||||
$this->file = $file;
|
||||
|
||||
if (is_file($file)) {
|
||||
$this->setImageFile($file);
|
||||
} else {
|
||||
$this->setImageString($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from file
|
||||
*
|
||||
* @param string $file Path to image file
|
||||
* @return self
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setImageFile(string $file): self
|
||||
{
|
||||
if (!(is_readable($file) && is_file($file))) {
|
||||
throw new \InvalidArgumentException("Image file $file is not readable");
|
||||
}
|
||||
|
||||
if (isset($this->image) && $this->isValidImageResource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
[$width, $height, $type] = getimagesize($file);
|
||||
|
||||
if ($width === false || $height === false) {
|
||||
throw new \InvalidArgumentException("Unable to get image size for $file");
|
||||
}
|
||||
|
||||
error_log("Loaded image size from file: width: $width, height: $height, type: $type");
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_GIF:
|
||||
$this->image = imagecreatefromgif($file);
|
||||
break;
|
||||
case IMAGETYPE_JPEG:
|
||||
$this->image = imagecreatefromjpeg($file);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$this->image = imagecreatefrompng($file);
|
||||
break;
|
||||
case IMAGETYPE_WEBP:
|
||||
$this->image = imagecreatefromwebp($file);
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Image type $type not supported");
|
||||
}
|
||||
|
||||
if (!$this->isValidImageResource($this->image)) {
|
||||
throw new \InvalidArgumentException("Failed to create image from $file");
|
||||
}
|
||||
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
|
||||
error_log("Set image dimensions: width: {$this->width}, height: {$this->height}");
|
||||
|
||||
if ($this->width === 0 || $this->height === 0) {
|
||||
throw new \InvalidArgumentException("Image dimensions are invalid (width: $this->width, height: $this->height)");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set image resource from string data
|
||||
*
|
||||
* @param string $data Image data as string
|
||||
* @return self
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function setImageString(string $data): self
|
||||
{
|
||||
if (isset($this->image) && $this->isValidImageResource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$image = imagecreatefromstring($data);
|
||||
if (!$this->isValidImageResource($image)) {
|
||||
throw new \RuntimeException('Cannot create image from data string');
|
||||
}
|
||||
|
||||
$this->image = $image;
|
||||
$this->width = imagesx($this->image);
|
||||
$this->height = imagesy($this->image);
|
||||
|
||||
error_log("Set image dimensions from string: width: {$this->width}, height: {$this->height}");
|
||||
|
||||
if ($this->width === 0 || $this->height === 0) {
|
||||
throw new \RuntimeException("Image dimensions are invalid (width: $this->width, height: $this->height)");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resamples the current image
|
||||
*
|
||||
* @param int $width New width
|
||||
* @param int $height New height
|
||||
* @param bool $constrainProportions Constrain current image proportions when resizing
|
||||
* @return self
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function resample(int $width, int $height, bool $constrainProportions = true): self
|
||||
{
|
||||
if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
|
||||
if ($constrainProportions) {
|
||||
if ($this->height === 0) {
|
||||
throw new \RuntimeException('Image height is zero, cannot calculate aspect ratio');
|
||||
}
|
||||
|
||||
$aspectRatio = $this->width / $this->height;
|
||||
|
||||
// Ustaw domyślną wysokość, jeśli podana jest równa zero
|
||||
if ($height === 0) {
|
||||
$height = (int) round($width / $aspectRatio);
|
||||
}
|
||||
|
||||
if ($width / $height > $aspectRatio) {
|
||||
$width = (int) round($height * $aspectRatio);
|
||||
} else {
|
||||
$height = (int) round($width / $aspectRatio);
|
||||
}
|
||||
|
||||
if ($width <= 0 || $height <= 0) {
|
||||
throw new \RuntimeException('Calculated dimensions are invalid (width: ' . $width . ', height: ' . $height . ')');
|
||||
}
|
||||
}
|
||||
|
||||
// reszta kodu metody
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Enlarge canvas
|
||||
*
|
||||
* @param int $width Canvas width
|
||||
* @param int $height Canvas height
|
||||
* @param array $rgb RGB colour values [R, G, B]
|
||||
* @param int|null $xpos X-Position of image in new canvas, null for centre
|
||||
* @param int|null $ypos Y-Position of image in new canvas, null for centre
|
||||
* @return self
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function enlargeCanvas(int $width, int $height, array $rgb = [], ?int $xpos = null, ?int $ypos = null): self
|
||||
{
|
||||
if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
|
||||
$width = max($width, $this->width);
|
||||
$height = max($height, $this->height);
|
||||
|
||||
$temp = imagecreatetruecolor($width, $height);
|
||||
if (!$this->isValidImageResource($temp)) {
|
||||
throw new \RuntimeException('Failed to create a new image for enlarging canvas');
|
||||
}
|
||||
|
||||
// Fill background if RGB provided
|
||||
if (count($rgb) === 3) {
|
||||
[$r, $g, $b] = $rgb;
|
||||
$bg = imagecolorallocate($temp, $r, $g, $b);
|
||||
imagefill($temp, 0, 0, $bg);
|
||||
} else {
|
||||
// Preserve transparency
|
||||
imagealphablending($temp, false);
|
||||
imagesavealpha($temp, true);
|
||||
$transparent = imagecolorallocatealpha($temp, 255, 255, 255, 127);
|
||||
imagefilledrectangle($temp, 0, 0, $width, $height, $transparent);
|
||||
}
|
||||
|
||||
// Calculate positions
|
||||
if ($xpos === null) {
|
||||
$xpos = (int) round(($width - $this->width) / 2);
|
||||
}
|
||||
if ($ypos === null) {
|
||||
$ypos = (int) round(($height - $this->height) / 2);
|
||||
}
|
||||
|
||||
// Logowanie przed kopiowaniem obrazu na nowe płótno
|
||||
error_log("Enlarging canvas: xpos: $xpos, ypos: $ypos");
|
||||
|
||||
if (!imagecopy(
|
||||
$temp,
|
||||
$this->image,
|
||||
$xpos,
|
||||
$ypos,
|
||||
0,
|
||||
0,
|
||||
$this->width,
|
||||
$this->height
|
||||
)) {
|
||||
throw new \RuntimeException('Failed to copy image onto enlarged canvas');
|
||||
}
|
||||
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crop image
|
||||
*
|
||||
* @param int|array $x1 Top left x-coordinate of crop box or array of coordinates [x1, y1, x2, y2]
|
||||
* @param int $y1 Top left y-coordinate of crop box
|
||||
* @param int $x2 Bottom right x-coordinate of crop box
|
||||
* @param int $y2 Bottom right y-coordinate of crop box
|
||||
* @return self
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function crop($x1, int $y1 = 0, int $x2 = 0, int $y2 = 0): self
|
||||
{
|
||||
if (!isset($this->image) || !$this->isValidImageResource($this->image)) {
|
||||
throw new \RuntimeException('No image set');
|
||||
}
|
||||
|
||||
if (is_array($x1) && count($x1) === 4) {
|
||||
[$x1, $y1, $x2, $y2] = $x1;
|
||||
}
|
||||
|
||||
$x1 = max((int)$x1, 0);
|
||||
$y1 = max($y1, 0);
|
||||
$x2 = min($x2, $this->width);
|
||||
$y2 = min($y2, $this->height);
|
||||
|
||||
$cropWidth = $x2 - $x1;
|
||||
$cropHeight = $y2 - $y1;
|
||||
|
||||
// Logowanie wymiarów do przycięcia
|
||||
error_log("Cropping image: x1: $x1, y1: $y1, x2: $x2, y2: $y2, cropWidth: $cropWidth, cropHeight: $cropHeight");
|
||||
|
||||
if ($cropWidth <= 0 || $cropHeight <= 0) {
|
||||
throw new \RuntimeException('Invalid crop dimensions');
|
||||
}
|
||||
|
||||
$temp = imagecreatetruecolor($cropWidth, $cropHeight);
|
||||
if (!$this->isValidImageResource($temp)) {
|
||||
throw new \RuntimeException('Failed to create a new image for cropping');
|
||||
}
|
||||
|
||||
// Preserve transparency
|
||||
imagealphablending($temp, false);
|
||||
imagesavealpha($temp, true);
|
||||
$transparent = imagecolorallocatealpha($temp, 255, 255, 255, 127);
|
||||
imagefilledrectangle($temp, 0, 0, $cropWidth, $cropHeight, $transparent);
|
||||
|
||||
if (!imagecopy(
|
||||
$temp,
|
||||
$this->image,
|
||||
0,
|
||||
0,
|
||||
$x1,
|
||||
$y1,
|
||||
$cropWidth,
|
||||
$cropHeight
|
||||
)) {
|
||||
throw new \RuntimeException('Failed to crop image');
|
||||
}
|
||||
|
||||
return $this->_replace($temp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current image resource with a new one
|
||||
*
|
||||
* @param resource|\GdImage $res New image resource
|
||||
* @return self
|
||||
* @throws \UnexpectedValueException
|
||||
*/
|
||||
protected function _replace($res): self
|
||||
{
|
||||
if (!$this->isValidImageResource($res)) {
|
||||
throw new \UnexpectedValueException('Invalid image resource');
|
||||
}
|
||||
|
||||
if (isset($this->image) && $this->isValidImageResource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
$this->image = $res;
|
||||
$this->width = imagesx($res);
|
||||
$this->height = imagesy($res);
|
||||
|
||||
error_log("Replaced image dimensions: width: {$this->width}, height: {$this->height}");
|
||||
|
||||
if ($this->width === 0 || $this->height === 0) {
|
||||
throw new \UnexpectedValueException("Replaced image has invalid dimensions (width: $this->width, height: $this->height)");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current image to file
|
||||
*
|
||||
* @param string $fileName Path to save the image
|
||||
* @param int|null $type Image type (IMAGETYPE_*) or null to auto-detect from file extension
|
||||
* @return void
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function save(string $fileName, ?int $type = null): void
|
||||
{
|
||||
$dir = dirname($fileName);
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0755, true) && !is_dir($dir)) {
|
||||
throw new \RuntimeException('Error creating directory ' . $dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect type from file extension if not provided
|
||||
if ($type === null) {
|
||||
$extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
||||
switch ($extension) {
|
||||
case 'gif':
|
||||
$type = IMAGETYPE_GIF;
|
||||
break;
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
$type = IMAGETYPE_JPEG;
|
||||
break;
|
||||
case 'png':
|
||||
$type = IMAGETYPE_PNG;
|
||||
break;
|
||||
case 'webp':
|
||||
$type = IMAGETYPE_WEBP;
|
||||
break;
|
||||
default:
|
||||
$type = IMAGETYPE_JPEG;
|
||||
}
|
||||
}
|
||||
|
||||
error_log("Saving image to $fileName with type $type");
|
||||
|
||||
try {
|
||||
switch ($type) {
|
||||
case IMAGETYPE_WEBP:
|
||||
if (!imagewebp($this->image, $fileName)) {
|
||||
throw new \RuntimeException('Failed to save image as WEBP');
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
if (!imagegif($this->image, $fileName)) {
|
||||
throw new \RuntimeException('Failed to save image as GIF');
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
if (!imagepng($this->image, $fileName)) {
|
||||
throw new \RuntimeException('Failed to save image as PNG');
|
||||
}
|
||||
break;
|
||||
case IMAGETYPE_JPEG:
|
||||
default:
|
||||
if (!imagejpeg($this->image, $fileName, 95)) {
|
||||
throw new \RuntimeException('Failed to save image as JPEG');
|
||||
}
|
||||
}
|
||||
error_log("Image saved successfully to $fileName");
|
||||
} catch (\Exception $ex) {
|
||||
throw new \RuntimeException('Error saving image file to ' . $fileName . ': ' . $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GD image resource
|
||||
*
|
||||
* @return resource|\GdImage
|
||||
*/
|
||||
public function getResource()
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image width
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current image height
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor to clean up the image resource
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (isset($this->image) && $this->isValidImageResource($this->image)) {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compatibility helper for PHP 7.4 (resource) and PHP 8+ (GdImage).
|
||||
*
|
||||
* @param mixed $image
|
||||
*/
|
||||
private function isValidImageResource($image): bool
|
||||
{
|
||||
if (is_resource($image)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return class_exists('GdImage', false) && $image instanceof \GdImage;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user