ImageMagick Support for PHP Thumb Library
Imagick.php class that provides ImageMagick support for the PHP Thumb Library:
PHP
<?php
namespace PHPThumb;
use Exception;
use Imagick;
use ImagickDraw;
use ImagickPixel;
use InvalidArgumentException;
use RuntimeException;
/**
* PhpThumb : PHP Thumb Library <https://github.com/PHPThumb/PHPThumb>
* Copyright (c) 2009, Ian Selby
*
* Author(s): Ian Selby <ianrselby@gmail.com>
*
* Licensed under the MIT License
* Redistributions of files must retain the above copyright notice.
*
* @author Ian Selby <ianrselby@gmail.com>
* @copyright Copyright (c) 2009 Ian Selby
* @link https://github.com/PHPThumb/PHPThumb
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
class Imagick extends \PHPThumb\PHPThumb
{
/**
* The prior image (before manipulation)
*
* @var Imagick
*/
protected $old_image;
/**
* The working image (used during manipulation)
*
* @var Imagick
*/
protected $working_image;
/**
* The current dimensions of the image
*/
protected array $current_dimensions;
/**
* The new, calculated dimensions of the image
*/
protected array $new_dimensions;
/**
* The options for this class
*/
protected array $options = [];
/**
* The maximum width an image can be after resizing (in pixels)
*/
protected int $max_width;
/**
* The maximum height an image can be after resizing (in pixels)
*/
protected int $max_height;
/**
* The percentage to resize the image by
*/
protected int $percent;
/**
* @throws Exception
*/
public function __construct(string $file_name, array $options = [], array $plugins = [])
{
parent::__construct($file_name, $options, $plugins);
$this->determineFormat();
$this->verifyFormatCompatibility();
$this->old_image = new Imagick();
if ($this->remote_image)
{
$this->old_image->readImage($this->file_name);
}
else
{
$this->old_image->readImage($this->file_name);
}
$this->current_dimensions = [
'width' => $this->old_image->getImageWidth(),
'height' => $this->old_image->getImageHeight()
];
}
/**
* Destructor
*/
public function __destruct()
{
if ($this->old_image instanceof Imagick)
{
$this->old_image->clear();
$this->old_image->destroy();
}
if ($this->working_image instanceof Imagick)
{
$this->working_image->clear();
$this->working_image->destroy();
}
}
/**
* Pad an image to desired dimensions. Moves the image into the center and fills the rest with $color.
*/
public function pad(int $width, int $height, array $color = [255, 255, 255]): Imagick
{
// no resize - woohoo!
if ($width == $this->current_dimensions['width'] && $height == $this->current_dimensions['height'])
{
return $this;
}
$this->working_image = new Imagick();
$pixel = new ImagickPixel('rgb(' . $color[0] . ', ' . $color[1] . ', ' . $color[2] . ')');
$this->working_image->newImage($width, $height, $pixel);
$pixel->destroy();
$this->working_image->setFormat($this->old_image->getFormat());
$x = intval(($width - $this->current_dimensions['width']) / 2);
$y = intval(($height - $this->current_dimensions['height']) / 2);
$this->working_image->compositeImage(
$this->old_image,
Imagick::COMPOSITE_DEFAULT,
$x,
$y
);
$this->old_image->clear();
$this->old_image->destroy();
$this->old_image = $this->working_image;
$this->current_dimensions['width'] = $width;
$this->current_dimensions['height'] = $height;
return $this;
}
/**
* Check if the image can be scaled up
*/
private function checkingMaxSize(int $max_width, int $max_height): void
{
if ($this->options['resizeUp'] === false)
{
$this->max_height = ($max_height > $this->current_dimensions['height']) ? $this->current_dimensions['height'] : $max_height;
$this->max_width = ($max_width > $this->current_dimensions['width']) ? $this->current_dimensions['width'] : $max_width;
}
else
{
$this->max_height = $max_height;
$this->max_width = $max_width;
}
}
/**
* Resizes an image to be no larger than $max_width or $max_height
*/
public function resize(int $max_width = 0, int $max_height = 0): Imagick
{
$this->checkingMaxSize($max_width, $max_height);
$this->calcImageSize($this->current_dimensions['width'], $this->current_dimensions['height']);
$this->old_image->thumbnailImage(
$this->new_dimensions['new_width'],
$this->new_dimensions['new_height'],
false
);
$this->current_dimensions['width'] = $this->new_dimensions['new_width'];
$this->current_dimensions['height'] = $this->new_dimensions['new_height'];
return $this;
}
/**
* Adaptively Resizes the Image
*/
public function adaptiveResize(int $width, int $height): Imagick
{
if ($width == 0 && $height == 0)
{
throw new InvalidArgumentException('$width and $height must be numeric and greater than zero');
}
if ($width == 0)
{
$width = intval(($height * $this->current_dimensions['width']) / $this->current_dimensions['height']);
}
if ($height == 0)
{
$height = intval(($width * $this->current_dimensions['height']) / $this->current_dimensions['width']);
}
$this->checkingMaxSize($width, $height);
$this->calcImageSizeStrict($this->current_dimensions['width'], $this->current_dimensions['height']);
$resize_width = $this->new_dimensions['new_width'];
$resize_height = $this->new_dimensions['new_height'];
$this->old_image->thumbnailImage($resize_width, $resize_height, false);
$this->checkingMaxSize($width, $height);
$crop_width = $this->max_width;
$crop_height = $this->max_height;
$crop_x = 0;
$crop_y = 0;
if ($this->current_dimensions['width'] > $this->max_width)
{
$crop_x = intval(($this->current_dimensions['width'] - $this->max_width) / 2);
}
else if ($this->current_dimensions['height'] > $this->max_height)
{
$crop_y = intval(($this->current_dimensions['height'] - $this->max_height) / 2);
}
$this->old_image->cropImage($crop_width, $crop_height, $crop_x, $crop_y);
$this->current_dimensions['width'] = $crop_width;
$this->current_dimensions['height'] = $crop_height;
return $this;
}
/**
* Adaptively Resizes the Image and Crops Using a Percentage
*/
public function adaptiveResizePercent(int $width, int $height, int $percent = 50): Imagick
{
if ($width == 0)
{
throw new InvalidArgumentException('$width must be numeric and greater than zero');
}
if ($height == 0)
{
throw new InvalidArgumentException('$height must be numeric and greater than zero');
}
$this->checkingMaxSize($width, $height);
$this->calcImageSizeStrict($this->current_dimensions['width'], $this->current_dimensions['height']);
$resize_width = $this->new_dimensions['new_width'];
$resize_height = $this->new_dimensions['new_height'];
$this->old_image->thumbnailImage($resize_width, $resize_height, false);
$this->checkingMaxSize($width, $height);
$crop_width = $this->max_width;
$crop_height = $this->max_height;
$crop_x = 0;
$crop_y = 0;
if ($percent > 100)
{
$percent = 100;
}
else if ($percent < 1)
{
$percent = 1;
}
if ($this->current_dimensions['width'] > $this->max_width)
{
$max_crop_x = $this->current_dimensions['width'] - $this->max_width;
$crop_x = intval(($percent / 100) * $max_crop_x);
}
else if ($this->current_dimensions['height'] > $this->max_height)
{
$max_crop_y = $this->current_dimensions['height'] - $this->max_height;
$crop_y = intval(($percent / 100) * $max_crop_y);
}
$this->old_image->cropImage($crop_width, $crop_height, $crop_x, $crop_y);
$this->current_dimensions['width'] = $crop_width;
$this->current_dimensions['height'] = $crop_height;
return $this;
}
/**
* Adaptively Resizes the Image and Crops Using a Quadrant
*/
public function adaptiveResizeQuadrant(int $width, int $height, string $quadrant = 'C'): Imagick
{
if ($width == 0)
{
throw new InvalidArgumentException('$width must be numeric and greater than zero');
}
if ($height == 0)
{
throw new InvalidArgumentException('$height must be numeric and greater than zero');
}
$this->checkingMaxSize($width, $height);
$this->calcImageSizeStrict($this->current_dimensions['width'], $this->current_dimensions['height']);
$resize_width = $this->new_dimensions['new_width'];
$resize_height = $this->new_dimensions['new_height'];
$this->old_image->thumbnailImage($resize_width, $resize_height, false);
$this->checkingMaxSize($width, $height);
$crop_width = $this->max_width;
$crop_height = $this->max_height;
$crop_x = 0;
$crop_y = 0;
if ($this->current_dimensions['width'] > $this->max_width)
{
$crop_x = match ($quadrant) {
'L' => 0,
'R' => intval(($this->current_dimensions['width'] - $this->max_width)),
default => intval(($this->current_dimensions['width'] - $this->max_width) / 2),
};
}
else if ($this->current_dimensions['height'] > $this->max_height)
{
$crop_y = match ($quadrant) {
'T' => 0,
'B' => intval(($this->current_dimensions['height'] - $this->max_height)),
default => intval(($this->current_dimensions['height'] - $this->max_height) / 2),
};
}
$this->old_image->cropImage($crop_width, $crop_height, $crop_x, $crop_y);
$this->current_dimensions['width'] = $crop_width;
$this->current_dimensions['height'] = $crop_height;
return $this;
}
/**
* Resizes an image by a given percent uniformly
*/
public function resizePercent(int $percent = 0): Imagick
{
$this->percent = $percent;
$this->calcImageSizePercent($this->current_dimensions['width'], $this->current_dimensions['height']);
return $this->resize($this->new_dimensions['new_width'], $this->new_dimensions['new_height']);
}
/**
* Crops an image from the center with provided dimensions
*/
public function cropFromCenter(int $crop_width, ?int $crop_height = 0): Imagick
{
if ($crop_height == 0)
{
$crop_height = $crop_width;
}
$crop_width = ($this->current_dimensions['width'] < $crop_width) ? $this->current_dimensions['width'] : $crop_width;
$crop_height = ($this->current_dimensions['height'] < $crop_height) ? $this->current_dimensions['height'] : $crop_height;
$crop_x = intval(($this->current_dimensions['width'] - $crop_width) / 2);
$crop_y = intval(($this->current_dimensions['height'] - $crop_height) / 2);
$this->crop($crop_x, $crop_y, $crop_width, $crop_height);
return $this;
}
/**
* Vanilla Cropping - Crops from x,y with specified width and height
*/
public function crop(int $start_x, int $start_y, int $crop_width, int $crop_height): Imagick
{
$crop_width = ($this->current_dimensions['width'] < $crop_width) ? $this->current_dimensions['width'] : $crop_width;
$crop_height = ($this->current_dimensions['height'] < $crop_height) ? $this->current_dimensions['height'] : $crop_height;
if (($start_x + $crop_width) > $this->current_dimensions['width'])
{
$start_x = ($this->current_dimensions['width'] - $crop_width);
}
if (($start_y + $crop_height) > $this->current_dimensions['height'])
{
$start_y = ($this->current_dimensions['height'] - $crop_height);
}
if ($start_x < 0)
{
$start_x = 0;
}
if ($start_y < 0)
{
$start_y = 0;
}
$this->old_image->cropImage($crop_width, $crop_height, $start_x, $start_y);
$this->current_dimensions['width'] = $crop_width;
$this->current_dimensions['height'] = $crop_height;
return $this;
}
/**
* Rotates image either 90 degrees clockwise or counter-clockwise
*/
public function rotateImage(string $direction = 'CW'): Imagick
{
$degrees = match($direction) {
'CW' => 90,
default => -90,
};
$this->rotateImageNDegrees($degrees);
return $this;
}
/**
* Rotates image specified number of degrees
*/
public function rotateImageNDegrees(int $degrees): Imagick
{
$background_color = new ImagickPixel('rgb(' . $this->options['alphaMaskColor'][0] . ', ' . $this->options['alphaMaskColor'][1] . ', ' . $this->options['alphaMaskColor'][2] . ')');
$this->old_image->rotateImage($background_color, $degrees);
$background_color->destroy();
$this->current_dimensions['width'] = $this->old_image->getImageWidth();
$this->current_dimensions['height'] = $this->old_image->getImageHeight();
return $this;
}
/**
* Applies a filter to the image
*/
public function imageFilter(int $filter, bool $arg1 = false, bool $arg2 = false, bool $arg3 = false, bool $arg4 = false): Imagick
{
$arguments = [];
if ($arg1 !== false)
{
$arguments[] = $arg1;
}
if ($arg2 !== false)
{
$arguments[] = $arg2;
}
if ($arg3 !== false)
{
$arguments[] = $arg3;
}
if ($arg4 !== false)
{
$arguments[] = $arg4;
}
$this->old_image->filter($filter, ...$arguments);
return $this;
}
/**
* Shows an image
*/
public function show(bool $raw_data = false): Imagick
{
if ($this->plugins)
{
foreach ($this->plugins as $plugin)
{
$plugin->execute($this);
}
}
if (headers_sent() && php_sapi_name() != 'cli')
{
throw new RuntimeException('Cannot show image, headers have already been sent');
}
$format = strtolower($this->old_image->getImageFormat());
$mime_type = match ($format) {
'avif' => 'image/avif',
'gif' => 'image/gif',
'jpeg', 'jpg' => 'image/jpeg',
'png' => 'image/png',
'webp' => 'image/webp',
'bmp' => 'image/bmp',
default => 'image/' . $format,
};
if ($raw_data === false)
{
header('Content-type: ' . $mime_type);
}
echo $this->old_image->getImagesBlob();
return $this;
}
/**
* Returns the Working Image as a String
*/
public function getImageAsString(): string
{
return $this->old_image->getImagesBlob();
}
/**
* Saves an image
*/
public function save(string $file_name, ?string $format = null): Imagick
{
$format = ($format !== null) ? strtoupper($format) : strtoupper($this->format);
if (!is_writeable(dirname($file_name)))
{
if ($this->options['correctPermissions'] === true)
{
@chmod(dirname($file_name), 0777);
if (!is_writeable(dirname($file_name)))
{
throw new RuntimeException('File is not writeable, and could not correct permissions: ' . $file_name);
}
}
else
{
throw new RuntimeException('File not writeable: ' . $file_name);
}
}
$output_format = match ($format) {
'AVIF' => 'AVIF',
'GIF' => 'GIF',
'JPEG', 'JPG' => 'JPEG',
'PNG' => 'PNG',
'WEBP' => 'WEBP',
default => strtoupper($format),
};
$this->old_image->setFormat($output_format);
$this->old_image->setImageFormat($output_format);
$quality = match ($output_format) {
'AVIF' => $this->options['avifQuality'],
'JPEG', 'JPG' => $this->options['jpegQuality'],
'WEBP' => $this->options['webpQuality'],
default => null,
};
if ($quality !== null)
{
$this->old_image->setImageCompressionQuality($quality);
}
$this->old_image->writeImage($file_name);
return $this;
}
#################################
# ----- GETTERS / SETTERS ----- #
#################################
/**
* Sets options for all operations.
*/
public function setOptions(array $options = []): Imagick
{
if (count($this->options) == 0)
{
$default_options = [
'resizeUp' => false,
'avifQuality' => 100,
'jpegQuality' => 100,
'webpQuality' => 100,
'correctPermissions' => false,
'preserveAlpha' => true,
'alphaMaskColor' => [255, 255, 255],
'preserveTransparency' => true,
'transparencyMaskColor' => [0, 0, 0],
'interlace' => null
];
}
else
{
$default_options = $this->options;
}
$this->options = array_merge($default_options, $options);
return $this;
}
/**
* Returns $current_dimensions.
*/
public function getCurrentDimensions(): array
{
return $this->current_dimensions;
}
public function setCurrentDimensions(array $current_dimensions): Imagick
{
$this->current_dimensions = $current_dimensions;
return $this;
}
public function getMaxHeight(): int
{
return $this->max_height;
}
public function setMaxHeight(int $max_height): Imagick
{
$this->max_height = $max_height;
return $this;
}
public function getMaxWidth(): int
{
return $this->max_width;
}
public function setMaxWidth(int $max_width): Imagick
{
$this->max_width = $max_width;
return $this;
}
/**
* Returns $new_dimensions.
*/
public function getNewDimensions(): array
{
return $this->new_dimensions;
}
/**
* Sets $new_dimensions.
*/
public function setNewDimensions(array $new_dimensions): Imagick
{
$this->new_dimensions = $new_dimensions;
return $this;
}
/**
* Returns $options.
*/
public function getOptions(): array
{
return $this->options;
}
/**
* Returns $percent.
*/
public function getPercent(): int
{
return $this->percent;
}
/**
* Sets $percent.
*/
public function setPercent(int $percent): Imagick
{
$this->percent = $percent;
return $this;
}
/**
* Returns $old_image.
*/
public function getOldImage(): Imagick
{
return $this->old_image;
}
/**
* Sets $old_image.
*/
public function setOldImage(Imagick $old_image): static
{
$this->old_image = $old_image;
return $this;
}
/**
* Returns $working_image.
*/
public function getWorkingImage(): Imagick
{
return $this->working_image;
}
/**
* Sets $working_image.
*/
public function setWorkingImage(Imagick $working_image): static
{
$this->working_image = $working_image;
return $this;
}
#################################
# ----- UTILITY FUNCTIONS ----- #
#################################
/**
* Calculates a new width and height for the image based on $this->max_width and the provided dimensions
*/
protected function calcWidth(int $width, int $height): array
{
$new_width_percentage = (100 * $this->max_width) / $width;
$new_height = ($height * $new_width_percentage) / 100;
return [
'new_width' => $this->max_width,
'new_height' => intval($new_height)
];
}
/**
* Calculates a new width and height for the image based on $this->max_height and the provided dimensions
*/
protected function calcHeight(int $width, int $height): array
{
$new_height_percentage = (100 * $this->max_height) / $height;
$new_width = ($width * $new_height_percentage) / 100;
return [
'new_width' => ceil($new_width),
'new_height' => ceil($this->max_height)
];
}
/**
* Calculates a new width and height for the image based on $this->percent and the provided dimensions
*/
protected function calcPercent(int $width, int $height): array
{
$new_width = ($width * $this->percent) / 100;
$new_height = ($height * $this->percent) / 100;
return [
'new_width' => ceil($new_width),
'new_height' => ceil($new_height)
];
}
/**
* Calculates the new image dimensions
*/
protected function calcImageSize(int $width, int $height): void
{
$new_size = [
'new_width' => $width,
'new_height' => $height
];
if ($this->max_width > 0)
{
$new_size = $this->calcWidth($width, $height);
if ($this->max_height > 0 && $new_size['new_height'] > $this->max_height)
{
$new_size = $this->calcHeight($new_size['new_width'], $new_size['new_height']);
}
}
if ($this->max_height > 0)
{
$new_size = $this->calcHeight($width, $height);
if ($this->max_width > 0 && $new_size['new_width'] > $this->max_width)
{
$new_size = $this->calcWidth($new_size['new_width'], $new_size['new_height']);
}
}
$this->new_dimensions = $new_size;
}
/**
* Calculates new image dimensions, not allowing the width and height to be less than either the max width or height
*/
protected function calcImageSizeStrict(int $width, int $height): void
{
$new_dimensions = $this->getCurrentDimensions();
if ($this->max_width >= $this->max_height)
{
if ($width > $height)
{
$new_dimensions = $this->calcHeight($width, $height);
if ($new_dimensions['new_width'] < $this->max_width)
{
$new_dimensions = $this->calcWidth($width, $height);
}
}
else if ($height >= $width)
{
$new_dimensions = $this->calcWidth($width, $height);
if ($new_dimensions['new_height'] < $this->max_height)
{
$new_dimensions = $this->calcHeight($width, $height);
}
}
}
else if ($this->max_height > $this->max_width)
{
if ($width >= $height)
{
$new_dimensions = $this->calcWidth($width, $height);
if ($new_dimensions['new_height'] < $this->max_height)
{
$new_dimensions = $this->calcHeight($width, $height);
}
}
else if ($height > $width)
{
$new_dimensions = $this->calcHeight($width, $height);
if ($new_dimensions['new_width'] < $this->max_width)
{
$new_dimensions = $this->calcWidth($width, $height);
}
}
}
$this->new_dimensions = $new_dimensions;
}
/**
* Calculates new dimensions based on $this->percent and the provided dimensions
*/
protected function calcImageSizePercent(int $width, int $height): void
{
if ($this->percent > 0)
{
$this->new_dimensions = $this->calcPercent($width, $height);
}
}
/**
* Determines the file format by mime-type
*/
protected function determineFormat(): void
{
if ($this->remote_image)
{
$format_info = getimagesize($this->file_name);
if ($format_info === false)
{
throw new Exception('Could not determine format of remote image: ' . $this->file_name);
}
$mime_type = $format_info['mime'] ?? null;
}
else
{
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($finfo, $this->file_name);
finfo_close($finfo);
}
$this->format = match ($mime_type) {
'image/avif' => 'AVIF',
'image/bmp' => 'BMP',
'image/gif' => 'GIF',
'image/heic' => 'HEIC',
'image/jpeg' => 'JPEG',
'image/png' => 'PNG',
'image/tiff' => 'TIFF',
'image/webp' => 'WEBP',
default => throw new Exception('Image format not supported: ' . $mime_type),
};
}
/**
* Makes sure the correct ImageMagick format is supported
*/
protected function verifyFormatCompatibility(): void
{
$imagick = new Imagick();
$formats = $imagick->queryFormats();
$format_to_check = strtoupper($this->format);
if (!in_array($format_to_check, $formats))
{
throw new Exception('Your ImageMagick installation does not support ' . $this->format . ' image types');
}
$imagick->destroy();
}
}
Usage Example
PHP
<?php
require_once 'Imagick.php';
use PHPThumb\Imagick;
// Create an instance with ImageMagick
$thumb = new Imagick('path/to/image.jpg');
// Chain methods like with GD
$thumb->resize(800, 600)
->show();
// Save to file
$thumb->save('path/to/output.png', 'PNG');
// Get image as string
$imageData = $thumb->getImageAsString();
Key Differences from GD Implementation
| Feature | GD | Imagick |
|---|---|---|
| Image Loading | imagecreatefrom*() |
new Imagick() + readImage() |
| Image Output | image*() functions |
getImagesBlob() |
| Resize | imagecopyresampled() |
thumbnailImage() |
| Crop | imagecopyresampled() |
cropImage() |
| Rotate | imagerotate() |
rotateImage() |
| Memory Management | Manual | Automatic + destroy() in destructor |
| Format Support | Limited by GD build | Extensive (60+ formats) |
| Remote Images | Via file_get_contents() |
Native readImage() with URL |
The Imagick class provides the same API as the GD class, making it easy to switch between the two implementations based on your needs.
ImageMagick Tests for PHP Thumb Library
Test Resources
You may need to add these test resource files to your
./resources/directory:test.avif- AVIF format test imagetest.bmp- BMP format test imagetest.heic- HEIC format test imagetest.tiff- TIFF format test imageRunning the Tests
Test Categories
ImagickTest.phpImagickLoadTest.phpImagickOperationsTest.phpImagickOutputTest.phpImagickAdvancedTest.phpImagickPluginTest.phpImagickRemoteImageTest.phpPlugin Analysis and Fixes for PHPThumb Library
Issues Found
Reflection.php
imageFlipVertical()using nested loopsTrim.php
rgb2int()method has confusing parameter documentationsetWorkingImage()properlyWatermark.php
GDinstance in constructor, making it GD-specificimagecreatetruecolor,imagecopy,imagecopymergeusedFixed Plugins
Plugin Tests
Summary of Changes
imageflip()fallback with efficient row copyingusestatementshexdec(sprintf())to bitwise operationsGD|Imagicktype hintUsage Examples
Example Analysis and Fixes for PHPThumb Library
Issues Found
../tests/resources/should be../../resources/)image_filter.phpIMG_FILTER_COLORIZEremote_image_resize.phpcrop_basic_fromstring.phprotate_basic.phprotate()methodsave_differentformat.phpFixed Examples
Summary of Fixes
../tests/resources/to../../resources/require_once DIR . '/../vendor/autoload.php'imageFilter()argument ordermkdir()for output directoriesoutput/directory before savingFile Structure
GD and ImageMagick
GD is a lightweight, procedural library pre-installed on most PHP environments. It is faster for simple operations like resizing, cropping, and generating thumbnails, with lower memory overhead. However, it supports a limited range of formats (JPEG, PNG, GIF, WebP, AVIF) and offers basic quality for complex scaling, often requiring manual aspect ratio calculations.
ImageMagick is an external binary extension that provides an object-oriented API and supports over 100 image formats, including TIFF, PDF, and SVG. It delivers superior image quality with advanced anti-aliasing and filters (e.g., Lanczos), making it ideal for complex overlays, artistic effects, and high-resolution photography. While it has a slower startup time and can be more resource-intensive, it handles large files better by offloading to disk and simplifies code through its intuitive class structure.
Recommendation: Use GD for simple, high-speed tasks like user avatars or basic thumbnails where dependency minimization is key. Use ImageMagick for professional-grade image processing, format conversion, or when advanced filters and extensive format support are required.