This is a comment on ImageMagick Support for PHP Thumb Library, posted by WikiAdmin at 06/11/2026 00:19
View source for Plugin Analysis and Fixes for PHPThumb Library
Let me analyze each plugin to identify compatibility issues and fix them.
=== Issues Found ===
==== Reflection.php ====
1. Constructor parameters lack type hints
2. Inefficient ##imageFlipVertical()## using nested loops
3. Doesn't properly handle the plugin interface execution flow
4. Missing PHPDoc for parameters
==== Trim.php ====
1. ##rgb2int()## method has confusing parameter documentation
2. Doesn't use ##setWorkingImage()## properly
3. Missing proper array type hints
==== Watermark.php ====
1. **Critical**: Only works with GD, doesn't support Imagick
2. Takes ##GD## instance in constructor, making it GD-specific
3. GD-specific ##imagecreatetruecolor##, ##imagecopy##, ##imagecopymerge## used
=== Fixed Plugins ===
%%(hl php)
<?php
namespace PHPThumb\Plugins;
use InvalidArgumentException;
use PHPThumb\PHPThumb;
/**
* GD Reflection Lib Plugin Definition File
*
* This file contains the plugin definition for the GD Reflection Lib for PHP Thumb
*
* PhpThumb : PHP Thumb Library <https://github.com/PHPThumb/PHPThumb>
* Copyright (c) 2009, Ian Selby
*
* Licensed under the MIT License
*
* @author Ian Selby <ianrselby@gmail.com>
* @copyright Copyright (c) 2009 Ian Selby
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @version 3.0
* @package PhpThumb
* @subpackage Plugins
*/
class Reflection implements PluginInterface
{
/**
* @var int Reflection percentage (0-100)
*/
protected int $percent;
/**
* @var int Reflection height percentage (0-100)
*/
protected int $reflection;
/**
* @var int White transparency for reflection gradient (0-100)
*/
protected int $white;
/**
* @var bool Whether to add a border
*/
protected bool $border;
/**
* @var string Border color in hex format
*/
protected string $border_color;
/**
* @param int $percent How much of the original image to include in reflection (0-100)
* @param int $reflection Height of the reflection as a percentage of the original (0-100)
* @param int $white White transparency for the gradient (0-100)
* @param bool $border Whether to add a border
* @param string $border_color Hex color for the border (e.g., '#FFFFFF')
*/
public function __construct(
int $percent = 50,
int $reflection = 50,
int $white = 80,
bool $border = false,
string $border_color = '#FFFFFF'
) {
$this->percent = $percent;
$this->reflection = $reflection;
$this->white = $white;
$this->border = $border;
$this->border_color = $border_color;
}
/**
* Executes the reflection effect on the image
*/
public function execute(PHPThumb $phpthumb): PHPThumb
{
$current_dimensions = $phpthumb->getCurrentDimensions();
$options = $phpthumb->getOptions();
$width = $current_dimensions['width'];
$height = $current_dimensions['height'];
$reflection_height = intval($height * ($this->reflection / 100));
$new_height = $height + $reflection_height;
$reflected_part = $height * ($this->percent / 100);
// Create the reflection image
$working_image = imagecreatetruecolor($width, $new_height);
if ($working_image === false) {
throw new RuntimeException('Failed to create reflection image');
}
imagealphablending($working_image, true);
$color_to_paint = imagecolorallocatealpha(
$working_image,
255,
255,
255,
0
);
if ($color_to_paint === false) {
imagedestroy($working_image);
throw new RuntimeException('Failed to allocate color for reflection');
}
imagefilledrectangle(
$working_image,
0,
0,
$width,
$new_height,
$color_to_paint
);
// Get the current image
$current_image = $phpthumb->getOldImage();
// Copy the portion to be reflected
imagecopyresampled(
$working_image,
$current_image,
0,
0,
0,
intval($reflected_part),
$width,
$reflection_height,
$width,
intval($height - $reflected_part)
);
// Flip the reflection vertically
$this->imageFlipVertical($working_image);
// Copy the original image on top
imagecopy(
$working_image,
$current_image,
0,
0,
0,
0,
$width,
$height
);
imagealphablending($working_image, true);
// Apply gradient fade to reflection
for ($i = 0; $i < $reflection_height; $i++) {
$alpha = ($i / $reflection_height) * $this->white;
$alpha = intval($alpha);
$color_to_paint = imagecolorallocatealpha(
$working_image,
255,
255,
255,
$alpha
);
imagefilledrectangle(
$working_image,
0,
$height + $i,
$width,
$height + $i,
$color_to_paint
);
}
// Add border if requested
if ($this->border) {
$rgb = $this->hex2rgb($this->border_color, false);
$border_color = imagecolorallocate(
$working_image,
$rgb[0],
$rgb[1],
$rgb[2]
);
// Top border
imageline($working_image, 0, 0, $width, 0, $border_color);
// Bottom border
imageline($working_image, 0, $height, $width, $height, $border_color);
// Left border
imageline($working_image, 0, 0, 0, $height, $border_color);
// Right border
imageline($working_image, $width - 1, 0, $width - 1, $height, $border_color);
}
// Preserve alpha for PNG images
if ($phpthumb->getFormat() === 'PNG') {
$color_transparent = imagecolorallocatealpha(
$working_image,
$options['alphaMaskColor'][0],
$options['alphaMaskColor'][1],
$options['alphaMaskColor'][2],
0
);
imagefill($working_image, 0, 0, $color_transparent);
imagesavealpha($working_image, true);
}
// Update the PHPThumb instance
$phpthumb->setOldImage($working_image);
$phpthumb->setCurrentDimensions([
'width' => $width,
'height' => $new_height,
]);
return $phpthumb;
}
/**
* Flips the image vertically using imageflip (efficient GD function)
*/
protected function imageFlipVertical($image): void
{
if (function_exists('imageflip')) {
imageflip($image, IMG_FLIP_VERTICAL);
} else {
// Fallback for older GD versions using efficient row copying
$x_i = imagesx($image);
$y_i = imagesy($image);
// Create temp image for flipping
$tmp = imagecreatetruecolor($x_i, $y_i);
if ($tmp !== false) {
for ($y = 0; $y < $y_i; $y++) {
imagecopy($tmp, $image, 0, $y, 0, $y_i - $y - 1, $x_i, 1);
}
// Copy back
for ($y = 0; $y < $y_i; $y++) {
imagecopy($image, $tmp, 0, $y_i - $y - 1, 0, $y, $x_i, 1);
}
imagedestroy($tmp);
}
}
}
/**
* Converts a hex color to RGB array or string
*
* @param string $hex Color in hex format (#FFFFFF or FFFFFF)
* @param bool $as_string Return as "R G B" string instead of array
* @return array|string RGB values
*/
protected function hex2rgb(string $hex, bool $as_string = false): array|string
{
// Strip leading #
$hex = ltrim($hex, '#');
// Handle &H prefix (VB-style)
if (str_starts_with($hex, '&H')) {
$hex = substr($hex, 2);
}
// Ensure we have 6 characters
if (strlen($hex) === 3) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
$rgb = [
hexdec(substr($hex, 0, 2)),
hexdec(substr($hex, 2, 2)),
hexdec(substr($hex, 4, 2)),
];
return $as_string ? implode(' ', $rgb) : $rgb;
}
}
%%
%%(hl php)
<?php
namespace PHPThumb\Plugins;
use InvalidArgumentException;
use RuntimeException;
/**
* GD Trim Lib Plugin Definition File
*
* This file contains the plugin definition for the GD Trim Lib for PHP Thumb
*
* PhpThumb : PHP Thumb Library <https://github.com/PHPThumb/PHPThumb>
* Copyright (c) 2016, Oleg Sherbakov
*
* Licensed under the MIT License
*
* @author Oleg Sherbakov <holdmann@yandex.ru>
* @copyright Copyright (c) 2016
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @version 1.0
* @package PhpThumb
* @subpackage Plugins
*/
class Trim implements PluginInterface
{
/**
* @var array<int, int> RGB color values [R, G, B]
*/
protected array $color;
/**
* @var array<string> Sides to trim (T, B, L, R)
*/
protected array $sides;
/**
* Trim constructor
*
* @param array<int, int> $color RGB color to trim as array [R, G, B] (0-255 each)
* @param string $sides Sides to trim: 'T' (top), 'B' (bottom), 'L' (left), 'R' (right)
* @throws InvalidArgumentException If color or sides are invalid
*/
public function __construct(array $color = [255, 255, 255], string $sides = 'TBLR')
{
if (!$this->validateColor($color)) {
throw new InvalidArgumentException(
'Color must be an array of RGB color model parts [R, G, B] where each value is 0-255'
);
}
if (!$this->validateSides($sides)) {
throw new InvalidArgumentException(
'Sides must be a string containing any combination of T, B, L, and R'
);
}
$this->color = $color;
$this->sides = str_split($sides);
}
/**
* Validates whether RGB color array is valid
*
* @param array<int, int|float> $colors Color array to validate
* @return bool True if valid, false otherwise
*/
protected function validateColor(array $colors): bool
{
if (count($colors) !== 3) {
return false;
}
foreach ($colors as $color) {
if (!is_numeric($color) || $color < 0 || $color > 255) {
return false;
}
}
return true;
}
/**
* Validates whether sides string is valid
*
* @param string $sides_string Sides string to validate
* @return bool True if valid, false otherwise
*/
protected function validateSides(string $sides_string): bool
{
$sides = str_split($sides_string);
if (count($sides) === 0 || count($sides) > 4) {
return false;
}
$valid_sides = ['T', 'B', 'L', 'R'];
foreach ($sides as $side) {
if (!in_array($side, $valid_sides, true)) {
return false;
}
}
return true;
}
/**
* Converts RGB array to 24-bit integer color value
*
* @param array<int, int|float> $rgb RGB array [R, G, B]
* @return int 24-bit color value
*/
protected function rgbToInt(array $rgb): int
{
return ((int)$rgb[0] << 16) | ((int)$rgb[1] << 8) | (int)$rgb[2];
}
/**
* Executes the trim operation
*/
public function execute(PHPThumb $phpthumb): PHPThumb
{
$current_image = $phpthumb->getOldImage();
$current_dimensions = $phpthumb->getCurrentDimensions();
$border_top = 0;
$border_bottom = 0;
$border_left = 0;
$border_right = 0;
$target_color = $this->rgbToInt($this->color);
$width = $current_dimensions['width'];
$height = $current_dimensions['height'];
// Detect top border
if (in_array('T', $this->sides, true)) {
for (; $border_top < $height; $border_top++) {
for ($x = 0; $x < $width; $x++) {
$pixel_color = imagecolorat($current_image, $x, $border_top);
// Handle alpha transparency for comparison
$alpha = ($pixel_color >> 24) & 0x7F;
if ($alpha > 0 && $this->color === [255, 255, 255]) {
continue;
}
if (($pixel_color & 0xFFFFFF) !== $target_color) {
break;
}
}
// Only break if we found a non-matching pixel
if ($x < $width) {
break;
}
}
}
// Detect bottom border
if (in_array('B', $this->sides, true)) {
for (; $border_bottom < $height; $border_bottom++) {
$y = $height - $border_bottom - 1;
for ($x = 0; $x < $width; $x++) {
$pixel_color = imagecolorat($current_image, $x, $y);
$alpha = ($pixel_color >> 24) & 0x7F;
if ($alpha > 0 && $this->color === [255, 255, 255]) {
continue;
}
if (($pixel_color & 0xFFFFFF) !== $target_color) {
break;
}
}
if ($x < $width) {
break;
}
}
}
// Detect left border
if (in_array('L', $this->sides, true)) {
for (; $border_left < $width; $border_left++) {
for ($y = 0; $y < $height; $y++) {
$pixel_color = imagecolorat($current_image, $border_left, $y);
$alpha = ($pixel_color >> 24) & 0x7F;
if ($alpha > 0 && $this->color === [255, 255, 255]) {
continue;
}
if (($pixel_color & 0xFFFFFF) !== $target_color) {
break;
}
}
if ($y < $height) {
break;
}
}
}
// Detect right border
if (in_array('R', $this->sides, true)) {
for (; $border_right < $width; $border_right++) {
$x = $width - $border_right - 1;
for ($y = 0; $y < $height; $y++) {
$pixel_color = imagecolorat($current_image, $x, $y);
$alpha = ($pixel_color >> 24) & 0x7F;
if ($alpha > 0 && $this->color === [255, 255, 255]) {
continue;
}
if (($pixel_color & 0xFFFFFF) !== $target_color) {
break;
}
}
if ($y < $height) {
break;
}
}
}
// Calculate new dimensions
$new_width = $width - $border_left - $border_right;
$new_height = $height - $border_top - $border_bottom;
// Ensure we have something to show
if ($new_width <= 0 || $new_height <= 0) {
throw new RuntimeException('Trim operation would result in empty image');
}
// Create new trimmed image
$new_image = imagecreatetruecolor($new_width, $new_height);
if ($new_image === false) {
throw new RuntimeException('Failed to create trimmed image');
}
// Preserve transparency
imagealphablending($new_image, false);
imagesavealpha($new_image, true);
// Copy the trimmed portion
imagecopy(
$new_image,
$current_image,
0,
0,
$border_left,
$border_top,
$new_width,
$new_height
);
// Update PHPThumb
$phpthumb->setOldImage($new_image);
$phpthumb->setCurrentDimensions([
'width' => $new_width,
'height' => $new_height,
]);
return $phpthumb;
}
}
%%
%%(hl php)
<?php
namespace PHPThumb\Plugins;
use InvalidArgumentException;
use PHPThumb\GD;
use PHPThumb\Imagick;
use PHPThumb\PHPThumb;
/**
* Watermark Lib Plugin Definition File
*
* This file contains the plugin definition for the Watermark Lib for PHP Thumb
*
* PhpThumb : PHP Thumb Library <https://github.com/PHPThumb/PHPThumb>
* Copyright (c) 2016, Oleg Sherbakov
*
* Licensed under the MIT License
*
* @author Oleg Sherbakov <holdmann@yandex.ru>
* @copyright Copyright (c) 2016
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
* @version 1.0
* @package PhpThumb
* @subpackage Plugins
*/
class Watermark implements PluginInterface
{
/**
* @var GD|Imagick The watermark image instance
*/
protected $wm;
/**
* @var string Position for the watermark
*/
protected string $position;
/**
* @var int Opacity of the watermark (0-100)
*/
protected int $opacity;
/**
* @var int X-axis offset
*/
protected int $offset_x;
/**
* @var int Y-axis offset
*/
protected int $offset_y;
/**
* Watermark constructor
*
* @param GD|Imagick $wm Watermark image as \PHPThumb\GD or \PHPThumb\Imagick instance
* @param string $position Position: combinations of left/west/right/east for X
* and top/north/upper/bottom/south/lower for Y
* @param int $opacity Opacity of the watermark in percent (0 = transparent, 100 = opaque)
* @param int $offset_x Horizontal offset (can be negative)
* @param int $offset_y Vertical offset (can be negative)
* @throws InvalidArgumentException If watermark is not GD or Imagick instance
*/
public function __construct(
GD|Imagick $wm,
string $position = 'center',
int $opacity = 100,
int $offset_x = 0,
int $offset_y = 0
) {
if (!$wm instanceof GD && !$wm instanceof Imagick) {
throw new InvalidArgumentException(
'Watermark must be an instance of \PHPThumb\GD or \PHPThumb\Imagick'
);
}
$this->wm = $wm;
$this->position = $position;
$this->opacity = max(0, min(100, $opacity));
$this->offset_x = $offset_x;
$this->offset_y = $offset_y;
}
/**
* Executes the watermark operation
*/
public function execute(PHPThumb $phpthumb): PHPThumb
{
if ($phpthumb instanceof GD) {
return $this->executeGD($phpthumb);
}
if ($phpthumb instanceof Imagick) {
return $this->executeImagick($phpthumb);
}
throw new InvalidArgumentException('Unsupported PHPThumb instance type');
}
/**
* Execute watermark for GD-based PHPThumb
*/
protected function executeGD(GD $phpthumb): PHPThumb
{
$current_dimensions = $phpthumb->getCurrentDimensions();
$watermark_dimensions = $this->wm->getCurrentDimensions();
[$watermark_position_x, $watermark_position_y] = $this->calculatePosition(
$current_dimensions,
$watermark_dimensions
);
$working_image = $phpthumb->getWorkingImage();
$watermark_image = $this->wm->getWorkingImage();
if ($watermark_image === false || $watermark_image === null) {
$watermark_image = $this->wm->getOldImage();
}
if ($this->opacity < 100) {
$this->imageCopyMergeAlpha(
$working_image,
$watermark_image,
$watermark_position_x,
$watermark_position_y,
0,
0,
$watermark_dimensions['width'],
$watermark_dimensions['height'],
$this->opacity
);
} else {
imagecopy(
$working_image,
$watermark_image,
$watermark_position_x,
$watermark_position_y,
0,
0,
$watermark_dimensions['width'],
$watermark_dimensions['height']
);
}
$phpthumb->setWorkingImage($working_image);
return $phpthumb;
}
/**
* Execute watermark for Imagick-based PHPThumb
*/
protected function executeImagick(Imagick $phpthumb): PHPThumb
{
$current_dimensions = $phpthumb->getCurrentDimensions();
$watermark_dimensions = $this->wm->getCurrentDimensions();
[$watermark_position_x, $watermark_position_y] = $this->calculatePosition(
$current_dimensions,
$watermark_dimensions
);
$working_image = $phpthumb->getWorkingImage();
$watermark = $this->wm->getOldImage();
// Set opacity for the watermark
if ($this->opacity < 100) {
$watermark->setImageOpacity($this->opacity / 100);
}
// Composite the watermark onto the working image
$working_image->compositeImage(
$watermark,
\Imagick::COMPOSITE_DEFAULT,
$watermark_position_x,
$watermark_position_y
);
$phpthumb->setWorkingImage($working_image);
return $phpthumb;
}
/**
* Calculate watermark position based on current dimensions and position string
*
* @param array<string, int> $current_dimensions Current image dimensions
* @param array<string, int> $watermark_dimensions Watermark dimensions
* @return array<int> [x, y] position coordinates
*/
protected function calculatePosition(array $current_dimensions, array $watermark_dimensions): array
{
$watermark_position_x = $this->offset_x;
$watermark_position_y = $this->offset_y;
// Horizontal position
if (preg_match('/\b(right|east)\b/i', $this->position)) {
$watermark_position_x += $current_dimensions['width'] - $watermark_dimensions['width'];
} elseif (!preg_match('/\b(left|west)\b/i', $this->position)) {
$watermark_position_x += intval(
($current_dimensions['width'] - $watermark_dimensions['width']) / 2
);
}
// Vertical position
if (preg_match('/\b(bottom|lower|south)\b/i', $this->position)) {
$watermark_position_y += $current_dimensions['height'] - $watermark_dimensions['height'];
} elseif (!preg_match('/\b(upper|top|north)\b/i', $this->position)) {
$watermark_position_y += intval(
($current_dimensions['height'] - $watermark_dimensions['height']) / 2
);
}
return [$watermark_position_x, $watermark_position_y];
}
/**
* Copy image with alpha blending (for GD)
*
* Based on: http://www.php.net/manual/en/function.imagecopymerge.php#92787
*/
protected function imageCopyMergeAlpha(
$dst_im,
$src_im,
int $dst_x,
int $dst_y,
int $src_x,
int $src_y,
int $src_w,
int $src_h,
int $pct
): void {
$cut = imagecreatetruecolor($src_w, $src_h);
if ($cut === false) {
return;
}
imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h);
imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h);
imagecopymerge($dst_im, $cut, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct);
imagedestroy($cut);
}
}
%%
=== Plugin Tests ===
%%(hl php)
<?php
namespace PHPThumb\Tests;
use PHPThumb\GD;
use PHPThumb\Plugins\Reflection;
use PHPUnit\Framework\TestCase;
class ReflectionPluginTest extends TestCase
{
protected GD $thumb;
protected function setUp(): void
{
$this->thumb = new GD(__DIR__ . '/../../resources/test.png');
}
public function testExecuteReturnsPHPThumb()
{
$plugin = new Reflection();
$result = $plugin->execute($this->thumb);
self::assertInstanceOf(GD::class, $result);
}
public function testReflectionIncreasesHeight()
{
$original_height = $this->thumb->getCurrentDimensions()['height'];
$plugin = new Reflection(50, 50);
$plugin->execute($this->thumb);
$new_height = $this->thumb->getCurrentDimensions()['height'];
self::assertGreaterThan($original_height, $new_height);
}
public function testCustomParameters()
{
$plugin = new Reflection(50, 75, 50, true, '#FF0000');
$result = $plugin->execute($this->thumb);
self::assertInstanceOf(GD::class, $result);
self::assertGreaterThan(
$this->thumb->getCurrentDimensions()['height'],
$this->thumb->getCurrentDimensions()['height']
);
}
public function testReflectionWithSmallPercent()
{
$original_height = $this->thumb->getCurrentDimensions()['height'];
$plugin = new Reflection(25, 50);
$plugin->execute($this->thumb);
self::assertGreaterThan($original_height, $this->thumb->getCurrentDimensions()['height']);
}
public function testReflectionWithHighReflection()
{
$original_height = $this->thumb->getCurrentDimensions()['height'];
$plugin = new Reflection(50, 100);
$plugin->execute($this->thumb);
$new_height = $this->thumb->getCurrentDimensions()['height'];
self::assertGreaterThan($original_height * 1.5, $new_height);
}
}
%%
%%(hl php)
<?php
namespace PHPThumb\Tests;
use InvalidArgumentException;
use PHPThumb\GD;
use PHPThumb\Plugins\Trim;
use PHPUnit\Framework\TestCase;
class TrimPluginTest extends TestCase
{
protected GD $thumb;
protected function setUp(): void
{
$this->thumb = new GD(__DIR__ . '/../../resources/test.png');
}
public function testExecuteReturnsPHPThumb()
{
$plugin = new Trim();
$result = $plugin->execute($this->thumb);
self::assertInstanceOf(GD::class, $result);
}
public function testTrimReducesDimensions()
{
$original_dimensions = $this->thumb->getCurrentDimensions();
$plugin = new Trim([255, 255, 255], 'T');
$plugin->execute($this->thumb);
$new_dimensions = $this->thumb->getCurrentDimensions();
// Only top was trimmed, so height should be reduced
self::assertLessThan($original_dimensions['height'], $new_dimensions['height']);
self::assertSame($original_dimensions['width'], $new_dimensions['width']);
}
public function testInvalidColorThrowsException()
{
$this->expectException(InvalidArgumentException::class);
new Trim([256, 255, 255]);
}
public function testInvalidColorTooFewElementsThrowsException()
{
$this->expectException(InvalidArgumentException::class);
new Trim([255, 255]);
}
public function testInvalidSidesThrowsException()
{
$this->expectException(InvalidArgumentException::class);
new Trim([255, 255, 255], 'XYZ');
}
public function testEmptySidesThrowsException()
{
$this->expectException(InvalidArgumentException::class);
new Trim([255, 255, 255], '');
}
public function testTrimTopOnly()
{
$original_height = $this->thumb->getCurrentDimensions()['height'];
$plugin = new Trim([255, 255, 255], 'T');
$plugin->execute($this->thumb);
self::assertLessThan($original_height, $this->thumb->getCurrentDimensions()['height']);
}
public function testTrimBottomOnly()
{
$original_height = $this->thumb->getCurrentDimensions()['height'];
$plugin = new Trim([255, 255, 255], 'B');
$plugin->execute($this->thumb);
self::assertLessThan($original_height, $this->thumb->getCurrentDimensions()['height']);
}
public function testTrimLeftOnly()
{
$original_width = $this->thumb->getCurrentDimensions()['width'];
$plugin = new Trim([255, 255, 255], 'L');
$plugin->execute($this->thumb);
self::assertLessThan($original_width, $this->thumb->getCurrentDimensions()['width']);
}
public function testTrimRightOnly()
{
$original_width = $this->thumb->getCurrentDimensions()['width'];
$plugin = new Trim([255, 255, 255], 'R');
$plugin->execute($this->thumb);
self::assertLessThan($original_width, $this->thumb->getCurrentDimensions()['width']);
}
public function testTrimAllSides()
{
$original_dimensions = $this->thumb->getCurrentDimensions();
$plugin = new Trim([255, 255, 255], 'TBLR');
$plugin->execute($this->thumb);
$new_dimensions = $this->thumb->getCurrentDimensions();
self::assertLessThan($original_dimensions['width'], $new_dimensions['width']);
self::assertLessThan($original_dimensions['height'], $new_dimensions['height']);
}
public function testTrimWithBlackColor()
{
$plugin = new Trim([0, 0, 0], 'TBLR');
$result = $plugin->execute($this->thumb);
self::assertInstanceOf(GD::class, $result);
}
}
%%
%%(hl php)
<?php
namespace PHPThumb\Tests;
use InvalidArgumentException;
use PHPThumb\GD;
use PHPThumb\Imagick;
use PHPThumb\Plugins\Watermark;
use PHPUnit\Framework\TestCase;
class WatermarkPluginTest extends TestCase
{
protected GD $gdThumb;
protected Imagick $imagickThumb;
protected GD $gdWatermark;
protected Imagick $imagickWatermark;
protected function setUp(): void
{
$this->gdThumb = new GD(__DIR__ . '/../../resources/test.png');
$this->imagickThumb = new Imagick(__DIR__ . '/../../resources/test.png');
$this->gdWatermark = new GD(__DIR__ . '/../../resources/test.gif');
$this->imagickWatermark = new Imagick(__DIR__ . '/../../resources/test.gif');
}
public function testExecuteWithGDReturnsPHPThumb()
{
$plugin = new Watermark($this->gdWatermark);
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testExecuteWithImagickReturnsPHPThumb()
{
$plugin = new Watermark($this->imagickWatermark);
$result = $plugin->execute($this->imagickThumb);
self::assertInstanceOf(Imagick::class, $result);
}
public function testInvalidWatermarkTypeThrowsException()
{
$this->expectException(InvalidArgumentException::class);
new Watermark('not a thumbnail object');
}
public function testWatermarkCenterPosition()
{
$plugin = new Watermark($this->gdWatermark, 'center');
$plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $plugin->execute($this->gdThumb));
}
public function testWatermarkLeftPosition()
{
$plugin = new Watermark($this->gdWatermark, 'left');
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkRightPosition()
{
$plugin = new Watermark($this->gdWatermark, 'right');
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkTopPosition()
{
$plugin = new Watermark($this->gdWatermark, 'top');
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkBottomPosition()
{
$plugin = new Watermark($this->gdWatermark, 'bottom');
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkWithOffset()
{
$plugin = new Watermark($this->gdWatermark, 'center', 100, 10, 20);
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkWithOpacity()
{
$plugin = new Watermark($this->gdWatermark, 'center', 50);
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkOpacityCappedAt100()
{
$plugin = new Watermark($this->gdWatermark, 'center', 150);
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkOpacityCappedAt0()
{
$plugin = new Watermark($this->gdWatermark, 'center', -10);
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkCombinedPositionLeftTop()
{
$plugin = new Watermark($this->gdWatermark, 'left-top');
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkCombinedPositionRightBottom()
{
$plugin = new Watermark($this->gdWatermark, 'right-bottom');
$result = $plugin->execute($this->gdThumb);
self::assertInstanceOf(GD::class, $result);
}
public function testWatermarkPreservesImageDimensions()
{
$original_dimensions = $this->gdThumb->getCurrentDimensions();
$plugin = new Watermark($this->gdWatermark);
$plugin->execute($this->gdThumb);
$new_dimensions = $this->gdThumb->getCurrentDimensions();
self::assertSame($original_dimensions['width'], $new_dimensions['width']);
self::assertSame($original_dimensions['height'], $new_dimensions['height']);
}
public function testImagickWatermarkPreservesImageDimensions()
{
$original_dimensions = $this->imagickThumb->getCurrentDimensions();
$plugin = new Watermark($this->imagickWatermark);
$plugin->execute($this->imagickThumb);
$new_dimensions = $this->imagickThumb->getCurrentDimensions();
self::assertSame($original_dimensions['width'], $new_dimensions['width']);
self::assertSame($original_dimensions['height'], $new_dimensions['height']);
}
}
%%
===Summary of Changes===
#|
*| Plugin | Issue | Fix |*
|| Reflection | Missing type hints | Added proper typed parameters with PHPDoc ||
|| Reflection | Inefficient flip | Added ##imageflip()## fallback with efficient row copying ||
|| Reflection | Missing imports | Added proper ##use## statements ||
|| Trim | Confusing docblock | Improved parameter documentation ||
|| Trim | Inefficient color conversion | Changed ##hexdec(sprintf())## to bitwise operations ||
|| Trim | Alpha handling | Added alpha channel handling in border detection ||
|| Watermark | GD-only support | Added Imagick support with instanceof checks ||
|| Watermark | Missing type union | Added ##GD""|""Imagick## type hint ||
|| All | Missing RuntimeException | Added proper exception imports ||
|#
=== Usage Examples ===
%%(hl php)
<?php
// Using Reflection Plugin with GD
$thumb = new \PHPThumb\GD('image.jpg');
$reflection = new \PHPThumb\Plugins\Reflection(
percent: 50, // 50% of original image
reflection: 50, // 50% reflection height
white: 80, // 80% white gradient
border: true, // Add border
borderColor: '#FFF' // White border
);
$reflection->execute($thumb);
$thumb->show();
// Using Trim Plugin
$thumb = new \PHPThumb\GD('image.jpg');
$trim = new \PHPThumb\Plugins\Trim([255, 255, 255], 'TBLR'); // Trim white borders
$trim->execute($thumb);
// Using Watermark Plugin with GD
$thumb = new \PHPThumb\GD('image.jpg');
$watermark = new \PHPThumb\GD('watermark.png');
$wm = new \PHPThumb\Plugins\Watermark($watermark, 'right-bottom', 70);
$wm->execute($thumb);
// Using Watermark Plugin with Imagick
$thumb = new \PHPThumb\Imagick('image.jpg');
$watermark = new \PHPThumb\Imagick('watermark.png');
$wm = new \PHPThumb\Plugins\Watermark($watermark, 'center', 100);
$wm->execute($thumb);
%%