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);
%%