Файловый менеджер - Редактировать - /home/pimjdymy/public_html/safrandsi/pixelandtonic.tar
Назад
imagine/LICENSE 0000644 00000002545 15141212321 0007160 0 ustar 00 Copyright (c) 2004-2012 Bulat Shakirzyanov Copyright (c) 2016 Pixel & Tonic, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This software embeds Adobe ICC Profiles, see license at http://www.adobe.com/support/downloads/iccprofiles/icc_eula_mac_dist.html . This software also embeds ICC Profile from colormanagement.org. Please find information about their license at http://colormanagement.org/ . imagine/CHANGELOG.md 0000644 00000034635 15141212321 0007771 0 ustar 00 # CHANGELOG ### 1.3.3 (2022-11-16) * Fix invalid format MIME type definition (#835, @xdanik) ### 1.3.2 (2022-04-01) * Workaround for a bug in PHP 7.3+opcache that causes segmentation faults (#826, #829, #828, @ausi, @mnocon, @mlocati) ### 1.3.1 (2022-03-15) * Fix undefined variable $engineRawVersion in Imagick/Gmagick DriverInfo (#825, @fxbt) ### 1.3.0 (2022-03-15) * Minimum PHP supported version is now 5.5 (#820, @PowerKiKi) * Support AVIF, HEIC, and JXL in Imagick driver (#759, #765, @ausi) * Support AVIF in GD driver (#791, @mlocati) * Make the $filter parameter of the resize method invariant (#776, @mlocati) * Ability to specify the alpha-blending of the GD drawer (#790, @mlocati) * Add support for SINCFAST filter in Imagick driver (#823, @mlocati) * Fix applyMask() for GD driver (#708, @ninze) * Fix PHP 8.1 compatibility (#768, #791, @ausi, @mlocati) * Fix error handling in grayscale() for Gmagick driver (#757, @dmitry-kulikov) * Fix convolve() for Imagick driver (#775, @mlocati) * Fix array retuned by histogram() method of GD and Imagick drivers (#797, @mlocati) * Fix handling alpha channel in Imagick (#775, #798, @mlocati) * New Driver\Info classes to inspect installed drivers (#802, #805, #806, @mlocati) * Fix wrong load logic in Imagick (#807, @mlocati) ### 1.2.4 (2020-11-03) * Fix PHP 8.0 compatibility, except gmagick - see https://bugs.php.net/bug.php?id=80106 (#740, @ausi) * Optimize reading EXIF metadata from local files (#741, @jorrit) * Fix rotation with imagick (#734, @lashus @ausi) * Fix saving multi-layer images (eg animated GIFs) as plain images with gmagick and imagick (#746, @alexander-schranz @mlocati) * Fix gmagick not resolving the correct export format in some edge cases (#750, @ausi) ### 1.2.3 (2019-12-04) * Handle jfif extension in GD driver (#727, @sylvain-msl-talkspirit) * Improve detection of unsupported Exit Metadata Reader (#729, @mlocati, @ausi) ### 1.2.2 (2019-07-09) * The GD driver can now load WebP files (#711, #718, @lashus, @ausi) * Avoid calling `imageantialias` if it's not available (#713, @ahukkanen) ### 1.2.1 (2019-06-03) * Silence call to `\Imagick::setImageOpacity()` in order to prevent deprecation error with Imagick 3.4.4 and ImageMagick 6 (#715, @samdark, @mlocati) ### 1.2.0 (2018-12-07) * `ExifMetadataReader` now returns all the available metadata, not only EXIF and IFD0 (#701, @mlocati) ### 1.1.0 (2018-10-25) * New `ImageInterface::THUMBNAIL_FLAG_NOCLONE` flag for `thumbnail()` to let it modify the original image instance in order to save memory (@mlocati) ### 1.0.2 (2018-10-24) * Check that the Imagick PHP extension is not compiled using ImageMagick version 7.0.7-32 because it does not work correctly (@mlocati) ### 1.0.1 (2018-09-27) * `Box` now rounds the width/height it receives (previously it discarded the decimal points) (@mlocati) ### 1.0.0 (2018-09-25) * New `FontInterface` method: `wrapText` - split a text into multiple lines, so that it fits a specific width (@mlocati) **BREAKING CHANGE** if you have your own `FontInterface` implementation, it now must implement `wrapText` * Drawer methods can now accept a thickness of zero (@mlocati) * Fix drawing unfilled chords with GD driver (@mlocati) * Fix thickness drawing of unfilled chords with Imagick and Gmagick drivers (@mlocati) * Fix handling of radius in `circle` method implementations (@mlocati) * The `dissolve` method of `ColorInterface` normalizes the final value of alpha (@mlocati) **BREAKING CHANGE** `dissolve` doesn't throw a `Imagine\Exception\InvalidArgumentException` anymore ### 1.0.0-alpha2 (2018-09-08) * The `coalesce` method of `LayerInterface` instances now returns the LayerInterface itself (@mlocati) **BREAKING CHANGE** if you have your own `LayerInterface` implementation, it now must return `$this` * The `__toString` method has been added to `ColorInterface` since all its implementations have it (@mlocati) **BREAKING CHANGE** if you have your own `ColorInterface` implementation, it now must implement `__toString` * New Imagick save option: `optimize` if set, the size of animated GIF files is optimized (@mlocati) **NOTE** Imagick requires that the image frames have the same size * The `paste` method now accepts images not fully included in the destination image (@mlocati) **BREAKING CHANGE** the paste method doesn't throw an OutOfBoundsException anymore * Fix handling of PNG compression in Imagick `save` method (@mlocati) * New drawer methods: `rectangle` and `circle` (@mlocati) **BREAKING CHANGE** if you have your own implementation of `DrawerInterface` you should add these two new methods * The `getChannelsMaxValue` method has been added to `PaletteInterface` (@mlocati) **BREAKING CHANGE** if you have your own `PaletteInterface` implementation, it now must implement this new method ### 1.0.0-alpha1 (2018-08-28) * Imagine is now tested under Windows too (@mlocati) * Add support to webp image format (@chregu, @antoligy, @alexander-schranz) * Add `Imagine\File\LoaderInterface` that allows loading remote images with any imaging driver (@mlocati). You can use your own `LoaderInterface` implementation so that you can for instance use curl or any other library. * Fix some phpdoc issues (@mlocati) * `flipHorizontally` and `flipVertically` methods of GD images is now much faster on PHP 5.5+ (@mlocati) * Fix loading of PNG indexed images with GD (@mlocati) * Loading indexed images with GD is now much faster on PHP 5.5+ (@mlocati) * Add support to grayscale images with Gmagick (@mlocati) * Add support to alpha channels of Gmagick images (@mlocati) * Fix `getColorAt` method of Gmagick images (@mlocati) * Add `getTransformations` to the `Autorotate` filter, so that you can get the list of transformations that should be applied to an image accordingly to the EXIF metadata (@mlocati) * The metadata reader now doesn't throw exceptions or warnings (@lentex, @mlocati) * Fix documentation (@ZhangChaoWN, @Mark-H, @mlocati) * Fix pixel range issue with Gmagick image (@b-viguier) * Fix `text` drawer method on Windows when using relative font file paths (@mlocati) * Fix `box` font method on Windows when using relative font file paths (@mlocati) * Fix crash on Windows when loading an image with Imagick (@mlocati) * Fix generation of API documentation (@mlocati) * Add `jpeg_sampling_factors` option when saving JPEG images (Gmagick/Imagick only) (@ausi) * Add BMP as supported image format (@mlocati) * Add support to new image type constants of Imagick (@ausi) * Check that Imagick correctly supports profiles (@ausi) * Add `setMetadataReader`/`getMetadataReader` to `ImagineInterface` (@mlocati) **BREAKING CHANGE** if you have your own `ImagineInterface` implementation, it now must implement those two methods * Fix creating Gmagick images with alpha colors when palette doesn't support alpha (@FractalizeR) * Fix warning about deprecated clone method in copy method of Imagick images (@mlocati) * Fix copy methods of Images (the original image and its new copy are now fully detached) (@mlocati) * It's now possible to use `clone $image` as an alternative to `$image->copy()` (@mlocati) * Add support to custom classes for `BoxInterface`, `MetadataReaderInterface`, `FontInterface`, `LoaderInterface`, `LayersInterface`, `ImageInterface` (@mlocati) **BREAKING CHANGE** if you have your own `ImagineInterface` implementation, it now must implement the methods of `ClassFactoryAwareInterface` * Add support for pasting with alpha for GD and Imagick (@AlloVince, @mlocati) * Downscaling a `Box` until it reaches a dimension less than 1 returns a box with dimension of 1 instead of throwing an exception (@mlocati) **BREAKING CHANGE** if you relied on `Box::scale` throwing an exception in this case * New filters: `BlackWhite`, `BorderDetection`, `Negation`, `Neighborhood` (@rejinka) * Minor optimization of filters based on `OnPixelBased` (@rejinka, @mlocati) * Add flag to `thumbnail` to allow upscaling images (@vlakoff) **BREAKING CHANGE** the `$mode` argument has been renamed to `$settings`, and it's now an integer (but old string values are accepted for backward compatibility). In this case the `ManipulatorInterface` constants `THUMBNAIL_INSET`, `THUMBNAIL_OUTBOUND` were changed from string values to integers. * New filter: `brightness` (@lenybernard, @mlocati) * New filter: `colvolve` available for all graphics libraries except gmagick with version prior to 2.0.1RC2 (@armatronic, @mlocati) * Fix bug in Imagine\Image\Palette\RGB::blend() (@dmolineus, @mlocati) * Autoload was moved from PSR-0 to PSR-4, and code files moved from `/lib/Imagine` to `/src` (@mlocati) ### 0.7.1 (2017-05-16) * Remove Symfony PHPUnit bridge as dependency (@craue) ### 0.7.0 (2017-05-02) * Fix memory usage on metadata reading (@Seldaek) * PHP 7.1 support * Latest Imagemagick compatibility (@jdewit) ### 0.6.3.2 (2016-12-07) * Fixed some artifacts left over from a merge conflict in Imagine\Imagick\Imagine. ### 0.6.3.1 (2016-11-28) * Optimize images for storage when resizing with Imagick * Allow images to be saved with a specific PNG format with Imagick * Preserve palette transparency for 8-bit PNG files when saving with Imagick * Add support for GIF files with variable frame delays * Add support for GIF files with non-infinite loop iterations * Fix compatibility issues with Imagick 7.0 ### 0.6.3 (2015-09-19) * Fix wrong array_merge when calling Transformation::getFilters without filters * Add export-ignore git attribute (@Benoth) * Fix docblocks (@Sm0ke0ut and @norkunas) * Fix animated gif loop length options (@jygaulier) * Multiple tweaks for the repository and travis builds (@localheinz, @vrkansagara and @dunzun) * Fix metadata extraction from streams (@armatronic) * Fix autorotation (@tarleb) * Load exifmetadata reader whenever possible * Add metadata getter ### 0.6.2 (2014-11-11) * Stripping image containing an invalid ICC profile fails * MetadataBag now implements \Countable * Fix wrong array_merge in MetadataBag giving invalid results with HTTP resources (@javaguirre) * Fix Imagick merge strategy (@GrahamCampbell) * Fixed various alpha issues (@RadekDvorak) * Fix Image cloning on HHVM (@RdeWilde) * Fix exception on invalid file using GD driver (@vlakoff). * Fix ImageInterface::getSize on animated GIFs (@sokac) ### 0.6.1 (2014-06-16) * Fix invalid namespace usage (#336 @csanquer). ### 0.6.0 (2014-06-13) * BC break: Colors are now provided through the PaletteInterface. Any call to previous Imagine\Image\Color constructor must be removed and use the palette provided by Imagine\Image\ImageInterface::getPalette to create colors. * BC break : Animated GIF default delay is no longer 800ms but null. This avoids resettings a delay on animated image. * Add support for ICC profiles * Add support for CMYK and grayscale colorspace images. * Add filter argument to ImageInterface::thumbnail method. * Add priority to filters (@Richtermeister). * Add blur effect (@Nokrosis). * Rename "quality" option to "jpeg_quality" and apply it only to JPEG files (@vlakoff). * Add "png_compression_level" option (@vlakoff). * Rename "filters" option to "png_compression_filter" (@vlakoff). * Deprecate `quality` and `filters` ManipulatorInterface::save options, use `jpeg_quality`, `png_compression_level` and `png_compression_filter` instead. * Add support for alpha blending in GD drawer (@salem). * Add width parameter to Drawer::text (@salemgolemugoo). * Add NotSupportedException when a driver does not support an operation (@rouffj). * Add support for metadata. * Fix #158: GD alpha detection + Color::isOpaque are broken. * Fix color extraction for non-RGB palettes. ### 0.5.0 (2013-07-10) * Add `Layers::coalesce`. * Add filter option to `ImageInterface::resize`. * Add sharpen effect. * Add interlace support. * `LayersInterface` now extends `ArrayAccess`, gives support for animated gifs. * Remove Imagick and Gmagick flatten after composite. * Fix pixel opacity reading in `Gmagick::histogram`. * Deprecate pear channel installation. * Deprecate phar installation. ### 0.4.1 (2012-12-13) * Lazy-load GD layers. ### 0.4.0 (2012-12-10) * Add support for image Layers. * Add Colorize effect. * Add documentation for the Grayscale effect. * Port RelativeResize filter from JmikolaImagineBundle. ### 0.3.1 (2012-11-12) * Add Grayscale effect. * `Drawer::text` position fix. ### 0.3.0 (2012-07-28) * Add configurable border thickness to drawer interface and implementations. * Add `ImageInterface`::strip. * Add Canvas filter. * Add resolution option on image saving. * Add Grayscale filter. * Add sami API documentation. * Add compression quality to Gmagick. * Add effects API. * Add method to get pixel at point in Gmagick. * Ensure valid background color in rotations. * Fill lines with color to prevent semi-transparency issues. * Use `Imagick::resizeImage` instead of `Imagick::thumbnailImage` for resizing. * Fix PNG transparency on save ; do not flatten if not necessary. ### 0.2.8 (2011-11-29) * Add support for Travis CI. ### 0.2.7 (2011-11-17) * Use composer for autoloading. ### 0.2.6 (2011-11-09) * Documentation enhancements. ### 0.2.5 (2011-10-29) * Add PEAR support. * Documentation enhancements. ### 0.2.4 (2011-10-17) * Add imagine.phar, phar and rake tasks. * Add `ImagineInterface::read` to read from a stream resource. * Documentation enhancements. * Fix gifs transparency issues. ### 0.2.3 (2011-10-16) * Documentation enhancements. ### 0.2.2 (2011-10-16) * Documentation enhancements. ### 0.2.1 (2011-10-15) * Add `PointInterface::move`. * `BoxInterface::scale` can accept floats. * Set antialias mode for GD images. * Fix png compression. ### 0.2.0 (2011-10-06) * Add `Imagine\Fill\Gradient\Linear::getStart`/`getEnd`. * Add `Imagine\Image\Color::isOpaque`. * Add Gmagick transparency exceptions. * Add support for transparency for gif export. * Add widen/heighten methods for easy scaling to target width/height. * Add functionals tests to unit test thumbnails creation. * Add the ability to use hexadecimal notation for `Imagine\Image\Color` construction. * Implement fast linear gradient for Imagick. * Remove lengthy image histogram comparisons. * Extract `ManipulatorInterface` from `ImageInterface`. * Switch methods to final. * New method `ImageInterface::getColorAt`. * Introduce `ImagineAware` abstract filter class. ### 0.1.5 (2011-05-18) * Fix bug in GD rotate. ### 0.1.4 (2011-03-21) * Add environment check to gracefuly skip test. ### 0.1.3 (2011-03-21) * Improve api docs. * Extract `FontInterface`. ### 0.1.2 (2011-03-21) * Add check for GD. ### 0.1.1 (2011-03-21) * Add rounding and fixed thumbnail logic. ### 0.1.0 (2011-03-14) * First tagged version. imagine/src/Exception/Exception.php 0000644 00000000517 15141212321 0013344 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Exception; /** * Imagine-specific exception. */ interface Exception { } imagine/src/Exception/OutOfBoundsException.php 0000644 00000000627 15141212321 0015476 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Exception; /** * Imagine-specific out of bounds exception. */ class OutOfBoundsException extends \OutOfBoundsException implements Exception { } imagine/src/Exception/NotSupportedException.php 0000644 00000000645 15141212321 0015735 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Exception; /** * Should be used when a driver does not support an operation. */ class NotSupportedException extends RuntimeException implements Exception { } imagine/src/Exception/RuntimeException.php 0000644 00000000611 15141212321 0014703 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Exception; /** * Imagine-specific runtime exception. */ class RuntimeException extends \RuntimeException implements Exception { } imagine/src/Exception/InvalidArgumentException.php 0000644 00000000642 15141212321 0016355 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Exception; /** * Imagine-specific invalid argument exception. */ class InvalidArgumentException extends \InvalidArgumentException implements Exception { } imagine/src/Factory/ClassFactoryAwareInterface.php 0000644 00000001071 15141212321 0016251 0 ustar 00 <?php namespace Imagine\Factory; /** * An interface that classes that accepts a class factory should implement. */ interface ClassFactoryAwareInterface { /** * Set the class factory instance to be used. * * @param \Imagine\Factory\ClassFactoryInterface $classFactory * * @return $this */ public function setClassFactory(ClassFactoryInterface $classFactory); /** * Get the class factory instance to be used. * * @return \Imagine\Factory\ClassFactoryInterface */ public function getClassFactory(); } imagine/src/Factory/ClassFactoryInterface.php 0000644 00000006667 15141212321 0015311 0 ustar 00 <?php namespace Imagine\Factory; use Imagine\Image\Palette\Color\ColorInterface; /** * The interface that class factories must implement. */ interface ClassFactoryInterface { /** * The handle to be used for the GD manipulation library. * * @var string */ const HANDLE_GD = 'gd'; /** * The handle to be used for the Gmagick manipulation library. * * @var string */ const HANDLE_GMAGICK = 'gmagick'; /** * The handle to be used for the Imagick manipulation library. * * @var string */ const HANDLE_IMAGICK = 'imagick'; /** * Create a new instance of a metadata reader. * * @return \Imagine\Image\Metadata\MetadataReaderInterface */ public function createMetadataReader(); /** * Create new BoxInterface instance. * * @param int $width The box width * @param int $height The box height * * @return \Imagine\Image\BoxInterface */ public function createBox($width, $height); /** * Create new FontInterface instance. * * @param string $handle The handle that identifies the manipulation library (one of the HANDLE_... constants, or your own implementation). * @param string $file * @param int $size the font size in points (e.g. 10pt means 10) * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @return \Imagine\Image\FontInterface */ public function createFont($handle, $file, $size, ColorInterface $color); /** * Create a new instance of a file loader. * * @param string|mixed $path * * @return \Imagine\File\LoaderInterface */ public function createFileLoader($path); /** * Crate a new instance of a layers interface. * * @param string $handle The handle that identifies the manipulation library (one of the HANDLE_... constants, or your own implementation). * @param \Imagine\Image\ImageInterface $image * @param mixed|null $initialKey the key of the initially selected layer * * @return \Imagine\Image\LayersInterface */ public function createLayers($handle, \Imagine\Image\ImageInterface $image, $initialKey = null); /** * Create a new ImageInterface instance. * * @param string $handle The handle that identifies the manipulation library (one of the HANDLE_... constants, or your own implementation). * @param mixed $resource * @param \Imagine\Image\Palette\PaletteInterface $palette * @param \Imagine\Image\Metadata\MetadataBag $metadata * * @return \Imagine\Image\ImageInterface */ public function createImage($handle, $resource, \Imagine\Image\Palette\PaletteInterface $palette, \Imagine\Image\Metadata\MetadataBag $metadata); /** * Create a new DrawerInterface instance. * * @param string $handle The handle that identifies the manipulation library (one of the HANDLE_... constants, or your own implementation). * @param mixed $resource * * @return \Imagine\Draw\DrawerInterface */ public function createDrawer($handle, $resource); /** * Create a new EffectsInterface instance. * * @param string $handle The handle that identifies the manipulation library (one of the HANDLE_... constants, or your own implementation). * @param mixed $resource * * @return \Imagine\Effects\EffectsInterface */ public function createEffects($handle, $resource); } imagine/src/Factory/ClassFactory.php 0000644 00000014135 15141212321 0013455 0 ustar 00 <?php namespace Imagine\Factory; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\File\Loader; use Imagine\Image\Box; use Imagine\Image\ImageInterface; use Imagine\Image\Metadata\DefaultMetadataReader; use Imagine\Image\Metadata\ExifMetadataReader; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\PaletteInterface; /** * The default implementation of Imagine\Factory\ClassFactoryInterface. */ class ClassFactory implements ClassFactoryInterface { /** * @var array|null */ private static $gdInfo; /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createMetadataReader() */ public function createMetadataReader() { return $this->finalize(ExifMetadataReader::isSupported() ? new ExifMetadataReader() : new DefaultMetadataReader()); } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createBox() */ public function createBox($width, $height) { return $this->finalize(new Box($width, $height)); } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createFileLoader() */ public function createFileLoader($path) { return $this->finalize(new Loader($path)); } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createDrawer() */ public function createDrawer($handle, $resource) { switch ($handle) { case self::HANDLE_GD: return $this->finalize(new \Imagine\Gd\Drawer($resource)); case self::HANDLE_GMAGICK: return $this->finalize(new \Imagine\Gmagick\Drawer($resource)); case self::HANDLE_IMAGICK: return $this->finalize(new \Imagine\Imagick\Drawer($resource)); default: throw new InvalidArgumentException(sprintf('Unrecognized handle %s in %s', $handle, __FUNCTION__)); } } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createLayers() */ public function createLayers($handle, ImageInterface $image, $initialKey = null) { switch ($handle) { case self::HANDLE_GD: return $this->finalize(new \Imagine\Gd\Layers($image, $image->palette(), $image->getGdResource(), (int) $initialKey)); case self::HANDLE_GMAGICK: return $this->finalize(new \Imagine\Gmagick\Layers($image, $image->palette(), $image->getGmagick(), (int) $initialKey)); case self::HANDLE_IMAGICK: return $this->finalize(new \Imagine\Imagick\Layers($image, $image->palette(), $image->getImagick(), (int) $initialKey)); default: throw new InvalidArgumentException(sprintf('Unrecognized handle %s in %s', $handle, __FUNCTION__)); } } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createEffects() */ public function createEffects($handle, $resource) { switch ($handle) { case self::HANDLE_GD: return $this->finalize(new \Imagine\Gd\Effects($resource)); case self::HANDLE_GMAGICK: return $this->finalize(new \Imagine\Gmagick\Effects($resource)); case self::HANDLE_IMAGICK: return $this->finalize(new \Imagine\Imagick\Effects($resource)); default: throw new InvalidArgumentException(sprintf('Unrecognized handle %s in %s', $handle, __FUNCTION__)); } } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createImage() */ public function createImage($handle, $resource, PaletteInterface $palette, MetadataBag $metadata) { switch ($handle) { case self::HANDLE_GD: return $this->finalize(new \Imagine\Gd\Image($resource, $palette, $metadata)); case self::HANDLE_GMAGICK: return $this->finalize(new \Imagine\Gmagick\Image($resource, $palette, $metadata)); case self::HANDLE_IMAGICK: return $this->finalize(new \Imagine\Imagick\Image($resource, $palette, $metadata)); default: throw new InvalidArgumentException(sprintf('Unrecognized handle %s in %s', $handle, __FUNCTION__)); } } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryInterface::createFont() */ public function createFont($handle, $file, $size, ColorInterface $color) { switch ($handle) { case self::HANDLE_GD: $gdInfo = static::getGDInfo(); if (!$gdInfo['FreeType Support']) { throw new RuntimeException('GD is not compiled with FreeType support'); } return $this->finalize(new \Imagine\Gd\Font($file, $size, $color)); case self::HANDLE_GMAGICK: $gmagick = new \Gmagick(); $gmagick->newimage(1, 1, 'transparent'); return $this->finalize(new \Imagine\Gmagick\Font($gmagick, $file, $size, $color)); case self::HANDLE_IMAGICK: return $this->finalize(new \Imagine\Imagick\Font(new \Imagick(), $file, $size, $color)); default: throw new InvalidArgumentException(sprintf('Unrecognized handle %s in %s', $handle, __FUNCTION__)); } } /** * Finalize the newly created object. * * @param object $object * * @return object */ protected function finalize($object) { if ($object instanceof ClassFactoryAwareInterface) { $object->setClassFactory($this); } return $object; } /** * @return array */ protected static function getGDInfo() { if (self::$gdInfo === null) { if (!function_exists('gd_info')) { throw new RuntimeException('GD is not installed'); } self::$gdInfo = gd_info(); } return self::$gdInfo; } } imagine/src/Image/AbstractImage.php 0000644 00000016074 15141212321 0013205 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Exception\InvalidArgumentException; use Imagine\Factory\ClassFactory; use Imagine\Factory\ClassFactoryAwareInterface; use Imagine\Factory\ClassFactoryInterface; abstract class AbstractImage implements ImageInterface, ClassFactoryAwareInterface { /** * @var \Imagine\Image\Metadata\MetadataBag */ protected $metadata; /** * @var \Imagine\Factory\ClassFactoryInterface|null */ private $classFactory; /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::thumbnail() */ public function thumbnail(BoxInterface $size, $settings = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED) { $settings = $this->checkThumbnailSettings($settings); $mode = $settings & 0xffff; $allowUpscale = (bool) ($settings & ImageInterface::THUMBNAIL_FLAG_UPSCALE); $noClone = (bool) ($settings & ImageInterface::THUMBNAIL_FLAG_NOCLONE); $imageSize = $this->getSize(); $palette = $this->palette(); $thumbnail = $noClone ? $this : $this->copy(); $thumbnail->usePalette($palette); $thumbnail->strip(); if ($size->getWidth() === $imageSize->getWidth() && $size->getHeight() === $imageSize->getHeight()) { // The thumbnail size is the same as the wanted size. return $thumbnail; } if (!$allowUpscale && $size->contains($imageSize)) { // Thumbnail is smaller than the image and we are not upscaling return $thumbnail; } $ratios = array( $size->getWidth() / $imageSize->getWidth(), $size->getHeight() / $imageSize->getHeight(), ); switch ($mode) { case ImageInterface::THUMBNAIL_OUTBOUND: // Crop the image so that it fits the wanted size $ratio = max($ratios); if ($imageSize->contains($size)) { // Downscale the image $imageSize = $imageSize->scale($ratio); $thumbnail->resize($imageSize, $filter); $thumbnailSize = $size; } else { if ($allowUpscale) { // Upscale the image so that the max dimension will be the wanted one $imageSize = $imageSize->scale($ratio); $thumbnail->resize($imageSize, $filter); } $thumbnailSize = new Box( min($imageSize->getWidth(), $size->getWidth()), min($imageSize->getHeight(), $size->getHeight()) ); } $thumbnail->crop( new Point( max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2)), max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2)) ), $thumbnailSize ); break; case ImageInterface::THUMBNAIL_INSET: default: // Scale the image so that it fits the wanted size $ratio = min($ratios); $thumbnailSize = $imageSize->scale($ratio); $thumbnail->resize($thumbnailSize, $filter); break; } return $thumbnail; } /** * Check the settings argument in thumbnail() method. * * @param int $settings */ private function checkThumbnailSettings($settings) { // Preserve BC until version 1.0 if (is_string($settings)) { if ($settings === 'inset') { $settings = ImageInterface::THUMBNAIL_INSET; } elseif ($settings === 'outbound') { $settings = ImageInterface::THUMBNAIL_OUTBOUND; } elseif (is_numeric($settings)) { $settings = (int) $settings; } } if (!is_int($settings)) { throw new InvalidArgumentException('Invalid setting specified'); } $mode = $settings & 0xffff; if ($mode === 0) { $settings |= ImageInterface::THUMBNAIL_INSET; } else { if (!in_array($mode, $this->getAllThumbnailModes())) { if (strlen(str_replace('0', '', decbin($mode))) === 1) { throw new InvalidArgumentException('Invalid setting specified'); } throw new InvalidArgumentException('Only one mode should be specified'); } } return $settings; } /** * Get all the available thumbnail modes. * * @return int[] */ protected function getAllThumbnailModes() { return array( ImageInterface::THUMBNAIL_INSET, ImageInterface::THUMBNAIL_OUTBOUND, ); } /** * Updates a given array of save options for backward compatibility with legacy names. * * @param array $options * * @return array */ protected function updateSaveOptions(array $options) { // Preserve BC until version 1.0 if (isset($options['quality']) && !isset($options['jpeg_quality'])) { $options['jpeg_quality'] = $options['quality']; } return $options; } /** * Get all the available filter defined in ImageInterface. * * @return string[] */ protected static function getAllFilterValues() { static $result; if (!is_array($result)) { $values = array(); $interface = new \ReflectionClass('Imagine\Image\ImageInterface'); foreach ($interface->getConstants() as $name => $value) { if (strpos($name, 'FILTER_') === 0) { $values[] = $value; } } $result = $values; } return $result; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::metadata() */ public function metadata() { return $this->metadata; } /** * Clones all the resources associated to this instance. */ public function __clone() { if ($this->metadata !== null) { $this->metadata = clone $this->metadata; } } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::getClassFactory() */ public function getClassFactory() { if ($this->classFactory === null) { $this->classFactory = new ClassFactory(); } return $this->classFactory; } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::setClassFactory() */ public function setClassFactory(ClassFactoryInterface $classFactory) { $this->classFactory = $classFactory; return $this; } } imagine/src/Image/Point/Center.php 0000644 00000003427 15141212321 0013006 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Point; use Imagine\Image\BoxInterface; use Imagine\Image\Point as OriginalPoint; use Imagine\Image\PointInterface; /** * Center point of a box. */ final class Center implements PointInterface { /** * @var \Imagine\Image\BoxInterface */ private $box; /** * Constructs coordinate with size instance, it needs to be relative to. * * @param \Imagine\Image\BoxInterface $box */ public function __construct(BoxInterface $box) { $this->box = $box; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getX() */ public function getX() { return ceil($this->box->getWidth() / 2); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getY() */ public function getY() { return ceil($this->box->getHeight() / 2); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::in() */ public function in(BoxInterface $box) { return $this->getX() < $box->getWidth() && $this->getY() < $box->getHeight(); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::move() */ public function move($amount) { return new OriginalPoint($this->getX() + $amount, $this->getY() + $amount); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::__toString() */ public function __toString() { return sprintf('(%d, %d)', $this->getX(), $this->getY()); } } imagine/src/Image/ImageInterface.php 0000644 00000014452 15141212321 0013340 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Image\Palette\PaletteInterface; /** * The image interface. */ interface ImageInterface extends ManipulatorInterface { /** * Resolution units: pixels per inch. * * @var string */ const RESOLUTION_PIXELSPERINCH = 'ppi'; /** * Resolution units: pixels per centimeter. * * @var string */ const RESOLUTION_PIXELSPERCENTIMETER = 'ppc'; /** * Image interlacing: none. * * @var string */ const INTERLACE_NONE = 'none'; /** * Image interlacing: scanline. * * @var string */ const INTERLACE_LINE = 'line'; /** * Image interlacing: plane. * * @var string */ const INTERLACE_PLANE = 'plane'; /** * Image interlacing: like plane interlacing except the different planes are saved to individual files. * * @var string */ const INTERLACE_PARTITION = 'partition'; /** * Image filter: none/undefined. * * @var string */ const FILTER_UNDEFINED = 'undefined'; /** * Resampling filter: point (interpolated). * * @var string */ const FILTER_POINT = 'point'; /** * Resampling filter: box. * * @var string */ const FILTER_BOX = 'box'; /** * Resampling filter: triangle. * * @var string */ const FILTER_TRIANGLE = 'triangle'; /** * Resampling filter: hermite. * * @var string */ const FILTER_HERMITE = 'hermite'; /** * Resampling filter: hanning. * * @var string */ const FILTER_HANNING = 'hanning'; /** * Resampling filter: hamming. * * @var string */ const FILTER_HAMMING = 'hamming'; /** * Resampling filter: blackman. * * @var string */ const FILTER_BLACKMAN = 'blackman'; /** * Resampling filter: gaussian. * * @var string */ const FILTER_GAUSSIAN = 'gaussian'; /** * Resampling filter: quadratic. * * @var string */ const FILTER_QUADRATIC = 'quadratic'; /** * Resampling filter: cubic. * * @var string */ const FILTER_CUBIC = 'cubic'; /** * Resampling filter: catrom. * * @var string */ const FILTER_CATROM = 'catrom'; /** * Resampling filter: mitchell. * * @var string */ const FILTER_MITCHELL = 'mitchell'; /** * Resampling filter: lanczos. * * @var string */ const FILTER_LANCZOS = 'lanczos'; /** * Resampling filter: bessel. * * @var string */ const FILTER_BESSEL = 'bessel'; /** * Resampling filter: sinc. * * @var string */ const FILTER_SINC = 'sinc'; /** * Resampling filter: sincfast. * * @var string */ const FILTER_SINCFAST = 'sincfast'; /** * Returns the image content as a binary string. * * @param string $format * @param array $options * * @throws \Imagine\Exception\RuntimeException * * @return string binary */ public function get($format, array $options = array()); /** * Returns the image content as a PNG binary string. * * @throws \Imagine\Exception\RuntimeException * * @return string binary */ public function __toString(); /** * Instantiates and returns a DrawerInterface instance for image drawing. * Some drivers may also return a DrawerInterface drawer that's also AlphaBlendingAwareDrawerInterface. * * @return \Imagine\Draw\DrawerInterface|\Imagine\Draw\AlphaBlendingAwareDrawerInterface */ public function draw(); /** * @return \Imagine\Effects\EffectsInterface */ public function effects(); /** * Returns current image size. * * @return \Imagine\Image\BoxInterface */ public function getSize(); /** * Transforms creates a grayscale mask from current image, returns a new * image, while keeping the existing image unmodified. * * @return static */ public function mask(); /** * Returns array of image colors as Imagine\Image\Palette\Color\ColorInterface instances. * * @return \Imagine\Image\Palette\Color\ColorInterface[] */ public function histogram(); /** * Returns color at specified positions of current image. * * @param \Imagine\Image\PointInterface $point * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function getColorAt(PointInterface $point); /** * Returns the image layers when applicable. * * @throws \Imagine\Exception\RuntimeException In case the layer can not be returned * @throws \Imagine\Exception\OutOfBoundsException In case the index is not a valid value * * @return \Imagine\Image\LayersInterface */ public function layers(); /** * Enables or disables interlacing. * * @param string $scheme * * @throws \Imagine\Exception\InvalidArgumentException When an unsupported Interface type is supplied * * @return $this */ public function interlace($scheme); /** * Return the current color palette. * * @return \Imagine\Image\Palette\PaletteInterface */ public function palette(); /** * Set a palette for the image. Useful to change colorspace. * * @param \Imagine\Image\Palette\PaletteInterface $palette * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function usePalette(PaletteInterface $palette); /** * Applies a color profile on the Image. * * @param \Imagine\Image\ProfileInterface $profile * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function profile(ProfileInterface $profile); /** * Returns the Image's meta data. * * @return \Imagine\Image\Metadata\MetadataBag */ public function metadata(); } imagine/src/Image/ImagineInterface.php 0000644 00000005236 15141212321 0013667 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Factory\ClassFactoryAwareInterface; use Imagine\Image\Metadata\MetadataReaderInterface; use Imagine\Image\Palette\Color\ColorInterface; /** * The imagine interface. */ interface ImagineInterface extends ClassFactoryAwareInterface { const VERSION = '1.3.3'; /** * Creates a new empty image with an optional background color. * * @param \Imagine\Image\BoxInterface $size * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ public function create(BoxInterface $size, ColorInterface $color = null); /** * Opens an existing image from $path. * * @param string|\Imagine\File\LoaderInterface|mixed $path the file path, a LoaderInterface instance, or an object whose string representation is the image path * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ public function open($path); /** * Loads an image from a binary $string. * * @param string $string * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ public function load($string); /** * Loads an image from a resource $resource. * * @param resource $resource * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ public function read($resource); /** * Constructs a font with specified $file, $size and $color. * * The font size is to be specified in points (e.g. 10pt means 10) * * @param string $file * @param int $size * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @return \Imagine\Image\FontInterface */ public function font($file, $size, ColorInterface $color); /** * Set the object to be used to read image metadata. * * @param \Imagine\Image\Metadata\MetadataReaderInterface $metadataReader * * @return $this */ public function setMetadataReader(MetadataReaderInterface $metadataReader); /** * Get the object to be used to read image metadata. * * @return \Imagine\Image\Metadata\MetadataReaderInterface */ public function getMetadataReader(); } imagine/src/Image/ProfileInterface.php 0000644 00000001007 15141212321 0013706 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; interface ProfileInterface { /** * Returns the name of the profile. * * @return string */ public function name(); /** * Returns the profile data. * * @return string */ public function data(); } imagine/src/Image/Format.php 0000644 00000006227 15141212321 0011726 0 ustar 00 <?php namespace Imagine\Image; use ReflectionClass; /** * Represent an image format. * * @since 1.3.0 */ class Format { const ID_AVIF = 'avif'; const ID_BMP = 'bmp'; const ID_GIF = 'gif'; const ID_HEIC = 'heic'; const ID_JPEG = 'jpeg'; const ID_JXL = 'jxl'; const ID_PNG = 'png'; const ID_WBMP = 'wbmp'; const ID_WEBP = 'webp'; const ID_XBM = 'xbm'; /** * @var \Imagine\Image\FormatList|null */ private static $all = null; /** * @var string */ private $id; /** * @var string */ private $mimeType; /** * @var string */ private $canonicalFileExtension; /** * @var string[] */ private $alternativeIDs; /** * @param string $id * @param string $fileExtension * @param string $mimeType * @param string[] $alternativeIDs * @param mixed $canonicalFileExtension */ private function __construct($id, $mimeType, $canonicalFileExtension, $alternativeIDs = array()) { $this->id = $id; $this->mimeType = $mimeType; $this->canonicalFileExtension = $canonicalFileExtension; $this->alternativeIDs = $alternativeIDs; } /** * @return string */ public function getID() { return $this->id; } /** * @return string */ public function getMimeType() { return $this->mimeType; } /** * @return string */ public function getCanonicalFileExtension() { return $this->canonicalFileExtension; } /** * @return string[] */ public function getAlternativeIDs() { return $this->alternativeIDs; } /** * Get a format given its ID. * * @param static|string $format the format (a Format instance of a format ID) * * @return static|null */ public static function get($format) { return static::getList()->find($format); } /** * @return static[] */ public static function getAll() { return static::getList()->getAll(); } /** * @return \Imagine\Image\FormatList */ protected static function getList() { if (self::$all !== null) { return self::$all; } $class = new ReflectionClass(get_called_class()); $formats = array(); foreach ($class->getConstants() as $constantName => $constantValue) { if (strpos($constantName, 'ID_') === 0) { $formats[] = static::create($constantValue); } } self::$all = new FormatList($formats); return self::$all; } /** * @param string $formatID * * @return static */ protected static function create($formatID) { switch ($formatID) { case static::ID_JPEG: return new static($formatID, 'image/jpeg', 'jpg', array('jpg', 'pjpeg', 'jfif')); case static::ID_WBMP: return new static($formatID, 'image/vnd.wap.wbmp', $formatID); default: return new static($formatID, "image/{$formatID}", $formatID); } } } imagine/src/Image/Box.php 0000644 00000006236 15141212321 0011226 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Exception\InvalidArgumentException; /** * A box implementation. */ final class Box implements BoxInterface { /** * @var int */ private $width; /** * @var int */ private $height; /** * Constructs the Size with given width and height. * * @param int $width * @param int $height * * @throws \Imagine\Exception\InvalidArgumentException */ public function __construct($width, $height) { if (!\is_int($width)) { $width = (int) round($width); } if (!\is_int($height)) { $height = (int) round($height); } $this->width = $width; $this->height = $height; if ($this->width < 1 || $this->height < 1) { throw new InvalidArgumentException(sprintf('Length of either side cannot be 0 or negative, current size is %sx%s', $width, $height)); } } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::getWidth() */ public function getWidth() { return $this->width; } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::getHeight() */ public function getHeight() { return $this->height; } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::scale() */ public function scale($ratio) { $width = max(1, round($ratio * $this->width)); $height = max(1, round($ratio * $this->height)); return new self($width, $height); } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::increase() */ public function increase($size) { return new self((int) $size + $this->width, (int) $size + $this->height); } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::contains() */ public function contains(BoxInterface $box, PointInterface $start = null) { $start = $start ? $start : new Point(0, 0); return $start->in($this) && $this->width >= $box->getWidth() + $start->getX() && $this->height >= $box->getHeight() + $start->getY(); } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::square() */ public function square() { return $this->width * $this->height; } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::__toString() */ public function __toString() { return sprintf('%dx%d px', $this->width, $this->height); } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::widen() */ public function widen($width) { return $this->scale($width / $this->width); } /** * {@inheritdoc} * * @see \Imagine\Image\BoxInterface::heighten() */ public function heighten($height) { return $this->scale($height / $this->height); } } imagine/src/Image/FontInterface.php 0000644 00000002376 15141212321 0013226 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; /** * The font interface. */ interface FontInterface { /** * Gets the fontfile for current font. * * @return string */ public function getFile(); /** * Gets font's integer point size. * * @return int */ public function getSize(); /** * Gets font's color. * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function getColor(); /** * Gets BoxInterface of font size on the image based on string and angle. * * @param string $string * @param int $angle * * @return \Imagine\Image\BoxInterface */ public function box($string, $angle = 0); /** * Split a string into multiple lines so that it fits a specific width. * * @param string $string The text to be wrapped * @param int $maxWidth The maximum width of the text * @param int $angle * * @return string */ public function wrapText($string, $maxWidth, $angle = 0); } imagine/src/Image/AbstractLayers.php 0000644 00000004135 15141212321 0013415 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Factory\ClassFactory; use Imagine\Factory\ClassFactoryAwareInterface; use Imagine\Factory\ClassFactoryInterface; abstract class AbstractLayers implements LayersInterface, ClassFactoryAwareInterface { /** * @var \Imagine\Factory\ClassFactoryInterface|null */ private $classFactory; /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::add() */ public function add(ImageInterface $image) { $this[] = $image; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::set() */ public function set($offset, ImageInterface $image) { $this[$offset] = $image; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::remove() */ public function remove($offset) { unset($this[$offset]); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::get() */ public function get($offset) { return $this[$offset]; } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::has() */ public function has($offset) { return isset($this[$offset]); } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::setClassFactory() */ public function setClassFactory(ClassFactoryInterface $classFactory) { $this->classFactory = $classFactory; return $this; } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::getClassFactory() */ public function getClassFactory() { if ($this->classFactory === null) { $this->classFactory = new ClassFactory(); } return $this->classFactory; } } imagine/src/Image/BoxInterface.php 0000644 00000003664 15141212321 0013051 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; /** * Interface for a box. */ interface BoxInterface { /** * Gets box height. * * @return int */ public function getHeight(); /** * Gets box width. * * @return int */ public function getWidth(); /** * Creates new BoxInterface instance with ratios applied to both sides. * * @param float $ratio * * @return static */ public function scale($ratio); /** * Creates new BoxInterface, adding given size to both sides. * * @param int $size * * @return static */ public function increase($size); /** * Checks whether current box can fit given box at a given start position, * start position defaults to top left corner xy(0,0). * * @param \Imagine\Image\BoxInterface $box * @param \Imagine\Image\PointInterface $start * * @return bool */ public function contains(BoxInterface $box, PointInterface $start = null); /** * Gets current box square, useful for getting total number of pixels in a * given box. * * @return int */ public function square(); /** * Returns a string representation of the current box. * * @return string */ public function __toString(); /** * Resizes box to given width, constraining proportions and returns the new box. * * @param int $width * * @return static */ public function widen($width); /** * Resizes box to given height, constraining proportions and returns the new box. * * @param int $height * * @return static */ public function heighten($height); } imagine/src/Image/Palette/CMYK.php 0000644 00000007460 15141212321 0012637 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\Color\CMYK as CMYKColor; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Profile; use Imagine\Image\ProfileInterface; /** * The CMYK palette. */ class CMYK implements PaletteInterface { /** * @var \Imagine\Image\Palette\ColorParser */ private $parser; /** * @var \Imagine\Image\ProfileInterface|null */ private $profile; /** * @var \Imagine\Image\Palette\Color\CMYK[] */ private static $colors = array(); public function __construct() { $this->parser = new ColorParser(); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::name() */ public function name() { return PaletteInterface::PALETTE_CMYK; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::pixelDefinition() */ public function pixelDefinition() { return array( ColorInterface::COLOR_CYAN, ColorInterface::COLOR_MAGENTA, ColorInterface::COLOR_YELLOW, ColorInterface::COLOR_KEYLINE, ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::supportsAlpha() */ public function supportsAlpha() { return false; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::getChannelsMaxValue() */ public function getChannelsMaxValue() { return 100; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::color() */ public function color($color, $alpha = null) { if ($alpha !== null && $alpha !== 100) { throw new InvalidArgumentException('CMYK palette does not support alpha'); } $color = $this->parser->parseToCMYK($color); $index = sprintf('cmyk(%d, %d, %d, %d)', $color[0], $color[1], $color[2], $color[3]); if (array_key_exists($index, self::$colors) === false) { self::$colors[$index] = new CMYKColor($this, $color); } return self::$colors[$index]; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::blend() */ public function blend(ColorInterface $color1, ColorInterface $color2, $amount) { if (!$color1 instanceof CMYKColor || !$color2 instanceof CMYKColor) { throw new RuntimeException('CMYK palette can only blend CMYK colors'); } $max = $this->getChannelsMaxValue(); return $this->color(array( min($max, $color1->getCyan() + $color2->getCyan() * $amount), min($max, $color1->getMagenta() + $color2->getMagenta() * $amount), min($max, $color1->getYellow() + $color2->getYellow() * $amount), min($max, $color1->getKeyline() + $color2->getKeyline() * $amount), )); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::useProfile() */ public function useProfile(ProfileInterface $profile) { $this->profile = $profile; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::profile() */ public function profile() { if (!$this->profile) { $this->profile = Profile::fromPath(__DIR__ . '/../../resources/Adobe/CMYK/USWebUncoated.icc'); } return $this->profile; } } imagine/src/Image/Palette/ColorParser.php 0000644 00000011217 15141212321 0014322 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette; use Imagine\Exception\InvalidArgumentException; class ColorParser { /** * Parses a color to a RGB tuple. * * @param string|array|int $color * * @throws \Imagine\Exception\InvalidArgumentException * * @return array */ public function parseToRGB($color) { $color = $this->parse($color); if (count($color) === 4) { $color = array( 255 * (1 - $color[0] / 100) * (1 - $color[3] / 100), 255 * (1 - $color[1] / 100) * (1 - $color[3] / 100), 255 * (1 - $color[2] / 100) * (1 - $color[3] / 100), ); } return $color; } /** * Parses a color to a CMYK tuple. * * @param string|array|int $color * * @throws \Imagine\Exception\InvalidArgumentException * * @return array */ public function parseToCMYK($color) { $color = $this->parse($color); if (count($color) === 3) { $r = $color[0] / 255; $g = $color[1] / 255; $b = $color[2] / 255; $k = 1 - max($r, $g, $b); $color = array( $k === 1 ? 0 : round((1 - $r - $k) / (1 - $k) * 100), $k === 1 ? 0 : round((1 - $g - $k) / (1 - $k) * 100), $k === 1 ? 0 : round((1 - $b - $k) / (1 - $k) * 100), round($k * 100), ); } return $color; } /** * Parses a color to a grayscale value. * * @param string|array|int $color * * @throws \Imagine\Exception\InvalidArgumentException * * @return int[] */ public function parseToGrayscale($color) { if (is_array($color) && count($color) === 1) { return array((int) round(array_pop($color))); } $color = array_unique($this->parse($color)); if (count($color) !== 1) { throw new InvalidArgumentException('The provided color has different values of red, green and blue components. Grayscale colors must have the same values for these.'); } return $color; } /** * Parses a color. * * @param string|array|int $color * * @throws \Imagine\Exception\InvalidArgumentException * * @return int[] */ private function parse($color) { if (!is_string($color) && !is_array($color) && !is_int($color)) { throw new InvalidArgumentException(sprintf('Color must be specified as a hexadecimal string, array or integer, %s given', gettype($color))); } if (is_array($color)) { if (count($color) === 3 || count($color) === 4) { return array_map( function ($component) { return (int) round($component); }, array_values($color) ); } throw new InvalidArgumentException('Color argument if array, must look like array(R, G, B), or array(C, M, Y, K) where R, G, B are the integer values between 0 and 255 for red, green and blue or cyan, magenta, yellow and black color indexes accordingly'); } if (is_string($color)) { if (strpos($color, 'cmyk(') === 0) { $substrColor = substr($color, 5, strlen($color) - 6); $components = array_map(function ($component) { return (int) round(trim($component, ' %')); }, explode(',', $substrColor)); if (count($components) !== 4) { throw new InvalidArgumentException(sprintf('Unable to parse color %s', $color)); } return $components; } else { $color = ltrim($color, '#'); if (strlen($color) !== 3 && strlen($color) !== 6) { throw new InvalidArgumentException(sprintf('Color must be a hex value in regular (6 characters) or short (3 characters) notation, "%s" given', $color)); } if (strlen($color) === 3) { $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2]; } $color = array_map('hexdec', str_split($color, 2)); } } if (is_int($color)) { $color = array(255 & ($color >> 16), 255 & ($color >> 8), 255 & $color); } return $color; } } imagine/src/Image/Palette/PaletteInterface.php 0000644 00000005232 15141212321 0015306 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\ProfileInterface; /** * Interface that any palette must implement. */ interface PaletteInterface { /** * Palette name: grayscale. * * @var string */ const PALETTE_GRAYSCALE = 'gray'; /** * Palette name: RGB. * * @var string */ const PALETTE_RGB = 'rgb'; /** * Palette name: CMYK. * * @var string */ const PALETTE_CMYK = 'cmyk'; /** * Returns a color given some values. * * @param string|int[]|int $color A color * @param int|null $alpha Set alpha to null to disable it * * @throws \Imagine\Exception\InvalidArgumentException In case you pass an alpha value to a Palette that does not support alpha * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function color($color, $alpha = null); /** * Blend two colors given an amount. * * @param \Imagine\Image\Palette\Color\ColorInterface $color1 * @param \Imagine\Image\Palette\Color\ColorInterface $color2 * @param float $amount The amount of color2 in color1 * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function blend(ColorInterface $color1, ColorInterface $color2, $amount); /** * Attachs an ICC profile to this Palette. * * (A default profile is provided by default) * * @param \Imagine\Image\ProfileInterface $profile * * @return $this */ public function useProfile(ProfileInterface $profile); /** * Returns the ICC profile attached to this Palette. * * @return \Imagine\Image\ProfileInterface */ public function profile(); /** * Returns the name of this Palette, one of PaletteInterface::PALETTE_ constants. * * @return string */ public function name(); /** * Returns an array containing ColorInterface::COLOR_* constants that * define the structure of colors for a pixel. * * @return string[] */ public function pixelDefinition(); /** * Tells if alpha channel is supported in this palette. * * @return bool */ public function supportsAlpha(); /** * Get the max value of palette components (255 for RGB and Grayscale, 100 for CMYK). * * @return int */ public function getChannelsMaxValue(); } imagine/src/Image/Palette/Grayscale.php 0000644 00000007116 15141212321 0014004 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Color\Gray as GrayColor; use Imagine\Image\Profile; use Imagine\Image\ProfileInterface; /** * The grayscale palette. */ class Grayscale implements PaletteInterface { /** * @var \Imagine\Image\Palette\ColorParser */ private $parser; /** * @var \Imagine\Image\ProfileInterface|null */ private $profile; /** * @var \Imagine\Image\Palette\Color\Gray[] */ protected static $colors = array(); public function __construct() { $this->parser = new ColorParser(); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::name() */ public function name() { return PaletteInterface::PALETTE_GRAYSCALE; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::pixelDefinition() */ public function pixelDefinition() { return array(ColorInterface::COLOR_GRAY); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::supportsAlpha() */ public function supportsAlpha() { return true; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::getChannelsMaxValue() */ public function getChannelsMaxValue() { return 255; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::useProfile() */ public function useProfile(ProfileInterface $profile) { $this->profile = $profile; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::profile() */ public function profile() { if (!$this->profile) { $this->profile = Profile::fromPath(__DIR__ . '/../../resources/colormanagement.org/ISOcoated_v2_grey1c_bas.ICC'); } return $this->profile; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::color() */ public function color($color, $alpha = null) { if ($alpha === null) { $alpha = 0; } $color = $this->parser->parseToGrayscale($color); $index = sprintf('#%02x%02x%02x-%d', $color[0], $color[0], $color[0], $alpha); if (array_key_exists($index, static::$colors) === false) { static::$colors[$index] = new GrayColor($this, $color, $alpha); } return static::$colors[$index]; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::blend() */ public function blend(ColorInterface $color1, ColorInterface $color2, $amount) { if (!$color1 instanceof GrayColor || !$color2 instanceof GrayColor) { throw new RuntimeException('Grayscale palette can only blend Grayscale colors'); } $max = $this->getChannelsMaxValue(); return $this->color( array( (int) min($max, min($color1->getGray(), $color2->getGray()) + round(abs($color2->getGray() - $color1->getGray()) * $amount)), ), (int) min(100, min($color1->getAlpha(), $color2->getAlpha()) + round(abs($color2->getAlpha() - $color1->getAlpha()) * $amount)) ); } } imagine/src/Image/Palette/RGB.php 0000644 00000007624 15141212321 0012510 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Color\RGB as RGBColor; use Imagine\Image\Profile; use Imagine\Image\ProfileInterface; /** * The RGB palette. */ class RGB implements PaletteInterface { /** * @var \Imagine\Image\Palette\ColorParser */ private $parser; /** * @var \Imagine\Image\ProfileInterface|null */ private $profile; /** * @var \Imagine\Image\Palette\Color\RGB[] */ protected static $colors = array(); public function __construct() { $this->parser = new ColorParser(); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::name() */ public function name() { return PaletteInterface::PALETTE_RGB; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::pixelDefinition() */ public function pixelDefinition() { return array( ColorInterface::COLOR_RED, ColorInterface::COLOR_GREEN, ColorInterface::COLOR_BLUE, ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::supportsAlpha() */ public function supportsAlpha() { return true; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::getChannelsMaxValue() */ public function getChannelsMaxValue() { return 255; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::useProfile() */ public function useProfile(ProfileInterface $profile) { $this->profile = $profile; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::profile() */ public function profile() { if (!$this->profile) { $this->profile = Profile::fromPath(__DIR__ . '/../../resources/color.org/sRGB_IEC61966-2-1_black_scaled.icc'); } return $this->profile; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::color() */ public function color($color, $alpha = null) { if ($alpha === null) { $alpha = 100; } $color = $this->parser->parseToRGB($color); $index = sprintf('#%02x%02x%02x-%d', $color[0], $color[1], $color[2], $alpha); if (array_key_exists($index, static::$colors) === false) { static::$colors[$index] = new RGBColor($this, $color, $alpha); } return static::$colors[$index]; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\PaletteInterface::blend() */ public function blend(ColorInterface $color1, ColorInterface $color2, $amount) { if (!$color1 instanceof RGBColor || !$color2 instanceof RGBColor) { throw new RuntimeException('RGB palette can only blend RGB colors'); } $amount = (float) $amount; $amountComplement = 1 - $amount; $max = $this->getChannelsMaxValue(); return $this->color( array( min(max((int) round($color2->getRed() * $amount + $color1->getRed() * $amountComplement), 0), $max), min(max((int) round($color2->getGreen() * $amount + $color1->getGreen() * $amountComplement), 0), $max), min(max((int) round($color2->getBlue() * $amount + $color1->getBlue() * $amountComplement), 0), $max), ), min(max((int) round($color2->getAlpha() * $amount + $color1->getAlpha() * $amountComplement), 0), 100) ); } } imagine/src/Image/Palette/Color/CMYK.php 0000644 00000012401 15141212321 0013704 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette\Color; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\CMYK as CMYKPalette; final class CMYK implements ColorInterface { /** * @var int */ private $c; /** * @var int */ private $m; /** * @var int */ private $y; /** * @var int */ private $k; /** * @var \Imagine\Image\Palette\CMYK */ private $palette; /** * @param \Imagine\Image\Palette\CMYK $palette * @param int[] $color */ public function __construct(CMYKPalette $palette, array $color) { $this->palette = $palette; $this->setColor($color); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getValue() */ public function getValue($component) { switch ($component) { case ColorInterface::COLOR_CYAN: return $this->getCyan(); case ColorInterface::COLOR_MAGENTA: return $this->getMagenta(); case ColorInterface::COLOR_YELLOW: return $this->getYellow(); case ColorInterface::COLOR_KEYLINE: return $this->getKeyline(); default: throw new InvalidArgumentException(sprintf('Color component %s is not valid', $component)); } } /** * Returns Cyan value of the color (from 0 to 100). * * @return int */ public function getCyan() { return $this->c; } /** * Returns Magenta value of the color (from 0 to 100). * * @return int */ public function getMagenta() { return $this->m; } /** * Returns Yellow value of the color (from 0 to 100). * * @return int */ public function getYellow() { return $this->y; } /** * Returns Key value of the color (from 0 to 100). * * @return int */ public function getKeyline() { return $this->k; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getPalette() */ public function getPalette() { return $this->palette; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getAlpha() */ public function getAlpha() { return null; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::dissolve() */ public function dissolve($alpha) { throw new RuntimeException('CMYK does not support dissolution'); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::lighten() */ public function lighten($shade) { return $this->palette->color( array( $this->c, $this->m, $this->y, max(0, $this->k - $shade), ) ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::darken() */ public function darken($shade) { return $this->palette->color( array( $this->c, $this->m, $this->y, min(100, $this->k + $shade), ) ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::grayscale() */ public function grayscale() { $color = array( $this->c * (1 - $this->k / 100) + $this->k, $this->m * (1 - $this->k / 100) + $this->k, $this->y * (1 - $this->k / 100) + $this->k, ); $gray = min(100, round(0.299 * $color[0] + 0.587 * $color[1] + 0.114 * $color[2])); return $this->palette->color(array($gray, $gray, $gray, $this->k)); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::isOpaque() */ public function isOpaque() { return true; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::__toString() */ public function __toString() { return sprintf('cmyk(%d%%, %d%%, %d%%, %d%%)', $this->c, $this->m, $this->y, $this->k); } /** * Internal, Performs checks for color validity (an of array(C, M, Y, K)). * * @param int[] $color * * @throws \Imagine\Exception\InvalidArgumentException */ private function setColor(array $color) { if (count($color) !== 4) { throw new InvalidArgumentException('Color argument must look like array(C, M, Y, K), where C, M, Y, K are the integer values between 0 and 100 for cyan, magenta, yellow and black color indexes accordingly'); } $colors = array_values($color); array_walk($colors, function (&$color) { $color = max(0, min(100, $color)); }); list($this->c, $this->m, $this->y, $this->k) = $colors; } } imagine/src/Image/Palette/Color/Gray.php 0000644 00000010353 15141212321 0014047 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette\Color; use Imagine\Exception\InvalidArgumentException; use Imagine\Image\Palette\Grayscale; final class Gray implements ColorInterface { /** * @var int */ private $gray; /** * @var int */ private $alpha; /** * @var \Imagine\Image\Palette\Grayscale */ private $palette; /** * @param \Imagine\Image\Palette\Grayscale $palette * @param int[] $color * @param int $alpha */ public function __construct(Grayscale $palette, array $color, $alpha) { $this->palette = $palette; $this->setColor($color); $this->setAlpha($alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getValue() */ public function getValue($component) { switch ($component) { case ColorInterface::COLOR_GRAY: return $this->getGray(); default: throw new InvalidArgumentException(sprintf('Color component %s is not valid', $component)); } } /** * Returns Gray value of the color (from 0 to 255). * * @return int */ public function getGray() { return $this->gray; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getPalette() */ public function getPalette() { return $this->palette; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getAlpha() */ public function getAlpha() { return $this->alpha; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::dissolve() */ public function dissolve($alpha) { return $this->palette->color( array($this->gray), min(max((int) round($this->alpha + $alpha), 0), 100) ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::lighten() */ public function lighten($shade) { return $this->palette->color(array(min(255, $this->gray + $shade)), $this->alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::darken() */ public function darken($shade) { return $this->palette->color(array(max(0, $this->gray - $shade)), $this->alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::grayscale() */ public function grayscale() { return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::isOpaque() */ public function isOpaque() { return $this->alpha === 100; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::__toString() */ public function __toString() { return sprintf('#%02x%02x%02x', $this->gray, $this->gray, $this->gray); } /** * Performs checks for validity of given alpha value and sets it. * * @param int $alpha * * @throws \Imagine\Exception\InvalidArgumentException */ private function setAlpha($alpha) { if (!is_int($alpha) || $alpha < 0 || $alpha > 100) { throw new InvalidArgumentException(sprintf('Alpha must be an integer between 0 and 100, %s given', $alpha)); } $this->alpha = $alpha; } /** * Performs checks for color validity (array of array(gray)). * * @param int[] $color * * @throws \Imagine\Exception\InvalidArgumentException */ private function setColor(array $color) { if (count($color) !== 1) { throw new InvalidArgumentException('Color argument must look like array(gray), where gray is the integer value between 0 and 255 for the grayscale'); } $color = array_values($color); $color[0] = max(0, min(255, $color[0])); list($this->gray) = $color; } } imagine/src/Image/Palette/Color/RGB.php 0000644 00000012702 15141212321 0013557 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette\Color; use Imagine\Exception\InvalidArgumentException; use Imagine\Image\Palette\RGB as RGBPalette; final class RGB implements ColorInterface { /** * @var int */ private $r; /** * @var int */ private $g; /** * @var int */ private $b; /** * @var int */ private $alpha; /** * @var \Imagine\Image\Palette\RGB */ private $palette; /** * @param \Imagine\Image\Palette\RGB $palette * @param int[] $color * @param int $alpha */ public function __construct(RGBPalette $palette, array $color, $alpha) { $this->palette = $palette; $this->setColor($color); $this->setAlpha($alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getValue() */ public function getValue($component) { switch ($component) { case ColorInterface::COLOR_RED: return $this->getRed(); case ColorInterface::COLOR_GREEN: return $this->getGreen(); case ColorInterface::COLOR_BLUE: return $this->getBlue(); default: throw new InvalidArgumentException(sprintf('Color component %s is not valid', $component)); } } /** * Returns RED value of the color (from 0 to 255). * * @return int */ public function getRed() { return $this->r; } /** * Returns GREEN value of the color (from 0 to 255). * * @return int */ public function getGreen() { return $this->g; } /** * Returns BLUE value of the color (from 0 to 255). * * @return int */ public function getBlue() { return $this->b; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getPalette() */ public function getPalette() { return $this->palette; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::getAlpha() */ public function getAlpha() { return $this->alpha; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::dissolve() */ public function dissolve($alpha) { return $this->palette->color( array($this->r, $this->g, $this->b), min(max((int) round($this->alpha + $alpha), 0), 100) ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::lighten() */ public function lighten($shade) { return $this->palette->color( array( min(255, $this->r + $shade), min(255, $this->g + $shade), min(255, $this->b + $shade), ), $this->alpha ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::darken() */ public function darken($shade) { return $this->palette->color( array( max(0, $this->r - $shade), max(0, $this->g - $shade), max(0, $this->b - $shade), ), $this->alpha ); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::grayscale() */ public function grayscale() { $gray = min(255, round(0.299 * $this->getRed() + 0.114 * $this->getBlue() + 0.587 * $this->getGreen())); return $this->palette->color(array($gray, $gray, $gray), $this->alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::isOpaque() */ public function isOpaque() { return $this->alpha === 100; } /** * {@inheritdoc} * * @see \Imagine\Image\Palette\Color\ColorInterface::__toString() */ public function __toString() { return sprintf('#%02x%02x%02x', $this->r, $this->g, $this->b); } /** * Internal. * * Performs checks for validity of given alpha value and sets it * * @param int $alpha * * @throws \Imagine\Exception\InvalidArgumentException */ private function setAlpha($alpha) { if (!is_int($alpha) || $alpha < 0 || $alpha > 100) { throw new InvalidArgumentException(sprintf('Alpha must be an integer between 0 and 100, %s given', $alpha)); } $this->alpha = $alpha; } /** * Internal. * * Performs checks for color validity (array of array(R, G, B)) * * @param array $color * * @throws \Imagine\Exception\InvalidArgumentException */ private function setColor(array $color) { if (count($color) !== 3) { throw new InvalidArgumentException('Color argument must look like array(R, G, B), where R, G, B are the integer values between 0 and 255 for red, green and blue color indexes accordingly'); } $colors = array_values($color); array_walk($colors, function (&$color) { $color = max(0, min(255, $color)); }); list($this->r, $this->g, $this->b) = $colors; } } imagine/src/Image/Palette/Color/ColorInterface.php 0000644 00000005646 15141212321 0016055 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Palette\Color; interface ColorInterface { /** * Channel name: red. * * @var string */ const COLOR_RED = 'red'; /** * Channel name: green. * * @var string */ const COLOR_GREEN = 'green'; /** * Channel name: blue. * * @var string */ const COLOR_BLUE = 'blue'; /** * Channel name: cyan. * * @var string */ const COLOR_CYAN = 'cyan'; /** * Channel name: magenta. * * @var string */ const COLOR_MAGENTA = 'magenta'; /** * Channel name: yellow. * * @var string */ const COLOR_YELLOW = 'yellow'; /** * Channel name: key (black). * * @var string */ const COLOR_KEYLINE = 'keyline'; /** * Channel name: gray. * * @var string */ const COLOR_GRAY = 'gray'; /** * Return the value of one of the component. * * @param string $component One of the ColorInterface::COLOR_* component * * @throws \Imagine\Exception\InvalidArgumentException if $component is not valid * * @return int|null */ public function getValue($component); /** * Returns percentage of transparency of the color (from 0 - fully transparent, to 100 - fully opaque). * * @return int|null return NULL if the color type does not support transparency */ public function getAlpha(); /** * Returns the palette attached to the current color. * * @return \Imagine\Image\Palette\PaletteInterface */ public function getPalette(); /** * Returns a copy of current color, incrementing the alpha channel by the given amount. * * @param int $alpha * * @throws \Imagine\Exception\RuntimeException if the color type does not support transparency * * @return static */ public function dissolve($alpha); /** * Returns a copy of the current color, lightened by the specified number of shades. * * @param int $shade * * @return static */ public function lighten($shade); /** * Returns a copy of the current color, darkened by the specified number of shades. * * @param int $shade * * @return static */ public function darken($shade); /** * Returns a gray related to the current color. * * @return static */ public function grayscale(); /** * Checks if the current color is opaque. * * @return bool */ public function isOpaque(); /** * Returns hex representation of the color. * * @return string */ public function __toString(); } imagine/src/Image/AbstractImagine.php 0000644 00000005153 15141212321 0013530 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Exception\InvalidArgumentException; use Imagine\Factory\ClassFactory; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\Metadata\MetadataReaderInterface; abstract class AbstractImagine implements ImagineInterface { /** * @var \Imagine\Image\Metadata\MetadataReaderInterface|null */ private $metadataReader; /** * @var \Imagine\Factory\ClassFactoryInterface */ private $classFactory; /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::setMetadataReader() */ public function setMetadataReader(MetadataReaderInterface $metadataReader) { $this->metadataReader = $metadataReader; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::getMetadataReader() */ public function getMetadataReader() { if ($this->metadataReader === null) { $this->metadataReader = $this->getClassFactory()->createMetadataReader(); } return $this->metadataReader; } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::setClassFactory() */ public function setClassFactory(ClassFactoryInterface $classFactory) { $this->classFactory = $classFactory; return $this; } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::getClassFactory() */ public function getClassFactory() { if ($this->classFactory === null) { $this->classFactory = new ClassFactory(); } return $this->classFactory; } /** * Checks a path that could be used with ImagineInterface::open and returns * a proper string. * * @param string|object $path * * @throws \Imagine\Exception\InvalidArgumentException in case the given path is invalid * * @return string */ protected function checkPath($path) { // provide compatibility with objects such as \SplFileInfo if (is_object($path) && method_exists($path, '__toString')) { $path = (string) $path; } $handle = @fopen($path, 'r'); if ($handle === false) { throw new InvalidArgumentException(sprintf('File %s does not exist.', $path)); } fclose($handle); return $path; } } imagine/src/Image/Point.php 0000644 00000003557 15141212321 0011572 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Exception\InvalidArgumentException; /** * The point class. */ final class Point implements PointInterface { /** * @var int */ private $x; /** * @var int */ private $y; /** * Constructs a point of coordinates. * * @param int $x * @param int $y * * @throws \Imagine\Exception\InvalidArgumentException */ public function __construct($x, $y) { if ($x < 0 || $y < 0) { throw new InvalidArgumentException(sprintf('A coordinate cannot be positioned outside of a bounding box (x: %s, y: %s given)', $x, $y)); } $this->x = $x; $this->y = $y; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getX() */ public function getX() { return $this->x; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getY() */ public function getY() { return $this->y; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::in() */ public function in(BoxInterface $box) { return $this->x < $box->getWidth() && $this->y < $box->getHeight(); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::move() */ public function move($amount) { return new self($this->x + $amount, $this->y + $amount); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::__toString() */ public function __toString() { return sprintf('(%d, %d)', $this->x, $this->y); } } imagine/src/Image/AbstractFont.php 0000644 00000007143 15141212321 0013066 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Exception\InvalidArgumentException; use Imagine\Factory\ClassFactory; use Imagine\Factory\ClassFactoryAwareInterface; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\Palette\Color\ColorInterface; /** * Abstract font base class. */ abstract class AbstractFont implements FontInterface, ClassFactoryAwareInterface { /** * @var \Imagine\Factory\ClassFactoryInterface|null */ private $classFactory; /** * @var string */ protected $file; /** * @var int */ protected $size; /** * @var \Imagine\Image\Palette\Color\ColorInterface */ protected $color; /** * Constructs a font with specified $file, $size and $color. * * The font size is to be specified in points (e.g. 10pt means 10) * * @param string $file * @param int $size * @param \Imagine\Image\Palette\Color\ColorInterface $color */ public function __construct($file, $size, ColorInterface $color) { $this->file = $file; $this->size = $size; $this->color = $color; } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::getFile() */ final public function getFile() { return $this->file; } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::getSize() */ final public function getSize() { return $this->size; } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::getColor() */ final public function getColor() { return $this->color; } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::wrapText() */ public function wrapText($string, $maxWidth, $angle = 0) { $string = (string) $string; if ($string === '') { return $string; } $maxWidth = (int) round($maxWidth); if ($maxWidth < 1) { throw new InvalidArgumentException(sprintf('The $maxWidth parameter of wrapText must be greater than 0.')); } $words = explode(' ', $string); $lines = array(); $currentLine = null; foreach ($words as $word) { if ($currentLine === null) { $currentLine = $word; } else { $testLine = $currentLine . ' ' . $word; $testbox = $this->box($testLine, $angle); if ($testbox->getWidth() <= $maxWidth) { $currentLine = $testLine; } else { $lines[] = $currentLine; $currentLine = $word; } } } if ($currentLine !== null) { $lines[] = $currentLine; } return implode("\n", $lines); } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::getClassFactory() */ public function getClassFactory() { if ($this->classFactory === null) { $this->classFactory = new ClassFactory(); } return $this->classFactory; } /** * {@inheritdoc} * * @see \Imagine\Factory\ClassFactoryAwareInterface::setClassFactory() */ public function setClassFactory(ClassFactoryInterface $classFactory) { $this->classFactory = $classFactory; return $this; } } imagine/src/Image/PointSigned.php 0000644 00000003317 15141212321 0012716 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; /** * A point class that allows negative values of coordinates. */ final class PointSigned implements PointInterface { /** * @var int */ private $x; /** * @var int */ private $y; /** * Constructs a point of coordinates. * * @param int $x * @param int $y * * @throws \Imagine\Exception\InvalidArgumentException */ public function __construct($x, $y) { $this->x = $x; $this->y = $y; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getX() */ public function getX() { return $this->x; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::getY() */ public function getY() { return $this->y; } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::in() */ public function in(BoxInterface $box) { return $this->x >= 0 && $this->x < $box->getWidth() && $this->y >= 0 && $this->y < $box->getHeight(); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::move() */ public function move($amount) { return new self($this->x + $amount, $this->y + $amount); } /** * {@inheritdoc} * * @see \Imagine\Image\PointInterface::__toString() */ public function __toString() { return sprintf('(%d, %d)', $this->x, $this->y); } } imagine/src/Image/Profile.php 0000644 00000003115 15141212321 0012067 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Exception\InvalidArgumentException; /** * The default implementation of ProfileInterface. */ class Profile implements ProfileInterface { /** * @var string */ private $data; /** * @var string */ private $name; /** * @param string $name * @param string $data */ public function __construct($name, $data) { $this->name = $name; $this->data = $data; } /** * {@inheritdoc} * * @see \Imagine\Image\ProfileInterface::name() */ public function name() { return $this->name; } /** * {@inheritdoc} * * @see \Imagine\Image\ProfileInterface::data() */ public function data() { return $this->data; } /** * Creates a profile from a path to a file. * * @param string $path * * @throws \Imagine\Exception\InvalidArgumentException In case the provided path is not valid * * @return static */ public static function fromPath($path) { if (!file_exists($path) || !is_file($path) || !is_readable($path)) { throw new InvalidArgumentException(sprintf('Path %s is an invalid profile file or is not readable', $path)); } return new static(basename($path), file_get_contents($path)); } } imagine/src/Image/FormatList.php 0000644 00000002542 15141212321 0012556 0 ustar 00 <?php namespace Imagine\Image; /** * Holds a list of image formats. * * @since 1.3.0 */ class FormatList { /** * @var \Imagine\Image\Format[] */ private $formats; public function __construct(array $formats) { $this->formats = $formats; } /** * @return \Imagine\Image\Format[] */ public function getAll() { return $this->formats; } /** * @return string[] */ public function getAllIDs() { $result = array(); foreach ($this->getAll() as $format) { $result[] = $format->getID(); } return $result; } /** * Get a format given its ID. * * @param \Imagine\Image\Format|string $format the format (a Format instance of a format ID) * * @return \Imagine\Image\Format|null */ public function find($format) { if (is_string($format)) { $format = strtolower(trim($format)); if ($format === '') { return null; } foreach ($this->getAll() as $f) { if ($f->getID() === $format || in_array($format, $f->getAlternativeIDs(), true)) { return $f; } } return null; } return in_array($format, $this->getAll(), true) ? $format : null; } } imagine/src/Image/Histogram/Bucket.php 0000644 00000002116 15141212321 0013641 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Histogram; /** * Bucket histogram. */ final class Bucket implements \Countable { /** * @var \Imagine\Image\Histogram\Range */ private $range; /** * @var int */ private $count; /** * @param \Imagine\Image\Histogram\Range $range * @param int $count */ public function __construct(Range $range, $count = 0) { $this->range = $range; $this->count = $count; } /** * @param int $value * * @return $this */ public function add($value) { if ($this->range->contains($value)) { $this->count++; } return $this; } /** * @return int the number of elements in the bucket */ #[\ReturnTypeWillChange] public function count() { return $this->count; } } imagine/src/Image/Histogram/Range.php 0000644 00000002075 15141212321 0013464 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Histogram; use Imagine\Exception\OutOfBoundsException; /** * Range histogram. */ final class Range { /** * @var int */ private $start; /** * @var int */ private $end; /** * @param int $start * @param int $end * * @throws \Imagine\Exception\OutOfBoundsException */ public function __construct($start, $end) { if ($end <= $start) { throw new OutOfBoundsException(sprintf('Range end cannot be bigger than start, %d %d given accordingly', $this->start, $this->end)); } $this->start = $start; $this->end = $end; } /** * @param int $value * * @return bool */ public function contains($value) { return $value >= $this->start && $value < $this->end; } } imagine/src/Image/Metadata/DefaultMetadataReader.php 0000644 00000002016 15141212321 0016356 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Metadata; /** * A metadata reader that actually doesn't try to extract metadata. */ class DefaultMetadataReader extends AbstractMetadataReader { /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\AbstractMetadataReader::extractFromFile() */ protected function extractFromFile($file) { return array(); } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\AbstractMetadataReader::extractFromData() */ protected function extractFromData($data) { return array(); } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\AbstractMetadataReader::extractFromStream() */ protected function extractFromStream($resource) { return array(); } } imagine/src/Image/Metadata/MetadataBag.php 0000644 00000004704 15141212321 0014346 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Metadata; /** * The container of the data extracted from metadata. */ class MetadataBag implements \ArrayAccess, \IteratorAggregate, \Countable { /** * @var array */ private $data; /** * @param array $data */ public function __construct(array $data = array()) { $this->data = $data; } /** * Returns the metadata key, default value if it does not exist. * * @param string $key * @param mixed|null $default * * @return mixed */ public function get($key, $default = null) { return array_key_exists($key, $this->data) ? $this->data[$key] : $default; } /** * {@inheritdoc} * * @see \Countable::count() * * @return int */ #[\ReturnTypeWillChange] public function count() { return count($this->data); } /** * {@inheritdoc} * * @see \IteratorAggregate::getIterator() * * @return \ArrayIterator */ #[\ReturnTypeWillChange] public function getIterator() { return new \ArrayIterator($this->data); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetExists() * * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return array_key_exists($offset, $this->data); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetSet() * * @return void */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { $this->data[$offset] = $value; } /** * {@inheritdoc} * * @see \ArrayAccess::offsetUnset() * * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { unset($this->data[$offset]); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetGet() * * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->get($offset); } /** * Returns metadata as an associative array. * * @return array */ public function toArray() { return $this->data; } } imagine/src/Image/Metadata/MetadataReaderInterface.php 0000644 00000002532 15141212321 0016675 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Metadata; /** * Interface that metadata readers must implement. */ interface MetadataReaderInterface { /** * Reads metadata from a file. * * @param string|\Imagine\File\LoaderInterface $file the path to the file where to read metadata * * @throws \Imagine\Exception\InvalidArgumentException in case the file does not exist * * @return \Imagine\Image\Metadata\MetadataBag */ public function readFile($file); /** * Reads metadata from a binary string. * * @param string $data the binary string to read * @param resource $originalResource an optional resource to gather stream metadata * * @return \Imagine\Image\Metadata\MetadataBag */ public function readData($data, $originalResource = null); /** * Reads metadata from a stream. * * @param resource $resource the stream to read * * @throws \Imagine\Exception\InvalidArgumentException in case the resource is not valid * * @return \Imagine\Image\Metadata\MetadataBag */ public function readStream($resource); } imagine/src/Image/Metadata/ExifMetadataReader.php 0000644 00000007631 15141212321 0015675 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Metadata; use Imagine\Exception\NotSupportedException; use Imagine\File\Loader; use Imagine\File\LoaderInterface; use Imagine\Utils\ErrorHandling; /** * Metadata driven by Exif information. */ class ExifMetadataReader extends AbstractMetadataReader { /** * @throws \Imagine\Exception\NotSupportedException */ public function __construct() { $whyNot = static::getUnsupportedReason(); if ($whyNot !== '') { throw new NotSupportedException($whyNot); } } /** * Get the reason why this metadata reader is not supported. * * @return string empty string if the reader is available */ public static function getUnsupportedReason() { if (!function_exists('exif_read_data')) { return 'The PHP EXIF extension is required to use the ExifMetadataReader'; } if (!in_array('data', stream_get_wrappers(), true)) { return 'The data:// stream wrapper must be enabled'; } if (in_array(ini_get('allow_url_fopen'), array('', '0', 0), true)) { return 'The allow_url_fopen php.ini configuration key must be set to 1'; } return ''; } /** * Is this metadata reader supported? * * @return bool */ public static function isSupported() { return static::getUnsupportedReason() === ''; } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\AbstractMetadataReader::extractFromFile() */ protected function extractFromFile($file) { $loader = $file instanceof LoaderInterface ? $file : new Loader($file); if ($loader->isLocalFile()) { return $this->extract($loader->getPath()); } return $this->doReadData($loader->getData()); } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\AbstractMetadataReader::extractFromData() */ protected function extractFromData($data) { return $this->doReadData($data); } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\AbstractMetadataReader::extractFromStream() */ protected function extractFromStream($resource) { return $this->doReadData(stream_get_contents($resource)); } /** * Extracts metadata from raw data, merges with existing metadata. * * @param string $data * * @return array */ private function doReadData($data) { if (substr($data, 0, 2) === 'II') { $mime = 'image/tiff'; } else { $mime = 'image/jpeg'; } return $this->extract('data://' . $mime . ';base64,' . base64_encode($data)); } /** * Performs the exif data extraction given a path or data-URI representation. * * @param string $path the path to the file or the data-URI representation * * @return array */ private function extract($path) { try { $exifData = ErrorHandling::ignoring(-1, function () use ($path) { return @exif_read_data($path, null, true, false); }); } catch (\Exception $e) { $exifData = false; } catch (\Throwable $e) { $exifData = false; } if (!is_array($exifData)) { return array(); } $metadata = array(); foreach ($exifData as $prefix => $values) { if (is_array($values)) { $prefix = strtolower($prefix); foreach ($values as $prop => $value) { $metadata[$prefix . '.' . $prop] = $value; } } } return $metadata; } } imagine/src/Image/Metadata/AbstractMetadataReader.php 0000644 00000006211 15141212321 0016536 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Metadata; use Imagine\Exception\InvalidArgumentException; use Imagine\File\Loader; use Imagine\File\LoaderInterface; /** * Base class for the default metadata readers. */ abstract class AbstractMetadataReader implements MetadataReaderInterface { /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\MetadataReaderInterface::readFile() */ public function readFile($file) { $loader = $file instanceof LoaderInterface ? $file : new Loader($file); return new MetadataBag(array_merge($this->getStreamMetadata($loader), $this->extractFromFile($loader))); } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\MetadataReaderInterface::readData() */ public function readData($data, $originalResource = null) { if ($originalResource !== null) { return new MetadataBag(array_merge($this->getStreamMetadata($originalResource), $this->extractFromData($data))); } return new MetadataBag($this->extractFromData($data)); } /** * {@inheritdoc} * * @see \Imagine\Image\Metadata\MetadataReaderInterface::readStream() */ public function readStream($resource) { if (!is_resource($resource)) { throw new InvalidArgumentException('Invalid resource provided.'); } return new MetadataBag(array_merge($this->getStreamMetadata($resource), $this->extractFromStream($resource))); } /** * Gets the URI from a stream resource. * * @param resource|\Imagine\File\LoaderInterface $resource * * @return array */ private function getStreamMetadata($resource) { $metadata = array(); if ($resource instanceof LoaderInterface) { $metadata['uri'] = $resource->getPath(); if ($resource->isLocalFile()) { $metadata['filepath'] = realpath($resource->getPath()); } } elseif (false !== $data = @stream_get_meta_data($resource)) { if (isset($data['uri'])) { $metadata['uri'] = $data['uri']; if (stream_is_local($resource)) { $metadata['filepath'] = realpath($data['uri']); } } } return $metadata; } /** * Extracts metadata from a file. * * @param string|\Imagine\File\LoaderInterface $file * * @return array An associative array of metadata */ abstract protected function extractFromFile($file); /** * Extracts metadata from raw data. * * @param $data * * @return array An associative array of metadata */ abstract protected function extractFromData($data); /** * Extracts metadata from a stream. * * @param $resource * * @return array An associative array of metadata */ abstract protected function extractFromStream($resource); } imagine/src/Image/LayersInterface.php 0000644 00000005176 15141212321 0013560 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; /** * The layers interface. */ interface LayersInterface extends \Iterator, \Countable, \ArrayAccess { /** * Merge layers into the original objects. * * @throws \Imagine\Exception\RuntimeException */ public function merge(); /** * Animates layers. * * @param string $format The output output format * @param int $delay The delay in milliseconds between two frames * @param int $loops The number of loops, 0 means infinite * * @throws \Imagine\Exception\InvalidArgumentException In case an invalid argument is provided * @throws \Imagine\Exception\RuntimeException In case the driver fails to animate * * @return $this */ public function animate($format, $delay, $loops); /** * Coalesce layers. Each layer in the sequence is the same size as the first and composited with the next layer in * the sequence. * * @throws \Imagine\Exception\NotSupportedException * * @return $this */ public function coalesce(); /** * Adds an image at the end of the layers stack. * * @param \Imagine\Image\ImageInterface $image * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function add(ImageInterface $image); /** * Set an image at offset. * * @param int $offset * @param \Imagine\Image\ImageInterface $image * * @throws \Imagine\Exception\RuntimeException * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\OutOfBoundsException * * @return $this */ public function set($offset, ImageInterface $image); /** * Removes the image at offset. * * @param int $offset * * @throws \Imagine\Exception\RuntimeException * @throws \Imagine\Exception\InvalidArgumentException * * @return $this */ public function remove($offset); /** * Returns the image at offset. * * @param int $offset * * @throws \Imagine\Exception\RuntimeException * @throws \Imagine\Exception\InvalidArgumentException * * @return \Imagine\Image\ImageInterface */ public function get($offset); /** * Returns true if a layer at offset is preset. * * @param int $offset * * @return bool */ public function has($offset); } imagine/src/Image/ManipulatorInterface.php 0000644 00000013432 15141212321 0014606 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; use Imagine\Image\Fill\FillInterface; use Imagine\Image\Palette\Color\ColorInterface; /** * The manipulator interface. */ interface ManipulatorInterface { /** * The original image is scaled so it is fully contained within the thumbnail dimensions (the image width/height ratio doesn't change). * * @var int */ const THUMBNAIL_INSET = 0x00000001; /** * The thumbnail is scaled so that its smallest side equals the length of the corresponding side in the original image (the width or the height are cropped). * * @var int */ const THUMBNAIL_OUTBOUND = 0x00000002; /** * Allow upscaling the image if it's smaller than the wanted thumbnail size. * * @var int */ const THUMBNAIL_FLAG_UPSCALE = 0x00010000; /** * Instead of creating a new image instance, the thumbnail method modifies the original image (saving memory. * * @var int */ const THUMBNAIL_FLAG_NOCLONE = 0x00020000; /** * Copies current source image into a new ImageInterface instance. * * @throws \Imagine\Exception\RuntimeException * * @return static */ public function copy(); /** * Crops a specified box out of the source image (modifies the source image) * Returns cropped self. * * @param \Imagine\Image\PointInterface $start * @param \Imagine\Image\BoxInterface $size * * @throws \Imagine\Exception\OutOfBoundsException * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function crop(PointInterface $start, BoxInterface $size); /** * Resizes current image and returns self. * * @param \Imagine\Image\BoxInterface $size * @param string $filter * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED); /** * Rotates an image at the given angle. * Optional $background can be used to specify the fill color of the empty * area of rotated image. * * @param int $angle * @param \Imagine\Image\Palette\Color\ColorInterface $background * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function rotate($angle, ColorInterface $background = null); /** * Pastes an image into a parent image * Throws exceptions if image exceeds parent image borders or if paste * operation fails. * * Returns source image * * @param \Imagine\Image\ImageInterface $image * @param \Imagine\Image\PointInterface $start * @param int $alpha How to paste the image, from 0 (fully transparent) to 100 (fully opaque) * * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function paste(ImageInterface $image, PointInterface $start, $alpha = 100); /** * Saves the image at a specified path, the target file extension is used * to determine file format, only jpg, jpeg, gif, png, wbmp, xbm, webp and bmp are * supported. * Please remark that bmp is supported by the GD driver only since PHP 7.2. * * @param string $path * @param array $options * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function save($path = null, array $options = array()); /** * Outputs the image content. * * @param string $format * @param array $options * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function show($format, array $options = array()); /** * Flips current image using vertical axis. * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function flipHorizontally(); /** * Flips current image using horizontal axis. * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function flipVertically(); /** * Remove all profiles and comments. * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function strip(); /** * Generates a thumbnail from a current image * Returns it as a new image without modifying the current image unless the THUMBNAIL_FLAG_NOCLONE flag is specified. * * @param \Imagine\Image\BoxInterface $size * @param int|string $settings One or more of the ManipulatorInterface::THUMBNAIL_ flags (joined with |). It may be a string for backward compatibility with old constant values that were strings. * @param string $filter The filter to use for resizing, one of ImageInterface::FILTER_* * * @throws \Imagine\Exception\RuntimeException * * @return static */ public function thumbnail(BoxInterface $size, $settings = self::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED); /** * Applies a given mask to current image's alpha channel. * * @param \Imagine\Image\ImageInterface $mask * * @return $this */ public function applyMask(ImageInterface $mask); /** * Fills image with provided filling, by replacing each pixel's color in * the current image with corresponding color from FillInterface, and * returns modified image. * * @param \Imagine\Image\Fill\FillInterface $fill * * @return $this */ public function fill(FillInterface $fill); } imagine/src/Image/Fill/Gradient/Linear.php 0000644 00000004321 15141212321 0014324 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Fill\Gradient; use Imagine\Image\Fill\FillInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\PointInterface; /** * Linear gradient fill. */ abstract class Linear implements FillInterface { /** * @var int */ private $length; /** * @var \Imagine\Image\Palette\Color\ColorInterface */ private $start; /** * @var \Imagine\Image\Palette\Color\ColorInterface */ private $end; /** * Constructs a linear gradient with overall gradient length, and start and * end shades, which default to 0 and 255 accordingly. * * @param int $length * @param \Imagine\Image\Palette\Color\ColorInterface $start * @param \Imagine\Image\Palette\Color\ColorInterface $end */ final public function __construct($length, ColorInterface $start, ColorInterface $end) { $this->length = $length; $this->start = $start; $this->end = $end; } /** * {@inheritdoc} * * @see \Imagine\Image\Fill\FillInterface::getColor() */ final public function getColor(PointInterface $position) { $l = $this->getDistance($position); if ($l >= $this->length) { return $this->end; } if ($l < 0) { return $this->start; } return $this->start->getPalette()->blend($this->start, $this->end, $l / $this->length); } /** * @return \Imagine\Image\Palette\Color\ColorInterface */ final public function getStart() { return $this->start; } /** * @return \Imagine\Image\Palette\Color\ColorInterface */ final public function getEnd() { return $this->end; } /** * Get the distance of the position relative to the beginning of the gradient. * * @param \Imagine\Image\PointInterface $position * * @return int */ abstract protected function getDistance(PointInterface $position); } imagine/src/Image/Fill/Gradient/Horizontal.php 0000644 00000001137 15141212321 0015245 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Fill\Gradient; use Imagine\Image\PointInterface; /** * Horizontal gradient fill. */ final class Horizontal extends Linear { /** * {@inheritdoc} * * @see \Imagine\Image\Fill\Gradient\Linear::getDistance() */ public function getDistance(PointInterface $position) { return $position->getX(); } } imagine/src/Image/Fill/Gradient/Vertical.php 0000644 00000001133 15141212321 0014661 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Fill\Gradient; use Imagine\Image\PointInterface; /** * Vertical gradient fill. */ final class Vertical extends Linear { /** * {@inheritdoc} * * @see \Imagine\Image\Fill\Gradient\Linear::getDistance() */ public function getDistance(PointInterface $position) { return $position->getY(); } } imagine/src/Image/Fill/FillInterface.php 0000644 00000001160 15141212321 0014062 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image\Fill; use Imagine\Image\PointInterface; /** * Interface for the fill. */ interface FillInterface { /** * Gets color of the fill for the given position. * * @param \Imagine\Image\PointInterface $position * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function getColor(PointInterface $position); } imagine/src/Image/PointInterface.php 0000644 00000002104 15141212321 0013376 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Image; /** * The point interface. */ interface PointInterface { /** * Gets points x coordinate. * * @return int */ public function getX(); /** * Gets points y coordinate. * * @return int */ public function getY(); /** * Checks if current coordinate is inside a given box. * * @param \Imagine\Image\BoxInterface $box * * @return bool */ public function in(BoxInterface $box); /** * Returns another point, moved by a given amount from current coordinates. * * @param int $amount * * @return \Imagine\Image\PointInterface */ public function move($amount); /** * Gets a string representation for the current point. * * @return string */ public function __toString(); } imagine/src/Driver/AbstractInfo.php 0000644 00000010314 15141212321 0013256 0 ustar 00 <?php namespace Imagine\Driver; use Imagine\Exception\NotSupportedException; use Imagine\Image\Palette\PaletteInterface; /** * Base class for the default DriverInfo classes. * * @since 1.3.0 */ abstract class AbstractInfo implements Info { /** * @var static|\Imagine\Exception\NotSupportedException|null */ private static $instance; /** * @var string */ private $driverRawVersion; /** * @var string */ private $driverSemverVersion; /** * @var string */ private $engineRawVersion; /** * @var string */ private $engineSemverVersion; /** * @var \Imagine\Image\FormatList|null */ private $supportedFormats = null; /** * @param string $driverRawVersion * @param string $driverSemverVersion * @param string $engineRawVersion * @param string $engineSemverVersion */ protected function __construct($driverRawVersion, $driverSemverVersion, $engineRawVersion, $engineSemverVersion) { $this->driverRawVersion = $driverRawVersion; $this->driverSemverVersion = $driverSemverVersion; $this->engineRawVersion = $engineRawVersion; $this->engineSemverVersion = $engineSemverVersion; } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::getDriverVersion() */ public function getDriverVersion($raw = false) { return $raw ? $this->driverRawVersion : $this->driverSemverVersion; } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::getEngineVersion() */ public function getEngineVersion($raw = false) { return $raw ? $this->engineRawVersion : $this->engineSemverVersion; } /** * Check if the driver has a specific feature. * * @param int $feature The feature to be checked (see the Info::FEATURE_... constants) * * @throws \Imagine\Exception\NotSupportedException if any of the requested features is not supported */ abstract protected function checkFeature($feature); /** * {@inheritdoc} * * @see \Imagine\Driver\Info::checkVersionIsSupported() */ public function checkVersionIsSupported() { if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50500) { throw new NotSupportedException('Imagine requires PHP 5.5 or later'); } } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::requireFeature() */ public function requireFeature($features) { $features = array_map('intval', is_array($features) ? $features : array($features)); foreach ($features as $feature) { $this->checkFeature($feature); } } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::hasFeature() */ public function hasFeature($features) { try { $this->requireFeature($features); } catch (NotSupportedException $x) { return false; } return true; } /** * Build the list of supported file formats. * * @return \Imagine\Image\FormatList */ abstract protected function buildSupportedFormats(); /** * {@inheritdoc} * * @see \Imagine\Driver\Info::getSupportedFormats() */ public function getSupportedFormats() { if ($this->supportedFormats === null) { $this->supportedFormats = $this->buildSupportedFormats(); } return $this->supportedFormats; } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::isFormatSupported() */ public function isFormatSupported($format) { return $this->getSupportedFormats()->find($format) !== null; } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::requirePaletteSupport() */ public function requirePaletteSupport(PaletteInterface $palette) { } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::isPaletteSupported() */ public function isPaletteSupported(PaletteInterface $palette) { try { $this->requirePaletteSupport($palette); } catch (NotSupportedException $x) { return false; } return true; } } imagine/src/Driver/Info.php 0000644 00000016155 15141212321 0011603 0 ustar 00 <?php namespace Imagine\Driver; use Imagine\Image\Palette\PaletteInterface; /** * Provide information and features supported by a graphics driver. * * @since 1.3.0 */ interface Info { /** * Affected functions: Imagine\Image\ImageInterface::profile(), Imagine\Image\ImageInterface::usePalette(). * * @var int */ const FEATURE_COLORPROFILES = 1; /** * Affected functions: Imagine\Image\ImageInterface::usePalette(), opening images with particular colorspaces. * * See also the requirePaletteSupport/isPaletteSupported driver info methods. * * @var int */ const FEATURE_COLORSPACECONVERSION = 2; /** * Affected functions: Imagine\Effects\EffectsInterface::grayscale(). * * @var int */ const FEATURE_GRAYSCALEEFFECT = 3; /** * Affected functions: Imagine\Image\LayersInterface::coalesce(). * * See also the FEATURE_MULTIPLELAYERS feature * * @var int */ const FEATURE_COALESCELAYERS = 4; /** * Affected functions: Imagine\Effects\EffectsInterface::negative(). * * @var int */ const FEATURE_NEGATEIMAGE = 5; /** * Affected functions: Imagine\Effects\EffectsInterface::colorize(). * * @var int */ const FEATURE_COLORIZEIMAGE = 6; /** * Affected functions: Imagine\Effects\EffectsInterface::sharpen(). * * @var int */ const FEATURE_SHARPENIMAGE = 7; /** * Affected functions: Imagine\Effects\EffectsInterface::convolve(). * * @var int */ const FEATURE_CONVOLVEIMAGE = 8; /** * Affected functions: Imagine\Draw\DrawerInterface::text() and Imagine\Image\FontInterface::box(). * * @var int */ const FEATURE_TEXTFUNCTIONS = 9; /** * Affected functions: Imagine\Image\LayersInterface methods that would create more that 1 layer or 0 layers. * * @var int */ const FEATURE_MULTIPLELAYERS = 10; /** * Affected functions: none at the moment. * * @var int */ const FEATURE_CUSTOMRESOLUTION = 11; /** * Affected functions: Imagine\Image\ImageInterface::get(), Imagine\Image\ImageInterface::save(), Imagine\Image\ImageInterface::show(). * * @var int */ const FEATURE_EXPORTWITHCUSTOMRESOLUTION = 12; /** * Affected functions: Imagine\Draw\DrawerInterface::chord() with $fill == true. * * @var int */ const FEATURE_DRAWFILLEDCHORDSCORRECTLY = 13; /** * Affected functions: Imagine\Draw\DrawerInterface::circle() with $fill == false and $thickness > 1. * * @var int */ const FEATURE_DRAWUNFILLEDCIRCLESWITHTICHKESSCORRECTLY = 14; /** * Affected functions: Imagine\Draw\DrawerInterface::ellipse() with $fill == false and $thickness > 1. * * @var int */ const FEATURE_DRAWUNFILLEDELLIPSESWITHTICHKESSCORRECTLY = 15; /** * Affected functions: Imagine\Image\ImageInterface::getColorAt() when the palette is CMYK. * * @var int */ const FEATURE_GETCMYKCOLORSCORRECTLY = 16; /** * Affected functions: any that uses colors with an alpha channel. * * @var int */ const FEATURE_TRANSPARENCY = 17; /** * Affected functions: Imagine\Image\ImageInterface::rotate(), Imagine\Filter\Basic\Rotate. * * @var int */ const FEATURE_ROTATEIMAGEWITHCORRECTSIZE = 18; /** * Affected functions: Imagine\Image\ImageInterface::get(), Imagine\Image\ImageInterface::save(), Imagine\Image\ImageInterface::show(). * * @var int */ const FEATURE_EXPORTWITHCUSTOMJPEGSAMPLINGFACTORS = 19; /** * Adding frames to a image with no previously loaded layers works. * * @var int */ const FEATURE_ADDLAYERSTOEMPTYIMAGE = 20; /** * Affected functions: Imagine\Image\ImagineInterface::open(), Imagine\Image\ImagineInterface::load(), Imagine\Image\ImagineInterface::read(). * * @var int */ const FEATURE_DETECTGRAYCOLORSPACE = 21; /** * Get the Info instance for a specific driver. * * @param bool $required when the driver is not available: if FALSE the function returns NULL, if TRUE the driver throws a \Imagine\Exception\NotSupportedException * * @throws \Imagine\Exception\NotSupportedException if $required is TRUE and the driver is not available * * @return static|null return NULL if the driver is not available and $required is FALSE */ public static function get($required = true); /** * Check if the current driver/engine version combination is supported. * * @throws \Imagine\Exception\NotSupportedException if the version combination is not supported */ public function checkVersionIsSupported(); /** * Get the version of the driver. * For example: * - for GD: it's the version of PHP * - for gmagick: it's the version of the gmagick PHP extension * - for imagick: it's the version of the imagick PHP extension. * * @param bool $raw if false the result will be in the format <major>.<minor>.<patch>, if TRUE the result will be the raw version */ public function getDriverVersion($raw = false); /** * Get the version of the library used by the driver. * For example: * - for GD: it's the version of libgd * - for gmagick: it's the version of the GraphicsMagick * - for imagick: it's the version of the ImageMagick. * * @param bool $raw if false the result will be in the format <major>.<minor>.<patch>, if TRUE the result will be the raw version */ public function getEngineVersion($raw = false); /** * Check if the driver the features requested. * * @param int|int[] $features The features to be checked (see the Info::FEATURE_... constants) * * @return bool returns TRUE if the driver supports all the specified features, FALSE otherwise */ public function hasFeature($features); /** * Check if the driver has the features requested. * * @param int|int[] $features The features to be checked (see the Info::FEATURE_... constants) * * @throws \Imagine\Exception\NotSupportedException if any of the requested features is not supported */ public function requireFeature($features); /** * Get the list of supported file formats. * * @return \Imagine\Image\FormatList */ public function getSupportedFormats(); /** * Check if a format is supported. * * @param \Imagine\Image\Format|string $format * * @return bool */ public function isFormatSupported($format); /** * Check if a palette is supported. * * @param \Imagine\Image\Palette\PaletteInterface $palette * * @throws \Imagine\Exception\NotSupportedException if the palette is not supported */ public function requirePaletteSupport(PaletteInterface $palette); /** * Check if a palette is supported. * * @param \Imagine\Image\Palette\PaletteInterface $palette * * @return bool */ public function isPaletteSupported(PaletteInterface $palette); } imagine/src/Driver/InfoProvider.php 0000644 00000000771 15141212321 0013313 0 ustar 00 <?php namespace Imagine\Driver; /** * Interface implemented by classes that provide info about a graphics driver. * * @since 1.3.0 */ interface InfoProvider { /** * Get the info about this driver. * * @param bool $required when the driver is not available: if FALSE the function returns NULL, if TRUE the driver throws a \Imagine\Exception\NotSupportedException * * @return \Imagine\Driver\Info|null */ public static function getDriverInfo($required = true); } imagine/src/Imagick/Font.php 0000644 00000003562 15141212321 0011725 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Imagick; use Imagine\Driver\InfoProvider; use Imagine\Image\AbstractFont; use Imagine\Image\Palette\Color\ColorInterface; /** * Font implementation using the Imagick PHP extension. */ final class Font extends AbstractFont implements InfoProvider { /** * @var \Imagick */ private $imagick; /** * @param \Imagick $imagick * @param string $file * @param int $size * @param \Imagine\Image\Palette\Color\ColorInterface $color */ public function __construct(\Imagick $imagick, $file, $size, ColorInterface $color) { $this->imagick = $imagick; parent::__construct($file, $size, $color); } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::box() */ public function box($string, $angle = 0) { $text = new \ImagickDraw(); $text->setFont($this->file); // ensure font resolution is the same as GD's hard-coded 96 if (static::getDriverInfo()->hasFeature(DriverInfo::FEATURE_CUSTOMRESOLUTION)) { $text->setResolution(96, 96); $text->setFontSize($this->size); } else { $text->setFontSize((int) ($this->size * (96 / 72))); } $info = $this->imagick->queryFontMetrics($text, $string); $box = $this->getClassFactory()->createBox($info['textWidth'], $info['textHeight']); return $box; } } imagine/src/Imagick/Imagine.php 0000644 00000017052 15141212321 0012367 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Imagick; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\NotSupportedException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\File\LoaderInterface; use Imagine\Image\AbstractImagine; use Imagine\Image\BoxInterface; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\CMYK; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Grayscale; use Imagine\Image\Palette\RGB; use Imagine\Utils\ErrorHandling; /** * Imagine implementation using the Imagick PHP extension. * * @final */ class Imagine extends AbstractImagine implements InfoProvider { /** * @throws \Imagine\Exception\RuntimeException */ public function __construct() { static::getDriverInfo()->checkVersionIsSupported(); } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::open() */ public function open($path) { $loader = $path instanceof LoaderInterface ? $path : $this->getClassFactory()->createFileLoader($path); $path = $loader->getPath(); try { if ($loader->isLocalFile()) { if (DIRECTORY_SEPARATOR === '\\' && PHP_INT_SIZE === 8 && defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70200) { // Passing the file name to the Imagick constructor may break PHP 7.1 64 bit on Windows - see https://github.com/mkoppanen/imagick/issues/252 $imagick = new Imagick(); $imagick->readImageBlob($loader->getData(), $path); } else { $imagick = new Imagick($path); } } else { $imagick = new Imagick(); $imagick->readImageBlob($loader->getData()); } $image = $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_IMAGICK, $imagick, $this->createPalette($imagick), $this->getMetadataReader()->readFile($loader)); } catch (\ImagickException $e) { throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e); } return $image; } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::create() */ public function create(BoxInterface $size, ColorInterface $color = null) { $width = $size->getWidth(); $height = $size->getHeight(); $palette = $color !== null ? $color->getPalette() : new RGB(); $color = $color !== null ? $color : $palette->color('fff'); try { $pixel = new \ImagickPixel((string) $color); $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); $imagick = new Imagick(); $imagick->newImage($width, $height, $pixel); $imagick->setImageMatte(true); $imagick->setImageBackgroundColor($pixel); // Setting image alpha requires ImageMagick version 6.3.1 or higher is required if (version_compare(static::getDriverInfo()->getEngineVersion(), '6.3.1') >= 0) { // setImageOpacity was replaced with setImageAlpha in php-imagick v3.4.3 if (method_exists($imagick, 'setImageAlpha')) { $imagick->setImageAlpha($pixel->getColorValue(\Imagick::COLOR_ALPHA)); } else { ErrorHandling::ignoring(E_DEPRECATED, function () use ($imagick, $pixel) { $imagick->setImageOpacity($pixel->getColorValue(\Imagick::COLOR_ALPHA)); }); } } $pixel->clear(); $pixel->destroy(); return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_IMAGICK, $imagick, $palette, new MetadataBag()); } catch (\ImagickException $e) { throw new RuntimeException('Could not create empty image', $e->getCode(), $e); } } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::load() */ public function load($string) { try { $imagick = new Imagick(); $imagick->readImageBlob($string); $imagick->setImageMatte(true); return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_IMAGICK, $imagick, $this->createPalette($imagick), $this->getMetadataReader()->readData($string)); } catch (\ImagickException $e) { throw new RuntimeException('Could not load image from string', $e->getCode(), $e); } } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::read() */ public function read($resource) { if (!is_resource($resource)) { throw new InvalidArgumentException('Variable does not contain a stream resource'); } $content = stream_get_contents($resource); try { $imagick = new Imagick(); $imagick->readImageBlob($content); } catch (\ImagickException $e) { throw new RuntimeException('Could not read image from resource', $e->getCode(), $e); } return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_IMAGICK, $imagick, $this->createPalette($imagick), $this->getMetadataReader()->readData($content, $resource)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::font() */ public function font($file, $size, ColorInterface $color) { return $this->getClassFactory()->createFont(ClassFactoryInterface::HANDLE_IMAGICK, $file, $size, $color); } /** * Returns the palette corresponding to an \Imagick resource colorspace. * * @param \Imagick $imagick * * @throws \Imagine\Exception\NotSupportedException * * @return \Imagine\Image\Palette\CMYK|\Imagine\Image\Palette\Grayscale|\Imagine\Image\Palette\RGB */ private function createPalette(\Imagick $imagick) { switch ($imagick->getImageColorspace()) { case \Imagick::COLORSPACE_RGB: case \Imagick::COLORSPACE_SRGB: return new RGB(); case \Imagick::COLORSPACE_CMYK: return new CMYK(); case \Imagick::COLORSPACE_GRAY: return new Grayscale(); case \Imagick::COLORSPACE_YCBCR: static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORSPACECONVERSION); try { $profile = $imagick->getImageProfile('icc'); } catch (\ImagickException $e) { $profile = null; } $imagick->transformImageColorspace(\Imagick::COLORSPACE_SRGB); if ($profile) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORPROFILES); $imagick->setImageProfile('icc', $profile); } return new RGB(); default: throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported'); } } } imagine/src/Imagick/DriverInfo.php 0000644 00000015601 15141212321 0013063 0 ustar 00 <?php namespace Imagine\Imagick; use Imagine\Driver\AbstractInfo; use Imagine\Exception\NotSupportedException; use Imagine\Image\Format; use Imagine\Image\FormatList; /** * Provide information and features supported by the Imagick graphics driver. * * @since 1.3.0 */ class DriverInfo extends AbstractInfo { /** * @var static|\Imagine\Exception\NotSupportedException|null */ private static $instance; /** * @var bool|null */ private $colorProfilesSupported = null; /** * @var bool|null */ private $colorspaceConversionAvailable = null; /** * @throws \Imagine\Exception\NotSupportedException */ protected function __construct() { if (!class_exists('Imagick') || !extension_loaded('imagick')) { throw new NotSupportedException('Imagick driver not installed'); } $m = null; $extensionVersion = phpversion('imagick'); $driverRawVersion = is_string($extensionVersion) ? $extensionVersion : ''; $driverSemverVersion = preg_match('/^.*?(\d+\.\d+\.\d+)/', $driverRawVersion, $m) ? $m[1] : ''; $imagick = new \Imagick(); $engineVersion = $imagick->getversion(); if (is_array($engineVersion) && isset($engineVersion['versionString']) && is_string($engineVersion['versionString'])) { if (preg_match('/^.*?(\d+\.\d+\.\d+(-\d+)?(\s+Q\d+)?)/i', $engineVersion['versionString'], $m)) { $engineRawVersion = $m[1]; } else { $engineRawVersion = $engineVersion['versionString']; } } else { $engineRawVersion = ''; } $engineSemverVersion = preg_match('/^.*?(\d+\.\d+\.\d+)/', $engineRawVersion, $m) ? $m[1] : ''; parent::__construct($driverRawVersion, $driverSemverVersion, $engineRawVersion, $engineSemverVersion); } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::get() */ public static function get($required = true) { if (self::$instance === null) { try { self::$instance = new static(); } catch (NotSupportedException $x) { self::$instance = $x; } } if (self::$instance instanceof self) { return self::$instance; } if ($required) { throw self::$instance; } return null; } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::checkVersionIsSupported() * @see \Imagine\Driver\AbstractInfo::checkVersionIsSupported() */ public function checkVersionIsSupported() { parent::checkVersionIsSupported(); if (version_compare($this->getEngineVersion(), '6.2.9') < 0) { throw new NotSupportedException(sprintf('ImageMagick version 6.2.9 or higher is required, %s provided', $this->getEngineVersion())); } if ($this->getEngineVersion(true) === '7.0.7-32') { // https://github.com/php-imagine/Imagine/issues/689 throw new NotSupportedException(sprintf('ImageMagick version %s has known bugs that prevent it from working', $this->getEngineVersion())); } } /** * {@inheritdoc} * * @see \Imagine\Driver\AbstractInfo::checkFeature() */ protected function checkFeature($feature) { switch ($feature) { case static::FEATURE_COLORPROFILES: if (!$this->areColorProfilesSupported()) { throw new NotSupportedException('Unable to manage color profiles: be sure to compile ImageMagick with the `--with-lcms2` option'); } break; case static::FEATURE_COLORSPACECONVERSION: if (!$this->isColorspaceConversionAvailable()) { throw new NotSupportedException('Your version of Imagick does not support colorspace conversions.'); } break; case static::FEATURE_GRAYSCALEEFFECT: if (version_compare($this->getEngineVersion(), '6.8.5') <= 0) { throw new NotSupportedException(sprintf('Converting an image to grayscale requires ImageMagick version 6.8.5 or higher is required, %s provided', $this->getEngineVersion())); } break; case static::FEATURE_CUSTOMRESOLUTION: // We can't do version_compare($this->getDriverVersion(), '3.1.0') < 0 because phpversion('imagick') may return @PACKAGE_VERSION@ // @see https://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 // So, let's check ImagickDraw::setResolution (which has been introduced in 3.1.0b1 if (!method_exists('ImagickDraw', 'setResolution')) { throw new NotSupportedException(sprintf('Setting image resolution requires imagick version 3.1.0 or higher is required, %s provided', $this->getDriverVersion(true))); } break; } } /** * {@inheritdoc} * * @see \Imagine\Driver\AbstractInfo::buildSupportedFormats() */ protected function buildSupportedFormats() { $supportedFormats = array(); $imagick = new \Imagick(); $magickFormats = array_map('strtolower', $imagick->queryformats()); foreach (Format::getAll() as $format) { if (in_array($format->getID(), $magickFormats, true) || array_intersect($magickFormats, $format->getAlternativeIDs()) !== array()) { $supportedFormats[] = $format; } } return new FormatList($supportedFormats); } /** * ImageMagick without the lcms delegate cannot handle profiles well. * This detection is needed because there is no way to directly check for lcms. * * @return bool */ private function areColorProfilesSupported() { if ($this->colorProfilesSupported === null) { $imagick = new \Imagick(); if (method_exists($imagick, 'profileImage')) { try { $imagick->newImage(1, 1, new \ImagickPixel('#fff')); $imagick->profileImage('icc', 'x'); $this->colorProfilesSupported = false; } catch (\ImagickException $exception) { // If ImageMagick has support for profiles, it detects the invalid profile data 'x' and throws an exception. $this->colorProfilesSupported = true; } } else { $this->colorProfilesSupported = false; } } return $this->colorProfilesSupported; } /** * @return bool */ private function isColorspaceConversionAvailable() { if ($this->colorspaceConversionAvailable === null) { $this->colorspaceConversionAvailable = method_exists('Imagick', 'setColorspace'); } return $this->colorspaceConversionAvailable; } } imagine/src/Imagick/Effects.php 0000644 00000013500 15141212321 0012367 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Imagick; use Imagine\Driver\InfoProvider; use Imagine\Effects\EffectsInterface; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\NotSupportedException; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Color\RGB; use Imagine\Utils\Matrix; /** * Effects implementation using the Imagick PHP extension. */ class Effects implements EffectsInterface, InfoProvider { /** * @var \Imagick */ private $imagick; /** * Initialize the instance. * * @param \Imagick $imagick */ public function __construct(\Imagick $imagick) { $this->imagick = $imagick; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::gamma() */ public function gamma($correction) { try { $this->imagick->gammaImage($correction, \Imagick::CHANNEL_ALL); } catch (\ImagickException $e) { throw new RuntimeException('Failed to apply gamma correction to the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::negative() */ public function negative() { try { $this->imagick->negateImage(false, \Imagick::CHANNEL_ALL); } catch (\ImagickException $e) { throw new RuntimeException('Failed to negate the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::grayscale() */ public function grayscale() { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_GRAYSCALEEFFECT); try { $this->imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE); } catch (\ImagickException $e) { throw new RuntimeException('Failed to grayscale the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::colorize() */ public function colorize(ColorInterface $color) { if (!$color instanceof RGB) { throw new NotSupportedException('Colorize with non-rgb color is not supported'); } try { $this->imagick->colorizeImage((string) $color, new \ImagickPixel(sprintf('rgba(%d, %d, %d, 1)', $color->getRed(), $color->getGreen(), $color->getBlue()))); } catch (\ImagickException $e) { throw new RuntimeException('Failed to colorize the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::sharpen() */ public function sharpen() { try { $this->imagick->sharpenImage(2, 1); } catch (\ImagickException $e) { throw new RuntimeException('Failed to sharpen the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::blur() */ public function blur($sigma = 1) { try { $this->imagick->gaussianBlurImage(0, $sigma); } catch (\ImagickException $e) { throw new RuntimeException('Failed to blur the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::brightness() */ public function brightness($brightness) { $brightness = (int) round($brightness); if ($brightness < -100 || $brightness > 100) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$brightness', -100, 100, $brightness)); } try { if (method_exists($this->imagick, 'brightnesscontrastimage')) { // Available since Imagick 3.3.0 $this->imagick->brightnesscontrastimage($brightness, 0); } else { // This *emulates* brightnesscontrastimage $sign = $brightness < 0 ? -1 : 1; $v = abs($brightness) / 100; $v = (1 / (sin(($v * .99999 * M_PI_2) + M_PI_2))) - 1; $this->imagick->modulateimage(100 + $sign * $v * 100, 100, 100); } } catch (\ImagickException $e) { throw new RuntimeException('Failed to brightness the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::convolve() */ public function convolve(Matrix $matrix) { if ($matrix->getWidth() !== 3 || $matrix->getHeight() !== 3) { throw new InvalidArgumentException(sprintf('A convolution matrix must be 3x3 (%dx%d provided).', $matrix->getWidth(), $matrix->getHeight())); } try { if (class_exists('ImagickKernel', false) && version_compare(static::getDriverInfo()->getEngineVersion(), '7.0.0') >= 0) { $kernel = \ImagickKernel::fromMatrix($matrix->getMatrix()); } else { $kernel = $matrix->getValueList(); } $this->imagick->convolveImage($kernel); } catch (\ImagickException $e) { throw new RuntimeException('Failed to convolve the image'); } return $this; } } imagine/src/Imagick/Image.php 0000644 00000117115 15141212321 0012041 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Imagick; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\OutOfBoundsException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\AbstractImage; use Imagine\Image\BoxInterface; use Imagine\Image\Fill\FillInterface; use Imagine\Image\Fill\Gradient\Horizontal; use Imagine\Image\Fill\Gradient\Linear; use Imagine\Image\Format; use Imagine\Image\ImageInterface; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\PaletteInterface; use Imagine\Image\Point; use Imagine\Image\PointInterface; use Imagine\Image\ProfileInterface; use Imagine\Utils\ErrorHandling; /** * Image implementation using the Imagick PHP extension. */ final class Image extends AbstractImage implements InfoProvider { /** * @var \Imagick */ private $imagick; /** * @var \Imagine\Imagick\Layers|null */ private $layers; /** * @var \Imagine\Image\Palette\PaletteInterface */ private $palette; /** * @var array */ private static $colorspaceMapping = array( PaletteInterface::PALETTE_CMYK => \Imagick::COLORSPACE_CMYK, PaletteInterface::PALETTE_RGB => \Imagick::COLORSPACE_RGB, PaletteInterface::PALETTE_GRAYSCALE => \Imagick::COLORSPACE_GRAY, ); /** * Constructs a new Image instance. * * @param \Imagick $imagick * @param \Imagine\Image\Palette\PaletteInterface $palette * @param \Imagine\Image\Metadata\MetadataBag $metadata */ public function __construct(\Imagick $imagick, PaletteInterface $palette, MetadataBag $metadata) { $this->metadata = $metadata; $this->imagick = $imagick; if (static::getDriverInfo()->hasFeature(DriverInfo::FEATURE_COLORSPACECONVERSION)) { $this->setColorspace($palette); } $this->palette = $palette; } /** * {@inheritdoc} * * @see \Imagine\Image\AbstractImage::__clone() */ public function __clone() { parent::__clone(); if ($this->imagick instanceof \Imagick) { $this->imagick = $this->cloneImagick(); } $this->palette = clone $this->palette; if ($this->layers !== null) { $this->layers = $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_IMAGICK, $this, $this->layers->key()); } } /** * Destroys allocated imagick resources. */ public function __destruct() { if ($this->imagick instanceof \Imagick) { $this->imagick->clear(); $this->imagick->destroy(); } } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * Returns the underlying \Imagick instance. * * @return \Imagick */ public function getImagick() { return $this->imagick; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::copy() */ public function copy() { try { return clone $this; } catch (\ImagickException $e) { throw new RuntimeException('Copy operation failed', $e->getCode(), $e); } } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::crop() */ public function crop(PointInterface $start, BoxInterface $size) { if (!$start->in($this->getSize())) { throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders'); } try { if ($this->layers()->count() > 1) { // Crop each layer separately $this->imagick = $this->imagick->coalesceImages(); foreach ($this->imagick as $frame) { $frame->cropImage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY()); // Reset canvas for gif format $frame->setImagePage(0, 0, 0, 0); } $this->imagick = $this->imagick->deconstructImages(); } else { $this->imagick->cropImage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY()); // Reset canvas for gif format $this->imagick->setImagePage(0, 0, 0, 0); } } catch (\ImagickException $e) { throw new RuntimeException('Crop operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipHorizontally() */ public function flipHorizontally() { try { $this->imagick->flopImage(); } catch (\ImagickException $e) { throw new RuntimeException('Horizontal Flip operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipVertically() */ public function flipVertically() { try { $this->imagick->flipImage(); } catch (\ImagickException $e) { throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::strip() */ public function strip() { try { try { $this->profile($this->palette->profile()); } catch (\Exception $e) { // here we discard setting the profile as the previous incorporated profile // is corrupted, let's now strip the image } $this->imagick->stripImage(); } catch (\ImagickException $e) { throw new RuntimeException('Strip operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::paste() */ public function paste(ImageInterface $image, PointInterface $start, $alpha = 100) { if (!$image instanceof self) { throw new InvalidArgumentException(sprintf('Imagick\Image can only paste() Imagick\Image instances, %s given', get_class($image))); } $alpha = (int) round($alpha); if ($alpha < 0 || $alpha > 100) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$alpha', 0, 100, $alpha)); } if ($alpha === 100) { $pasteMe = $image->imagick; } elseif ($alpha > 0) { $pasteMe = $image->cloneImagick(); // setImageOpacity was replaced with setImageAlpha in php-imagick v3.4.3 if (method_exists($pasteMe, 'setImageAlpha')) { $pasteMe->setImageAlpha($alpha / 100); } else { ErrorHandling::ignoring(E_DEPRECATED, function () use ($pasteMe, $alpha) { $pasteMe->setImageOpacity($alpha / 100); }); } } else { $pasteMe = null; } if ($pasteMe !== null) { try { $this->imagick->compositeImage($pasteMe, \Imagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY()); $error = null; } catch (\ImagickException $e) { $error = $e; } if ($pasteMe !== $image->imagick) { $pasteMe->clear(); $pasteMe->destroy(); } if ($error !== null) { throw new RuntimeException('Paste operation failed', $error->getCode(), $error); } } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::resize() */ public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) { try { if ($this->layers()->count() > 1) { $this->imagick = $this->imagick->coalesceImages(); foreach ($this->imagick as $frame) { $frame->resizeImage($size->getWidth(), $size->getHeight(), $this->getFilter($filter), 1); } $this->imagick = $this->imagick->deconstructImages(); } else { $this->imagick->resizeImage($size->getWidth(), $size->getHeight(), $this->getFilter($filter), 1); } } catch (\ImagickException $e) { throw new RuntimeException('Resize operation failed', $e->getCode(), $e); } return $this; } /** * Resize an image with optimised settings for slightly reduced quality and significantly * reduced file size. * * @param BoxInterface $size * @param bool $keepImageProfiles Whether to keep ICP and ICM image profiles. Defaults to false. * @param bool $keepExifData Whether to keep EXIF data. Defaults to false. * @param int $quality defaults to 82 which produces a very similar image * * @throws \Imagine\Exception\RuntimeException * * @return ImageInterface */ public function smartResize(BoxInterface $size, $keepImageProfiles = false, $keepExifData = false, $quality = 82) { try { if ($this->imagick instanceof Imagick) { $this->imagick->smartResize($size->getWidth(), $size->getHeight(), $keepImageProfiles, $keepExifData, $quality); } else { $this->resize($size); } } catch (\ImagickException $e) { throw new RuntimeException('Resize operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::rotate() */ public function rotate($angle, ColorInterface $background = null) { if ($background === null) { $background = $this->palette->color('fff'); } try { $pixel = $this->getColor($background); $this->imagick->rotateimage($pixel, $angle); $this->imagick->setImagePage(0, 0, 0, 0); $pixel->clear(); $pixel->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Rotate operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::save() */ public function save($path = null, array $options = array()) { $path = $path === null ? $this->imagick->getImageFilename() : $path; if ($path === null) { throw new RuntimeException('You can omit save path only if image has been open from a file'); } try { $this->prepareOutput($options, $path); // If a specific PNG format is requested, set it if (isset($options['png_format'])) { // Unless it's PNG8. Then we attempt to force Imagick to save it // as PNG, that is palette-based with transparency if ($options['png_format'] == 'png8') { $this->imagick->quantizeImage(255, \Imagick::COLORSPACE_YUV, 8, false, false); } else { $path = (isset($options['png_format']) ? $options['png_format'] . ':' : '') . $path; } } $this->imagick->writeImages($path, true); } catch (\ImagickException $e) { throw new RuntimeException('Save operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::show() */ public function show($format, array $options = array()) { $formatInfo = static::getDriverInfo()->getSupportedFormats()->find($format); if ($formatInfo === null) { throw new InvalidArgumentException(sprintf( 'Displaying an image in "%s" format is not supported, please use one of the following formats: "%s"', $format, implode('", "', static::getDriverInfo()->getSupportedFormats()->getAllIDs()) )); } header('Content-type: ' . $formatInfo->getMimeType()); echo $this->get($format, $options); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::get() */ public function get($format, array $options = array()) { try { $options['format'] = $format; $this->prepareOutput($options); } catch (\ImagickException $e) { throw new RuntimeException('Get operation failed', $e->getCode(), $e); } return $this->imagick->getImagesBlob(); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::interlace() */ public function interlace($scheme) { static $supportedInterlaceSchemes = array( ImageInterface::INTERLACE_NONE => \Imagick::INTERLACE_NO, ImageInterface::INTERLACE_LINE => \Imagick::INTERLACE_LINE, ImageInterface::INTERLACE_PLANE => \Imagick::INTERLACE_PLANE, ImageInterface::INTERLACE_PARTITION => \Imagick::INTERLACE_PARTITION, ); if (!array_key_exists($scheme, $supportedInterlaceSchemes)) { throw new InvalidArgumentException('Unsupported interlace type'); } $this->imagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]); return $this; } /** * @param array $options * @param string $path */ private function prepareOutput(array $options, $path = null) { if (isset($options['animated']) && $options['animated'] === true) { $format = isset($options['format']) ? $options['format'] : Format::ID_GIF; $delay = isset($options['animated.delay']) ? $options['animated.delay'] : null; $loops = 0; if (isset($options['animated.loops'])) { $loops = $options['animated.loops']; } else { // Calculating animated GIF iterations is a crap-shoot: https://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=23276 // Looks like it started working around 6.8.8-3: http://git.imagemagick.org/repos/ImageMagick/blob/a01518e08c840577cabd7d3ff291a9ba735f7276/ChangeLog#L914-916 $versionInfo = $this->imagick->getVersion(); if (isset($versionInfo['versionString'])) { preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+-[0-9])/', $versionInfo['versionString'], $versionInfo); if (isset($versionInfo[1])) { if (version_compare($versionInfo[1], '6.8.8-3', '>=')) { $loops = $this->imagick->getImageIterations(); } } } } $options['flatten'] = false; $this->layers()->animate($format, $delay, $loops); } else { $this->layers()->merge(); } $this->imagick = $this->applyImageOptions($this->imagick, $options, $path); // flatten only if image has multiple layers if ((!isset($options['flatten']) || $options['flatten'] === true) && $this->layers()->count() > 1) { $this->flatten(); } if (isset($options['format'])) { $this->imagick->setImageFormat($options['format']); } } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::__toString() */ public function __toString() { return $this->get(Format::ID_PNG); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::draw() */ public function draw() { return $this->getClassFactory()->createDrawer(ClassFactoryInterface::HANDLE_IMAGICK, $this->imagick); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::effects() */ public function effects() { return $this->getClassFactory()->createEffects(ClassFactoryInterface::HANDLE_IMAGICK, $this->imagick); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::getSize() */ public function getSize() { try { $i = $this->imagick->getIteratorIndex(); $this->imagick->rewind(); $width = $this->imagick->getImageWidth(); $height = $this->imagick->getImageHeight(); $this->imagick->setIteratorIndex($i); } catch (\ImagickException $e) { throw new RuntimeException('Could not get size', $e->getCode(), $e); } return $this->getClassFactory()->createBox($width, $height); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::applyMask() */ public function applyMask(ImageInterface $mask) { if (!$mask instanceof self) { throw new InvalidArgumentException('Can only apply instances of Imagine\Imagick\Image as masks'); } $size = $this->getSize(); $maskSize = $mask->getSize(); if ($size != $maskSize) { throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size)); } $mask = $mask->mask(); $mask->imagick->negateImage(true); try { // remove transparent areas of the original from the mask $mask->imagick->compositeImage($this->imagick, \Imagick::COMPOSITE_DSTIN, 0, 0); $this->imagick->compositeImage($mask->imagick, \Imagick::COMPOSITE_COPYOPACITY, 0, 0); $mask->imagick->clear(); $mask->imagick->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::mask() */ public function mask() { $mask = $this->copy(); try { $mask->imagick->modulateImage(100, 0, 100); $mask->imagick->setImageMatte(false); } catch (\ImagickException $e) { throw new RuntimeException('Mask operation failed', $e->getCode(), $e); } return $mask; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::fill() */ public function fill(FillInterface $fill) { try { if ($this->isLinearOpaque($fill)) { $this->applyFastLinear($fill); } else { $iterator = $this->imagick->getPixelIterator(); foreach ($iterator as $y => $pixels) { foreach ($pixels as $x => $pixel) { $color = $fill->getColor(new Point($x, $y)); $pixel->setColor((string) $color); $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); } $iterator->syncIterator(); } } } catch (\ImagickException $e) { throw new RuntimeException('Fill operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::histogram() */ public function histogram() { try { $pixels = $this->imagick->getImageHistogram(); } catch (\ImagickException $e) { throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e); } $image = $this; return array_values(array_unique(array_map(function (\ImagickPixel $pixel) use ($image) { return $image->pixelToColor($pixel); }, $pixels))); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::getColorAt() */ public function getColorAt(PointInterface $point) { if (!$point->in($this->getSize())) { throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight())); } try { $pixel = $this->imagick->getImagePixelColor($point->getX(), $point->getY()); } catch (\ImagickException $e) { throw new RuntimeException('Error while getting image pixel color', $e->getCode(), $e); } return $this->pixelToColor($pixel); } /** * Returns a color given a pixel, depending the Palette context. * * Note : this method is public for PHP 5.3 compatibility * * @param \ImagickPixel $pixel * * @throws \Imagine\Exception\InvalidArgumentException In case a unknown color is requested * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function pixelToColor(\ImagickPixel $pixel) { static $colorMapping = array( ColorInterface::COLOR_RED => \Imagick::COLOR_RED, ColorInterface::COLOR_GREEN => \Imagick::COLOR_GREEN, ColorInterface::COLOR_BLUE => \Imagick::COLOR_BLUE, ColorInterface::COLOR_CYAN => \Imagick::COLOR_CYAN, ColorInterface::COLOR_MAGENTA => \Imagick::COLOR_MAGENTA, ColorInterface::COLOR_YELLOW => \Imagick::COLOR_YELLOW, ColorInterface::COLOR_KEYLINE => \Imagick::COLOR_BLACK, // There is no gray component in \Imagick, let's use one of the RGB comp ColorInterface::COLOR_GRAY => \Imagick::COLOR_RED, ); $alpha = $this->palette->supportsAlpha() ? (int) round($pixel->getColorValue(\Imagick::COLOR_ALPHA) * 100) : null; if ($alpha) { $alpha = min(max($alpha, 0), 100); } $multiplier = $this->palette()->getChannelsMaxValue(); return $this->palette->color(array_map(function ($color) use ($multiplier, $pixel, $colorMapping) { if (!isset($colorMapping[$color])) { throw new InvalidArgumentException(sprintf('Color %s is not mapped in Imagick', $color)); } return $pixel->getColorValue($colorMapping[$color]) * $multiplier; }, $this->palette->pixelDefinition()), $alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::layers() */ public function layers() { if ($this->layers === null) { $this->layers = $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_IMAGICK, $this); } return $this->layers; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::usePalette() */ public function usePalette(PaletteInterface $palette) { if ($this->palette->name() === $palette->name()) { return $this; } if (!isset(static::$colorspaceMapping[$palette->name()])) { throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name())); } static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORSPACECONVERSION); try { try { $hasICCProfile = (bool) $this->imagick->getImageProfile('icc'); } catch (\ImagickException $e) { $hasICCProfile = false; } if (!$hasICCProfile) { $this->profile($this->palette->profile()); } $this->profile($palette->profile()); $this->setColorspace($palette); } catch (\ImagickException $e) { throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::palette() */ public function palette() { return $this->palette; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::profile() */ public function profile(ProfileInterface $profile) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORPROFILES); try { $this->imagick->profileImage('icc', $profile->data()); } catch (\ImagickException $e) { throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e); } return $this; } /** * Flatten the image. */ private function flatten() { // @see https://github.com/mkoppanen/imagick/issues/45 try { if (method_exists($this->imagick, 'mergeImageLayers') && defined('Imagick::LAYERMETHOD_UNDEFINED')) { $this->imagick = $this->imagick->mergeImageLayers(\Imagick::LAYERMETHOD_UNDEFINED); } elseif (method_exists($this->imagick, 'flattenImages')) { $this->imagick = $this->imagick->flattenImages(); } } catch (\ImagickException $e) { throw new RuntimeException('Flatten operation failed', $e->getCode(), $e); } } /** * Applies options before save or output. * * @param \Imagick $image * @param array $options * @param string $path * * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\RuntimeException * * @return \Imagick */ private function applyImageOptions(\Imagick $image, array $options, $path) { if (isset($options['format'])) { $format = $options['format']; } elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) { $format = $extension; } else { $format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION); } $formatInfo = Format::get($format); switch ($formatInfo === null ? '' : $formatInfo->getID()) { case Format::ID_JPEG: if (!isset($options['jpeg_quality'])) { if (isset($options['quality'])) { $options['jpeg_quality'] = $options['quality']; } } if (isset($options['jpeg_quality'])) { $image->setimagecompressionquality($options['jpeg_quality']); $image->setcompressionquality($options['jpeg_quality']); } if (isset($options['jpeg_sampling_factors'])) { if (!is_array($options['jpeg_sampling_factors']) || \count($options['jpeg_sampling_factors']) < 1) { throw new InvalidArgumentException('jpeg_sampling_factors option should be an array of integers'); } $image->setSamplingFactors(array_map(function ($factor) { return (int) $factor; }, $options['jpeg_sampling_factors'])); } break; case Format::ID_PNG: if (!isset($options['png_compression_level'])) { if (isset($options['quality'])) { $options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100); } } if (isset($options['png_compression_level'])) { if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) { throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9'); } } if (isset($options['png_compression_filter'])) { if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) { throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9'); } } if (isset($options['png_compression_level']) || isset($options['png_compression_filter'])) { // first digit: compression level (default: 7) $compression = isset($options['png_compression_level']) ? $options['png_compression_level'] * 10 : 70; // second digit: compression filter (default: 5) $compression += isset($options['png_compression_filter']) ? $options['png_compression_filter'] : 5; $image->setimagecompressionquality($compression); $image->setcompressionquality($compression); } break; case Format::ID_WEBP: if (!isset($options['webp_quality'])) { if (isset($options['quality'])) { $options['webp_quality'] = $options['quality']; } } if (isset($options['webp_quality'])) { $image->setImageCompressionQuality($options['webp_quality']); } if (isset($options['webp_lossless'])) { $image->setOption('webp:lossless', $options['webp_lossless']); } break; case Format::ID_AVIF: case Format::ID_HEIC: if (!empty($options[$formatInfo->getID() . '_lossless'])) { $image->setimagecompressionquality(100); $image->setcompressionquality(100); } else { if (!isset($options[$formatInfo->getID() . '_quality'])) { if (isset($options['quality'])) { $options[$formatInfo->getID() . '_quality'] = $options['quality']; } } if (isset($options[$formatInfo->getID() . '_quality'])) { $options[$formatInfo->getID() . '_quality'] = max(1, min(99, $options[$formatInfo->getID() . '_quality'])); $image->setimagecompressionquality($options[$formatInfo->getID() . '_quality']); $image->setcompressionquality($options[$formatInfo->getID() . '_quality']); } } break; case Format::ID_JXL: if (!empty($options['jxl_lossless'])) { $image->setimagecompressionquality(100); $image->setcompressionquality(100); } else { if (!isset($options['jxl_quality'])) { if (isset($options['quality'])) { $options['jxl_quality'] = $options['quality']; } } if (isset($options['jxl_quality'])) { $options['jxl_quality'] = max(9, min(99, $options['jxl_quality'])); $image->setimagecompressionquality($options['jxl_quality']); $image->setcompressionquality($options['jxl_quality']); } } break; } if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) { if (empty($options['resampling-filter'])) { $filterName = ImageInterface::FILTER_UNDEFINED; } else { $filterName = $options['resampling-filter']; } $filter = $this->getFilter($filterName); switch ($options['resolution-units']) { case ImageInterface::RESOLUTION_PIXELSPERCENTIMETER: $image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERCENTIMETER); break; case ImageInterface::RESOLUTION_PIXELSPERINCH: $image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH); break; default: throw new RuntimeException('Unsupported image unit format'); } $image->setImageResolution($options['resolution-x'], $options['resolution-y']); $image->resampleImage($options['resolution-x'], $options['resolution-y'], $filter, 0); } if (!empty($options['optimize'])) { try { $image = $image->coalesceImages(); $optimized = $image->optimizeimagelayers(); } catch (\ImagickException $e) { throw new RuntimeException('Image optimization failed', $e->getCode(), $e); } if ($optimized === false) { throw new RuntimeException('Image optimization failed'); } if ($optimized instanceof \Imagick) { $image = $optimized; } } return $image; } /** * Gets specifically formatted color string from Color instance. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @return \ImagickPixel */ private function getColor(ColorInterface $color) { $pixel = new \ImagickPixel((string) $color); $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); return $pixel; } /** * Checks whether given $fill is linear and opaque. * * @param \Imagine\Image\Fill\FillInterface $fill * * @return bool */ private function isLinearOpaque(FillInterface $fill) { return $fill instanceof Linear && $fill->getStart()->isOpaque() && $fill->getEnd()->isOpaque(); } /** * Performs optimized gradient fill for non-opaque linear gradients. * * @param \Imagine\Image\Fill\Gradient\Linear $fill */ private function applyFastLinear(Linear $fill) { $gradient = new Imagick(); $size = $this->getSize(); $color = sprintf('gradient:%s-%s', (string) $fill->getStart(), (string) $fill->getEnd()); if ($fill instanceof Horizontal) { $gradient->newPseudoImage($size->getHeight(), $size->getWidth(), $color); $gradient->rotateImage(new \ImagickPixel(), 90); } else { $gradient->newPseudoImage($size->getWidth(), $size->getHeight(), $color); } $this->imagick->compositeImage($gradient, \Imagick::COMPOSITE_OVER, 0, 0); $gradient->clear(); $gradient->destroy(); } /** * Sets colorspace and image type, assigns the palette. * * @param \Imagine\Image\Palette\PaletteInterface $palette * * @throws \Imagine\Exception\InvalidArgumentException */ private function setColorspace(PaletteInterface $palette) { $typeMapping = array( // We use Matte variants to preserve alpha // // (the IMGTYPE_...ALPHA constants are only available since ImageMagick 7 and Imagick 3.4.3, previously they were named // IMGTYPE_...MATTE but in some combinations of different Imagick and ImageMagick versions none of them are avaiable at all, // so we found no other way to fix it as to hard code the values here) PaletteInterface::PALETTE_CMYK => defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : (defined('\Imagick::IMGTYPE_TRUECOLORMATTE') ? \Imagick::IMGTYPE_TRUECOLORMATTE : 7), PaletteInterface::PALETTE_RGB => defined('\Imagick::IMGTYPE_TRUECOLORALPHA') ? \Imagick::IMGTYPE_TRUECOLORALPHA : (defined('\Imagick::IMGTYPE_TRUECOLORMATTE') ? \Imagick::IMGTYPE_TRUECOLORMATTE : 7), PaletteInterface::PALETTE_GRAYSCALE => defined('\Imagick::IMGTYPE_GRAYSCALEALPHA') ? \Imagick::IMGTYPE_GRAYSCALEALPHA : (defined('\Imagick::IMGTYPE_GRAYSCALEMATTE') ? \Imagick::IMGTYPE_GRAYSCALEMATTE : 3), ); if (!isset(static::$colorspaceMapping[$palette->name()])) { throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name())); } $this->imagick->setType($typeMapping[$palette->name()]); $this->imagick->setColorspace(static::$colorspaceMapping[$palette->name()]); if ($this->imagick->getnumberimages() > 0 && defined('Imagick::ALPHACHANNEL_REMOVE') && defined('Imagick::ALPHACHANNEL_SET')) { $originalImageIndex = $this->imagick->getiteratorindex(); foreach ($this->imagick as $frame) { if ($palette->supportsAlpha()) { $frame->setimagealphachannel(\Imagick::ALPHACHANNEL_SET); } else { $frame->setimagealphachannel(\Imagick::ALPHACHANNEL_REMOVE); } } $this->imagick->setiteratorindex($originalImageIndex); } $this->palette = $palette; } /** * Returns the filter if it's supported. * * @param string $filter * * @throws \Imagine\Exception\InvalidArgumentException if the filter is unsupported * * @return string */ private function getFilter($filter = ImageInterface::FILTER_UNDEFINED) { static $supportedFilters = null; if ($supportedFilters === null) { $supportedFilters = array( ImageInterface::FILTER_UNDEFINED => \Imagick::FILTER_UNDEFINED, ImageInterface::FILTER_BESSEL => \Imagick::FILTER_BESSEL, ImageInterface::FILTER_BLACKMAN => \Imagick::FILTER_BLACKMAN, ImageInterface::FILTER_BOX => \Imagick::FILTER_BOX, ImageInterface::FILTER_CATROM => \Imagick::FILTER_CATROM, ImageInterface::FILTER_CUBIC => \Imagick::FILTER_CUBIC, ImageInterface::FILTER_GAUSSIAN => \Imagick::FILTER_GAUSSIAN, ImageInterface::FILTER_HANNING => \Imagick::FILTER_HANNING, ImageInterface::FILTER_HAMMING => \Imagick::FILTER_HAMMING, ImageInterface::FILTER_HERMITE => \Imagick::FILTER_HERMITE, ImageInterface::FILTER_LANCZOS => \Imagick::FILTER_LANCZOS, ImageInterface::FILTER_MITCHELL => \Imagick::FILTER_MITCHELL, ImageInterface::FILTER_POINT => \Imagick::FILTER_POINT, ImageInterface::FILTER_QUADRATIC => \Imagick::FILTER_QUADRATIC, ImageInterface::FILTER_SINC => \Imagick::FILTER_SINC, ImageInterface::FILTER_TRIANGLE => \Imagick::FILTER_TRIANGLE, ); if (defined('Imagick::FILTER_SINCFAST')) { $supportedFilters[ImageInterface::FILTER_SINCFAST] = \Imagick::FILTER_SINCFAST; } } if (!in_array($filter, static::getAllFilterValues(), true)) { throw new InvalidArgumentException('Unsupported filter type'); } if (!array_key_exists($filter, $supportedFilters)) { $filter = ImageInterface::FILTER_UNDEFINED; } return $supportedFilters[$filter]; } /** * Clone the Imagick resource of this instance. * * @throws \ImagickException * * @return \Imagick */ protected function cloneImagick() { // the clone method has been deprecated in imagick 3.1.0b1. // we can't use phpversion('imagick') because it may return `@PACKAGE_VERSION@` // so, let's check if ImagickDraw has the setResolution method, which has been introduced in the same version 3.1.0b1 if (method_exists('ImagickDraw', 'setResolution')) { return clone $this->imagick; } return $this->imagick->clone(); } } imagine/src/Imagick/Drawer.php 0000644 00000034067 15141212321 0012247 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Imagick; use Imagine\Draw\DrawerInterface; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\AbstractFont; use Imagine\Image\Box; use Imagine\Image\BoxInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Point; use Imagine\Image\PointInterface; /** * Drawer implementation using the Imagick PHP extension. */ final class Drawer implements DrawerInterface, InfoProvider { /** * @var \Imagick */ private $imagick; /** * @param \Imagick $imagick */ public function __construct(\Imagick $imagick) { $this->imagick = $imagick; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::arc() */ public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0) { return $this; } $x = $center->getX(); $y = $center->getY(); $width = $size->getWidth(); $height = $size->getHeight(); try { $pixel = $this->getColor($color); $arc = new \ImagickDraw(); $arc->setStrokeColor($pixel); $arc->setStrokeWidth($thickness); $arc->setFillColor('transparent'); $arc->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end); $this->imagick->drawImage($arc); $pixel->clear(); $pixel->destroy(); $arc->clear(); $arc->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::chord() */ public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $x = $center->getX(); $y = $center->getY(); $width = $size->getWidth(); $height = $size->getHeight(); try { $pixel = $this->getColor($color); $chord = new \ImagickDraw(); $chord->setStrokeColor($pixel); $chord->setStrokeWidth($thickness); if ($fill) { $chord->setFillColor($pixel); } else { $from = new Point(round($x + $width / 2 * cos(deg2rad($start))), round($y + $height / 2 * sin(deg2rad($start)))); $to = new Point(round($x + $width / 2 * cos(deg2rad($end))), round($y + $height / 2 * sin(deg2rad($end)))); $this->line($from, $to, $color, $thickness); $chord->setFillColor('transparent'); } $chord->arc( $x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end ); $this->imagick->drawImage($chord); $pixel->clear(); $pixel->destroy(); $chord->clear(); $chord->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::circle() */ public function circle(PointInterface $center, $radius, ColorInterface $color, $fill = false, $thickness = 1) { $diameter = $radius * 2; return $this->ellipse($center, new Box($diameter, $diameter), $color, $fill, $thickness); } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::ellipse() */ public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $width = $size->getWidth(); $height = $size->getHeight(); try { $pixel = $this->getColor($color); $ellipse = new \ImagickDraw(); $ellipse->setStrokeColor($pixel); $ellipse->setStrokeWidth($thickness); if ($fill) { $ellipse->setFillColor($pixel); } else { $ellipse->setFillColor('transparent'); } $ellipse->ellipse( $center->getX(), $center->getY(), $width / 2, $height / 2, 0, 360 ); if ($this->imagick->drawImage($ellipse) === false) { throw new RuntimeException('Ellipse operation failed'); } $pixel->clear(); $pixel->destroy(); $ellipse->clear(); $ellipse->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::line() */ public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0) { return $this; } try { $pixel = $this->getColor($color); $line = new \ImagickDraw(); $line->setStrokeColor($pixel); $line->setStrokeWidth($thickness); $line->setFillColor($pixel); $line->line( $start->getX(), $start->getY(), $end->getX(), $end->getY() ); $this->imagick->drawImage($line); $pixel->clear(); $pixel->destroy(); $line->clear(); $line->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw line operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::pieSlice() */ public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $width = $size->getWidth(); $height = $size->getHeight(); $x1 = round($center->getX() + $width / 2 * cos(deg2rad($start))); $y1 = round($center->getY() + $height / 2 * sin(deg2rad($start))); $x2 = round($center->getX() + $width / 2 * cos(deg2rad($end))); $y2 = round($center->getY() + $height / 2 * sin(deg2rad($end))); if ($fill) { $this->chord($center, $size, $start, $end, $color, true, $thickness); $this->polygon( array( $center, new Point($x1, $y1), new Point($x2, $y2), ), $color, true, $thickness ); } else { $this->arc($center, $size, $start, $end, $color, $thickness); $this->line($center, new Point($x1, $y1), $color, $thickness); $this->line($center, new Point($x2, $y2), $color, $thickness); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::dot() */ public function dot(PointInterface $position, ColorInterface $color) { $x = $position->getX(); $y = $position->getY(); try { $pixel = $this->getColor($color); $point = new \ImagickDraw(); $point->setFillColor($pixel); $point->point($x, $y); $this->imagick->drawimage($point); $pixel->clear(); $pixel->destroy(); $point->clear(); $point->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw point operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::rectangle() */ public function rectangle(PointInterface $leftTop, PointInterface $rightBottom, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $minX = min($leftTop->getX(), $rightBottom->getX()); $maxX = max($leftTop->getX(), $rightBottom->getX()); $minY = min($leftTop->getY(), $rightBottom->getY()); $maxY = max($leftTop->getY(), $rightBottom->getY()); try { $pixel = $this->getColor($color); $rectangle = new \ImagickDraw(); $rectangle->setStrokeColor($pixel); $rectangle->setStrokeWidth($thickness); if ($fill) { $rectangle->setFillColor($pixel); } else { $rectangle->setFillColor('transparent'); } $rectangle->rectangle($minX, $minY, $maxX, $maxY); $this->imagick->drawImage($rectangle); $pixel->clear(); $pixel->destroy(); $rectangle->clear(); $rectangle->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw rectangle operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::polygon() */ public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1) { if (count($coordinates) < 3) { throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates))); } $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $points = array_map(function (PointInterface $p) { return array('x' => $p->getX(), 'y' => $p->getY()); }, $coordinates); try { $pixel = $this->getColor($color); $polygon = new \ImagickDraw(); $polygon->setStrokeColor($pixel); $polygon->setStrokeWidth($thickness); if ($fill) { $polygon->setFillColor($pixel); } else { $polygon->setFillColor('transparent'); } $polygon->polygon($points); $this->imagick->drawImage($polygon); $pixel->clear(); $pixel->destroy(); $polygon->clear(); $polygon->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::text() */ public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null) { try { $pixel = $this->getColor($font->getColor()); $text = new \ImagickDraw(); $text->setFont($font->getFile()); // Ensure font resolution is the same as GD's hard-coded 96 if (static::getDriverInfo()->hasFeature(DriverInfo::FEATURE_CUSTOMRESOLUTION)) { $text->setResolution(96, 96); $text->setFontSize($font->getSize()); } else { $text->setFontSize((int) ($font->getSize() * (96 / 72))); } $text->setFillColor($pixel); $text->setTextAntialias(true); if ($width !== null) { $string = $font->wrapText($string, $width, $angle); } $info = $this->imagick->queryFontMetrics($text, $string); $rad = deg2rad($angle); $cos = cos($rad); $sin = sin($rad); // round(0 * $cos - 0 * $sin) $x1 = 0; $x2 = round($info['characterWidth'] * $cos - $info['characterHeight'] * $sin); // round(0 * $sin + 0 * $cos) $y1 = 0; $y2 = round($info['characterWidth'] * $sin + $info['characterHeight'] * $cos); $xdiff = 0 - min($x1, $x2); $ydiff = 0 - min($y1, $y2); $this->imagick->annotateImage( $text, $position->getX() + $x1 + $xdiff, $position->getY() + $y2 + $ydiff, $angle, $string ); $pixel->clear(); $pixel->destroy(); $text->clear(); $text->destroy(); } catch (\ImagickException $e) { throw new RuntimeException('Draw text operation failed', $e->getCode(), $e); } return $this; } /** * Gets specifically formatted color string from ColorInterface instance. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @return string */ private function getColor(ColorInterface $color) { $pixel = new \ImagickPixel((string) $color); $pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100); return $pixel; } } imagine/src/Imagick/Layers.php 0000644 00000021350 15141212321 0012251 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Imagick; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\OutOfBoundsException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\AbstractLayers; use Imagine\Image\Format; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\PaletteInterface; class Layers extends AbstractLayers implements InfoProvider { /** * @var \Imagine\Imagick\Image */ private $image; /** * @var \Imagick */ private $resource; /** * @var int */ private $offset; /** * @var \Imagine\Imagick\Image[] */ private $layers = array(); /** * @var \Imagine\Image\Palette\PaletteInterface */ private $palette; public function __construct(Image $image, PaletteInterface $palette, \Imagick $resource, $initialOffset = 0) { $this->image = $image; $this->resource = $resource; $this->palette = $palette; $this->offset = (int) $initialOffset; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::merge() */ public function merge() { foreach ($this->layers as $offset => $image) { try { $this->resource->setIteratorIndex($offset); $this->resource->setImage($image->getImagick()); } catch (\ImagickException $e) { throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e); } } } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::animate() */ public function animate($format, $delay, $loops) { $formatInfo = Format::get($format); if ($formatInfo === null || $formatInfo->getID() !== Format::ID_GIF) { throw new InvalidArgumentException('Animated picture is currently only supported on gif'); } if (!is_int($loops) || $loops < 0) { throw new InvalidArgumentException('Loops must be a positive integer.'); } if ($delay !== null && (!is_int($delay) || $delay < 0)) { throw new InvalidArgumentException('Delay must be either null or a positive integer.'); } try { foreach ($this as $offset => $layer) { $imagick = $layer->getImagick(); $this->resource->setIteratorIndex($offset); $this->resource->setFormat(Format::ID_GIF); if ($delay !== null) { $imagick->setImageDelay($delay); $imagick->setImageTicksPerSecond(100); } elseif ($frameDelay = $layer->getImagick()->getImageDelay()) { $imagick->setImageDelay($frameDelay); $imagick->setImageTicksPerSecond(100); } $imagick->setImageIterations($loops); $this->resource->setImage($imagick); } } catch (\ImagickException $e) { throw new RuntimeException('Failed to animate layers', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::coalesce() */ public function coalesce() { try { $coalescedResource = $this->resource->coalesceImages(); } catch (\ImagickException $e) { throw new RuntimeException('Failed to coalesce layers', $e->getCode(), $e); } $count = $coalescedResource->getNumberImages(); for ($offset = 0; $offset < $count; $offset++) { try { $coalescedResource->setIteratorIndex($offset); $this->layers[$offset] = $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_IMAGICK, $coalescedResource->getImage(), $this->palette, new MetadataBag()); } catch (\ImagickException $e) { throw new RuntimeException('Failed to retrieve layer', $e->getCode(), $e); } } return $this; } /** * {@inheritdoc} * * @see \Iterator::current() */ #[\ReturnTypeWillChange] public function current() { return $this->extractAt($this->offset); } /** * Tries to extract layer at given offset. * * @param int $offset * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Imagick\Image */ private function extractAt($offset) { if (!isset($this->layers[$offset])) { try { $this->resource->setIteratorIndex($offset); $this->layers[$offset] = $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_IMAGICK, $this->resource->getImage(), $this->palette, new MetadataBag()); } catch (\ImagickException $e) { throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e); } } return $this->layers[$offset]; } /** * {@inheritdoc} * * @see \Iterator::key() */ #[\ReturnTypeWillChange] public function key() { return $this->offset; } /** * {@inheritdoc} * * @see \Iterator::next() */ #[\ReturnTypeWillChange] public function next() { ++$this->offset; } /** * {@inheritdoc} * * @see \Iterator::rewind() */ #[\ReturnTypeWillChange] public function rewind() { $this->offset = 0; } /** * {@inheritdoc} * * @see \Iterator::valid() */ #[\ReturnTypeWillChange] public function valid() { return $this->offset < count($this); } /** * {@inheritdoc} * * @see \Countable::count() */ #[\ReturnTypeWillChange] public function count() { try { return $this->resource->getNumberImages(); } catch (\ImagickException $e) { throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e); } } /** * {@inheritdoc} * * @see \ArrayAccess::offsetExists() */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return is_int($offset) && $offset >= 0 && $offset < count($this); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetGet() */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->extractAt($offset); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetSet() */ #[\ReturnTypeWillChange] public function offsetSet($offset, $image) { if (!$image instanceof Image) { throw new InvalidArgumentException('Only an Imagick Image can be used as layer'); } if ($offset === null) { $offset = count($this) - 1; } else { if (!is_int($offset)) { throw new InvalidArgumentException('Invalid offset for layer, it must be an integer'); } if (count($this) < $offset || $offset < 0) { throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset)); } if (isset($this[$offset])) { unset($this[$offset]); $offset = $offset - 1; } } $frame = $image->getImagick(); try { if (count($this) > 0) { $this->resource->setIteratorIndex($offset); } $this->resource->addImage($frame); } catch (\ImagickException $e) { throw new RuntimeException('Unable to set the layer', $e->getCode(), $e); } $this->layers = array(); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetUnset() */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { try { $this->extractAt($offset); } catch (RuntimeException $e) { return; } try { $this->resource->setIteratorIndex($offset); $this->resource->removeImage(); } catch (\ImagickException $e) { throw new RuntimeException('Unable to remove layer', $e->getCode(), $e); } } } imagine/src/Imagick/Imagick.php 0000644 00000023221 15141212321 0012355 0 ustar 00 <?php namespace Imagine\Imagick; /** * An Imagick extension to provide better (higher quality, lower file size) image resizes. * * This class extends Imagick (<http://php.net/manual/en/book.imagick.php>) based on * research into optimal image resizing techniques (<https://github.com/nwtn/image-resize-tests>). * * Using these methods with their default settings should provide image resizing that is * visually indistinguishable from Photoshop’s “Save for Web…”, but at lower file sizes. * * @author David Newton <david@davidnewton.ca> * @copyright 2015 David Newton * @license https://raw.githubusercontent.com/nwtn/php-respimg/master/LICENSE MIT * * @version 1.0.1 */ class Imagick extends \Imagick { /** * Resizes the image using smart defaults for high quality and low file size. * * @param int $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. * @param int $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. * @param bool $keepImageProfiles Whether to keep ICP and ICM image profiles. Defaults to false. * @param bool $keepExifData Whether to keep EXIF data. Defaults to false. * @param int $quality defaults to 82 which produces a very similar image */ public function smartResize($columns, $rows, $keepImageProfiles = false, $keepExifData = false, $quality = 82) { $this->setOption('filter:support', '2.0'); $this->_thumbnailImage($columns, $rows, false, false, \Imagick::FILTER_TRIANGLE, $keepImageProfiles, $keepExifData); $this->unsharpMaskImage(0.25, 0.25, 8, 0.065); // Image posterizing can cause serious performance issues on some systems / Imagick configs. // It also does not serve that much in reducing filesize, so we're leaving it out. // If your system handles it just fine, feel free to enable it. // $this->posterizeImage(136, false); $this->setImageCompressionQuality($quality); $this->setOption('jpeg:fancy-upsampling', 'off'); $this->setOption('png:compression-filter', '5'); $this->setOption('png:compression-level', '9'); $this->setOption('png:compression-strategy', '1'); $this->setOption('png:exclude-chunk', 'all'); $this->setInterlaceScheme(\Imagick::INTERLACE_NO); // Older Imagick versions might not have this. Better make sure. if (!$keepImageProfiles && !$keepExifData && method_exists($this, 'transformimagecolorspace')) { $this->stripImage(); $this->transformimagecolorspace(\Imagick::COLORSPACE_SRGB); } } /** * Changes the size of an image to the given dimensions and removes any associated profiles. * * `_thumbnailImage` changes the size of an image to the given dimensions and * removes any associated profiles. The goal is to produce small low cost * thumbnail images suited for display on the Web. * * With the original Imagick _thumbnailImage implementation, there is no way to choose a * resampling filter. This class recreates Imagick’s C implementation and adds this * additional feature. * * Note: <https://github.com/mkoppanen/imagick/issues/90> has been filed for this issue. * * * @param int $columns The number of columns in the output image. 0 = maintain aspect ratio based on $rows. * @param int $rows The number of rows in the output image. 0 = maintain aspect ratio based on $columns. * @param bool $bestfit treat $columns and $rows as a bounding box in which to fit the image * @param bool $fill fill in the bounding box with the background colour * @param int $filter The resampling filter to use. Refer to the list of filter constants at <http://php.net/manual/en/imagick.constants.php>. * @param bool $keepImageProfiles Whether to keep ICP and ICM image profiles. Defaults to false. * @param bool $keepExifData Whether to keep EXIF data. Defaults to false. * * @return bool indicates whether the operation was performed successfully */ private function _thumbnailImage($columns, $rows, $bestfit = false, $fill = false, $filter = \Imagick::FILTER_TRIANGLE, $keepImageProfiles = false, $keepExifData = false) { // sample factor; defined in original ImageMagick _thumbnailImage function // the scale to which the image should be resized using the `sample` function $SampleFactor = 5; // filter whitelist $filters = array( \Imagick::FILTER_POINT, \Imagick::FILTER_BOX, \Imagick::FILTER_TRIANGLE, \Imagick::FILTER_HERMITE, \Imagick::FILTER_HANNING, \Imagick::FILTER_HAMMING, \Imagick::FILTER_BLACKMAN, \Imagick::FILTER_GAUSSIAN, \Imagick::FILTER_QUADRATIC, \Imagick::FILTER_CUBIC, \Imagick::FILTER_CATROM, \Imagick::FILTER_MITCHELL, \Imagick::FILTER_LANCZOS, \Imagick::FILTER_BESSEL, \Imagick::FILTER_SINC, ); // Parse parameters given to function $columns = (float) ($columns); $rows = (float) ($rows); $bestfit = (bool) $bestfit; $fill = (bool) $fill; // We can’t resize to (0,0) if ($rows < 1 && $columns < 1) { return false; } // Set a default filter if an acceptable one wasn’t passed if (!in_array($filter, $filters)) { $filter = \Imagick::FILTER_TRIANGLE; } // figure out the output width and height $width = (float) $this->getImageWidth(); $height = (float) $this->getImageHeight(); $new_width = $columns; $new_height = $rows; $x_factor = $columns / $width; $y_factor = $rows / $height; if ($rows < 1) { $new_height = round($x_factor * $height); } elseif ($columns < 1) { $new_width = round($y_factor * $width); } // if bestfit is true, the new_width/new_height of the image will be different than // the columns/rows parameters; those will define a bounding box in which the image will be fit if ($bestfit && $x_factor > $y_factor) { $x_factor = $y_factor; $new_width = round($y_factor * $width); } elseif ($bestfit && $y_factor > $x_factor) { $y_factor = $x_factor; $new_height = round($x_factor * $height); } if ($new_width < 1) { $new_width = 1; } if ($new_height < 1) { $new_height = 1; } // if we’re resizing the image to more than about 1/3 it’s original size // then just use the resize function if (($x_factor * $y_factor) > 0.1) { $this->resizeImage($new_width, $new_height, $filter, 1); // if we’d be using sample to scale to smaller than 128x128, just use resize } elseif ((($SampleFactor * $new_width) < 128) || (($SampleFactor * $new_height) < 128)) { $this->resizeImage($new_width, $new_height, $filter, 1); // otherwise, use sample first, then resize } else { $this->sampleImage($SampleFactor * $new_width, $SampleFactor * $new_height); $this->resizeImage($new_width, $new_height, $filter, 1); } // if the alpha channel is not defined, make it opaque if ($this->getImageAlphaChannel() == \Imagick::ALPHACHANNEL_UNDEFINED) { $this->setImageAlphaChannel(defined('\\Imagick::ALPHACHANNEL_OPAQUE') ? Imagick::ALPHACHANNEL_OPAQUE : Imagick::ALPHACHANNEL_OFF); } // set the image’s bit depth to 8 bits $this->setImageDepth(8); // turn off interlacing $this->setInterlaceScheme(\Imagick::INTERLACE_NO); // Strip all profiles except color profiles. foreach ($this->getImageProfiles('*', true) as $key => $value) { $remove = false; if ($key == 'icc' || $key == 'icm') { $remove = !$keepImageProfiles; } elseif ($key == 'exif' || $key == 'iptc') { $remove = !$keepExifData; } else { $remove = true; } if ($remove) { try { $this->removeImageProfile($key); } catch (\Exception $e) { // Some Imagick versions have trouble removing an image profile that they found. } } } $properties = array('commment', 'Thumb::URI', 'Thumb::MTime', 'Thumb::Size', 'Thumb::Mimetype', 'software', 'Thumb::Image::Width', 'Thumb::Image::Height', 'Thumb::Document::Pages'); $delete = method_exists($this, 'deleteImageProperty'); foreach ($properties as $property) { try { if ($delete) { $this->deleteImageProperty($property); } else { $this->setImageProperty($property, ''); } } catch (\Exception $e) { // Mere exceptions cannot stop me! } } // In case user wants to fill use extent for it rather than creating a new canvas // …fill out the bounding box if ($bestfit && $fill && ($new_width != $columns || $new_height != $rows)) { $extent_x = 0; $extent_y = 0; if ($columns > $new_width) { $extent_x = ($columns - $new_width) / 2; } if ($rows > $new_height) { $extent_y = ($rows - $new_height) / 2; } $this->extentImage($columns, $rows, 0 - $extent_x, $extent_y); } return true; } } imagine/src/File/LoaderInterface.php 0000644 00000001672 15141212321 0013361 0 ustar 00 <?php /* * This file is part of the Imagine package. * * For the full copyright and license information, please view the LICENSE file that was distributed with this source code. */ namespace Imagine\File; /** * Interface for classes that can load local or remote files. */ interface LoaderInterface { /** * Is this a local file. * * @return bool */ public function isLocalFile(); /** * Get the path of the file (local or remote). * * @return string */ public function getPath(); /** * Is the binary content already loaded? * * @return bool */ public function hasReadData(); /** * Get the file binary contents. * * @return string */ public function getData(); /** * The string representation of this object must be the file path (local or remote). * * @return string */ public function __toString(); } imagine/src/File/Loader.php 0000644 00000020706 15141212321 0011537 0 ustar 00 <?php /* * This file is part of the Imagine package. * * For the full copyright and license information, please view the LICENSE file that was distributed with this source code. */ namespace Imagine\File; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; /** * Default implementation of Imagine\File\LoaderInterface. */ class Loader implements LoaderInterface { /** * The mimimum supported version of curl. * * @var string */ const MINIMUM_CURL_VERSION = '7.34.0'; /** * The file path. * * @var string */ protected $path; /** * Does $path contain an URL? * * @var bool */ protected $isUrl; /** * The loaded data. * * @var string|null */ protected $data; /** * Is curl available, with a decent version? * * @var bool|null */ protected $isCurlSupported; /** * Initialize the instance. * * @param string|mixed $path the file path (or an object whose string representation is the file path) * * @throws \Imagine\Exception\InvalidArgumentException throws an InvalidArgumentException is $path is an empty string, or is not an object that has a __toString method */ public function __construct($path) { if (is_object($path) && !method_exists($path, '__toString')) { throw new InvalidArgumentException(sprintf('$path is an object of file %s which does not implement the __toString method', get_class($path))); } $this->path = (string) $path; if ($this->path === '') { throw new InvalidArgumentException('$path is empty'); } $this->isUrl = filter_var($this->path, FILTER_VALIDATE_URL) !== false; if (!$this->isUrl) { $this->checkLocalFile(); } } /** * {@inheritdoc} * * @see \Imagine\File\LoaderInterface::isLocalFile() */ public function isLocalFile() { return !$this->isUrl; } /** * {@inheritdoc} * * @see \Imagine\File\LoaderInterface::getPath() */ public function getPath() { return $this->path; } /** * {@inheritdoc} * * @see \Imagine\File\LoaderInterface::hasReadData() */ public function hasReadData() { return $this->data !== null; } /** * {@inheritdoc} * * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\RuntimeException * * @see \Imagine\File\LoaderInterface::getData() */ public function getData() { if (!$this->hasReadData()) { if ($this->isLocalFile()) { $this->data = $this->readLocalFile(); } else { $this->data = $this->readRemoteFile(); } } return $this->data; } /** * {@inheritdoc} * * @see \Imagine\File\LoaderInterface::__toString() */ public function __toString() { return $this->getPath(); } /** * Read a local file. * * @throws \Imagine\Exception\InvalidArgumentException * * @return string */ protected function readLocalFile() { $this->checkLocalFile(); $data = @file_get_contents($this->path); if ($data === false) { throw new InvalidArgumentException(sprintf('Failed to read from file %s.', $this->path)); } return $data; } /** * Check that the file exists and it's readable. * * @throws \Imagine\Exception\InvalidArgumentException */ protected function checkLocalFile() { if (!is_file($this->path)) { throw new InvalidArgumentException(sprintf('File %s does not exist.', $this->path)); } if (!is_readable($this->path)) { throw new InvalidArgumentException(sprintf('File %s is not readable.', $this->path)); } } /** * Read a remote file. * * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\RuntimeException * * @return string */ protected function readRemoteFile() { if ($this->isCurlSupported()) { return $this->readRemoteFileWithCurl(); } return $this->readRemoteFileWithFileGetContents(); } /** * Check if curl is available and it's a decent version. * * @return bool */ protected function isCurlSupported() { if ($this->isCurlSupported === null) { $isCurlSupported = false; if (function_exists('curl_init') && function_exists('curl_version')) { $curlVersion = curl_version(); if (is_array($curlVersion) && !empty($curlVersion['version'])) { if (version_compare($curlVersion['version'], static::MINIMUM_CURL_VERSION) >= 0) { $isCurlSupported = true; } } } $this->isCurlSupported = $isCurlSupported; } return $this->isCurlSupported; } /** * Read a remote file using the cURL extension. * * @throws \Imagine\Exception\InvalidArgumentException * * @return string */ protected function readRemoteFileWithCurl() { $curl = @curl_init($this->path); if ($curl === false) { throw new RuntimeException('curl_init() failed.'); } if (!@curl_setopt($curl, CURLOPT_RETURNTRANSFER, true)) { throw new RuntimeException('curl_setopt(CURLOPT_RETURNTRANSFER) failed.'); } $this->setCurlOptions($curl); $response = @curl_exec($curl); if ($response === false) { $errorMessage = curl_error($curl); if ($errorMessage === '') { $errorMessage = 'curl_exec() failed.'; } $errorCode = curl_errno($curl); curl_close($curl); throw new RuntimeException($errorMessage, $errorCode); } $responseInfo = curl_getinfo($curl); curl_close($curl); if ($responseInfo['http_code'] == 404) { throw new InvalidArgumentException(sprintf('File %s does not exist.', $this->path)); } if ($responseInfo['http_code'] < 200 || $responseInfo['http_code'] >= 300) { throw new InvalidArgumentException(sprintf('Failed to download "%s": %s', $this->path, $responseInfo['http_code'])); } return $response; } /** * Set curl options. * * * @param resource $curl * * @throws \Imagine\Exception\RuntimeException */ protected function setCurlOptions($curl) { if (!@curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept-Encoding: identity'))) { throw new RuntimeException('curl_setopt(CURLOPT_HTTPHEADER) failed.'); } if (!@curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true)) { throw new RuntimeException('curl_setopt(CURLOPT_FOLLOWLOCATION) failed.'); } if (defined('CURL_SSLVERSION_TLSv1_1')) { if (!@curl_setopt($curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1)) { throw new RuntimeException('curl_setopt(CURLOPT_SSLVERSION) failed.'); } } else { // Manually checked that CURL_SSLVERSION_TLSv1_1 is 5 for any version of curl from 7.34.0 to 7.61.0 // See for example https://github.com/curl/curl/blob/curl-7_34_0/include/curl/curl.h#L1668 if (!@curl_setopt($curl, CURLOPT_SSLVERSION, 5)) { throw new RuntimeException('curl_setopt(CURLOPT_SSLVERSION) failed.'); } } } /** * Read a remote file using the file_get_contents. * * @throws \Imagine\Exception\InvalidArgumentException * * @return string */ protected function readRemoteFileWithFileGetContents() { $http_response_header = null; $data = @file_get_contents($this->path); if ($data === false) { $matches = null; if (is_array($http_response_header) && isset($http_response_header[0]) && preg_match('/^HTTP\/\d+(?:\.\d+)*\s+(\d+\s+\w.*)/i', $http_response_header[0], $matches)) { throw new InvalidArgumentException(sprintf('Failed to read from URL %s: %s', $this->path, $matches[1])); } throw new InvalidArgumentException(sprintf('Failed to read from URL %s', $this->path)); } return $data; } } imagine/src/Effects/EffectsInterface.php 0000644 00000004135 15141212321 0014227 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Effects; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Utils\Matrix; /** * Interface for the effects. */ interface EffectsInterface { /** * Apply gamma correction. * * @param float $correction * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function gamma($correction); /** * Invert the colors of the image. * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function negative(); /** * Grayscale the image. * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function grayscale(); /** * Colorize the image. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function colorize(ColorInterface $color); /** * Sharpens the image. * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function sharpen(); /** * Blur the image. * * @param float|int $sigma * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function blur($sigma); /** * Changes the brightness of the image. * * @param int $brightness The level of brightness (-100 (black) to 100 (white)) * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function brightness($brightness); /** * Convolves the image. * * @param \Imagine\Utils\Matrix $matrix The matrix from which derive the convolution kernel * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function convolve(Matrix $matrix); } imagine/src/Gmagick/Font.php 0000644 00000003513 15141212321 0011717 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gmagick; use Imagine\Driver\InfoProvider; use Imagine\Image\AbstractFont; use Imagine\Image\Palette\Color\ColorInterface; /** * Font implementation using the Gmagick PHP extension. */ final class Font extends AbstractFont implements InfoProvider { /** * @var \Gmagick */ private $gmagick; /** * @param \Gmagick $gmagick * @param string $file * @param int $size * @param \Imagine\Image\Palette\Color\ColorInterface $color */ public function __construct(\Gmagick $gmagick, $file, $size, ColorInterface $color) { $this->gmagick = $gmagick; parent::__construct($file, $size, $color); } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::box() */ public function box($string, $angle = 0) { $text = new \GmagickDraw(); $text->setfont($this->file); /* * @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 * * ensure font resolution is the same as GD's hard-coded 96 */ $text->setfontsize((int) ($this->size * (96 / 72))); $text->setfontstyle(\Gmagick::STYLE_OBLIQUE); $info = $this->gmagick->queryfontmetrics($text, $string); $box = $this->getClassFactory()->createBox($info['textWidth'], $info['textHeight']); return $box; } } imagine/src/Gmagick/Imagine.php 0000644 00000014563 15141212321 0012371 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gmagick; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\NotSupportedException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\File\LoaderInterface; use Imagine\Image\AbstractImagine; use Imagine\Image\BoxInterface; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\CMYK; use Imagine\Image\Palette\Color\CMYK as CMYKColor; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Grayscale; use Imagine\Image\Palette\RGB; /** * Imagine implementation using the Gmagick PHP extension. * * @final */ class Imagine extends AbstractImagine implements InfoProvider { /** * @throws \Imagine\Exception\RuntimeException */ public function __construct() { static::getDriverInfo()->checkVersionIsSupported(); } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::open() */ public function open($path) { $loader = $path instanceof LoaderInterface ? $path : $this->getClassFactory()->createFileLoader($path); $path = $loader->getPath(); try { if ($loader->isLocalFile()) { $gmagick = new \Gmagick($path); $image = $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GMAGICK, $gmagick, $this->createPalette($gmagick), $this->getMetadataReader()->readFile($loader)); } else { $image = $this->doLoad($loader->getData(), $this->getMetadataReader()->readFile($loader)); } } catch (\GmagickException $e) { throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e); } return $image; } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::create() */ public function create(BoxInterface $size, ColorInterface $color = null) { $width = $size->getWidth(); $height = $size->getHeight(); $palette = $color !== null ? $color->getPalette() : new RGB(); $color = $color !== null ? $color : $palette->color('fff'); try { $gmagick = new \Gmagick(); // Gmagick does not support creation of CMYK GmagickPixel // see https://bugs.php.net/bug.php?id=64466 if ($color instanceof CMYKColor) { $switchPalette = $palette; $palette = new RGB(); $pixel = new \GmagickPixel($palette->color((string) $color)); } else { $switchPalette = null; $pixel = new \GmagickPixel((string) $color); } if (!$color->getPalette()->supportsAlpha() && $color->getAlpha() !== null && $color->getAlpha() < 100) { throw new NotSupportedException('alpha transparency is not supported'); } $gmagick->newimage($width, $height, $pixel->getcolor(false)); $gmagick->setimagecolorspace(\Gmagick::COLORSPACE_TRANSPARENT); $gmagick->setimagebackgroundcolor($pixel); $image = $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GMAGICK, $gmagick, $palette, new MetadataBag()); if ($switchPalette) { $image->usePalette($switchPalette); } return $image; } catch (\GmagickException $e) { throw new RuntimeException('Could not create empty image', $e->getCode(), $e); } } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::load() */ public function load($string) { return $this->doLoad($string, $this->getMetadataReader()->readData($string)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::read() */ public function read($resource) { if (!is_resource($resource)) { throw new InvalidArgumentException('Variable does not contain a stream resource'); } $content = stream_get_contents($resource); if ($content === false) { throw new InvalidArgumentException('Couldn\'t read given resource'); } return $this->doLoad($content, $this->getMetadataReader()->readData($content, $resource)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::font() */ public function font($file, $size, ColorInterface $color) { return $this->getClassFactory()->createFont(ClassFactoryInterface::HANDLE_GMAGICK, $file, $size, $color); } /** * @param \Gmagick $gmagick * * @throws \Imagine\Exception\NotSupportedException * * @return \Imagine\Image\Palette\PaletteInterface */ private function createPalette(\Gmagick $gmagick) { switch ($gmagick->getimagecolorspace()) { case \Gmagick::COLORSPACE_SRGB: case \Gmagick::COLORSPACE_RGB: return new RGB(); case \Gmagick::COLORSPACE_CMYK: return new CMYK(); case \Gmagick::COLORSPACE_GRAY: return new Grayscale(); default: throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported'); } } /** * @param string $content * @param \Imagine\Image\Metadata\MetadataBag $metadata * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ private function doLoad($content, MetadataBag $metadata) { try { $gmagick = new \Gmagick(); $gmagick->readimageblob($content); } catch (\GmagickException $e) { throw new RuntimeException('Could not load image from string', $e->getCode(), $e); } return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GMAGICK, $gmagick, $this->createPalette($gmagick), $metadata); } } imagine/src/Gmagick/DriverInfo.php 0000644 00000012434 15141212321 0013062 0 ustar 00 <?php namespace Imagine\Gmagick; use Imagine\Driver\AbstractInfo; use Imagine\Exception\NotSupportedException; use Imagine\Image\Format; use Imagine\Image\FormatList; /** * Provide information and features supported by the Gmagick graphics driver. * * @since 1.3.0 */ class DriverInfo extends AbstractInfo { /** * @var static|\Imagine\Exception\NotSupportedException|null */ private static $instance; private $availableMethods = array(); /** * @throws \Imagine\Exception\NotSupportedException */ protected function __construct() { if (!class_exists('Gmagick') || !extension_loaded('gmagick')) { throw new NotSupportedException('Gmagick driver not installed'); } $m = null; $extensionVersion = phpversion('gmagick'); $driverRawVersion = is_string($extensionVersion) ? $extensionVersion : ''; $driverSemverVersion = preg_match('/^.*?(\d+\.\d+\.\d+)/', $driverRawVersion, $m) ? $m[1] : ''; $gmagick = new \Gmagick(); $engineVersion = $gmagick->getversion(); if (is_array($engineVersion) && isset($engineVersion['versionString']) && is_string($engineVersion['versionString'])) { if (preg_match('/^.*?(\d+\.\d+\.\d+(-\d+)?(\s+Q\d+)?)/i', $engineVersion['versionString'], $m)) { $engineRawVersion = $m[1]; } else { $engineRawVersion = $engineVersion['versionString']; } } else { $engineRawVersion = ''; } $engineSemverVersion = preg_match('/^.*?(\d+\.\d+\.\d+)/', $engineRawVersion, $m) ? $m[1] : ''; parent::__construct($driverRawVersion, $driverSemverVersion, $engineRawVersion, $engineSemverVersion); } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::get() */ public static function get($required = true) { if (self::$instance === null) { try { self::$instance = new static(); } catch (NotSupportedException $x) { self::$instance = $x; } } if (self::$instance instanceof self) { return self::$instance; } if ($required) { throw self::$instance; } return null; } /** * {@inheritdoc} * * @see \Imagine\Driver\AbstractInfo::checkFeature() */ protected function checkFeature($feature) { switch ($feature) { case static::FEATURE_COALESCELAYERS: throw new NotSupportedException('Gmagick does not support coalescing'); case static::FEATURE_NEGATEIMAGE: if (!$this->isMethodAvailale('negateimage')) { throw new NotSupportedException('Gmagick version 1.1.0 RC3 is required for negative effect'); } break; case static::FEATURE_COLORIZEIMAGE: throw new NotSupportedException('Gmagick does not support colorize'); case static::FEATURE_SHARPENIMAGE: throw new NotSupportedException('Gmagick does not support sharpen yet'); case static::FEATURE_CONVOLVEIMAGE: if (!$this->isMethodAvailale('convolveimage')) { throw new NotSupportedException('The version of Gmagick extension is too old: it does not support convolve (you need gmagick 2.0.1RC2 or later.'); } break; case static::FEATURE_CUSTOMRESOLUTION: throw new NotSupportedException('Gmagick does not support setting custom resolutions'); case static::FEATURE_GETCMYKCOLORSCORRECTLY: throw new NotSupportedException('Gmagick fails to read CMYK colors properly, see https://bugs.php.net/bug.php?id=67435'); case static::FEATURE_TRANSPARENCY: throw new NotSupportedException("Gmagick doesn't support transparency"); case static::FEATURE_ADDLAYERSTOEMPTYIMAGE: throw new NotSupportedException("Can't animate empty images because Gmagick is affected by bug https://bugs.php.net/bug.php?id=62309"); case static::FEATURE_DETECTGRAYCOLORSPACE: throw new NotSupportedException('Gmagick does not support gray colorspace, because of the lack of image type support'); } } /** * {@inheritdoc} * * @see \Imagine\Driver\AbstractInfo::buildSupportedFormats() */ protected function buildSupportedFormats() { $supportedFormats = array(); $gmagick = new \Gmagick(); $magickFormats = array_map('strtolower', $gmagick->queryFormats()); foreach (Format::getAll() as $format) { if (in_array($format->getID(), $magickFormats, true) || array_intersect($magickFormats, $format->getAlternativeIDs()) !== array()) { $supportedFormats[] = $format; } } return new FormatList($supportedFormats); } /** * @param string $methodName * * @return bool */ private function isMethodAvailale($methodName) { if (!isset($this->availableMethods[$methodName])) { $gmagick = new \Gmagick(); $this->availableMethods[$methodName] = method_exists($gmagick, $methodName); } return $this->availableMethods[$methodName]; } } imagine/src/Gmagick/Effects.php 0000644 00000011500 15141212321 0012363 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gmagick; use Imagine\Driver\InfoProvider; use Imagine\Effects\EffectsInterface; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Utils\Matrix; /** * Effects implementation using the Gmagick PHP extension. */ class Effects implements EffectsInterface, InfoProvider { /** * @var \Gmagick */ private $gmagick; /** * Initialize the instance. * * @param \Gmagick $gmagick */ public function __construct(\Gmagick $gmagick) { $this->gmagick = $gmagick; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::gamma() */ public function gamma($correction) { try { $this->gmagick->gammaimage($correction); } catch (\GmagickException $e) { throw new RuntimeException('Failed to apply gamma correction to the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::negative() */ public function negative() { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_NEGATEIMAGE); try { $this->gmagick->negateimage(false, \Gmagick::CHANNEL_ALL); } catch (\GmagickException $e) { throw new RuntimeException('Failed to negate the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::grayscale() */ public function grayscale() { try { $this->gmagick->setimagetype(defined('Gmagick::IMGTYPE_GRAYSCALE') ? \Gmagick::IMGTYPE_GRAYSCALE : 2); } catch (\GmagickException $e) { throw new RuntimeException('Failed to grayscale the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::colorize() */ public function colorize(ColorInterface $color) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORIZEIMAGE); } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::sharpen() */ public function sharpen() { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_SHARPENIMAGE); } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::blur() */ public function blur($sigma = 1) { try { $this->gmagick->blurimage(0, $sigma); } catch (\GmagickException $e) { throw new RuntimeException('Failed to blur the image', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::brightness() */ public function brightness($brightness) { $brightness = (int) round($brightness); if ($brightness < -100 || $brightness > 100) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$brightness', -100, 100, $brightness)); } try { // This *emulates* setting the brightness $sign = $brightness < 0 ? -1 : 1; $v = abs($brightness) / 100; if ($sign > 0) { $v = (2 / (sin(($v * .99999 * M_PI_2) + M_PI_2))) - 2; } $this->gmagick->modulateimage(100 + $sign * $v * 100, 100, 100); } catch (\GmagickException $e) { throw new RuntimeException('Failed to brightness the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::convolve() */ public function convolve(Matrix $matrix) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_CONVOLVEIMAGE); if ($matrix->getWidth() !== 3 || $matrix->getHeight() !== 3) { throw new InvalidArgumentException(sprintf('A convolution matrix must be 3x3 (%dx%d provided).', $matrix->getWidth(), $matrix->getHeight())); } try { $this->gmagick->convolveimage($matrix->getValueList()); } catch (\GmagickException $e) { throw new RuntimeException('Failed to convolve the image'); } return $this; } } imagine/src/Gmagick/Image.php 0000644 00000071166 15141212321 0012044 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gmagick; use Imagine\Driver\Info; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\NotSupportedException; use Imagine\Exception\OutOfBoundsException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\AbstractImage; use Imagine\Image\BoxInterface; use Imagine\Image\Fill\FillInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\PaletteInterface; use Imagine\Image\Point; use Imagine\Image\PointInterface; use Imagine\Image\ProfileInterface; /** * Image implementation using the Gmagick PHP extension. */ final class Image extends AbstractImage implements InfoProvider { /** * @var \Gmagick */ private $gmagick; /** * @var \Imagine\Gmagick\Layers|null */ private $layers; /** * @var \Imagine\Image\Palette\PaletteInterface */ private $palette; /** * @var array|null */ private static $colorspaceMapping = null; /** * Constructs a new Image instance. * * @param \Gmagick $gmagick * @param \Imagine\Image\Palette\PaletteInterface $palette * @param \Imagine\Image\Metadata\MetadataBag $metadata */ public function __construct(\Gmagick $gmagick, PaletteInterface $palette, MetadataBag $metadata) { $this->metadata = $metadata; $this->gmagick = $gmagick; $this->setColorspace($palette); } /** * Destroys allocated gmagick resources. */ public function __destruct() { if ($this->gmagick instanceof \Gmagick) { $this->gmagick->clear(); $this->gmagick->destroy(); } } /** * {@inheritdoc} * * @see \Imagine\Image\AbstractImage::__clone() */ public function __clone() { parent::__clone(); $this->gmagick = clone $this->gmagick; $this->palette = clone $this->palette; if ($this->layers !== null) { $this->layers = $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_GMAGICK, $this, $this->layers->key()); } } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * Returns gmagick instance. * * @return \Gmagick */ public function getGmagick() { return $this->gmagick; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::copy() */ public function copy() { return clone $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::crop() */ public function crop(PointInterface $start, BoxInterface $size) { if (!$start->in($this->getSize())) { throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders'); } try { $this->gmagick->cropimage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY()); } catch (\GmagickException $e) { throw new RuntimeException('Crop operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipHorizontally() */ public function flipHorizontally() { try { $this->gmagick->flopimage(); } catch (\GmagickException $e) { throw new RuntimeException('Horizontal flip operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipVertically() */ public function flipVertically() { try { $this->gmagick->flipimage(); } catch (\GmagickException $e) { throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::strip() */ public function strip() { try { try { $this->profile($this->palette->profile()); } catch (\Exception $e) { // here we discard setting the profile as the previous incorporated profile // is corrupted, let's now strip the image } $this->gmagick->stripimage(); } catch (\GmagickException $e) { throw new RuntimeException('Strip operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::paste() */ public function paste(ImageInterface $image, PointInterface $start, $alpha = 100) { if (!$image instanceof self) { throw new InvalidArgumentException(sprintf('Gmagick\Image can only paste() Gmagick\Image instances, %s given', get_class($image))); } $alpha = (int) round($alpha); if ($alpha < 0 || $alpha > 100) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$alpha', 0, 100, $alpha)); } if ($alpha === 100) { try { $this->gmagick->compositeimage($image->gmagick, \Gmagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY()); } catch (\GmagickException $e) { throw new RuntimeException('Paste operation failed', $e->getCode(), $e); } } elseif ($alpha > 0) { throw new NotSupportedException('Gmagick doesn\'t support paste with alpha.', 1); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::resize() */ public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) { if (!in_array($filter, static::getAllFilterValues(), true)) { throw new InvalidArgumentException('Unsupported filter type'); } static $supportedFilters = array( ImageInterface::FILTER_UNDEFINED => \Gmagick::FILTER_UNDEFINED, ImageInterface::FILTER_BESSEL => \Gmagick::FILTER_BESSEL, ImageInterface::FILTER_BLACKMAN => \Gmagick::FILTER_BLACKMAN, ImageInterface::FILTER_BOX => \Gmagick::FILTER_BOX, ImageInterface::FILTER_CATROM => \Gmagick::FILTER_CATROM, ImageInterface::FILTER_CUBIC => \Gmagick::FILTER_CUBIC, ImageInterface::FILTER_GAUSSIAN => \Gmagick::FILTER_GAUSSIAN, ImageInterface::FILTER_HANNING => \Gmagick::FILTER_HANNING, ImageInterface::FILTER_HAMMING => \Gmagick::FILTER_HAMMING, ImageInterface::FILTER_HERMITE => \Gmagick::FILTER_HERMITE, ImageInterface::FILTER_LANCZOS => \Gmagick::FILTER_LANCZOS, ImageInterface::FILTER_MITCHELL => \Gmagick::FILTER_MITCHELL, ImageInterface::FILTER_POINT => \Gmagick::FILTER_POINT, ImageInterface::FILTER_QUADRATIC => \Gmagick::FILTER_QUADRATIC, ImageInterface::FILTER_SINC => \Gmagick::FILTER_SINC, ImageInterface::FILTER_TRIANGLE => \Gmagick::FILTER_TRIANGLE, ); if (!array_key_exists($filter, $supportedFilters)) { $filter = ImageInterface::FILTER_UNDEFINED; } try { $this->gmagick->resizeimage($size->getWidth(), $size->getHeight(), $supportedFilters[$filter], 1); } catch (\GmagickException $e) { throw new RuntimeException('Resize operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::rotate() */ public function rotate($angle, ColorInterface $background = null) { try { if ($background === null) { $background = $this->palette->color('fff'); } $pixel = $this->getColor($background); $this->gmagick->rotateimage($pixel, $angle); unset($pixel); } catch (\GmagickException $e) { throw new RuntimeException('Rotate operation failed', $e->getCode(), $e); } return $this; } /** * Applies options before save or output. * * @param \Gmagick $image * @param array $options * @param string $path * * @throws \Imagine\Exception\InvalidArgumentException */ private function applyImageOptions(\Gmagick $image, array $options, $path) { if (isset($options['format'])) { $format = $options['format']; } elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) { $format = $extension; } else { $format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION); } $format = strtolower($format); switch ($format) { case 'jpeg': case 'jpg': case 'pjpeg': if (!isset($options['jpeg_quality'])) { if (isset($options['quality'])) { $options['jpeg_quality'] = $options['quality']; } } if (isset($options['jpeg_quality'])) { $image->setCompressionQuality($options['jpeg_quality']); } if (isset($options['jpeg_sampling_factors'])) { if (!is_array($options['jpeg_sampling_factors']) || \count($options['jpeg_sampling_factors']) < 1) { throw new InvalidArgumentException('jpeg_sampling_factors option should be an array of integers'); } $image->setSamplingFactors(array_map(function ($factor) { return (int) $factor; }, $options['jpeg_sampling_factors'])); } break; case 'png': if (!isset($options['png_compression_level'])) { if (isset($options['quality'])) { $options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100); } } if (isset($options['png_compression_level'])) { if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) { throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9'); } } if (isset($options['png_compression_filter'])) { if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) { throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9'); } } if (isset($options['png_compression_level']) || isset($options['png_compression_filter'])) { // first digit: compression level (default: 7) $compression = isset($options['png_compression_level']) ? $options['png_compression_level'] * 10 : 70; // second digit: compression filter (default: 5) $compression += isset($options['png_compression_filter']) ? $options['png_compression_filter'] : 5; $image->setCompressionQuality($compression); } break; case 'webp': if (!isset($options['webp_quality'])) { if (isset($options['quality'])) { $options['webp_quality'] = $options['quality']; } } if (isset($options['webp_quality'])) { $image->setCompressionQuality($options['webp_quality']); } break; } if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) { switch ($options['resolution-units']) { case ImageInterface::RESOLUTION_PIXELSPERCENTIMETER: $image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERCENTIMETER); break; case ImageInterface::RESOLUTION_PIXELSPERINCH: $image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERINCH); break; default: throw new InvalidArgumentException('Unsupported image unit format'); } $image->setimageresolution($options['resolution-x'], $options['resolution-y']); } } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::save() */ public function save($path = null, array $options = array()) { $path = $path === null ? $this->gmagick->getImageFilename() : $path; if (trim($path) === '') { throw new RuntimeException('You can omit save path only if image has been open from a file'); } try { $this->prepareOutput($options, $path); $allFrames = !isset($options['animated']) || $options['animated'] === false; $this->gmagick->writeimage($path, $allFrames); } catch (\GmagickException $e) { throw new RuntimeException('Save operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::show() */ public function show($format, array $options = array()) { header('Content-type: ' . $this->getMimeType($format)); echo $this->get($format, $options); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::get() */ public function get($format, array $options = array()) { try { $options['format'] = $format; $this->prepareOutput($options); } catch (\GmagickException $e) { throw new RuntimeException('Get operation failed', $e->getCode(), $e); } return $this->gmagick->getimagesblob(); } /** * @param array $options * @param string $path */ private function prepareOutput(array $options, $path = null) { if (isset($options['animated']) && $options['animated'] === true) { $format = isset($options['format']) ? $options['format'] : 'gif'; $delay = isset($options['animated.delay']) ? $options['animated.delay'] : null; $loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0; $options['flatten'] = false; $this->layers()->animate($format, $delay, $loops); } else { $this->layers()->merge(); } $this->applyImageOptions($this->gmagick, $options, $path); // flatten only if image has multiple layers if ((!isset($options['flatten']) || $options['flatten'] === true) && $this->layers()->count() > 1) { $this->flatten(); } if (isset($options['format'])) { $this->gmagick->setimageformat(strtoupper($options['format'])); } } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::__toString() */ public function __toString() { return $this->get('png'); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::draw() */ public function draw() { return $this->getClassFactory()->createDrawer(ClassFactoryInterface::HANDLE_GMAGICK, $this->gmagick); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::effects() */ public function effects() { return $this->getClassFactory()->createEffects(ClassFactoryInterface::HANDLE_GMAGICK, $this->gmagick); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::getSize() */ public function getSize() { try { $i = $this->gmagick->getimageindex(); $this->gmagick->setimageindex(0); //rewind $width = $this->gmagick->getimagewidth(); $height = $this->gmagick->getimageheight(); $this->gmagick->setimageindex($i); } catch (\GmagickException $e) { throw new RuntimeException('Get size operation failed', $e->getCode(), $e); } return $this->getClassFactory()->createBox($width, $height); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::applyMask() */ public function applyMask(ImageInterface $mask) { if (!$mask instanceof self) { throw new InvalidArgumentException('Can only apply instances of Imagine\Gmagick\Image as masks'); } $size = $this->getSize(); $maskSize = $mask->getSize(); if ($size != $maskSize) { throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size)); } try { $mask = $mask->copy(); $this->gmagick->compositeimage($mask->gmagick, \Gmagick::COMPOSITE_DEFAULT, 0, 0); } catch (\GmagickException $e) { throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::mask() */ public function mask() { $mask = $this->copy(); try { $mask->gmagick->modulateimage(100, 0, 100); } catch (\GmagickException $e) { throw new RuntimeException('Mask operation failed', $e->getCode(), $e); } return $mask; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::fill() */ public function fill(FillInterface $fill) { try { $draw = new \GmagickDraw(); $size = $this->getSize(); $w = $size->getWidth(); $h = $size->getHeight(); for ($x = 0; $x < $w; $x++) { for ($y = 0; $y < $h; $y++) { $pixel = $this->getColor($fill->getColor(new Point($x, $y))); $draw->setfillcolor($pixel); $draw->point($x, $y); $pixel = null; } } $this->gmagick->drawimage($draw); $draw = null; } catch (\GmagickException $e) { throw new RuntimeException('Fill operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::histogram() */ public function histogram() { try { $pixels = $this->gmagick->getimagehistogram(); } catch (\GmagickException $e) { throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e); } $image = $this; return array_map(function (\GmagickPixel $pixel) use ($image) { return $image->pixelToColor($pixel); }, $pixels); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::getColorAt() */ public function getColorAt(PointInterface $point) { if (!$point->in($this->getSize())) { throw new InvalidArgumentException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight())); } try { $cropped = clone $this->gmagick; $histogram = $cropped ->rollimage(-$point->getX(), -$point->getY()) ->cropImage(1, 1, 0, 0) ->getImageHistogram(); } catch (\GmagickException $e) { throw new RuntimeException('Unable to get the pixel', $e->getCode(), $e); } $pixel = array_shift($histogram); unset($histogram, $cropped); return $this->pixelToColor($pixel); } /** * Returns a color given a pixel, depending the Palette context. * * Note : this method is public for PHP 5.3 compatibility * * @param \GmagickPixel $pixel * * @throws \Imagine\Exception\InvalidArgumentException In case a unknown color is requested * * @return \Imagine\Image\Palette\Color\ColorInterface */ public function pixelToColor(\GmagickPixel $pixel) { static $colorMapping = array( ColorInterface::COLOR_RED => \Gmagick::COLOR_RED, ColorInterface::COLOR_GREEN => \Gmagick::COLOR_GREEN, ColorInterface::COLOR_BLUE => \Gmagick::COLOR_BLUE, ColorInterface::COLOR_CYAN => \Gmagick::COLOR_CYAN, ColorInterface::COLOR_MAGENTA => \Gmagick::COLOR_MAGENTA, ColorInterface::COLOR_YELLOW => \Gmagick::COLOR_YELLOW, ColorInterface::COLOR_KEYLINE => \Gmagick::COLOR_BLACK, // There is no gray component in \Gmagick, let's use one of the RGB comp ColorInterface::COLOR_GRAY => \Gmagick::COLOR_RED, ); $alpha = null; if ($this->palette->supportsAlpha()) { if ($alpha === null && defined('Gmagick::COLOR_ALPHA')) { try { $alpha = (int) round($pixel->getcolorvalue(\Gmagick::COLOR_ALPHA) * 100); } catch (\GmagickPixelException $e) { } } if ($alpha === null && defined('Gmagick::COLOR_OPACITY')) { try { $alpha = (int) round(100 - $pixel->getcolorvalue(\Gmagick::COLOR_OPACITY) * 100); } catch (\GmagickPixelException $e) { } } } $multiplier = $this->palette()->getChannelsMaxValue(); return $this->palette->color(array_map(function ($color) use ($multiplier, $pixel, $colorMapping) { if (!isset($colorMapping[$color])) { throw new InvalidArgumentException(sprintf('Color %s is not mapped in Gmagick', $color)); } return $pixel->getcolorvalue($colorMapping[$color]) * $multiplier; }, $this->palette->pixelDefinition()), $alpha); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::layers() */ public function layers() { if ($this->layers === null) { $this->layers = $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_GMAGICK, $this); } return $this->layers; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::interlace() */ public function interlace($scheme) { static $supportedInterlaceSchemes = array( ImageInterface::INTERLACE_NONE => \Gmagick::INTERLACE_NO, ImageInterface::INTERLACE_LINE => \Gmagick::INTERLACE_LINE, ImageInterface::INTERLACE_PLANE => \Gmagick::INTERLACE_PLANE, ImageInterface::INTERLACE_PARTITION => \Gmagick::INTERLACE_PARTITION, ); if (!array_key_exists($scheme, $supportedInterlaceSchemes)) { throw new InvalidArgumentException('Unsupported interlace type'); } $this->gmagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::usePalette() */ public function usePalette(PaletteInterface $palette) { if ($this->palette->name() === $palette->name()) { return $this; } $colorspaceMapping = self::getColorspaceMapping(); if (!isset($colorspaceMapping[$palette->name()])) { throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver', $palette->name())); } try { try { $hasICCProfile = (bool) $this->gmagick->getimageprofile('ICM'); } catch (\GmagickException $e) { $hasICCProfile = false; } if (!$hasICCProfile) { $this->profile($this->palette->profile()); } $this->profile($palette->profile()); $this->setColorspace($palette); } catch (\GmagickException $e) { throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::palette() */ public function palette() { return $this->palette; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::profile() */ public function profile(ProfileInterface $profile) { try { $this->gmagick->profileimage('ICM', $profile->data()); } catch (\GmagickException $e) { if (strpos($e->getMessage(), 'LCMS encoding not enabled') !== false) { throw new RuntimeException(sprintf('Unable to add profile %s to image, be sue to compile graphicsmagick with `--with-lcms2` option', $profile->name()), $e->getCode(), $e); } throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e); } return $this; } /** * Flatten the image. */ private function flatten() { // @see http://pecl.php.net/bugs/bug.php?id=22435 if (method_exists($this->gmagick, 'flattenImages')) { try { $this->gmagick = $this->gmagick->flattenImages(); } catch (\GmagickException $e) { throw new RuntimeException('Flatten operation failed', $e->getCode(), $e); } } } /** * Gets specifically formatted color string from Color instance. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\InvalidArgumentException * * @return \GmagickPixel */ private function getColor(ColorInterface $color) { if (!$color->isOpaque()) { static::getDriverInfo()->requireFeature(Info::FEATURE_TRANSPARENCY); } return new \GmagickPixel((string) $color); } /** * Get the mime type based on format. * * @param string $format * * @throws \Imagine\Exception\InvalidArgumentException * * @return string mime-type */ private function getMimeType($format) { static $mimeTypes = array( 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'gif' => 'image/gif', 'png' => 'image/png', 'webp' => 'image/webp', 'wbmp' => 'image/vnd.wap.wbmp', 'xbm' => 'image/xbm', 'bmp' => 'image/bmp', ); if (!isset($mimeTypes[$format])) { throw new InvalidArgumentException(sprintf('Unsupported format given. Only %s are supported, %s given', implode(', ', array_keys($mimeTypes)), $format)); } return $mimeTypes[$format]; } /** * Sets colorspace and image type, assigns the palette. * * @param \Imagine\Image\Palette\PaletteInterface $palette * * @throws \Imagine\Exception\InvalidArgumentException */ private function setColorspace(PaletteInterface $palette) { $colorspaceMapping = self::getColorspaceMapping(); if (!isset($colorspaceMapping[$palette->name()])) { throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver', $palette->name())); } $this->gmagick->setimagecolorspace($colorspaceMapping[$palette->name()]); $this->palette = $palette; } /** * @return array */ private static function getColorspaceMapping() { if (self::$colorspaceMapping === null) { $csm = array( PaletteInterface::PALETTE_CMYK => \Gmagick::COLORSPACE_CMYK, PaletteInterface::PALETTE_RGB => \Gmagick::COLORSPACE_RGB, ); if (defined('Gmagick::COLORSPACE_GRAY')) { $csm[PaletteInterface::PALETTE_GRAYSCALE] = \Gmagick::COLORSPACE_GRAY; } self::$colorspaceMapping = $csm; } return self::$colorspaceMapping; } } imagine/src/Gmagick/Drawer.php 0000644 00000032373 15141212321 0012243 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gmagick; use Imagine\Draw\DrawerInterface; use Imagine\Driver\Info; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\AbstractFont; use Imagine\Image\Box; use Imagine\Image\BoxInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Point; use Imagine\Image\PointInterface; /** * Drawer implementation using the Gmagick PHP extension. */ final class Drawer implements DrawerInterface, InfoProvider { /** * @var \Gmagick */ private $gmagick; /** * @param \Gmagick $gmagick */ public function __construct(\Gmagick $gmagick) { $this->gmagick = $gmagick; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::arc() */ public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0) { return $this; } $x = $center->getX(); $y = $center->getY(); $width = $size->getWidth(); $height = $size->getHeight(); try { $pixel = $this->getColor($color); $arc = new \GmagickDraw(); $arc->setstrokecolor($pixel); $arc->setstrokewidth($thickness); $arc->setfillcolor('transparent'); $arc->arc( $x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end ); $this->gmagick->drawImage($arc); $pixel = null; $arc = null; } catch (\GmagickException $e) { throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::chord() */ public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $x = $center->getX(); $y = $center->getY(); $width = $size->getWidth(); $height = $size->getHeight(); try { $pixel = $this->getColor($color); $chord = new \GmagickDraw(); $chord->setstrokecolor($pixel); $chord->setstrokewidth($thickness); if ($fill) { $chord->setfillcolor($pixel); } else { $x1 = round($x + $width / 2 * cos(deg2rad($start))); $y1 = round($y + $height / 2 * sin(deg2rad($start))); $x2 = round($x + $width / 2 * cos(deg2rad($end))); $y2 = round($y + $height / 2 * sin(deg2rad($end))); $this->line(new Point($x1, $y1), new Point($x2, $y2), $color, $thickness); $chord->setfillcolor('transparent'); } $chord->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end); $this->gmagick->drawImage($chord); $pixel = null; $chord = null; } catch (\GmagickException $e) { throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::circle() */ public function circle(PointInterface $center, $radius, ColorInterface $color, $fill = false, $thickness = 1) { $diameter = $radius * 2; return $this->ellipse($center, new Box($diameter, $diameter), $color, $fill, $thickness); } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::ellipse() */ public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $width = $size->getWidth(); $height = $size->getHeight(); try { $pixel = $this->getColor($color); $ellipse = new \GmagickDraw(); $ellipse->setstrokecolor($pixel); $ellipse->setstrokewidth($thickness); if ($fill) { $ellipse->setfillcolor($pixel); } else { $ellipse->setfillcolor('transparent'); } $ellipse->ellipse( $center->getX(), $center->getY(), $width / 2, $height / 2, 0, 360 ); $this->gmagick->drawImage($ellipse); $pixel = null; $ellipse = null; } catch (\GmagickException $e) { throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::line() */ public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0) { return $this; } try { $pixel = $this->getColor($color); $line = new \GmagickDraw(); $line->setstrokecolor($pixel); $line->setstrokewidth($thickness); $line->setfillcolor($pixel); $line->line( $start->getX(), $start->getY(), $end->getX(), $end->getY() ); $this->gmagick->drawImage($line); $pixel = null; $line = null; } catch (\GmagickException $e) { throw new RuntimeException('Draw line operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::pieSlice() */ public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $width = $size->getWidth(); $height = $size->getHeight(); $x1 = round($center->getX() + $width / 2 * cos(deg2rad($start))); $y1 = round($center->getY() + $height / 2 * sin(deg2rad($start))); $x2 = round($center->getX() + $width / 2 * cos(deg2rad($end))); $y2 = round($center->getY() + $height / 2 * sin(deg2rad($end))); if ($fill) { $this->chord($center, $size, $start, $end, $color, true, $thickness); $this->polygon( array( $center, new Point($x1, $y1), new Point($x2, $y2), ), $color, true, $thickness ); } else { $this->arc($center, $size, $start, $end, $color, $thickness); $this->line($center, new Point($x1, $y1), $color, $thickness); $this->line($center, new Point($x2, $y2), $color, $thickness); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::dot() */ public function dot(PointInterface $position, ColorInterface $color) { $x = $position->getX(); $y = $position->getY(); try { $pixel = $this->getColor($color); $point = new \GmagickDraw(); $point->setfillcolor($pixel); $point->point($x, $y); $this->gmagick->drawimage($point); $pixel = null; $point = null; } catch (\GmagickException $e) { throw new RuntimeException('Draw point operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::rectangle() */ public function rectangle(PointInterface $leftTop, PointInterface $rightBottom, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $minX = min($leftTop->getX(), $rightBottom->getX()); $maxX = max($leftTop->getX(), $rightBottom->getX()); $minY = min($leftTop->getY(), $rightBottom->getY()); $maxY = max($leftTop->getY(), $rightBottom->getY()); try { $pixel = $this->getColor($color); $rectangle = new \GmagickDraw(); $rectangle->setstrokecolor($pixel); $rectangle->setstrokewidth($thickness); if ($fill) { $rectangle->setfillcolor($pixel); } else { $rectangle->setfillcolor('transparent'); } $rectangle->rectangle($minX, $minY, $maxX, $maxY); $this->gmagick->drawImage($rectangle); } catch (\GmagickException $e) { throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::polygon() */ public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1) { if (count($coordinates) < 3) { throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates))); } $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } $points = array_map(function (PointInterface $p) { return array('x' => $p->getX(), 'y' => $p->getY()); }, $coordinates); try { $pixel = $this->getColor($color); $polygon = new \GmagickDraw(); $polygon->setstrokecolor($pixel); $polygon->setstrokewidth($thickness); if ($fill) { $polygon->setfillcolor($pixel); } else { $polygon->setfillcolor('transparent'); } $polygon->polygon($points); $this->gmagick->drawImage($polygon); unset($pixel, $polygon); } catch (\GmagickException $e) { throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::text() */ public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null) { try { $pixel = $this->getColor($font->getColor()); $text = new \GmagickDraw(); $text->setfont($font->getFile()); /* * @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027 * * ensure font resolution is the same as GD's hard-coded 96 */ $text->setfontsize((int) ($font->getSize() * (96 / 72))); $text->setfillcolor($pixel); if ($width !== null) { $string = $font->wrapText($string, $width, $angle); } $info = $this->gmagick->queryfontmetrics($text, $string); $rad = deg2rad($angle); $cos = cos($rad); $sin = sin($rad); $x1 = round(0 * $cos - 0 * $sin); $x2 = round($info['textWidth'] * $cos - $info['textHeight'] * $sin); $y1 = round(0 * $sin + 0 * $cos); $y2 = round($info['textWidth'] * $sin + $info['textHeight'] * $cos); $xdiff = 0 - min($x1, $x2); $ydiff = 0 - min($y1, $y2); $this->gmagick->annotateimage($text, $position->getX() + $x1 + $xdiff, $position->getY() + $y2 + $ydiff, $angle, $string); unset($pixel, $text); } catch (\GmagickException $e) { throw new RuntimeException('Draw text operation failed', $e->getCode(), $e); } return $this; } /** * Gets specifically formatted color string from Color instance. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\InvalidArgumentException In case a non-opaque color is passed * * @return \GmagickPixel */ private function getColor(ColorInterface $color) { if (!$color->isOpaque()) { static::getDriverInfo()->requireFeature(Info::FEATURE_TRANSPARENCY); } return new \GmagickPixel((string) $color); } } imagine/src/Gmagick/Layers.php 0000644 00000020404 15141212321 0012246 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gmagick; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\OutOfBoundsException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\AbstractLayers; use Imagine\Image\Format; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\PaletteInterface; class Layers extends AbstractLayers implements InfoProvider { /** * @var \Imagine\Gmagick\Image */ private $image; /** * @var \Gmagick */ private $resource; /** * @var int */ private $offset; /** * @var \Imagine\Gmagick\Image[] */ private $layers = array(); /** * @var \Imagine\Image\Palette\PaletteInterface */ private $palette; /** * @param \Imagine\Gmagick\Image $image * @param \Imagine\Image\Palette\PaletteInterface $palette * @param \Gmagick $resource * @param int $initialOffset */ public function __construct(Image $image, PaletteInterface $palette, \Gmagick $resource, $initialOffset = 0) { $this->image = $image; $this->resource = $resource; $this->palette = $palette; $this->offset = (int) $initialOffset; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::merge() */ public function merge() { foreach ($this->layers as $offset => $image) { try { $this->resource->setimageindex($offset); $this->resource->setimage($image->getGmagick()); } catch (\GmagickException $e) { throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e); } } } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::coalesce() */ public function coalesce() { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COALESCELAYERS); } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::animate() */ public function animate($format, $delay, $loops) { $formatInfo = Format::get($format); if ($formatInfo === null || $formatInfo->getID() !== Format::ID_GIF) { throw new InvalidArgumentException('Animated picture is currently only supported on gif'); } if (!is_int($loops) || $loops < 0) { throw new InvalidArgumentException('Loops must be a positive integer.'); } if ($delay !== null && (!is_int($delay) || $delay < 0)) { throw new InvalidArgumentException('Delay must be either null or a positive integer.'); } try { for ($offset = 0; $offset < $this->count(); $offset++) { $this->resource->setimageindex($offset); $this->resource->setimageformat($format); if ($delay !== null) { $this->resource->setimagedelay($delay / 10); } $this->resource->setimageiterations($loops); } } catch (\GmagickException $e) { throw new RuntimeException('Failed to animate layers', $e->getCode(), $e); } return $this; } /** * {@inheritdoc} * * @see \Iterator::current() */ #[\ReturnTypeWillChange] public function current() { return $this->extractAt($this->offset); } /** * Tries to extract layer at given offset. * * @param int $offset * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Gmagick\Image */ private function extractAt($offset) { if (!isset($this->layers[$offset])) { try { $this->resource->setimageindex($offset); $this->layers[$offset] = $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GMAGICK, $this->resource->getimage(), $this->palette, new MetadataBag()); } catch (\GmagickException $e) { throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e); } } return $this->layers[$offset]; } /** * {@inheritdoc} * * @see \Iterator::key() */ #[\ReturnTypeWillChange] public function key() { return $this->offset; } /** * {@inheritdoc} * * @see \Iterator::next() */ #[\ReturnTypeWillChange] public function next() { ++$this->offset; } /** * {@inheritdoc} * * @see \Iterator::rewind() */ #[\ReturnTypeWillChange] public function rewind() { $this->offset = 0; } /** * {@inheritdoc} * * @see \Iterator::valid() */ #[\ReturnTypeWillChange] public function valid() { return $this->offset < count($this); } /** * {@inheritdoc} * * @see \Countable::count() */ #[\ReturnTypeWillChange] public function count() { try { return $this->resource->getnumberimages(); } catch (\GmagickException $e) { throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e); } } /** * {@inheritdoc} * * @see \ArrayAccess::offsetExists() */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return is_int($offset) && $offset >= 0 && $offset < count($this); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetGet() */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->extractAt($offset); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetSet() */ #[\ReturnTypeWillChange] public function offsetSet($offset, $image) { if (!$image instanceof Image) { throw new InvalidArgumentException('Only a Gmagick Image can be used as layer'); } if ($offset === null) { $offset = count($this) - 1; } else { if (!is_int($offset)) { throw new InvalidArgumentException('Invalid offset for layer, it must be an integer'); } if (count($this) < $offset || $offset < 0) { throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset)); } if (isset($this[$offset])) { unset($this[$offset]); $offset = $offset - 1; } } $frame = $image->getGmagick(); try { if (count($this) > 0) { $this->resource->setimageindex($offset); $this->resource->nextimage(); } $this->resource->addimage($frame); // ugly hack to bypass issue https://bugs.php.net/bug.php?id=64623 if (count($this) == 2) { $this->resource->setimageindex($offset + 1); $this->resource->nextimage(); $this->resource->addimage($frame); unset($this[0]); } } catch (\GmagickException $e) { throw new RuntimeException('Unable to set the layer', $e->getCode(), $e); } $this->layers = array(); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetUnset() */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { try { $this->extractAt($offset); } catch (RuntimeException $e) { return; } try { $this->resource->setimageindex($offset); $this->resource->removeimage(); } catch (\GmagickException $e) { throw new RuntimeException('Unable to remove layer', $e->getCode(), $e); } } } imagine/src/Filter/Transformation.php 0000644 00000015231 15141212321 0013702 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter; use Imagine\Exception\InvalidArgumentException; use Imagine\Filter\Basic\ApplyMask; use Imagine\Filter\Basic\Copy; use Imagine\Filter\Basic\Crop; use Imagine\Filter\Basic\Fill; use Imagine\Filter\Basic\FlipHorizontally; use Imagine\Filter\Basic\FlipVertically; use Imagine\Filter\Basic\Paste; use Imagine\Filter\Basic\Resize; use Imagine\Filter\Basic\Rotate; use Imagine\Filter\Basic\Save; use Imagine\Filter\Basic\Show; use Imagine\Filter\Basic\Strip; use Imagine\Filter\Basic\Thumbnail; use Imagine\Image\BoxInterface; use Imagine\Image\Fill\FillInterface; use Imagine\Image\ImageInterface; use Imagine\Image\ImagineInterface; use Imagine\Image\ManipulatorInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\PointInterface; /** * A transformation filter. */ final class Transformation implements FilterInterface, ManipulatorInterface { /** * @var array[\Imagine\Filter\FilterInterface[]] */ private $filters = array(); /** * @var array[\Imagine\Filter\FilterInterface[]]|null */ private $sorted; /** * An ImagineInterface instance. * * @var \Imagine\Image\ImageInterface|null */ private $imagine; /** * Class constructor. * * @param \Imagine\Image\ImageInterface|null $imagine An ImagineInterface instance */ public function __construct(ImagineInterface $imagine = null) { $this->imagine = $imagine; } /** * Applies a given FilterInterface onto given ImageInterface and returns modified ImageInterface. * * @param \Imagine\Image\ImageInterface $image * @param \Imagine\Filter\FilterInterface $filter * * @throws \Imagine\Exception\InvalidArgumentException * * @return \Imagine\Image\ImageInterface */ public function applyFilter(ImageInterface $image, FilterInterface $filter) { if ($filter instanceof ImagineAware) { if ($this->imagine === null) { throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to Transformation constructor', get_class($filter))); } $filter->setImagine($this->imagine); } return $filter->apply($image); } /** * Returns a list of filters sorted by their priority. Filters with same priority will be returned in the order they were added. * * @return array */ public function getFilters() { if ($this->sorted === null) { if (!empty($this->filters)) { ksort($this->filters); $this->sorted = call_user_func_array('array_merge', $this->filters); } else { $this->sorted = array(); } } return $this->sorted; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return array_reduce( $this->getFilters(), array($this, 'applyFilter'), $image ); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::copy() */ public function copy() { return $this->add(new Copy()); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::crop() */ public function crop(PointInterface $start, BoxInterface $size) { return $this->add(new Crop($start, $size)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipHorizontally() */ public function flipHorizontally() { return $this->add(new FlipHorizontally()); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipVertically() */ public function flipVertically() { return $this->add(new FlipVertically()); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::strip() */ public function strip() { return $this->add(new Strip()); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::paste() */ public function paste(ImageInterface $image, PointInterface $start, $alpha = 100) { return $this->add(new Paste($image, $start, $alpha)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::applyMask() */ public function applyMask(ImageInterface $mask) { return $this->add(new ApplyMask($mask)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::fill() */ public function fill(FillInterface $fill) { return $this->add(new Fill($fill)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::resize() */ public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) { return $this->add(new Resize($size, $filter)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::rotate() */ public function rotate($angle, ColorInterface $background = null) { return $this->add(new Rotate($angle, $background)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::save() */ public function save($path = null, array $options = array()) { return $this->add(new Save($path, $options)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::show() */ public function show($format, array $options = array()) { return $this->add(new Show($format, $options)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::thumbnail() */ public function thumbnail(BoxInterface $size, $settings = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED) { return $this->add(new Thumbnail($size, $settings, $filter)); } /** * Registers a given FilterInterface in an internal array of filters for later application to an instance of ImageInterface. * * @param \Imagine\Filter\FilterInterface $filter * @param int $priority * * @return $this */ public function add(FilterInterface $filter, $priority = 0) { $this->filters[$priority][] = $filter; $this->sorted = null; return $this; } } imagine/src/Filter/ImagineAware.php 0000644 00000002442 15141212321 0013225 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter; use Imagine\Exception\InvalidArgumentException; use Imagine\Image\ImagineInterface; /** * ImagineAware base class. */ abstract class ImagineAware implements FilterInterface { /** * An ImagineInterface instance. * * @var \Imagine\Image\ImagineInterface */ private $imagine; /** * Set ImagineInterface instance. * * @param \Imagine\Image\ImagineInterface $imagine An ImagineInterface instance */ public function setImagine(ImagineInterface $imagine) { $this->imagine = $imagine; } /** * Get ImagineInterface instance. * * @throws \Imagine\Exception\InvalidArgumentException * * @return \Imagine\Image\ImagineInterface */ public function getImagine() { if (!$this->imagine instanceof ImagineInterface) { throw new InvalidArgumentException(sprintf('In order to use %s pass an Imagine\Image\ImagineInterface instance to filter constructor', get_class($this))); } return $this->imagine; } } imagine/src/Filter/FilterInterface.php 0000644 00000001235 15141212321 0013741 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter; use Imagine\Image\ImageInterface; /** * Interface for imagine filters. */ interface FilterInterface { /** * Applies scheduled transformation to an ImageInterface instance. * * @param \Imagine\Image\ImageInterface $image * * @return \Imagine\Image\ImageInterface returns the processed ImageInterface instance */ public function apply(ImageInterface $image); } imagine/src/Filter/Advanced/Canvas.php 0000644 00000003624 15141212321 0013617 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Filter\FilterInterface; use Imagine\Image\BoxInterface; use Imagine\Image\ImageInterface; use Imagine\Image\ImagineInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Point; use Imagine\Image\PointInterface; /** * A canvas filter. */ class Canvas implements FilterInterface { /** * @var \Imagine\Image\BoxInterface */ private $size; /** * @var \Imagine\Image\PointInterface */ private $placement; /** * @var \Imagine\Image\Palette\Color\ColorInterface */ private $background; /** * @var \Imagine\Image\ImagineInterface */ private $imagine; /** * Constructs Canvas filter with given width and height and the placement of the current image inside the new canvas. * * @param \Imagine\Image\ImagineInterface $imagine * @param \Imagine\Image\BoxInterface $size * @param \Imagine\Image\PointInterface $placement * @param \Imagine\Image\Palette\Color\ColorInterface $background */ public function __construct(ImagineInterface $imagine, BoxInterface $size, PointInterface $placement = null, ColorInterface $background = null) { $this->imagine = $imagine; $this->size = $size; $this->placement = $placement ?: new Point(0, 0); $this->background = $background; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { $canvas = $this->imagine->create($this->size, $this->background); $canvas->paste($image, $this->placement); return $canvas; } } imagine/src/Filter/Advanced/Border.php 0000644 00000004467 15141212321 0013627 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Point; /** * A border filter. */ class Border implements FilterInterface { /** * @var \Imagine\Image\Palette\Color\ColorInterface */ private $color; /** * @var int */ private $width; /** * @var int */ private $height; /** * Constructs Border filter with given color, width and height. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param int $width Width of the border on the left and right sides of the image * @param int $height Height of the border on the top and bottom sides of the image */ public function __construct(ColorInterface $color, $width = 1, $height = 1) { $this->color = $color; $this->width = $width; $this->height = $height; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { $size = $image->getSize(); $width = $size->getWidth(); $height = $size->getHeight(); $draw = $image->draw(); // Draw top and bottom lines $draw ->line( new Point(0, 0), new Point($width - 1, 0), $this->color, $this->height ) ->line( new Point($width - 1, $height - 1), new Point(0, $height - 1), $this->color, $this->height ) ; // Draw sides $draw ->line( new Point(0, 0), new Point(0, $height - 1), $this->color, $this->width ) ->line( new Point($width - 1, 0), new Point($width - 1, $height - 1), $this->color, $this->width ) ; return $image; } } imagine/src/Filter/Advanced/RelativeResize.php 0000644 00000002676 15141212321 0015347 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Exception\InvalidArgumentException; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * The RelativeResize filter allows images to be resized relative to their existing dimensions. */ class RelativeResize implements FilterInterface { /** * @var string */ private $method; /** * @var mixed */ private $parameter; /** * Constructs a RelativeResize filter with the given method and argument. * * @param string $method BoxInterface method * @param mixed $parameter Parameter for BoxInterface method */ public function __construct($method, $parameter) { if (!in_array($method, array('heighten', 'increase', 'scale', 'widen'))) { throw new InvalidArgumentException(sprintf('Unsupported method: ', $method)); } $this->method = $method; $this->parameter = $parameter; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->resize(call_user_func(array($image->getSize(), $this->method), $this->parameter)); } } imagine/src/Filter/Advanced/Grayscale.php 0000644 00000001370 15141212321 0014312 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Point; /** * The Grayscale filter calculates the gray-value based on RGB. */ class Grayscale extends OnPixelBased implements FilterInterface { public function __construct() { parent::__construct(function (ImageInterface $image, Point $point) { $color = $image->getColorAt($point); $image->draw()->dot($point, $color->grayscale()); }); } } imagine/src/Filter/Advanced/BorderDetection.php 0000644 00000004510 15141212321 0015453 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Exception\InvalidArgumentException; use Imagine\Filter\FilterInterface; use Imagine\Utils\Matrix; /** * BorderDetection based on Laplace-Operator. Three different variants are offered:. * <code><pre> * First Second Third * 0, 1, 0 1, 1, 1, -1, 2, -1, * 1, -4, 1 and 1, -8, 1, and 2, -4, 2, * 0, 1, 0 1, 1, 1 -1, 2, -1 * </pre></code>. * * Consider to apply this filter on a grayscaled image. */ class BorderDetection extends Neighborhood implements FilterInterface { /** * First variant of the detection matrix. * * @var int */ const VARIANT_ONE = 0; /** * Second variant of the detection matrix. * * @var int */ const VARIANT_TWO = 1; /** * Third variant of the detection matrix. * * @var int */ const VARIANT_THREE = 2; /** * Initialize this filter. * * @param int $variant One of the BorderDetection::VARIANT_... constants. * * @throws \Imagine\Exception\InvalidArgumentException throws an InvalidArgumentException if $variant is not valid */ public function __construct($variant = self::VARIANT_ONE) { $matrix = null; switch ($variant) { case self::VARIANT_ONE: $matrix = new Matrix(3, 3, array( 0, 1, 0, 1, -4, 1, 0, 1, 0, )); break; case self::VARIANT_TWO: $matrix = new Matrix(3, 3, array( 1, 1, 1, 1, -8, 1, 1, 1, 1, )); break; case self::VARIANT_THREE: $matrix = new Matrix(3, 3, array( -1, 2, -1, 2, -4, 2, -1, 2, -1, )); break; default: throw new InvalidArgumentException('Unknown variant ' . $variant); } parent::__construct($matrix); } } imagine/src/Filter/Advanced/BlackWhite.php 0000644 00000004134 15141212321 0014416 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Exception\InvalidArgumentException; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\RGB; use Imagine\Image\Point; /** * This filter calculates, for each pixel of an image, whether it is ligher or darker than a threshold. * If the pixel is lighter than the thresold it will be black, otherwise it will be light. * The result is an image with only black and white pixels (black pixels for ligher colors, white pixels for darker colors). */ class BlackWhite extends OnPixelBased implements FilterInterface { /** * @var \Imagine\Filter\Advanced\Grayscale */ protected $grayScaleFilter; /** * Initialize this filter. * * @param int $threshold the dask/light threshold, from 0 (all black) to 255 (all white) * * @throws \Imagine\Exception\InvalidArgumentException */ public function __construct($threshold) { if (!($threshold >= 0 && $threshold <= 255)) { throw new InvalidArgumentException('$threshold has to be between 0 and 255'); } $this->grayScaleFilter = new Grayscale(); $rgb = new RGB(); parent::__construct( function (ImageInterface $image, Point $point) use ($threshold, $rgb) { $newRedValue = $image->getColorAt($point)->getValue(ColorInterface::COLOR_RED) < $threshold ? 255 : 0; $image->draw()->dot($point, $rgb->color(array($newRedValue, $newRedValue, $newRedValue))); } ); } /** * {@inheritdoc} * * @see \Imagine\Filter\Advanced\OnPixelBased::apply() */ public function apply(ImageInterface $image) { $grayScaledImage = $this->grayScaleFilter->apply($image); return parent::apply($grayScaledImage); } } imagine/src/Filter/Advanced/Negation.php 0000644 00000001257 15141212321 0014150 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * This filter negates every color of every pixel of an image. */ class Negation implements FilterInterface { /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { $image->effects()->negative(); return $image; } } imagine/src/Filter/Advanced/OnPixelBased.php 0000644 00000003030 15141212321 0014710 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Exception\InvalidArgumentException; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Point; /** * The OnPixelBased takes a callable, and for each pixel, this callable is called with the * image (\Imagine\Image\ImageInterface) and the current point (\Imagine\Image\Point). */ class OnPixelBased implements FilterInterface { /** * @var callable */ protected $callback; /** * Initialize the instance. * * @param callable $callback * * @throws \Imagine\Exception\InvalidArgumentException */ public function __construct($callback) { if (!is_callable($callback)) { throw new InvalidArgumentException('$callback has to be callable'); } $this->callback = $callback; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { $size = $image->getSize(); $w = $size->getWidth(); $h = $size->getHeight(); for ($y = 0; $y < $h; $y++) { for ($x = 0; $x < $w; $x++) { call_user_func($this->callback, $image, new Point($x, $y)); } } return $image; } } imagine/src/Filter/Advanced/Neighborhood.php 0000644 00000006453 15141212321 0015016 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Advanced; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Point; use Imagine\Utils\Matrix; /** * The Neighborhood filter takes a matrix and calculates the color current pixel based on its neighborhood. * * @example * <code><pre> * a, b, c * Matrix = d, e, f * g, h, i * </pre></code> * * and color{i, j} the color of the pixel at position (i, j). It calculates the color of pixel (x, y) like that: * * <code><pre> * color (x, y) = a * color(x - 1, y - 1) + b * color(x, y - 1) + c * color(x + 1, y - 1) * + d * color(x - 1, y) + e * color(x, y) + f * color(x + 1, y) * + g * color(x - 1, y + 1) + h * color(x, y + 1) + i * color(x + 1, y + 1) * </pre></code> */ class Neighborhood implements FilterInterface { /** * @var \Imagine\Utils\Matrix */ protected $matrix; /** * Initialize the instance. * * @param \Imagine\Utils\Matrix $matrix */ public function __construct(Matrix $matrix) { $this->matrix = $matrix; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { // We reduce the usage of methods on the image to dramatically increase the performance of this algorithm. // Really... We need that performance... // Therefore we first build a matrix, that holds the colors of the image. $width = $image->getSize()->getWidth(); $height = $image->getSize()->getHeight(); $byteData = new Matrix($width, $height); for ($y = 0; $y < $height; $y++) { for ($x = 0; $x < $width; $x++) { $byteData->setElementAt($x, $y, $image->getColorAt(new Point($x, $y))); } } $dWidth = (int) (($this->matrix->getWidth() - 1) / 2); $dHeight = (int) (($this->matrix->getHeight() - 1) / 2); // foreach point, which has a big enough neighborhood for ($y = $dHeight; $y < $height - $dHeight; $y++) { for ($x = $dWidth; $x < $width - $dWidth; $x++) { $currentColor = array_fill(0, count($image->palette()->pixelDefinition()), 0); // calculate the new color based on the neighborhood for ($boxY = $y - $dHeight, $matrixY = 0; $boxY <= $y + $dHeight; $boxY++, $matrixY++) { for ($boxX = $x - $dWidth, $matrixX = 0; $boxX <= $x + $dWidth; $boxX++, $matrixX++) { foreach ($image->palette()->pixelDefinition() as $index => $stream) { $currentColor[$index] += $byteData->getElementAt($boxX, $boxY)->getValue($stream) * $this->matrix->getElementAt($matrixX, $matrixY) ; } } } $image->draw()->dot(new Point($x, $y), $image->palette()->color($currentColor)); } } return $image; } } imagine/src/Filter/Basic/Strip.php 0000644 00000001136 15141212321 0013015 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * A strip filter. */ class Strip implements FilterInterface { /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->strip(); } } imagine/src/Filter/Basic/Paste.php 0000644 00000003423 15141212321 0012771 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Exception\InvalidArgumentException; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\PointInterface; /** * A paste filter. */ class Paste implements FilterInterface { /** * @var \Imagine\Image\ImageInterface */ private $image; /** * @var \Imagine\Image\PointInterface */ private $start; /** * How to paste the image, from 0 (fully transparent) to 100 (fully opaque). * * @var int */ private $alpha; /** * Constructs a Paste filter with given ImageInterface to paste and x, y * coordinates of target position. * * @param \Imagine\Image\ImageInterface $image * @param \Imagine\Image\PointInterface $start * @param int $alpha how to paste the image, from 0 (fully transparent) to 100 (fully opaque) */ public function __construct(ImageInterface $image, PointInterface $start, $alpha = 100) { $this->image = $image; $this->start = $start; $alpha = (int) round($alpha); if ($alpha < 0 || $alpha > 100) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$alpha', 0, 100, $alpha)); } $this->alpha = $alpha; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->paste($this->image, $this->start, $this->alpha); } } imagine/src/Filter/Basic/Rotate.php 0000644 00000002244 15141212321 0013153 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Palette\Color\ColorInterface; /** * A rotate filter. */ class Rotate implements FilterInterface { /** * @var int */ private $angle; /** * @var \Imagine\Image\Palette\Color\ColorInterface */ private $background; /** * Constructs Rotate filter with given angle and background color. * * @param int $angle * @param \Imagine\Image\Palette\Color\ColorInterface $background */ public function __construct($angle, ColorInterface $background = null) { $this->angle = $angle; $this->background = $background; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->rotate($this->angle, $this->background); } } imagine/src/Filter/Basic/FlipVertically.php 0000644 00000001174 15141212321 0014647 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * A "flip vertically" filter. */ class FlipVertically implements FilterInterface { /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->flipVertically(); } } imagine/src/Filter/Basic/Autorotate.php 0000644 00000007237 15141212321 0014053 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Palette\Color\ColorInterface; /** * Rotates an image automatically based on exif information. * * Your attention please: This filter requires the use of the * ExifMetadataReader to work. * * @see https://imagine.readthedocs.org/en/latest/usage/metadata.html */ class Autorotate implements FilterInterface { /** * Image transformation: flip vertically. * * @var string */ const FLIP_VERTICALLY = 'V'; /** * Image transformation: flip horizontally. * * @var string */ const FLIP_HORIZONTALLY = 'H'; /** * @var string|array|\Imagine\Image\Palette\Color\ColorInterface */ private $color; /** * @param string|array|\Imagine\Image\Palette\Color\ColorInterface $color A color */ public function __construct($color = '000000') { $this->color = $color; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { foreach ($this->getTransformations($image) as $transformation) { if ($transformation === self::FLIP_HORIZONTALLY) { $image->flipHorizontally(); } elseif ($transformation === self::FLIP_VERTICALLY) { $image->flipVertically(); } elseif (is_int($transformation)) { $image->rotate($transformation, $this->getColor($image)); } } return $image; } /** * Get the transformations. * * @param \Imagine\Image\ImageInterface $image * * @return array an array containing Autorotate::FLIP_VERTICALLY, Autorotate::FLIP_HORIZONTALLY, rotation degrees */ public function getTransformations(ImageInterface $image) { $transformations = array(); $metadata = $image->metadata(); switch (isset($metadata['ifd0.Orientation']) ? $metadata['ifd0.Orientation'] : null) { case 1: // top-left break; case 2: // top-right $transformations[] = self::FLIP_HORIZONTALLY; break; case 3: // bottom-right $transformations[] = 180; break; case 4: // bottom-left $transformations[] = self::FLIP_HORIZONTALLY; $transformations[] = 180; break; case 5: // left-top $transformations[] = self::FLIP_HORIZONTALLY; $transformations[] = -90; break; case 6: // right-top $transformations[] = 90; break; case 7: // right-bottom $transformations[] = self::FLIP_HORIZONTALLY; $transformations[] = 90; break; case 8: // left-bottom $transformations[] = -90; break; default: // Invalid orientation break; } return $transformations; } /** * @param \Imagine\Image\ImageInterface $image * * @return \Imagine\Image\Palette\Color\ColorInterface */ private function getColor(ImageInterface $image) { if ($this->color instanceof ColorInterface) { return $this->color; } return $image->palette()->color($this->color); } } imagine/src/Filter/Basic/Fill.php 0000644 00000001603 15141212321 0012601 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\Fill\FillInterface; use Imagine\Image\ImageInterface; /** * A fill filter. */ class Fill implements FilterInterface { /** * @var \Imagine\Image\Fill\FillInterface */ private $fill; /** * @param \Imagine\Image\Fill\FillInterface $fill */ public function __construct(FillInterface $fill) { $this->fill = $fill; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->fill($this->fill); } } imagine/src/Filter/Basic/FlipHorizontally.php 0000644 00000001202 15141212321 0015217 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * A "flip horizontally" filter. */ class FlipHorizontally implements FilterInterface { /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->flipHorizontally(); } } imagine/src/Filter/Basic/Resize.php 0000644 00000002154 15141212321 0013156 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\BoxInterface; use Imagine\Image\ImageInterface; /** * A resize filter. */ class Resize implements FilterInterface { /** * @var \Imagine\Image\BoxInterface */ private $size; /** * @var string */ private $filter; /** * Constructs Resize filter with given width and height. * * @param \Imagine\Image\BoxInterface $size * @param string $filter */ public function __construct(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) { $this->size = $size; $this->filter = $filter; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->resize($this->size, $this->filter); } } imagine/src/Filter/Basic/Copy.php 0000644 00000001133 15141212321 0012623 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * A copy filter. */ class Copy implements FilterInterface { /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->copy(); } } imagine/src/Filter/Basic/ApplyMask.php 0000644 00000001616 15141212321 0013620 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * An apply mask filter. */ class ApplyMask implements FilterInterface { /** * @var \Imagine\Image\ImageInterface */ private $mask; /** * Initialize the instance. * * @param \Imagine\Image\ImageInterface $mask */ public function __construct(ImageInterface $mask) { $this->mask = $mask; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->applyMask($this->mask); } } imagine/src/Filter/Basic/WebOptimization.php 0000644 00000003163 15141212321 0015042 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; use Imagine\Image\Palette\RGB; /** * A filter to render web-optimized images. */ class WebOptimization implements FilterInterface { /** * @var \Imagine\Image\Palette\RGB */ private $palette; /** * @var string|callable|null */ private $path; /** * @var array */ private $options; /** * @param string|callable|null $path * @param array $options */ public function __construct($path = null, array $options = array()) { $this->path = $path; $this->options = array_replace(array( 'resolution-units' => ImageInterface::RESOLUTION_PIXELSPERINCH, 'resolution-y' => 72, 'resolution-x' => 72, ), $options); $this->palette = new RGB(); } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { $image ->usePalette($this->palette) ->strip(); if (is_callable($this->path)) { $path = call_user_func($this->path, $image); } elseif ($this->path !== null) { $path = $this->path; } else { return $image; } return $image->save($path, $this->options); } } imagine/src/Filter/Basic/Save.php 0000644 00000002005 15141212321 0012606 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * A save filter. */ class Save implements FilterInterface { /** * @var string */ private $path; /** * @var array */ private $options; /** * Constructs Save filter with given path and options. * * @param string $path * @param array $options */ public function __construct($path = null, array $options = array()) { $this->path = $path; $this->options = $options; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->save($this->path, $this->options); } } imagine/src/Filter/Basic/Crop.php 0000644 00000002276 15141212321 0012625 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\BoxInterface; use Imagine\Image\ImageInterface; use Imagine\Image\PointInterface; /** * A crop filter. */ class Crop implements FilterInterface { /** * @var \Imagine\Image\PointInterface */ private $start; /** * @var \Imagine\Image\BoxInterface */ private $size; /** * Constructs a Crop filter with given x, y, coordinates and crop width and height values. * * @param \Imagine\Image\PointInterface $start * @param \Imagine\Image\BoxInterface $size */ public function __construct(PointInterface $start, BoxInterface $size) { $this->start = $start; $this->size = $size; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->crop($this->start, $this->size); } } imagine/src/Filter/Basic/Show.php 0000644 00000002020 15141212321 0012625 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\ImageInterface; /** * A show filter. */ class Show implements FilterInterface { /** * @var string */ private $format; /** * @var array */ private $options; /** * Constructs the Show filter with given format and options. * * @param string $format * @param array $options */ public function __construct($format, array $options = array()) { $this->format = $format; $this->options = $options; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->show($this->format, $this->options); } } imagine/src/Filter/Basic/Thumbnail.php 0000644 00000002763 15141212321 0013646 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Filter\Basic; use Imagine\Filter\FilterInterface; use Imagine\Image\BoxInterface; use Imagine\Image\ImageInterface; /** * A thumbnail filter. */ class Thumbnail implements FilterInterface { /** * @var \Imagine\Image\BoxInterface */ private $size; /** * @var int|string */ private $settings; /** * @var string */ private $filter; /** * Constructs the Thumbnail filter. * * @param \Imagine\Image\BoxInterface $size * @param int|string $settings One or more of the ManipulatorInterface::THUMBNAIL_ flags (joined with |). It may be a string for backward compatibility with old constant values that were strings. * @param string $filter See ImageInterface::FILTER_... constants */ public function __construct(BoxInterface $size, $settings = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED) { $this->size = $size; $this->settings = $settings; $this->filter = $filter; } /** * {@inheritdoc} * * @see \Imagine\Filter\FilterInterface::apply() */ public function apply(ImageInterface $image) { return $image->thumbnail($this->size, $this->settings, $this->filter); } } imagine/src/Utils/ErrorHandling.php 0000644 00000004737 15141212321 0013316 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Utils; use ErrorException; use Exception; use Imagine\Exception\RuntimeException; use Throwable; class ErrorHandling { /** * Call a callback ignoring $flags warnings. * * @param int $flags The flags to be ignored (eg E_WARNING | E_NOTICE) * @param callable $callback The callable to be called * * @throws \Exception Throws an Exception if $callback throws an Exception * @throws \Throwable Throws an Throwable if $callback throws an Throwable * * @return mixed Returns the result of $callback */ public static function ignoring($flags, $callback) { set_error_handler( function () { }, $flags ); try { $result = $callback(); $exception = null; } catch (Exception $x) { $exception = $x; } catch (Throwable $x) { $exception = $x; } restore_error_handler(); if ($exception !== null) { throw $exception; } return $result; } /** * Call a callback and throws a RuntimeException if a $flags warning is thrown. * * @param int $flags The flags to be intercepted (eg E_WARNING | E_NOTICE) * @param callable $callback The callable to be called * * @throws RuntimeException * @throws \Imagine\Exception\RuntimeException * @throws \Exception * @throws \Throwable * * @return mixed Returns the result of $callback */ public static function throwingRuntimeException($flags, $callback) { set_error_handler( function ($errno, $errstr, $errfile, $errline) { if (error_reporting() !== 0) { throw new RuntimeException($errstr, $errno, new ErrorException($errstr, 0, $errno, $errfile, $errline)); } }, $flags ); try { $result = $callback(); $exception = null; } catch (Exception $x) { $exception = $x; } catch (Throwable $x) { $exception = $x; } restore_error_handler(); if ($exception !== null) { throw $exception; } return $result; } } imagine/src/Utils/Matrix.php 0000644 00000010434 15141212321 0012013 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Utils; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\OutOfBoundsException; class Matrix { /** * The array of elements. * * @var int[]|float[] */ protected $elements = array(); /** * The matrix width. * * @var int */ protected $width; /** * The matrix height. * * @var int */ protected $height; /** * The given $elements get arranged as follows: The elements will be set from left to right in a row until the * row is full. Then, the next line begins alike and so on. * * @param int $width the matrix width * @param int $height he matrix height * @param int[]|float[] $elements the matrix elements * * @throws \Imagine\Exception\InvalidArgumentException */ public function __construct($width, $height, $elements = array()) { $this->width = (int) round($width); if ($this->width < 1) { throw new InvalidArgumentException('width has to be > 0'); } $this->height = (int) round($height); if ($this->height < 1) { throw new InvalidArgumentException('height has to be > 0'); } $expectedElements = $width * $height; $providedElements = count($elements); if ($providedElements > $expectedElements) { throw new InvalidArgumentException('there are more provided elements than space in the matrix'); } $this->elements = array_values($elements); if ($providedElements < $expectedElements) { $this->elements = array_merge( $this->elements, array_fill($providedElements, $expectedElements - $providedElements, 0) ); } } /** * Get the matrix width. * * @return int */ public function getWidth() { return $this->width; } /** * Get the matrix height. * * @return int */ public function getHeight() { return $this->height; } /** * Set the value of a cell. * * @param int $x * @param int $y * @param int|float $value */ public function setElementAt($x, $y, $value) { $this->elements[$this->calculatePosition($x, $y)] = $value; } /** * Get the value of a cell. * * @param int $x * @param int $y * * @return int|float */ public function getElementAt($x, $y) { return $this->elements[$this->calculatePosition($x, $y)]; } /** * Return all the matrix values, as a monodimensional array. * * @return int[]|float[] */ public function getValueList() { return $this->elements; } /** * Return all the matrix values, as a bidimensional array (every array item contains the values of a row). * * @return int[]|float[] */ public function getMatrix() { return array_chunk($this->elements, $this->getWidth()); } /** * Returns a new Matrix instance, representing the normalized value of this matrix. * * @return static */ public function normalize() { $values = $this->getValueList(); $divisor = array_sum($values); if ($divisor == 0 || $divisor == 1) { return clone $this; } $normalizedElements = array(); foreach ($values as $value) { $normalizedElements[] = $value / $divisor; } return new static($this->getWidth(), $this->getHeight(), $normalizedElements); } /** * Calculate the offset position of a cell. * * @param int $x * @param int $y * * @throws \Imagine\Exception\OutOfBoundsException * * @return int */ protected function calculatePosition($x, $y) { if ($x < 0 || $y < 0 || $this->width <= $x || $this->height <= $y) { throw new OutOfBoundsException(sprintf('There is no position (%s, %s) in this matrix', $x, $y)); } return $y * $this->height + $x; } } imagine/src/Gd/Font.php 0000644 00000003221 15141212321 0010703 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gd; use Imagine\Driver\InfoProvider; use Imagine\Image\AbstractFont; /** * Font implementation using the GD library. */ final class Font extends AbstractFont implements InfoProvider { /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\FontInterface::box() */ public function box($string, $angle = 0) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_TEXTFUNCTIONS); $fontfile = $this->file; if ($fontfile && DIRECTORY_SEPARATOR === '\\') { // On Windows imageftbbox() throws a "Could not find/open font" error if $fontfile is not an absolute path. $fontfileRealpath = realpath($fontfile); if ($fontfileRealpath !== false) { $fontfile = $fontfileRealpath; } } $angle = -1 * $angle; $info = imageftbbox($this->size, $angle, $fontfile, $string); $xs = array($info[0], $info[2], $info[4], $info[6]); $ys = array($info[1], $info[3], $info[5], $info[7]); $width = abs(max($xs) - min($xs)); $height = abs(max($ys) - min($ys)); return $this->getClassFactory()->createBox($width, $height); } } imagine/src/Gd/Imagine.php 0000644 00000016657 15141212321 0011367 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gd; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\File\LoaderInterface; use Imagine\Image\AbstractImagine; use Imagine\Image\BoxInterface; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\PaletteInterface; use Imagine\Image\Palette\RGB; use Imagine\Utils\ErrorHandling; /** * Imagine implementation using the GD library. * * @final */ class Imagine extends AbstractImagine implements InfoProvider { /** * Initialize the class. */ public function __construct() { static::getDriverInfo()->checkVersionIsSupported(); } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::create() */ public function create(BoxInterface $size, ColorInterface $color = null) { $width = $size->getWidth(); $height = $size->getHeight(); $resource = imagecreatetruecolor($width, $height); if ($resource === false) { throw new RuntimeException('Create operation failed'); } if ($color === null) { $palette = new RGB(); $color = $palette->color('fff'); } else { $palette = $color->getPalette(); static::getDriverInfo()->requirePaletteSupport($palette); } $index = imagecolorallocatealpha($resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100)); if ($index === false) { throw new RuntimeException('Unable to allocate color'); } if (imagefill($resource, 0, 0, $index) === false) { throw new RuntimeException('Could not set background color fill'); } if ($color->getAlpha() <= 5) { imagecolortransparent($resource, $index); } return $this->wrap($resource, $palette, new MetadataBag()); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::open() */ public function open($path) { $loader = $path instanceof LoaderInterface ? $path : $this->getClassFactory()->createFileLoader($path); $path = $loader->getPath(); $data = $loader->getData(); $resource = $this->createImageFromString($data); if (!($resource instanceof \GdImage) && !\is_resource($resource)) { throw new RuntimeException(sprintf('Unable to open image %s', $path)); } return $this->wrap($resource, new RGB(), $this->getMetadataReader()->readFile($loader)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::load() */ public function load($string) { return $this->doLoad($string, $this->getMetadataReader()->readData($string)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::read() */ public function read($resource) { if (!\is_resource($resource)) { throw new InvalidArgumentException('Variable does not contain a stream resource'); } $content = stream_get_contents($resource); if ($content === false) { throw new InvalidArgumentException('Cannot read resource content'); } return $this->doLoad($content, $this->getMetadataReader()->readData($content, $resource)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImagineInterface::font() */ public function font($file, $size, ColorInterface $color) { return $this->getClassFactory()->createFont(ClassFactoryInterface::HANDLE_GD, $file, $size, $color); } /** * @param resource|\GdImage $resource * @param \Imagine\Image\Palette\PaletteInterface $palette * @param \Imagine\Image\Metadata\MetadataBag $metadata * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ private function wrap($resource, PaletteInterface $palette, MetadataBag $metadata) { if (!imageistruecolor($resource)) { if (\function_exists('imagepalettetotruecolor')) { if (imagepalettetotruecolor($resource) === false) { throw new RuntimeException('Could not convert a palette based image to true color'); } } else { list($width, $height) = array(imagesx($resource), imagesy($resource)); // create transparent truecolor canvas $truecolor = imagecreatetruecolor($width, $height); $transparent = imagecolorallocatealpha($truecolor, 255, 255, 255, 127); imagealphablending($truecolor, false); imagefilledrectangle($truecolor, 0, 0, $width, $height, $transparent); imagealphablending($truecolor, false); imagecopy($truecolor, $resource, 0, 0, 0, 0, $width, $height); imagedestroy($resource); $resource = $truecolor; } } if (imagealphablending($resource, false) === false || imagesavealpha($resource, true) === false) { throw new RuntimeException('Could not set alphablending, savealpha and antialias values'); } if (\function_exists('imageantialias')) { imageantialias($resource, true); } return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GD, $resource, $palette, $metadata); } /** * @param string $string * @param \Imagine\Image\Metadata\MetadataBag $metadata * * @throws \Imagine\Exception\RuntimeException * * @return \Imagine\Image\ImageInterface */ private function doLoad($string, MetadataBag $metadata) { $resource = $this->createImageFromString($string); if (!$resource instanceof \GdImage && !\is_resource($resource)) { throw new RuntimeException('An image could not be created from the given input'); } return $this->wrap($resource, new RGB(), $metadata); } /** * Check if the raw image data represents an image in WebP format. * * @param string $data * * @return bool */ private function isWebP(&$data) { return substr($data, 8, 7) === 'WEBPVP8'; } /** * Create an image resource starting from its raw daa. * * @param string $string * * @return resource|\GdImage|false */ private function createImageFromString(&$string) { return ErrorHandling::ignoring(-1, function () use (&$string) { // imagecreatefromstring() does not support webp images before PHP 7.3.0 if (PHP_VERSION_ID < 70300 && function_exists('imagecreatefromwebp') && $this->isWebP($string)) { return imagecreatefromwebp('data:image/webp;base64,' . base64_encode($string)); } return imagecreatefromstring($string); }); } } imagine/src/Gd/DriverInfo.php 0000644 00000013364 15141212321 0012055 0 ustar 00 <?php namespace Imagine\Gd; use Imagine\Driver\AbstractInfo; use Imagine\Exception\NotSupportedException; use Imagine\Image\Format; use Imagine\Image\FormatList; use Imagine\Image\Palette\PaletteInterface; use Imagine\Image\Palette\RGB; /** * Provide information and features supported by the GD graphics driver. * * @since 1.3.0 */ class DriverInfo extends AbstractInfo { /** * @var static|\Imagine\Exception\NotSupportedException|null */ private static $instance; /** * @throws \Imagine\Exception\NotSupportedException */ protected function __construct() { if (!function_exists('gd_info') || !defined('GD_VERSION')) { throw new NotSupportedException('Gd driver not installed'); } $m = null; $driverRawVersion = PHP_VERSION; $driverSemverVersion = defined('PHP_MAJOR_VERSION') ? implode('.', array(PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION)) : ''; $engineRawVersion = is_string(GD_VERSION) ? GD_VERSION : ''; $engineSemverVersion = preg_match('/^.*?(\d+\.\d+\.\d+)/', $engineRawVersion, $m) ? $m[1] : ''; parent::__construct($driverRawVersion, $driverSemverVersion, $engineRawVersion, $engineSemverVersion); } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::get() */ public static function get($required = true) { if (self::$instance === null) { try { self::$instance = new static(); } catch (NotSupportedException $x) { self::$instance = $x; } } if (self::$instance instanceof self) { return self::$instance; } if ($required) { throw self::$instance; } return null; } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::checkVersionIsSupported() * @see \Imagine\Driver\AbstractInfo::checkVersionIsSupported() */ public function checkVersionIsSupported() { parent::checkVersionIsSupported(); if ($this->getEngineVersion() === '' || version_compare($this->getEngineVersion(), '2.0.1') < 0) { throw new NotSupportedException(sprintf('GD2 version %s or higher is required, %s provided', '2.0.1', GD_VERSION)); } } /** * {@inheritdoc} * * @see \Imagine\Driver\AbstractInfo::checkFeature() */ protected function checkFeature($feature) { switch ($feature) { case static::FEATURE_COLORPROFILES: throw new NotSupportedException('GD driver does not support color profiles'); case static::FEATURE_TEXTFUNCTIONS: if (!function_exists('imageftbbox')) { throw new NotSupportedException('GD is not compiled with FreeType support'); } break; case static::FEATURE_MULTIPLELAYERS: throw new NotSupportedException('GD does not support layer sets'); case static::FEATURE_CUSTOMRESOLUTION: throw new NotSupportedException('GD does not support setting custom resolutions'); case static::FEATURE_EXPORTWITHCUSTOMRESOLUTION: throw new NotSupportedException('GD driver does not support exporting images with custom resolutions'); case static::FEATURE_DRAWFILLEDCHORDSCORRECTLY: throw new NotSupportedException('The GD Drawer can NOT draw correctly filled chords'); case static::FEATURE_DRAWUNFILLEDCIRCLESWITHTICHKESSCORRECTLY: throw new NotSupportedException('The GD Drawer can NOT draw correctly not filled circles with a thickness greater than 1'); case static::FEATURE_DRAWUNFILLEDELLIPSESWITHTICHKESSCORRECTLY: throw new NotSupportedException('The GD Drawer can NOT draw correctly not filled ellipses with a thickness greater than 1'); case static::FEATURE_ROTATEIMAGEWITHCORRECTSIZE: $vFrom = '5.5'; $vTo = '7.1.11'; if (version_compare($this->getDriverVersion(), $vFrom) >= 0 && version_compare($this->getDriverVersion(), $vTo) < 0) { // see https://bugs.php.net/bug.php?id=65148 throw new NotSupportedException("The GD driver is affected by bug https://bugs.php.net/bug.php?id=65148 from PHP version {$vFrom} to PHP version {$vTo}."); } break; case static::FEATURE_EXPORTWITHCUSTOMJPEGSAMPLINGFACTORS: throw new NotSupportedException('The GD driver does not support JPEG sampling factors'); } } /** * {@inheritdoc} * * @see \Imagine\Driver\AbstractInfo::buildSupportedFormats() */ protected function buildSupportedFormats() { $supportedFormats = array(); foreach (array( 'gif' => Format::ID_GIF, 'jpeg' => Format::ID_JPEG, 'png' => Format::ID_PNG, 'wbmp' => Format::ID_WBMP, 'xbm' => Format::ID_XBM, 'bmp' => Format::ID_BMP, 'webp' => Format::ID_WEBP, 'avif' => Format::ID_AVIF, ) as $suffix => $formatID) { if (function_exists("image{$suffix}") && function_exists("imagecreatefrom{$suffix}")) { $supportedFormats[] = Format::get($formatID); } } return new FormatList($supportedFormats); } /** * {@inheritdoc} * * @see \Imagine\Driver\Info::requirePaletteSupport() * @see \Imagine\Driver\AbstractInfo::requirePaletteSupport() */ public function requirePaletteSupport(PaletteInterface $palette) { if (!($palette instanceof RGB)) { throw new NotSupportedException('GD driver only supports RGB colors'); } } } imagine/src/Gd/Effects.php 0000644 00000011275 15141212321 0011364 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gd; use Imagine\Driver\InfoProvider; use Imagine\Effects\EffectsInterface; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Color\RGB as RGBColor; use Imagine\Utils\Matrix; /** * Effects implementation using the GD PHP extension. */ class Effects implements EffectsInterface, InfoProvider { /** * @var resource|\GdImage */ private $resource; /** * Initialize the instance. * * @param resource|\GdImage $resource */ public function __construct($resource) { $this->resource = $resource; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::gamma() */ public function gamma($correction) { if (imagegammacorrect($this->resource, 1.0, $correction) === false) { throw new RuntimeException('Failed to apply gamma correction to the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::negative() */ public function negative() { if (imagefilter($this->resource, IMG_FILTER_NEGATE) === false) { throw new RuntimeException('Failed to negate the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::grayscale() */ public function grayscale() { if (imagefilter($this->resource, IMG_FILTER_GRAYSCALE) === false) { throw new RuntimeException('Failed to grayscale the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::colorize() */ public function colorize(ColorInterface $color) { if (!$color instanceof RGBColor) { throw new RuntimeException('Colorize effects only accepts RGB color in GD context'); } if (imagefilter($this->resource, IMG_FILTER_COLORIZE, $color->getRed(), $color->getGreen(), $color->getBlue()) === false) { throw new RuntimeException('Failed to colorize the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::sharpen() */ public function sharpen() { $sharpenMatrix = array(array(-1, -1, -1), array(-1, 16, -1), array(-1, -1, -1)); $divisor = array_sum(array_map('array_sum', $sharpenMatrix)); if (imageconvolution($this->resource, $sharpenMatrix, $divisor, 0) === false) { throw new RuntimeException('Failed to sharpen the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::blur() */ public function blur($sigma = 1) { if (imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR) === false) { throw new RuntimeException('Failed to blur the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::brightness() */ public function brightness($brightness) { $gdBrightness = (int) round($brightness / 100 * 255); if ($gdBrightness < -255 || $gdBrightness > 255) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$brightness', -100, 100, $brightness)); } if (imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, $gdBrightness) === false) { throw new RuntimeException('Failed to brightness the image'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Effects\EffectsInterface::convolve() */ public function convolve(Matrix $matrix) { if ($matrix->getWidth() !== 3 || $matrix->getHeight() !== 3) { throw new InvalidArgumentException(sprintf('A convolution matrix must be 3x3 (%dx%d provided).', $matrix->getWidth(), $matrix->getHeight())); } if (imageconvolution($this->resource, $matrix->getMatrix(), 1, 0) === false) { throw new RuntimeException('Failed to convolve the image'); } return $this; } } imagine/src/Gd/Image.php 0000644 00000062667 15141212321 0011042 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gd; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\OutOfBoundsException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\AbstractImage; use Imagine\Image\BoxInterface; use Imagine\Image\Fill\FillInterface; use Imagine\Image\Format; use Imagine\Image\ImageInterface; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Color\RGB as RGBColor; use Imagine\Image\Palette\PaletteInterface; use Imagine\Image\Point; use Imagine\Image\PointInterface; use Imagine\Image\ProfileInterface; use Imagine\Utils\ErrorHandling; /** * Image implementation using the GD library. */ final class Image extends AbstractImage implements InfoProvider { /** * @var resource|\GdImage */ private $resource; /** * @var \Imagine\Gd\Layers|null */ private $layers; /** * @var \Imagine\Image\Palette\PaletteInterface */ private $palette; /** * Constructs a new Image instance. * * @param resource|\GdImage $resource * @param \Imagine\Image\Palette\PaletteInterface $palette * @param \Imagine\Image\Metadata\MetadataBag $metadata */ public function __construct($resource, PaletteInterface $palette, MetadataBag $metadata) { $this->metadata = $metadata; $this->palette = $palette; $this->resource = $resource; } /** * Makes sure the current image resource is destroyed. */ public function __destruct() { if ($this->resource) { if (is_resource($this->resource) && get_resource_type($this->resource) === 'gd' || $this->resource instanceof \GdImage) { imagedestroy($this->resource); } $this->resource = null; } } /** * {@inheritdoc} * * @see \Imagine\Image\AbstractImage::__clone() */ public function __clone() { parent::__clone(); $size = $this->getSize(); $copy = $this->createImage($size, 'copy'); if (imagecopy($copy, $this->resource, 0, 0, 0, 0, $size->getWidth(), $size->getHeight()) === false) { imagedestroy($copy); throw new RuntimeException('Image copy operation failed'); } $this->resource = $copy; $this->palette = clone $this->palette; if ($this->layers !== null) { $this->layers = $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_GD, $this, $this->layers->key()); } } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * Returns Gd resource. * * @return resource|\GdImage */ public function getGdResource() { return $this->resource; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::copy() */ final public function copy() { return clone $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::crop() */ final public function crop(PointInterface $start, BoxInterface $size) { if (!$start->in($this->getSize())) { throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders'); } $width = $size->getWidth(); $height = $size->getHeight(); $dest = $this->createImage($size, 'crop'); if (imagecopy($dest, $this->resource, 0, 0, $start->getX(), $start->getY(), $width, $height) === false) { imagedestroy($dest); throw new RuntimeException('Image crop operation failed'); } imagedestroy($this->resource); $this->resource = $dest; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::paste() */ final public function paste(ImageInterface $image, PointInterface $start, $alpha = 100) { if (!$image instanceof self) { throw new InvalidArgumentException(sprintf('Gd\Image can only paste() Gd\Image instances, %s given', get_class($image))); } $alpha = (int) round($alpha); if ($alpha < 0 || $alpha > 100) { throw new InvalidArgumentException(sprintf('The %1$s argument can range from %2$d to %3$d, but you specified %4$d.', '$alpha', 0, 100, $alpha)); } $size = $image->getSize(); if ($alpha === 100) { imagealphablending($this->resource, true); imagealphablending($image->resource, true); $success = imagecopy($this->resource, $image->resource, $start->getX(), $start->getY(), 0, 0, $size->getWidth(), $size->getHeight()); imagealphablending($this->resource, false); imagealphablending($image->resource, false); if ($success === false) { throw new RuntimeException('Image paste operation failed'); } } elseif ($alpha > 0) { if (imagecopymerge(/*dst_im*/$this->resource, /*src_im*/$image->resource, /*dst_x*/$start->getX(), /*dst_y*/$start->getY(), /*src_x*/0, /*src_y*/0, /*src_w*/$size->getWidth(), /*src_h*/$size->getHeight(), /*pct*/$alpha) === false) { throw new RuntimeException('Image paste operation failed'); } } return $this; } /** * {@inheritdoc} * * Please remark that GD doesn't support different filters, so the $filter argument is ignored. * * @see \Imagine\Image\ManipulatorInterface::resize() */ final public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED) { if (!in_array($filter, static::getAllFilterValues(), true)) { throw new InvalidArgumentException('Unsupported filter type'); } $width = $size->getWidth(); $height = $size->getHeight(); $dest = $this->createImage($size, 'resize'); imagealphablending($this->resource, true); imagealphablending($dest, true); $success = imagecopyresampled($dest, $this->resource, 0, 0, 0, 0, $width, $height, imagesx($this->resource), imagesy($this->resource)); imagealphablending($this->resource, false); imagealphablending($dest, false); if ($success === false) { imagedestroy($dest); throw new RuntimeException('Image resize operation failed'); } imagedestroy($this->resource); $this->resource = $dest; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::rotate() */ final public function rotate($angle, ColorInterface $background = null) { if ($background === null) { $background = $this->palette->color('fff'); } $color = $this->getColor($background); $resource = imagerotate($this->resource, -1 * $angle, $color); if ($resource === false) { throw new RuntimeException('Image rotate operation failed'); } imagedestroy($this->resource); $this->resource = $resource; return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::save() */ final public function save($path = null, array $options = array()) { $path = $path === null ? (isset($this->metadata['filepath']) ? $this->metadata['filepath'] : $path) : $path; if ($path === null) { throw new RuntimeException('You can omit save path only if image has been open from a file'); } if (isset($options['format'])) { $format = $options['format']; } elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) { $format = $extension; } else { $originalPath = isset($this->metadata['filepath']) ? $this->metadata['filepath'] : null; $format = pathinfo($originalPath, \PATHINFO_EXTENSION); } $formatInfo = static::getDriverInfo()->getSupportedFormats()->find($format); if ($formatInfo === null) { throw new InvalidArgumentException(sprintf( 'Saving image in "%s" format is not supported, please use one of the following extensions: "%s"', $format, implode('", "', static::getDriverInfo()->getSupportedFormats()->getAllIDs()) )); } $this->saveOrOutput($formatInfo, $options, $path); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::show() */ public function show($format, array $options = array()) { $formatInfo = static::getDriverInfo()->getSupportedFormats()->find($format); if ($formatInfo === null) { throw new InvalidArgumentException(sprintf( 'Displaying an image in "%s" format is not supported, please use one of the following formats: "%s"', $format, implode('", "', static::getDriverInfo()->getSupportedFormats()->getAllIDs()) )); } header('Content-type: ' . $formatInfo->getMimeType()); $this->saveOrOutput($formatInfo, $options); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::get() */ public function get($format, array $options = array()) { $formatInfo = static::getDriverInfo()->getSupportedFormats()->find($format); if ($formatInfo === null) { throw new InvalidArgumentException(sprintf( 'Creating an image in "%s" format is not supported, please use one of the following formats: "%s"', $format, implode('", "', static::getDriverInfo()->getSupportedFormats()->getAllIDs()) )); } ob_start(); $this->saveOrOutput($formatInfo, $options); return ob_get_clean(); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::__toString() */ public function __toString() { return $this->get(Format::ID_PNG); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipHorizontally() */ final public function flipHorizontally() { if (function_exists('imageflip')) { imageflip($this->resource, IMG_FLIP_HORIZONTAL); } else { $size = $this->getSize(); $width = $size->getWidth(); $height = $size->getHeight(); $dest = $this->createImage($size, 'flip'); for ($i = 0; $i < $width; $i++) { if (imagecopy($dest, $this->resource, $i, 0, ($width - 1) - $i, 0, 1, $height) === false) { imagedestroy($dest); throw new RuntimeException('Horizontal flip operation failed'); } } imagedestroy($this->resource); $this->resource = $dest; } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::flipVertically() */ final public function flipVertically() { if (function_exists('imageflip')) { imageflip($this->resource, IMG_FLIP_VERTICAL); } else { $size = $this->getSize(); $width = $size->getWidth(); $height = $size->getHeight(); $dest = $this->createImage($size, 'flip'); for ($i = 0; $i < $height; $i++) { if (imagecopy($dest, $this->resource, 0, $i, 0, ($height - 1) - $i, $width, 1) === false) { imagedestroy($dest); throw new RuntimeException('Vertical flip operation failed'); } } imagedestroy($this->resource); $this->resource = $dest; } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::strip() */ final public function strip() { // GD strips profiles and comment, so there's nothing to do here return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::draw() */ public function draw() { return $this->getClassFactory()->createDrawer(ClassFactoryInterface::HANDLE_GD, $this->resource); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::effects() */ public function effects() { return $this->getClassFactory()->createEffects(ClassFactoryInterface::HANDLE_GD, $this->resource); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::getSize() */ public function getSize() { return $this->getClassFactory()->createBox(imagesx($this->resource), imagesy($this->resource)); } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::applyMask() */ public function applyMask(ImageInterface $mask) { if (!$mask instanceof self) { throw new InvalidArgumentException('Cannot mask non-gd images'); } $size = $this->getSize(); $maskSize = $mask->getSize(); if ($size != $maskSize) { throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size)); } for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) { for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) { $position = new Point($x, $y); $color = $this->getColorAt($position); $maskColor = $mask->getColorAt($position); $delta = (int) round($color->getAlpha() * $maskColor->getRed() / 255) * -1; if (imagesetpixel($this->resource, $x, $y, $this->getColor($color->dissolve($delta))) === false) { throw new RuntimeException('Apply mask operation failed'); } } } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ManipulatorInterface::fill() */ public function fill(FillInterface $fill) { $size = $this->getSize(); for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) { for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) { if (imagesetpixel($this->resource, $x, $y, $this->getColor($fill->getColor(new Point($x, $y)))) === false) { throw new RuntimeException('Fill operation failed'); } } } return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::mask() */ public function mask() { $mask = $this->copy(); if (imagefilter($mask->resource, IMG_FILTER_GRAYSCALE) === false) { throw new RuntimeException('Mask operation failed'); } return $mask; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::histogram() */ public function histogram() { $size = $this->getSize(); $colors = array(); for ($x = 0, $width = $size->getWidth(); $x < $width; $x++) { for ($y = 0, $height = $size->getHeight(); $y < $height; $y++) { $colors[] = $this->getColorAt(new Point($x, $y)); } } return array_values(array_unique($colors)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::getColorAt() */ public function getColorAt(PointInterface $point) { if (!$point->in($this->getSize())) { throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight())); } $index = imagecolorat($this->resource, $point->getX(), $point->getY()); $info = imagecolorsforindex($this->resource, $index); return $this->palette->color(array($info['red'], $info['green'], $info['blue']), max(min(100 - (int) round($info['alpha'] / 127 * 100), 100), 0)); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::layers() */ public function layers() { if ($this->layers === null) { $this->layers = $this->getClassFactory()->createLayers(ClassFactoryInterface::HANDLE_GD, $this); } return $this->layers; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::interlace() */ public function interlace($scheme) { static $supportedInterlaceSchemes = array( ImageInterface::INTERLACE_NONE => 0, ImageInterface::INTERLACE_LINE => 1, ImageInterface::INTERLACE_PLANE => 1, ImageInterface::INTERLACE_PARTITION => 1, ); if (!array_key_exists($scheme, $supportedInterlaceSchemes)) { throw new InvalidArgumentException('Unsupported interlace type'); } imageinterlace($this->resource, $supportedInterlaceSchemes[$scheme]); return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::palette() */ public function palette() { return $this->palette; } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::profile() */ public function profile(ProfileInterface $profile) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_COLORPROFILES); } /** * {@inheritdoc} * * @see \Imagine\Image\ImageInterface::usePalette() */ public function usePalette(PaletteInterface $palette) { if ($this->palette->name() === $palette->name()) { return $this; } static::getDriverInfo()->requirePaletteSupport($palette); $this->palette = $palette; return $this; } /** * Performs save or show operation using one of GD's image... functions. * * @param \Imagine\Image\Format $format * @param array $options * @param string $filename * * @throws \Imagine\Exception\InvalidArgumentException * @throws \Imagine\Exception\RuntimeException */ private function saveOrOutput(Format $format, array $options, $filename = null) { switch ($format->getID()) { default: $saveFunction = 'image' . $format->getID(); break; } $args = array_merge(array(&$this->resource, $filename), $this->finalizeOptions($format, $options)); ErrorHandling::throwingRuntimeException(E_WARNING | E_NOTICE, function () use ($saveFunction, $args) { if (call_user_func_array($saveFunction, $args) === false) { throw new RuntimeException('Save operation failed'); } }); } /** * @param \Imagine\Image\Format $format * @param array $options * * @throws \Imagine\Exception\InvalidArgumentException * * @return array */ private function finalizeOptions(Format $format, array $options) { $result = array(); switch ($format->getID()) { case Format::ID_AVIF: // ranges from 0 (worst quality, smaller file) to 100 (best quality, larger file). If -1 is provided, the default value is used $quality = -1; // ranges from 0 (slow, smaller file) to 10 (fast, larger file). If -1 is provided, the default value is used $speed = -1; if (!empty($options['avif_lossless'])) { $quality = 100; } else { if (!isset($options['avif_quality'])) { if (isset($options['quality'])) { $options['avif_quality'] = $options['quality']; } } if (isset($options['avif_quality'])) { $quality = max(0, min(100, $options['avif_quality'])); } } $result[] = $quality; $result[] = $speed; break; case Format::ID_BMP: if (isset($options['compressed'])) { $result[] = (bool) $options['compressed']; } break; case Format::ID_JPEG: if (!isset($options['jpeg_quality'])) { if (isset($options['quality'])) { $options['jpeg_quality'] = $options['quality']; } } if (isset($options['jpeg_quality'])) { $result[] = $options['jpeg_quality']; } break; case Format::ID_PNG: if (!isset($options['png_compression_level'])) { if (isset($options['quality'])) { $options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100); } } if (isset($options['png_compression_level'])) { if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) { throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9'); } $result[] = $options['png_compression_level']; } else { $result[] = -1; // use default level } if (!isset($options['png_compression_filter'])) { if (isset($options['filters'])) { $options['png_compression_filter'] = $options['filters']; } } if (isset($options['png_compression_filter'])) { if (~PNG_ALL_FILTERS & $options['png_compression_filter']) { throw new InvalidArgumentException('png_compression_filter option should be a combination of the PNG_FILTER_XXX constants'); } $result[] = $options['png_compression_filter']; } break; case Format::ID_WEBP: if (!isset($options['webp_quality'])) { if (isset($options['quality'])) { $options['webp_quality'] = $options['quality']; } } if (isset($options['webp_quality'])) { if ($options['webp_quality'] < 0 || $options['webp_quality'] > 100) { throw new InvalidArgumentException('webp_quality option should be an integer from 0 to 100'); } $result[] = $options['webp_quality']; } break; case Format::ID_XBM: case Format::ID_WBMP: if (isset($options['foreground'])) { $result[] = $options['foreground']; } break; } return $result; } /** * Generates a GD image. * * @param \Imagine\Image\BoxInterface $size * @param string $operation the operation initiating the creation * * @throws \Imagine\Exception\RuntimeException * * @return resource|\GdImage */ private function createImage(BoxInterface $size, $operation) { $resource = imagecreatetruecolor($size->getWidth(), $size->getHeight()); if ($resource === false) { throw new RuntimeException('Image ' . $operation . ' failed'); } if (imagealphablending($resource, false) === false || imagesavealpha($resource, true) === false) { throw new RuntimeException('Image ' . $operation . ' failed'); } if (function_exists('imageantialias')) { imageantialias($resource, true); } $transparent = imagecolorallocatealpha($resource, 255, 255, 255, 127); imagefill($resource, 0, 0, $transparent); imagecolortransparent($resource, $transparent); return $resource; } /** * Generates a GD color from Color instance. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\RuntimeException * @throws \Imagine\Exception\InvalidArgumentException * * @return int A color identifier */ private function getColor(ColorInterface $color) { if (!$color instanceof RGBColor) { throw new InvalidArgumentException('GD driver only supports RGB colors'); } $index = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100)); if ($index === false) { throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha())); } return $index; } } imagine/src/Gd/Drawer.php 0000644 00000036224 15141212321 0011232 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gd; use Imagine\Draw\AlphaBlendingAwareDrawerInterface; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\RuntimeException; use Imagine\Image\AbstractFont; use Imagine\Image\Box; use Imagine\Image\BoxInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\Palette\Color\RGB as RGBColor; use Imagine\Image\PointInterface; /** * Drawer implementation using the GD PHP extension. */ final class Drawer implements AlphaBlendingAwareDrawerInterface, InfoProvider { /** * @var resource|\GdImage */ private $resource; /** * @var bool */ private $alphaBlending = true; /** * Constructs Drawer with a given gd image resource. * * @param resource|\GdImage $resource */ public function __construct($resource) { $this->resource = $resource; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::arc() */ public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0) { return $this; } imagesetthickness($this->resource, $thickness); if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw arc operation failed'); } if (imagearc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color)) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw arc operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw arc operation failed'); } return $this; } /** * This function does not work properly because of a bug in GD. * * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::chord() */ public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } imagesetthickness($this->resource, $thickness); if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw chord operation failed'); } if ($fill) { $style = IMG_ARC_CHORD; if (imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw chord operation failed'); } } else { foreach (array(IMG_ARC_NOFILL, IMG_ARC_NOFILL | IMG_ARC_CHORD) as $style) { if (imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw chord operation failed'); } } } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw chord operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::circle() */ public function circle(PointInterface $center, $radius, ColorInterface $color, $fill = false, $thickness = 1) { $diameter = $radius * 2; return $this->ellipse($center, new Box($diameter, $diameter), $color, $fill, $thickness); } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::ellipse() */ public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } if (function_exists('imageantialias')) { imageantialias($this->resource, true); } imagesetthickness($this->resource, $thickness); if ($fill) { $callback = 'imagefilledellipse'; } else { $callback = 'imageellipse'; } if (function_exists('imageantialias')) { imageantialias($this->resource, true); } if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw ellipse operation failed'); } if (function_exists('imageantialias')) { imageantialias($this->resource, true); } if ($callback($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $this->getColor($color)) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw ellipse operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw ellipse operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::line() */ public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0) { return $this; } imagesetthickness($this->resource, $thickness); if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw line operation failed'); } if (imageline($this->resource, $start->getX(), $start->getY(), $end->getX(), $end->getY(), $this->getColor($color)) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw line operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw line operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::pieSlice() */ public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } imagesetthickness($this->resource, $thickness); if ($fill) { $style = IMG_ARC_EDGED; } else { $style = IMG_ARC_EDGED | IMG_ARC_NOFILL; } if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw chord operation failed'); } if (imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw chord operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw chord operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::dot() */ public function dot(PointInterface $position, ColorInterface $color) { if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw point operation failed'); } if (imagesetpixel($this->resource, $position->getX(), $position->getY(), $this->getColor($color)) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw point operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw point operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::rectangle() */ public function rectangle(PointInterface $leftTop, PointInterface $rightBottom, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } imagesetthickness($this->resource, $thickness); $minX = min($leftTop->getX(), $rightBottom->getX()); $maxX = max($leftTop->getX(), $rightBottom->getX()); $minY = min($leftTop->getY(), $rightBottom->getY()); $maxY = max($leftTop->getY(), $rightBottom->getY()); if ($fill) { $callback = 'imagefilledrectangle'; } else { $callback = 'imagerectangle'; } if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw polygon operation failed'); } if ($callback($this->resource, $minX, $minY, $maxX, $maxY, $this->getColor($color)) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Draw polygon operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw polygon operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::polygon() */ public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1) { $thickness = max(0, (int) round($thickness)); if ($thickness === 0 && !$fill) { return $this; } imagesetthickness($this->resource, $thickness); if (count($coordinates) < 3) { throw new InvalidArgumentException(sprintf('A polygon must consist of at least 3 points, %d given', count($coordinates))); } $points = call_user_func_array('array_merge', array_map(function (PointInterface $p) { return array($p->getX(), $p->getY()); }, $coordinates)); if ($fill) { $callback = 'imagefilledpolygon'; } else { $callback = 'imagepolygon'; } if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Draw polygon operation failed'); } if (false === ( PHP_VERSION_ID < 80000 ? $callback($this->resource, $points, count($coordinates), $this->getColor($color)) : $callback($this->resource, $points, $this->getColor($color)) )) { $this->revertAlphaBlending(); throw new RuntimeException('Draw polygon operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Draw polygon operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\DrawerInterface::text() */ public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null) { static::getDriverInfo()->requireFeature(DriverInfo::FEATURE_TEXTFUNCTIONS); $angle = -1 * $angle; $fontsize = $font->getSize(); $fontfile = $font->getFile(); $x = $position->getX(); $y = $position->getY() + $fontsize; if ($width !== null) { $string = $font->wrapText($string, $width, $angle); } if ($this->applyAlphaBlending() === false) { throw new RuntimeException('Font mask operation failed'); } if ($fontfile && DIRECTORY_SEPARATOR === '\\') { // On Windows imagefttext() throws a "Could not find/open font" error if $fontfile is not an absolute path. $fontfileRealpath = realpath($fontfile); if ($fontfileRealpath !== false) { $fontfile = $fontfileRealpath; } } if (imagefttext($this->resource, $fontsize, $angle, $x, $y, $this->getColor($font->getColor()), $fontfile, $string) === false) { $this->revertAlphaBlending(); throw new RuntimeException('Font mask operation failed'); } if ($this->revertAlphaBlending() === false) { throw new RuntimeException('Font mask operation failed'); } return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\AlphaBlendingAwareDrawerInterface::getAlphaBlending() */ public function getAlphaBlending() { return $this->alphaBlending; } /** * {@inheritdoc} * * @see \Imagine\Draw\AlphaBlendingAwareDrawerInterface::setAlphaBlending() */ public function setAlphaBlending($value) { $this->alphaBlending = (bool) $value; return $this; } /** * {@inheritdoc} * * @see \Imagine\Draw\AlphaBlendingAwareDrawerInterface::withAlphaBlending() */ public function withAlphaBlending($value) { $result = clone $this; return $result->setAlphaBlending($value); } /** * Generates a GD color from Color instance. * * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\RuntimeException * @throws \Imagine\Exception\InvalidArgumentException * * @return resource */ private function getColor(ColorInterface $color) { if (!$color instanceof RGBColor) { throw new InvalidArgumentException('GD driver only supports RGB colors'); } $gdColor = imagecolorallocatealpha($this->resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round((100 - $color->getAlpha()) * 127 / 100)); if ($gdColor === false) { throw new RuntimeException(sprintf('Unable to allocate color "RGB(%s, %s, %s)" with transparency of %d percent', $color->getRed(), $color->getGreen(), $color->getBlue(), $color->getAlpha())); } return $gdColor; } /** * Apply the alpha blending value. * * @param resource|\GdImage|null $to the GD image. If null we'll apply the alpha blending to the current resource. * * @return bool */ protected function applyAlphaBlending($to = null) { return $this->getAlphaBlending() ? imagealphablending($to ? $to : $this->resource, true) : true; } /** * Revert the alpha blending value to the initial state. * * @param resource|\GdImage|null $to the GD image. If null we'll apply the alpha blending to the current resource. * * @return bool */ protected function revertAlphaBlending($to = null) { return $this->getAlphaBlending() ? imagealphablending($to ? $to : $this->resource, false) : true; } } imagine/src/Gd/Layers.php 0000644 00000010771 15141212321 0011244 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Gd; use Imagine\Driver\InfoProvider; use Imagine\Exception\InvalidArgumentException; use Imagine\Exception\NotSupportedException; use Imagine\Exception\RuntimeException; use Imagine\Factory\ClassFactoryInterface; use Imagine\Image\AbstractLayers; use Imagine\Image\Metadata\MetadataBag; use Imagine\Image\Palette\PaletteInterface; class Layers extends AbstractLayers implements InfoProvider { /** * @var \Imagine\Gd\Image */ private $image; /** * @var int */ private $offset; /** * @var resource|\GdImage */ private $resource; /** * @var \Imagine\Image\Palette\PaletteInterface */ private $palette; /** * @param \Imagine\Gd\Image $image * @param \Imagine\Image\Palette\PaletteInterface $palette * @param resource|\GdImage $resource * @param int $initialOffset * * @throws \Imagine\Exception\RuntimeException */ public function __construct(Image $image, PaletteInterface $palette, $resource, $initialOffset = 0) { if (!$resource instanceof \GdImage && !is_resource($resource)) { throw new RuntimeException('Invalid Gd resource provided'); } $this->image = $image; $this->resource = $resource; $this->offset = (int) $initialOffset; $this->palette = $palette; } /** * {@inheritdoc} * * @see \Imagine\Driver\InfoProvider::getDriverInfo() * @since 1.3.0 */ public static function getDriverInfo($required = true) { return DriverInfo::get($required); } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::merge() */ public function merge() { } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::coalesce() */ public function coalesce() { return $this; } /** * {@inheritdoc} * * @see \Imagine\Image\LayersInterface::animate() */ public function animate($format, $delay, $loops) { return $this; } /** * {@inheritdoc} * * @see \Iterator::current() */ #[\ReturnTypeWillChange] public function current() { return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GD, $this->resource, $this->palette, new MetadataBag()); } /** * {@inheritdoc} * * @see \Iterator::key() */ #[\ReturnTypeWillChange] public function key() { return $this->offset; } /** * {@inheritdoc} * * @see \Iterator::next() */ #[\ReturnTypeWillChange] public function next() { ++$this->offset; } /** * {@inheritdoc} * * @see \Iterator::rewind() */ #[\ReturnTypeWillChange] public function rewind() { $this->offset = 0; } /** * {@inheritdoc} * * @see \Iterator::valid() */ #[\ReturnTypeWillChange] public function valid() { return $this->offset < 1; } /** * {@inheritdoc} * * @see \Countable::count() */ #[\ReturnTypeWillChange] public function count() { return 1; } /** * {@inheritdoc} * * @see \ArrayAccess::offsetExists() */ #[\ReturnTypeWillChange] public function offsetExists($offset) { return $offset === 0; } /** * {@inheritdoc} * * @see \ArrayAccess::offsetGet() */ #[\ReturnTypeWillChange] public function offsetGet($offset) { if ($offset === 0) { return $this->getClassFactory()->createImage(ClassFactoryInterface::HANDLE_GD, $this->resource, $this->palette, new MetadataBag()); } throw new InvalidArgumentException('GD only supports one layer at offset 0'); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetSet() */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value) { throw new NotSupportedException('GD does not support layer set'); } /** * {@inheritdoc} * * @see \ArrayAccess::offsetUnset() */ #[\ReturnTypeWillChange] public function offsetUnset($offset) { throw new NotSupportedException('GD does not support layer unset'); } } imagine/src/Draw/AlphaBlendingAwareDrawerInterface.php 0000644 00000001256 15141212321 0017004 0 ustar 00 <?php namespace Imagine\Draw; /** * Interface for the drawers that support configuring the alpha blending. */ interface AlphaBlendingAwareDrawerInterface extends DrawerInterface { /** * Is the alpha blending activated? * * @return bool */ public function getAlphaBlending(); /** * Enable/disable the alpha blending. * * @param bool $value * * @return $this */ public function setAlphaBlending($value); /** * Create a new instance of this drawer with the specified alpha blending value. * * @param bool $value * * @return static */ public function withAlphaBlending($value); } imagine/src/Draw/DrawerInterface.php 0000644 00000013107 15141212321 0013411 0 ustar 00 <?php /* * This file is part of the Imagine package. * * (c) Bulat Shakirzyanov <mallluhuct@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Imagine\Draw; use Imagine\Image\AbstractFont; use Imagine\Image\BoxInterface; use Imagine\Image\Palette\Color\ColorInterface; use Imagine\Image\PointInterface; /** * Interface for the drawer. */ interface DrawerInterface { /** * Draws an arc on a starting at a given x, y coordinates under a given * start and end angles. * * @param \Imagine\Image\PointInterface $center * @param \Imagine\Image\BoxInterface $size * @param int $start * @param int $end * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1); /** * Same as arc, but also connects end points with a straight line. * * @param \Imagine\Image\PointInterface $center * @param \Imagine\Image\BoxInterface $size * @param int $start * @param int $end * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param bool $fill * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1); /** * Draws and circle with center at the given x, y coordinates, and given radius. * * @param \Imagine\Image\PointInterface $center * @param int $radius * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param bool $fill * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function circle(PointInterface $center, $radius, ColorInterface $color, $fill = false, $thickness = 1); /** * Draws and ellipse with center at the given x, y coordinates, and given width and height. * * @param \Imagine\Image\PointInterface $center * @param \Imagine\Image\BoxInterface $size * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param bool $fill * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1); /** * Draws a line from start(x, y) to end(x, y) coordinates. * * @param \Imagine\Image\PointInterface $start * @param \Imagine\Image\PointInterface $end * @param \Imagine\Image\Palette\Color\ColorInterface $outline * @param int $thickness * * @return $this */ public function line(PointInterface $start, PointInterface $end, ColorInterface $outline, $thickness = 1); /** * Same as arc, but connects end points and the center. * * @param \Imagine\Image\PointInterface $center * @param \Imagine\Image\BoxInterface $size * @param int $start * @param int $end * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param bool $fill * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1); /** * Places a one pixel point at specific coordinates and fills it with * specified color. * * @param \Imagine\Image\PointInterface $position * @param \Imagine\Image\Palette\Color\ColorInterface $color * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function dot(PointInterface $position, ColorInterface $color); /** * Draws a rectangle from left, top(x, y) to right, bottom(x, y) coordinates. * * @param \Imagine\Image\PointInterface $leftTop * @param \Imagine\Image\PointInterface $rightBottom * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param bool $fill * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function rectangle(PointInterface $leftTop, PointInterface $rightBottom, ColorInterface $color, $fill = false, $thickness = 1); /** * Draws a polygon using array of x, y coordinates. Must contain at least three coordinates. * * @param \Imagine\Image\PointInterface[] $coordinates * @param \Imagine\Image\Palette\Color\ColorInterface $color * @param bool $fill * @param int $thickness * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1); /** * Annotates image with specified text at a given position starting on the top left of the final text box. * * The rotation is done CW * * @param string $string * @param \Imagine\Image\AbstractFont $font * @param \Imagine\Image\PointInterface $position * @param int $angle * @param int $width * * @throws \Imagine\Exception\RuntimeException * * @return $this */ public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null); } imagine/src/resources/color.org/sRGB_IEC61966-2-1_black_scaled.icc 0000644 00000005750 15141212321 0020176 0 ustar 00 � mntrRGB XYZ � $ acsp �� �- )�=ޯ�U�xB��ʃ9 desc D ybXYZ � bTRC � dmdd � �gXYZ h gTRC � lumi | meas � $bkpt � rXYZ � rTRC � tech � vued � �wtpt p cprt � 7chad � ,desc sRGB IEC61966-2-1 black scaled XYZ $� � ��curv # ( - 2 7 ; @ E J O T Y ^ c h m r w | � � � � � � � � � � � � � � � � � � � � � � � � � %+28>ELRY`gnu|����������������&/8AKT]gqz������������ !-8COZfr~���������� -;HUcq~��������� +:IXgw��������'7HYj{�������+=Oat�������2FZn������� % : O d y � � � � � � ' = T j � � � � � �"9Qi������*C\u����� & @ Z t � � � � �.Id���� %A^z���� &Ca~����1Om����&Ed����#Cc����'Ij����4Vx���&Il����Ae����@e���� Ek���*Qw���;c���*R{���Gp���@j���>i��� A l � � �!!H!u!�!�!�"'"U"�"�"�# #8#f#�#�#�$$M$|$�$�% %8%h%�%�%�&'&W&�&�&�''I'z'�'�( (?(q(�(�))8)k)�)�**5*h*�*�++6+i+�+�,,9,n,�,�--A-v-�-�..L.�.�.�/$/Z/�/�/�050l0�0�11J1�1�1�2*2c2�2�3 3F33�3�4+4e4�4�55M5�5�5�676r6�6�7$7`7�7�88P8�8�99B99�9�:6:t:�:�;-;k;�;�<'<e<�<�="=a=�=�> >`>�>�?!?a?�?�@#@d@�@�A)AjA�A�B0BrB�B�C:C}C�DDGD�D�EEUE�E�F"FgF�F�G5G{G�HHKH�H�IIcI�I�J7J}J�KKSK�K�L*LrL�MMJM�M�N%NnN�O OIO�O�P'PqP�QQPQ�Q�R1R|R�SS_S�S�TBT�T�U(UuU�VV\V�V�WDW�W�X/X}X�YYiY�ZZVZ�Z�[E[�[�\5\�\�]']x]�^^l^�__a_�``W`�`�aOa�a�bIb�b�cCc�c�d@d�d�e=e�e�f=f�f�g=g�g�h?h�h�iCi�i�jHj�j�kOk�k�lWl�mm`m�nnkn�ooxo�p+p�p�q:q�q�rKr�ss]s�ttpt�u(u�u�v>v�v�wVw�xxnx�y*y�y�zFz�{{c{�|!|�|�}A}�~~b~�#��G��� �k�͂0����W�������G����r�ׇ;����i�Ή3�����d�ʋ0�����c�ʍ1�����f�Ώ6����n�֑?����z��M��� ����_�ɖ4��� �u��L���$�����h�՛B��������d�Ҟ@��������i�ءG���&����v��V�ǥ8��������n��R�ĩ7�������u��\�ЭD���-������ �u��`�ֲK�³8���%�������y��h��Y�ѹJ�º;���.���!������ �����z���p��g���_���X���Q���K���F���Aǿ�=ȼ�:ɹ�8ʷ�6˶�5̵�5͵�6ζ�7ϸ�9к�<Ѿ�?��D���I���N���U���\���d���l���v�ۀ�܊�ݖ�ޢ�)߯�6��D���S���c���s��� ����2��F���[���p�����(��@���X���r�����4��P��m��������8��W��w����)���K��m��desc .IEC 61966-2-1 Default RGB Colour Space - sRGB XYZ b� �� �XYZ P meas XYZ 3 �XYZ o� 8� �sig CRT desc -Reference Viewing Condition in IEC 61966-2-1 XYZ �� �-text Copyright International Color Consortium, 2009 sf32 D ���&