jackal / image-merge
A simple PHP library to manipulate images: resize, crop, rotate, filters, text and merging (JPEG, PNG, GIF, WebP)
Requires
- php: ^8.2
- ext-exif: *
- ext-gd: *
- symfony/http-foundation: ^6.4 || ^7.0
- symfony/process: ^6.4 || ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpunit/phpunit: ^11.5.50
- rector/rector: ^2.0
Suggests
- imagemagick: The Distortion effect needs the ImageMagick binary (magick or convert) on the PATH
Provides
None
Conflicts
None
Replaces
None
README
A simple PHP library to manipulate images. It supports JPEG, PNG, GIF and WebP.
Upgrading from 0.4.x? Version 1.0 has a new, safer API. See UPGRADE-1.0.md.
Requirements
- PHP >= 8.2 with the
gd(JPEG/PNG/WebP/FreeType support) andexifextensions - ImageMagick (
magickorconvert) only for theDistortioneffect
Getting Started
Install the library with composer
composer require jackal/image-merge
Usage
Loading an image
Pick the factory that matches your source:
use Jackal\ImageMerge\ImageMerge; $builder = ImageMerge::fromPath('/path/to/my/file.png'); // local file $builder = ImageMerge::fromContent($binaryString); // raw image bytes, e.g. an upload $builder = ImageMerge::fromUrl('https://example.com/a.jpg'); // remote http(s) file $builder = ImageMerge::fromStream($resource); // open stream $builder = ImageMerge::fromSplFileObject($splFileObject); $builder = ImageMerge::fromImage($image); // an existing Jackal\ImageMerge\Model\Image
Minimal example
$builder = ImageMerge::fromPath('/path/to/my/file.png') ->resize(620, 350) ->rotate(90);
Get the image content
echo $builder->getImage()->toPNG()->getContent();
Save the image to a path
$builder->getImage()->toPNG('/path/to/the/image.png');
Get a Response object (compatible with Symfony projects)
return $builder->getImage()->toPNG();
toJPG(), toGIF() and toWebP() work the same way.
Operations
resize
At least one parameter is required. If only one is passed, the aspect ratio of the image is kept
$builder->resize(620, null); // or $builder->resize(null, 200);
If both are passed, the image may be stretched
$builder->resize(400, 200);
thumbnail
Like resize, but if the aspect ratio doesn't match it crops the image (using cropCenter)
$builder->thumbnail(400, 400); $builder->thumbnail(400, null); // keeps the aspect ratio
rotate
Rotate the image (counterclockwise)
$builder->rotate(180);
*For angles that aren't multiples of 90, the empty areas are filled with transparent pixels.
flipHorizontal and flipVertical
$builder->flipHorizontal(); $builder->flipVertical();
grayScale
Add a grayscale filter to the image
$builder->grayScale();
brightness and contrast
$builder->brightness(10); $builder->contrast(-20);
blur
Add a blur effect to the image
$builder->blur(20);
pixelate
Add a "pixel" effect to the image
$builder->pixelate(20);
crop and cropCenter
Crop the image starting from the x and y coords, with the given output size.
The area must be inside the image, otherwise an InvalidArgumentException is thrown
$x = 10; $y = 15; $width = 50; $height = 50; $builder->crop($x, $y, $width, $height);
Crop at the center of the image, with the given output size
$builder->cropCenter(50, 50);
cropPolygon
Keep only the area inside the polygon (at least three x,y points), making the rest transparent
$builder->cropPolygon(10, 10, 200, 20, 100, 200);
border
Add a border to the image (drawn inside the image)
$builder->border(20, '3399ff');
merge
Draw another image on top, at the given position
$builder->merge(ImageMerge::fromPath('/path/to/logo.png')->getImage(), 10, 10);
Experimental features that will likely change in the future
addText
Add text to the image
use Jackal\ImageMerge\Model\Color; use Jackal\ImageMerge\Model\Font\Font; use Jackal\ImageMerge\Model\Text\Text; $text = new Text('this is the text', Font::liberationSans(), 12, new Color('ABCDEF')); $builder->addText($text, 10, 20);
addSquare
Add a color-filled square to the image
$builder->addSquare(10, 10, 20, 20, 'ABCDEF');
Security
The library is designed to be safe with untrusted input, within these limits:
- Remote files:
fromUrl()accepts onlyhttpandhttps, refuses private, loopback, link-local and reserved addresses, doesn't follow redirects, and enforces a timeout and a maximum size. Pass a configuredJackal\ImageMerge\Loader\UrlLoaderto change these settings, e.g.new UrlLoader(timeout: 5, maxBytes: 5_000_000). The host is checked before the request is made, so DNS rebinding is not prevented. If you load URLs supplied by users, also restrict outbound traffic at the network level. - Local files:
fromPath()refuses stream wrappers such asphar://orphp://. Never pass a path built from user input without validating it. - Resource limits: image dimensions and file size are checked from the header before
decoding, to stop "decompression bombs". Defaults are 50 megapixels, 50 MB and a blur level
of 100. You can change them globally:
use Jackal\ImageMerge\Limits; Limits::setDefault(new Limits(maxPixels: 20_000_000, maxFileSize: 10 * 1024 * 1024, maxBlurLevel: 50));
- Metadata: EXIF, IPTC and XMP values come from the file itself and are attacker-controlled. Escape them before displaying them, for example in HTML.
Development
composer install
composer check # everything CI checks: Rector, php-cs-fixer and PHPUnit
Or one tool at a time:
| Command | What it does |
|---|---|
composer test |
Run the PHPUnit suite |
composer cs / composer cs-fix |
Check / fix code style (php-cs-fixer, config in .php-cs-fixer.dist.php) |
composer rector / composer rector-fix |
Check / apply automated refactorings (Rector, config in rector.php) |
When you run the fixers, run Rector first and php-cs-fixer second, because Rector's output is then reformatted.
Tests that download a real image are in the network group. Skip them offline with vendor/bin/phpunit --exclude-group network.
The suite needs GD, EXIF and ImageMagick. If you don't have them locally, use the bundled Docker image (the PHP_VERSION build argument chooses the PHP version):
docker build --build-arg PHP_VERSION=8.3 -t imagemerge-test docker/
docker run --rm -v "$PWD":/app imagemerge-test composer install
docker run --rm -v "$PWD":/app imagemerge-test composer check
CI (GitHub Actions) runs the tests on PHP 8.2, 8.3, 8.4 and 8.5, plus PHP 8.2 with the lowest allowed dependencies. It also runs composer validate --strict, composer audit, and the Rector and php-cs-fixer checks.
Author
- Luca Giacalone (AKA JackalOne)
License
This project is licensed under the MIT License, see LICENSE.
The bundled font Liberation Sans (src/Resources/Fonts/LiberationSans-Regular.ttf)
is licensed under the SIL Open Font License 1.1, see src/Resources/Fonts/LICENSE-LiberationSans.txt.