Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

174
vendor/imagine/imagine/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,174 @@
# CHANGELOG
### 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.

25
vendor/imagine/imagine/LICENSE vendored Normal file
View File

@@ -0,0 +1,25 @@
Copyright (c) 2004-2012 Bulat Shakirzyanov
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 sofwtare also embeds ICC Profile from colormanagement.org. Please
find information about their license at http://colormanagement.org/ .

54
vendor/imagine/imagine/README.md vendored Normal file
View File

@@ -0,0 +1,54 @@
#Imagine
[![project status](http://stillmaintained.com/avalanche123/Imagine.png)](http://stillmaintained.com/avalanche123/Imagine)
[![Build Status](https://travis-ci.org/avalanche123/Imagine.svg?branch=develop)](https://travis-ci.org/avalanche123/Imagine)
Tweet about it using the [#php_imagine](https://twitter.com/search?q=%23php_imagine) hashtag.
Image manipulation library for PHP 5.3 inspired by Python's PIL and other image
libraries.
##Requirements##
The Imagine library has the following requirements:
- PHP 5.3+
Depending on the chosen Image implementation, you may need one of the following:
- GD2
- Imagick
- Gmagick
### Installation using composer
`php composer.phar require imagine/imagine`
##Basic Principles##
The main purpose of Imagine is to provide all the necessary functionality to bring all native low level image processing libraries in PHP to the same simple and intuitive OO API.
Several things are necessary to accomplish that:
* Image manipulation tools, such as resize, crop, etc.
* Drawing API - to create basic shapes and advanced charts, write text on the image
* Masking functionality - ability to apply black&white or grayscale images as masks, leading to semi-transparency or absolute transparency of the image the mask is being applied to
The above tools should be the basic foundation for a more powerful set of tools that are called ``Filters`` in Imagine.
Some of the ideas for upcoming filters:
* Charting and graphing filters - pie and bar charts, linear graphs with annotations
* Reflection - apple style
* Rounded corners - web 2.0
## Documentation ##
- [Hosted by Read The Docs](http://imagine.readthedocs.org/)
## Presentations ##
- [Introduction to Imagine](http://www.slideshare.net/avalanche123/introduction-toimagine)
- [How to Take Over the World with Lithium](http://speakerdeck.com/u/nateabele/p/how-to-take-over-the-world-with-lithium?slide=33)
## Articles ##
- [Image Processing with Imagine](http://www.phparch.com/2011/03/image-processing-with-imagine)

55
vendor/imagine/imagine/composer.json vendored Normal file
View File

@@ -0,0 +1,55 @@
{
"name": "imagine/imagine",
"description": "Image processing for PHP 5.3",
"keywords": [
"image manipulation",
"image processing",
"drawing",
"graphics"
],
"homepage": "http://imagine.readthedocs.org/",
"license": "MIT",
"authors": [
{
"name": "Bulat Shakirzyanov",
"email": "mallluhuct@gmail.com",
"homepage": "http://avalanche123.com"
}
],
"config": {
"bin-dir": "bin"
},
"require": {
"php": ">=5.3.2"
},
"require-dev": {
"sami/sami": "dev-master"
},
"suggest": {
"ext-gd": "to use the GD implementation",
"ext-imagick": "to use the Imagick implementation",
"ext-gmagick": "to use the Gmagick implementation"
},
"autoload": {
"psr-0": {
"Imagine": "lib/"
}
},
"extra": {
"branch-alias": {
"dev-develop": "0.7-dev"
}
},
"archive": {
"exclude": [
"/.*",
"/tests",
"/vendor",
"/bin",
"docs/_build",
"Imagine-*.tgz",
"imagine-*.phar",
"composer.phar"
]
}
}

View File

@@ -0,0 +1,149 @@
<?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;
use Imagine\Exception\RuntimeException;
/**
* 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 PointInterface $center
* @param BoxInterface $size
* @param integer $start
* @param integer $end
* @param ColorInterface $color
* @param integer $thickness
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
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 PointInterface $center
* @param BoxInterface $size
* @param integer $start
* @param integer $end
* @param ColorInterface $color
* @param Boolean $fill
* @param integer $thickness
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1);
/**
* Draws and ellipse with center at the given x, y coordinates, and given
* width and height
*
* @param PointInterface $center
* @param BoxInterface $size
* @param ColorInterface $color
* @param Boolean $fill
* @param integer $thickness
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
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 PointInterface $start
* @param PointInterface $end
* @param ColorInterface $outline
* @param integer $thickness
*
* @return DrawerInterface
*/
public function line(PointInterface $start, PointInterface $end, ColorInterface $outline, $thickness = 1);
/**
* Same as arc, but connects end points and the center
*
* @param PointInterface $center
* @param BoxInterface $size
* @param integer $start
* @param integer $end
* @param ColorInterface $color
* @param Boolean $fill
* @param integer $thickness
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
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 PointInterface $position
* @param ColorInterface $color
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
public function dot(PointInterface $position, ColorInterface $color);
/**
* Draws a polygon using array of x, y coordinates. Must contain at least
* three coordinates
*
* @param array $coordinates
* @param ColorInterface $color
* @param Boolean $fill
* @param integer $thickness
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
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 AbstractFont $font
* @param PointInterface $position
* @param integer $angle
* @param integer $width
*
* @throws RuntimeException
*
* @return DrawerInterface
*/
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null);
}

View File

@@ -0,0 +1,80 @@
<?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\Exception\RuntimeException;
use Imagine\Image\Palette\Color\ColorInterface;
/**
* Interface for the effects
*/
interface EffectsInterface
{
/**
* Apply gamma correction
*
* @param float $correction
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function gamma($correction);
/**
* Invert the colors of the image
*
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function negative();
/**
* Grayscale the image
*
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function grayscale();
/**
* Colorize the image
*
* @param ColorInterface $color
*
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function colorize(ColorInterface $color);
/**
* Sharpens the image
*
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function sharpen();
/**
* Blur the image
*
* @param float|int $sigma
*
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function blur($sigma);
}

View File

@@ -0,0 +1,19 @@
<?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
{
}

View File

@@ -0,0 +1,19 @@
<?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
{
}

View File

@@ -0,0 +1,19 @@
<?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
{
}

View File

@@ -0,0 +1,19 @@
<?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
{
}

View File

@@ -0,0 +1,19 @@
<?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
{
}

View File

@@ -0,0 +1,98 @@
<?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 ColorInterface
*/
private $color;
/**
* @var integer
*/
private $width;
/**
* @var integer
*/
private $height;
/**
* Constructs Border filter with given color, width and height
*
* @param ColorInterface $color
* @param integer $width Width of the border on the left and right sides of the image
* @param integer $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}
*/
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;
}
}

View File

@@ -0,0 +1,74 @@
<?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\BoxInterface;
use Imagine\Image\Point;
use Imagine\Image\PointInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\ImagineInterface;
/**
* A canvas filter
*/
class Canvas implements FilterInterface
{
/**
* @var BoxInterface
*/
private $size;
/**
* @var PointInterface
*/
private $placement;
/**
* @var ColorInterface
*/
private $background;
/**
* @var ImagineInterface
*/
private $imagine;
/**
* Constructs Canvas filter with given width and height and the placement of the current image
* inside the new canvas
*
* @param ImagineInterface $imagine
* @param BoxInterface $size
* @param PointInterface $placement
* @param 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}
*/
public function apply(ImageInterface $image)
{
$canvas = $this->imagine->create($this->size, $this->background);
$canvas->paste($image, $this->placement);
return $canvas;
}
}

View File

@@ -0,0 +1,30 @@
<?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());
});
}
}

View File

@@ -0,0 +1,57 @@
<?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
{
protected $callback;
public function __construct($callback)
{
if (!is_callable($callback)) {
throw new InvalidArgumentException('$callback has to be callable');
}
$this->callback = $callback;
}
/**
* Applies scheduled transformation to ImageInterface instance
* Returns processed ImageInterface instance
*
* @param ImageInterface $image
*
* @return ImageInterface
*/
public function apply(ImageInterface $image)
{
$w = $image->getSize()->getWidth();
$h = $image->getSize()->getHeight();
for ($x = 0; $x < $w; $x++) {
for ($y = 0; $y < $h; $y++) {
call_user_func($this->callback, $image, new Point($x, $y));
}
}
return $image;
}
}

View File

@@ -0,0 +1,50 @@
<?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
{
private $method;
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}
*/
public function apply(ImageInterface $image)
{
return $image->resize(call_user_func(array($image->getSize(), $this->method), $this->parameter));
}
}

View File

@@ -0,0 +1,42 @@
<?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 ImageInterface
*/
private $mask;
/**
* @param ImageInterface $mask
*/
public function __construct(ImageInterface $mask)
{
$this->mask = $mask;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->applyMask($this->mask);
}
}

View File

@@ -0,0 +1,87 @@
<?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
{
private $color;
/**
* @param string|array|ColorInterface $color A color
*/
public function __construct($color = '000000')
{
$this->color = $color;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
$metadata = $image->metadata();
switch (isset($metadata['ifd0.Orientation']) ? $metadata['ifd0.Orientation'] : null) {
case 1: // top-left
break;
case 2: // top-right
$image->flipHorizontally();
break;
case 3: // bottom-right
$image->rotate(180, $this->getColor($image));
break;
case 4: // bottom-left
$image->flipHorizontally();
$image->rotate(180, $this->getColor($image));
break;
case 5: // left-top
$image->flipHorizontally();
$image->rotate(-90, $this->getColor($image));
break;
case 6: // right-top
$image->rotate(90, $this->getColor($image));
break;
case 7: // right-bottom
$image->flipHorizontally();
$image->rotate(90, $this->getColor($image));
break;
case 8: // left-bottom
$image->rotate(-90, $this->getColor($image));
break;
default: // Invalid orientation
break;
}
return $image;
}
private function getColor(ImageInterface $image)
{
if ($this->color instanceof ColorInterface) {
return $this->color;
}
return $image->palette()->color($this->color);
}
}

View File

@@ -0,0 +1,29 @@
<?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}
*/
public function apply(ImageInterface $image)
{
return $image->copy();
}
}

View File

@@ -0,0 +1,54 @@
<?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\Image\ImageInterface;
use Imagine\Image\BoxInterface;
use Imagine\Image\PointInterface;
use Imagine\Filter\FilterInterface;
/**
* A crop filter
*/
class Crop implements FilterInterface
{
/**
* @var PointInterface
*/
private $start;
/**
* @var BoxInterface
*/
private $size;
/**
* Constructs a Crop filter with given x, y, coordinates and crop width and
* height values
*
* @param PointInterface $start
* @param BoxInterface $size
*/
public function __construct(PointInterface $start, BoxInterface $size)
{
$this->start = $start;
$this->size = $size;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->crop($this->start, $this->size);
}
}

View File

@@ -0,0 +1,43 @@
<?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 FillInterface
*/
private $fill;
/**
* @param FillInterface $fill
*/
public function __construct(FillInterface $fill)
{
$this->fill = $fill;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->fill($this->fill);
}
}

View File

@@ -0,0 +1,29 @@
<?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\Image\ImageInterface;
use Imagine\Filter\FilterInterface;
/**
* A "flip horizontally" filter
*/
class FlipHorizontally implements FilterInterface
{
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->flipHorizontally();
}
}

View File

@@ -0,0 +1,29 @@
<?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\Image\ImageInterface;
use Imagine\Filter\FilterInterface;
/**
* A "flip vertically" filter
*/
class FlipVertically implements FilterInterface
{
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->flipVertically();
}
}

View File

@@ -0,0 +1,53 @@
<?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\Image\ImageInterface;
use Imagine\Image\PointInterface;
use Imagine\Filter\FilterInterface;
/**
* A paste filter
*/
class Paste implements FilterInterface
{
/**
* @var ImageInterface
*/
private $image;
/**
* @var PointInterface
*/
private $start;
/**
* Constructs a Paste filter with given ImageInterface to paste and x, y
* coordinates of target position
*
* @param ImageInterface $image
* @param PointInterface $start
*/
public function __construct(ImageInterface $image, PointInterface $start)
{
$this->image = $image;
$this->start = $start;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->paste($this->image, $this->start);
}
}

View File

@@ -0,0 +1,48 @@
<?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\BoxInterface;
/**
* A resize filter
*/
class Resize implements FilterInterface
{
/**
* @var BoxInterface
*/
private $size;
private $filter;
/**
* Constructs Resize filter with given width and height
*
* @param BoxInterface $size
* @param string $filter
*/
public function __construct(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
{
$this->size = $size;
$this->filter = $filter;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->resize($this->size, $this->filter);
}
}

View File

@@ -0,0 +1,52 @@
<?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\Image\ImageInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Filter\FilterInterface;
/**
* A rotate filter
*/
class Rotate implements FilterInterface
{
/**
* @var integer
*/
private $angle;
/**
* @var ColorInterface
*/
private $background;
/**
* Constructs Rotate filter with given angle and background color
*
* @param integer $angle
* @param ColorInterface $background
*/
public function __construct($angle, ColorInterface $background = null)
{
$this->angle = $angle;
$this->background = $background;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->rotate($this->angle, $this->background);
}
}

View File

@@ -0,0 +1,51 @@
<?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\Image\ImageInterface;
use Imagine\Filter\FilterInterface;
/**
* 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}
*/
public function apply(ImageInterface $image)
{
return $image->save($this->path, $this->options);
}
}

View File

@@ -0,0 +1,51 @@
<?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\Image\ImageInterface;
use Imagine\Filter\FilterInterface;
/**
* 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}
*/
public function apply(ImageInterface $image)
{
return $image->show($this->format, $this->options);
}
}

View File

@@ -0,0 +1,29 @@
<?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\Image\ImageInterface;
use Imagine\Filter\FilterInterface;
/**
* A strip filter
*/
class Strip implements FilterInterface
{
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->strip();
}
}

View File

@@ -0,0 +1,59 @@
<?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\Image\ImageInterface;
use Imagine\Image\BoxInterface;
use Imagine\Filter\FilterInterface;
/**
* A thumbnail filter
*/
class Thumbnail implements FilterInterface
{
/**
* @var BoxInterface
*/
private $size;
/**
* @var string
*/
private $mode;
/**
* @var string
*/
private $filter;
/**
* Constructs the Thumbnail filter with given width, height and mode
*
* @param BoxInterface $size
* @param string $mode
* @param string $filter
*/
public function __construct(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
{
$this->size = $size;
$this->mode = $mode;
$this->filter = $filter;
}
/**
* {@inheritdoc}
*/
public function apply(ImageInterface $image)
{
return $image->thumbnail($this->size, $this->mode, $this->filter);
}
}

View File

@@ -0,0 +1,57 @@
<?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\Image\ImageInterface;
use Imagine\Image\Palette\RGB;
use Imagine\Filter\FilterInterface;
/**
* A filter to render web-optimized images
*/
class WebOptimization implements FilterInterface
{
private $palette;
private $path;
private $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}
*/
public function apply(ImageInterface $image)
{
$image
->usePalette($this->palette)
->strip();
if (is_callable($this->path)) {
$path = call_user_func($this->path, $image);
} elseif (null !== $this->path) {
$path = $this->path;
} else {
return $image;
}
return $image->save($path, $this->options);
}
}

View File

@@ -0,0 +1,30 @@
<?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 ImageInterface instance
* Returns processed ImageInterface instance
*
* @param ImageInterface $image
*
* @return ImageInterface
*/
public function apply(ImageInterface $image);
}

View File

@@ -0,0 +1,54 @@
<?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 ImagineInterface
*/
private $imagine;
/**
* Set ImagineInterface instance.
*
* @param ImagineInterface $imagine An ImagineInterface instance
*/
public function setImagine(ImagineInterface $imagine)
{
$this->imagine = $imagine;
}
/**
* Get ImagineInterface instance.
*
* @return ImagineInterface
*
* @throws InvalidArgumentException
*/
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;
}
}

View File

@@ -0,0 +1,240 @@
<?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\FlipVertically;
use Imagine\Filter\Basic\FlipHorizontally;
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\ImageInterface;
use Imagine\Image\ImagineInterface;
use Imagine\Image\BoxInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Fill\FillInterface;
use Imagine\Image\ManipulatorInterface;
use Imagine\Image\PointInterface;
/**
* A transformation filter
*/
final class Transformation implements FilterInterface, ManipulatorInterface
{
/**
* @var array
*/
private $filters = array();
/**
* @var array
*/
private $sorted;
/**
* An ImagineInterface instance.
*
* @var ImagineInterface
*/
private $imagine;
/**
* Class constructor.
*
* @param ImagineInterface $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 ImageInterface $image
* @param FilterInterface $filter
*
* @return ImageInterface
* @throws InvalidArgumentException
*/
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 (null === $this->sorted) {
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}
*/
public function apply(ImageInterface $image)
{
return array_reduce(
$this->getFilters(),
array($this, 'applyFilter'),
$image
);
}
/**
* {@inheritdoc}
*/
public function copy()
{
return $this->add(new Copy());
}
/**
* {@inheritdoc}
*/
public function crop(PointInterface $start, BoxInterface $size)
{
return $this->add(new Crop($start, $size));
}
/**
* {@inheritdoc}
*/
public function flipHorizontally()
{
return $this->add(new FlipHorizontally());
}
/**
* {@inheritdoc}
*/
public function flipVertically()
{
return $this->add(new FlipVertically());
}
/**
* {@inheritdoc}
*/
public function strip()
{
return $this->add(new Strip());
}
/**
* {@inheritdoc}
*/
public function paste(ImageInterface $image, PointInterface $start)
{
return $this->add(new Paste($image, $start));
}
/**
* {@inheritdoc}
*/
public function applyMask(ImageInterface $mask)
{
return $this->add(new ApplyMask($mask));
}
/**
* {@inheritdoc}
*/
public function fill(FillInterface $fill)
{
return $this->add(new Fill($fill));
}
/**
* {@inheritdoc}
*/
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
{
return $this->add(new Resize($size, $filter));
}
/**
* {@inheritdoc}
*/
public function rotate($angle, ColorInterface $background = null)
{
return $this->add(new Rotate($angle, $background));
}
/**
* {@inheritdoc}
*/
public function save($path = null, array $options = array())
{
return $this->add(new Save($path, $options));
}
/**
* {@inheritdoc}
*/
public function show($format, array $options = array())
{
return $this->add(new Show($format, $options));
}
/**
* {@inheritdoc}
*/
public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
{
return $this->add(new Thumbnail($size, $mode, $filter));
}
/**
* Registers a given FilterInterface in an internal array of filters for
* later application to an instance of ImageInterface
*
* @param FilterInterface $filter
* @param int $priority
* @return Transformation
*/
public function add(FilterInterface $filter, $priority = 0)
{
$this->filters[$priority][] = $filter;
$this->sorted = null;
return $this;
}
}

View File

@@ -0,0 +1,333 @@
<?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\DrawerInterface;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\AbstractFont;
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 library
*/
final class Drawer implements DrawerInterface
{
/**
* @var resource
*/
private $resource;
/**
* @var array
*/
private $info;
/**
* Constructs Drawer with a given gd image resource
*
* @param resource $resource
*/
public function __construct($resource)
{
$this->loadGdInfo();
$this->resource = $resource;
}
/**
* {@inheritdoc}
*/
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
{
imagesetthickness($this->resource, max(1, (int) $thickness));
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw arc operation failed');
}
if (false === imagearc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color))) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw arc operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw arc operation failed');
}
return $this;
}
/**
* This function does not work properly because of a bug in GD
*
* {@inheritdoc}
*/
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
{
imagesetthickness($this->resource, max(1, (int) $thickness));
if ($fill) {
$style = IMG_ARC_CHORD;
} else {
$style = IMG_ARC_CHORD | IMG_ARC_NOFILL;
}
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw chord operation failed');
}
if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw chord operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw chord operation failed');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
{
imagesetthickness($this->resource, max(1, (int) $thickness));
if ($fill) {
$callback = 'imagefilledellipse';
} else {
$callback = 'imageellipse';
}
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw ellipse operation failed');
}
if (false === $callback($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $this->getColor($color))) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw ellipse operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw ellipse operation failed');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
{
imagesetthickness($this->resource, max(1, (int) $thickness));
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw line operation failed');
}
if (false === imageline($this->resource, $start->getX(), $start->getY(), $end->getX(), $end->getY(), $this->getColor($color))) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw line operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw line operation failed');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
{
imagesetthickness($this->resource, max(1, (int) $thickness));
if ($fill) {
$style = IMG_ARC_EDGED;
} else {
$style = IMG_ARC_EDGED | IMG_ARC_NOFILL;
}
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw chord operation failed');
}
if (false === imagefilledarc($this->resource, $center->getX(), $center->getY(), $size->getWidth(), $size->getHeight(), $start, $end, $this->getColor($color), $style)) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw chord operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw chord operation failed');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function dot(PointInterface $position, ColorInterface $color)
{
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw point operation failed');
}
if (false === imagesetpixel($this->resource, $position->getX(), $position->getY(), $this->getColor($color))) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw point operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw point operation failed');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1)
{
imagesetthickness($this->resource, max(1, (int) $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 (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Draw polygon operation failed');
}
if (false === $callback($this->resource, $points, count($coordinates), $this->getColor($color))) {
imagealphablending($this->resource, false);
throw new RuntimeException('Draw polygon operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Draw polygon operation failed');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null)
{
if (!$this->info['FreeType Support']) {
throw new RuntimeException('GD is not compiled with FreeType support');
}
$angle = -1 * $angle;
$fontsize = $font->getSize();
$fontfile = $font->getFile();
$x = $position->getX();
$y = $position->getY() + $fontsize;
if ($width !== null) {
$string = $this->wrapText($string, $font, $angle, $width);
}
if (false === imagealphablending($this->resource, true)) {
throw new RuntimeException('Font mask operation failed');
}
if (false === imagefttext($this->resource, $fontsize, $angle, $x, $y, $this->getColor($font->getColor()), $fontfile, $string)) {
imagealphablending($this->resource, false);
throw new RuntimeException('Font mask operation failed');
}
if (false === imagealphablending($this->resource, false)) {
throw new RuntimeException('Font mask operation failed');
}
return $this;
}
/**
* Internal
*
* Generates a GD color from Color instance
*
* @param ColorInterface $color
*
* @return resource
*
* @throws RuntimeException
* @throws InvalidArgumentException
*/
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(), (100 - $color->getAlpha()) * 127 / 100);
if (false === $gdColor) {
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;
}
private function loadGdInfo()
{
if (!function_exists('gd_info')) {
throw new RuntimeException('Gd not installed');
}
$this->info = gd_info();
}
/**
* Internal
*
* Fits a string into box with given width
*/
private function wrapText($string, AbstractFont $font, $angle, $width)
{
$result = '';
$words = explode(' ', $string);
foreach ($words as $word) {
$teststring = $result . ' ' . $word;
$testbox = imagettfbbox($font->getSize(), $angle, $font->getFile(), $teststring);
if ($testbox[2] > $width) {
$result .= ($result == '' ? '' : "\n") . $word;
} else {
$result .= ($result == '' ? '' : ' ') . $word;
}
}
return $result;
}
}

View File

@@ -0,0 +1,109 @@
<?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\Effects\EffectsInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Palette\Color\RGB as RGBColor;
/**
* Effects implementation using the GD library
*/
class Effects implements EffectsInterface
{
private $resource;
public function __construct($resource)
{
$this->resource = $resource;
}
/**
* {@inheritdoc}
*/
public function gamma($correction)
{
if (false === imagegammacorrect($this->resource, 1.0, $correction)) {
throw new RuntimeException('Failed to apply gamma correction to the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function negative()
{
if (false === imagefilter($this->resource, IMG_FILTER_NEGATE)) {
throw new RuntimeException('Failed to negate the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function grayscale()
{
if (false === imagefilter($this->resource, IMG_FILTER_GRAYSCALE)) {
throw new RuntimeException('Failed to grayscale the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function colorize(ColorInterface $color)
{
if (!$color instanceof RGBColor) {
throw new RuntimeException('Colorize effects only accepts RGB color in GD context');
}
if (false === imagefilter($this->resource, IMG_FILTER_COLORIZE, $color->getRed(), $color->getGreen(), $color->getBlue())) {
throw new RuntimeException('Failed to colorize the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
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 (false === imageconvolution($this->resource, $sharpenMatrix, $divisor, 0)) {
throw new RuntimeException('Failed to sharpen the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function blur($sigma = 1)
{
if (false === imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR)) {
throw new RuntimeException('Failed to blur the image');
}
return $this;
}
}

View File

@@ -0,0 +1,41 @@
<?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\Exception\RuntimeException;
use Imagine\Image\AbstractFont;
use Imagine\Image\Box;
/**
* Font implementation using the GD library
*/
final class Font extends AbstractFont
{
/**
* {@inheritdoc}
*/
public function box($string, $angle = 0)
{
if (!function_exists('imageftbbox')) {
throw new RuntimeException('GD must have been compiled with `--with-freetype-dir` option to use the Font feature.');
}
$angle = -1 * $angle;
$info = imageftbbox($this->size, $angle, $this->file, $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 new Box($width, $height);
}
}

View File

@@ -0,0 +1,735 @@
<?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\Image\AbstractImage;
use Imagine\Image\ImageInterface;
use Imagine\Image\Box;
use Imagine\Image\BoxInterface;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Fill\FillInterface;
use Imagine\Image\Point;
use Imagine\Image\PointInterface;
use Imagine\Image\Palette\PaletteInterface;
use Imagine\Image\Palette\Color\RGB as RGBColor;
use Imagine\Image\ProfileInterface;
use Imagine\Image\Palette\RGB;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\OutOfBoundsException;
use Imagine\Exception\RuntimeException;
/**
* Image implementation using the GD library
*/
final class Image extends AbstractImage
{
/**
* @var resource
*/
private $resource;
/**
* @var Layers|null
*/
private $layers;
/**
* @var PaletteInterface
*/
private $palette;
/**
* Constructs a new Image instance
*
* @param resource $resource
* @param PaletteInterface $palette
* @param 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 (is_resource($this->resource) && 'gd' === get_resource_type($this->resource)) {
imagedestroy($this->resource);
}
}
/**
* Returns Gd resource
*
* @return resource
*/
public function getGdResource()
{
return $this->resource;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function copy()
{
$size = $this->getSize();
$copy = $this->createImage($size, 'copy');
if (false === imagecopy($copy, $this->resource, 0, 0, 0, 0, $size->getWidth(), $size->getHeight())) {
throw new RuntimeException('Image copy operation failed');
}
return new Image($copy, $this->palette, $this->metadata);
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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 (false === imagecopy($dest, $this->resource, 0, 0, $start->getX(), $start->getY(), $width, $height)) {
throw new RuntimeException('Image crop operation failed');
}
imagedestroy($this->resource);
$this->resource = $dest;
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function paste(ImageInterface $image, PointInterface $start)
{
if (!$image instanceof self) {
throw new InvalidArgumentException(sprintf('Gd\Image can only paste() Gd\Image instances, %s given', get_class($image)));
}
$size = $image->getSize();
if (!$this->getSize()->contains($size, $start)) {
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
}
imagealphablending($this->resource, true);
imagealphablending($image->resource, true);
if (false === imagecopy($this->resource, $image->resource, $start->getX(), $start->getY(), 0, 0, $size->getWidth(), $size->getHeight())) {
throw new RuntimeException('Image paste operation failed');
}
imagealphablending($this->resource, false);
imagealphablending($image->resource, false);
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
{
if (ImageInterface::FILTER_UNDEFINED !== $filter) {
throw new InvalidArgumentException('Unsupported filter type, GD only supports ImageInterface::FILTER_UNDEFINED filter');
}
$width = $size->getWidth();
$height = $size->getHeight();
$dest = $this->createImage($size, 'resize');
imagealphablending($this->resource, true);
imagealphablending($dest, true);
if (false === imagecopyresampled($dest, $this->resource, 0, 0, 0, 0, $width, $height, imagesx($this->resource), imagesy($this->resource))) {
throw new RuntimeException('Image resize operation failed');
}
imagealphablending($this->resource, false);
imagealphablending($dest, false);
imagedestroy($this->resource);
$this->resource = $dest;
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function rotate($angle, ColorInterface $background = null)
{
$color = $background ? $background : $this->palette->color('fff');
$resource = imagerotate($this->resource, -1 * $angle, $this->getColor($color));
if (false === $resource) {
throw new RuntimeException('Image rotate operation failed');
}
imagedestroy($this->resource);
$this->resource = $resource;
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function save($path = null, array $options = array())
{
$path = null === $path ? (isset($this->metadata['filepath']) ? $this->metadata['filepath'] : $path) : $path;
if (null === $path) {
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);
}
$this->saveOrOutput($format, $options, $path);
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function show($format, array $options = array())
{
header('Content-type: '.$this->getMimeType($format));
$this->saveOrOutput($format, $options);
return $this;
}
/**
* {@inheritdoc}
*/
public function get($format, array $options = array())
{
ob_start();
$this->saveOrOutput($format, $options);
return ob_get_clean();
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->get('png');
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function flipHorizontally()
{
$size = $this->getSize();
$width = $size->getWidth();
$height = $size->getHeight();
$dest = $this->createImage($size, 'flip');
for ($i = 0; $i < $width; $i++) {
if (false === imagecopy($dest, $this->resource, $i, 0, ($width - 1) - $i, 0, 1, $height)) {
throw new RuntimeException('Horizontal flip operation failed');
}
}
imagedestroy($this->resource);
$this->resource = $dest;
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function flipVertically()
{
$size = $this->getSize();
$width = $size->getWidth();
$height = $size->getHeight();
$dest = $this->createImage($size, 'flip');
for ($i = 0; $i < $height; $i++) {
if (false === imagecopy($dest, $this->resource, 0, $i, 0, ($height - 1) - $i, $width, 1)) {
throw new RuntimeException('Vertical flip operation failed');
}
}
imagedestroy($this->resource);
$this->resource = $dest;
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
final public function strip()
{
// GD strips profiles and comment, so there's nothing to do here
return $this;
}
/**
* {@inheritdoc}
*/
public function draw()
{
return new Drawer($this->resource);
}
/**
* {@inheritdoc}
*/
public function effects()
{
return new Effects($this->resource);
}
/**
* {@inheritdoc}
*/
public function getSize()
{
return new Box(imagesx($this->resource), imagesy($this->resource));
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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);
$round = (int) round(max($color->getAlpha(), (100 - $color->getAlpha()) * $maskColor->getRed() / 255));
if (false === imagesetpixel($this->resource, $x, $y, $this->getColor($color->dissolve($round - $color->getAlpha())))) {
throw new RuntimeException('Apply mask operation failed');
}
}
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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 (false === imagesetpixel($this->resource, $x, $y, $this->getColor($fill->getColor(new Point($x, $y))))) {
throw new RuntimeException('Fill operation failed');
}
}
}
return $this;
}
/**
* {@inheritdoc}
*/
public function mask()
{
$mask = $this->copy();
if (false === imagefilter($mask->resource, IMG_FILTER_GRAYSCALE)) {
throw new RuntimeException('Mask operation failed');
}
return $mask;
}
/**
* {@inheritdoc}
*/
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_unique($colors);
}
/**
* {@inheritdoc}
*/
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}
*/
public function layers()
{
if (null === $this->layers) {
$this->layers = new Layers($this, $this->palette, $this->resource);
}
return $this->layers;
}
/**
* {@inheritdoc}
**/
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}
*/
public function palette()
{
return $this->palette;
}
/**
* {@inheritdoc}
*/
public function profile(ProfileInterface $profile)
{
throw new RuntimeException('GD driver does not support color profiles');
}
/**
* {@inheritdoc}
*/
public function usePalette(PaletteInterface $palette)
{
if (!$palette instanceof RGB) {
throw new RuntimeException('GD driver only supports RGB palette');
}
$this->palette = $palette;
return $this;
}
/**
* Internal
*
* Performs save or show operation using one of GD's image... functions
*
* @param string $format
* @param array $options
* @param string $filename
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
private function saveOrOutput($format, array $options, $filename = null)
{
$format = $this->normalizeFormat($format);
if (!$this->supported($format)) {
throw new InvalidArgumentException(sprintf('Saving image in "%s" format is not supported, please use one of the following extensions: "%s"', $format, implode('", "', $this->supported())));
}
$save = 'image'.$format;
$args = array(&$this->resource, $filename);
// Preserve BC until version 1.0
if (isset($options['quality']) && !isset($options['png_compression_level'])) {
$options['png_compression_level'] = round((100 - $options['quality']) * 9 / 100);
}
if (isset($options['filters']) && !isset($options['png_compression_filter'])) {
$options['png_compression_filter'] = $options['filters'];
}
$options = $this->updateSaveOptions($options);
if ($format === 'jpeg' && isset($options['jpeg_quality'])) {
$args[] = $options['jpeg_quality'];
}
if ($format === 'png') {
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');
}
$args[] = $options['png_compression_level'];
} else {
$args[] = -1; // use default level
}
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');
}
$args[] = $options['png_compression_filter'];
}
}
if (($format === 'wbmp' || $format === 'xbm') && isset($options['foreground'])) {
$args[] = $options['foreground'];
}
$this->setExceptionHandler();
if (false === call_user_func_array($save, $args)) {
throw new RuntimeException('Save operation failed');
}
$this->resetExceptionHandler();
}
/**
* Internal
*
* Generates a GD image
*
* @param BoxInterface $size
* @param string the operation initiating the creation
*
* @return resource
*
* @throws RuntimeException
*
*/
private function createImage(BoxInterface $size, $operation)
{
$resource = imagecreatetruecolor($size->getWidth(), $size->getHeight());
if (false === $resource) {
throw new RuntimeException('Image '.$operation.' failed');
}
if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) {
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;
}
/**
* Internal
*
* Generates a GD color from Color instance
*
* @param ColorInterface $color
*
* @return integer A color identifier
*
* @throws RuntimeException
* @throws InvalidArgumentException
*/
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 (false === $index) {
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;
}
/**
* Internal
*
* Normalizes a given format name
*
* @param string $format
*
* @return string
*/
private function normalizeFormat($format)
{
$format = strtolower($format);
if ('jpg' === $format || 'pjpeg' === $format) {
$format = 'jpeg';
}
return $format;
}
/**
* Internal
*
* Checks whether a given format is supported by GD library
*
* @param string $format
*
* @return Boolean
*/
private function supported($format = null)
{
$formats = array('gif', 'jpeg', 'png', 'wbmp', 'xbm');
if (null === $format) {
return $formats;
}
return in_array($format, $formats);
}
private function setExceptionHandler()
{
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
if (0 === error_reporting()) {
return;
}
throw new RuntimeException($errstr, $errno, new \ErrorException($errstr, 0, $errno, $errfile, $errline));
}, E_WARNING | E_NOTICE);
}
private function resetExceptionHandler()
{
restore_error_handler();
}
/**
* Internal
*
* Get the mime type based on format.
*
* @param string $format
*
* @return string mime-type
*
* @throws RuntimeException
*/
private function getMimeType($format)
{
$format = $this->normalizeFormat($format);
if (!$this->supported($format)) {
throw new RuntimeException('Invalid format');
}
static $mimeTypes = array(
'jpeg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'wbmp' => 'image/vnd.wap.wbmp',
'xbm' => 'image/xbm',
);
return $mimeTypes[$format];
}
}

View File

@@ -0,0 +1,195 @@
<?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\Image\AbstractImagine;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Palette\RGB;
use Imagine\Image\Palette\PaletteInterface;
use Imagine\Image\BoxInterface;
use Imagine\Image\Palette\Color\RGB as RGBColor;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
/**
* Imagine implementation using the GD library
*/
final class Imagine extends AbstractImagine
{
/**
* @var array
*/
private $info;
/**
* @throws RuntimeException
*/
public function __construct()
{
$this->loadGdInfo();
$this->requireGdVersion('2.0.1');
}
/**
* {@inheritdoc}
*/
public function create(BoxInterface $size, ColorInterface $color = null)
{
$width = $size->getWidth();
$height = $size->getHeight();
$resource = imagecreatetruecolor($width, $height);
if (false === $resource) {
throw new RuntimeException('Create operation failed');
}
$palette = null !== $color ? $color->getPalette() : new RGB();
$color = $color ? $color : $palette->color('fff');
if (!$color instanceof RGBColor) {
throw new InvalidArgumentException('GD driver only supports RGB colors');
}
$index = imagecolorallocatealpha($resource, $color->getRed(), $color->getGreen(), $color->getBlue(), round(127 * (100 - $color->getAlpha()) / 100));
if (false === $index) {
throw new RuntimeException('Unable to allocate color');
}
if (false === imagefill($resource, 0, 0, $index)) {
throw new RuntimeException('Could not set background color fill');
}
if ($color->getAlpha() >= 95) {
imagecolortransparent($resource, $index);
}
return $this->wrap($resource, $palette, new MetadataBag());
}
/**
* {@inheritdoc}
*/
public function open($path)
{
$path = $this->checkPath($path);
$data = @file_get_contents($path);
if (false === $data) {
throw new RuntimeException(sprintf('Failed to open file %s', $path));
}
$resource = @imagecreatefromstring($data);
if (!is_resource($resource)) {
throw new RuntimeException(sprintf('Unable to open image %s', $path));
}
return $this->wrap($resource, new RGB(), $this->getMetadataReader()->readFile($path));
}
/**
* {@inheritdoc}
*/
public function load($string)
{
return $this->doLoad($string, $this->getMetadataReader()->readData($string));
}
/**
* {@inheritdoc}
*/
public function read($resource)
{
if (!is_resource($resource)) {
throw new InvalidArgumentException('Variable does not contain a stream resource');
}
$content = stream_get_contents($resource);
if (false === $content) {
throw new InvalidArgumentException('Cannot read resource content');
}
return $this->doLoad($content, $this->getMetadataReader()->readStream($resource));
}
/**
* {@inheritdoc}
*/
public function font($file, $size, ColorInterface $color)
{
if (!$this->info['FreeType Support']) {
throw new RuntimeException('GD is not compiled with FreeType support');
}
return new Font($file, $size, $color);
}
private function wrap($resource, PaletteInterface $palette, MetadataBag $metadata)
{
if (!imageistruecolor($resource)) {
list($width, $height) = array(imagesx($resource), imagesy($resource));
// create transparent truecolor canvas
$truecolor = imagecreatetruecolor($width, $height);
$transparent = imagecolorallocatealpha($truecolor, 255, 255, 255, 127);
imagefill($truecolor, 0, 0, $transparent);
imagecolortransparent($truecolor, $transparent);
imagecopymerge($truecolor, $resource, 0, 0, 0, 0, $width, $height, 100);
imagedestroy($resource);
$resource = $truecolor;
}
if (false === imagealphablending($resource, false) || false === imagesavealpha($resource, true)) {
throw new RuntimeException('Could not set alphablending, savealpha and antialias values');
}
if (function_exists('imageantialias')) {
imageantialias($resource, true);
}
return new Image($resource, $palette, $metadata);
}
private function loadGdInfo()
{
if (!function_exists('gd_info')) {
throw new RuntimeException('Gd not installed');
}
$this->info = gd_info();
}
private function requireGdVersion($version)
{
if (version_compare(GD_VERSION, $version, '<')) {
throw new RuntimeException(sprintf('GD2 version %s or higher is required', $version));
}
}
private function doLoad($string, MetadataBag $metadata)
{
$resource = @imagecreatefromstring($string);
if (!is_resource($resource)) {
throw new RuntimeException('An image could not be created from the given input');
}
return $this->wrap($resource, new RGB(), $metadata);
}
}

View File

@@ -0,0 +1,144 @@
<?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\Image\AbstractLayers;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\PaletteInterface;
use Imagine\Exception\NotSupportedException;
class Layers extends AbstractLayers
{
private $image;
private $offset;
private $resource;
private $palette;
public function __construct(Image $image, PaletteInterface $palette, $resource)
{
if (!is_resource($resource)) {
throw new RuntimeException('Invalid Gd resource provided');
}
$this->image = $image;
$this->resource = $resource;
$this->offset = 0;
$this->palette = $palette;
}
/**
* {@inheritdoc}
*/
public function merge()
{
}
/**
* {@inheritdoc}
*/
public function coalesce()
{
}
/**
* {@inheritdoc}
*/
public function animate($format, $delay, $loops)
{
return $this;
}
/**
* {@inheritdoc}
*/
public function current()
{
return new Image($this->resource, $this->palette, new MetadataBag());
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->offset;
}
/**
* {@inheritdoc}
*/
public function next()
{
++$this->offset;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->offset = 0;
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->offset < 1;
}
/**
* {@inheritdoc}
*/
public function count()
{
return 1;
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
{
return 0 === $offset;
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
if (0 === $offset) {
return new Image($this->resource, $this->palette, new MetadataBag());
}
throw new RuntimeException('GD only supports one layer at offset 0');
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value)
{
throw new NotSupportedException('GD does not support layer set');
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset)
{
throw new NotSupportedException('GD does not support layer unset');
}
}

View File

@@ -0,0 +1,356 @@
<?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\Exception\InvalidArgumentException;
use Imagine\Exception\NotSupportedException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\AbstractFont;
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
{
/**
* @var \Gmagick
*/
private $gmagick;
/**
* @param \Gmagick $gmagick
*/
public function __construct(\Gmagick $gmagick)
{
$this->gmagick = $gmagick;
}
/**
* {@inheritdoc}
*/
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
{
$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(max(1, (int) $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}
*/
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
{
$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(max(1, (int) $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);
$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}
*/
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
{
$width = $size->getWidth();
$height = $size->getHeight();
try {
$pixel = $this->getColor($color);
$ellipse = new \GmagickDraw();
$ellipse->setstrokecolor($pixel);
$ellipse->setstrokewidth(max(1, (int) $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}
*/
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
{
try {
$pixel = $this->getColor($color);
$line = new \GmagickDraw();
$line->setstrokecolor($pixel);
$line->setstrokewidth(max(1, (int) $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}
*/
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
{
$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}
*/
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}
*/
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)));
}
$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(max(1, (int) $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}
*/
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);
$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);
if ($width !== null) {
throw new NotSupportedException('Gmagick doesn\'t support queryfontmetrics function for multiline text', 1);
}
$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 ColorInterface $color
*
* @return \GmagickPixel
*
* @throws InvalidArgumentException In case a non-opaque color is passed
*/
private function getColor(ColorInterface $color)
{
if (!$color->isOpaque()) {
throw new InvalidArgumentException('Gmagick doesn\'t support transparency');
}
return new \GmagickPixel((string) $color);
}
}

View File

@@ -0,0 +1,106 @@
<?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\Effects\EffectsInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Exception\NotSupportedException;
/**
* Effects implementation using the Gmagick PHP extension
*/
class Effects implements EffectsInterface
{
private $gmagick;
public function __construct(\Gmagick $gmagick)
{
$this->gmagick = $gmagick;
}
/**
* {@inheritdoc}
*/
public function gamma($correction)
{
try {
$this->gmagick->gammaimage($correction);
} catch (\GmagickException $e) {
throw new RuntimeException('Failed to apply gamma correction to the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function negative()
{
if (!method_exists($this->gmagick, 'negateimage')) {
throw new NotSupportedException('Gmagick version 1.1.0 RC3 is required for negative effect');
}
try {
$this->gmagick->negateimage(false, \Gmagick::CHANNEL_ALL);
} catch (\GmagickException $e) {
throw new RuntimeException('Failed to negate the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function grayscale()
{
try {
$this->gmagick->setImageType(2);
} catch (\GmagickException $e) {
throw new RuntimeException('Failed to grayscale the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function colorize(ColorInterface $color)
{
throw new NotSupportedException('Gmagick does not support colorize');
}
/**
* {@inheritdoc}
*/
public function sharpen()
{
throw new NotSupportedException('Gmagick does not support sharpen yet');
}
/**
* {@inheritdoc}
*/
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;
}
}

View File

@@ -0,0 +1,63 @@
<?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\Image\AbstractFont;
use Imagine\Image\Box;
use Imagine\Image\Palette\Color\ColorInterface;
/**
* Font implementation using the Gmagick PHP extension
*/
final class Font extends AbstractFont
{
/**
* @var \Gmagick
*/
private $gmagick;
/**
* @param \Gmagick $gmagick
* @param string $file
* @param integer $size
* @param ColorInterface $color
*/
public function __construct(\Gmagick $gmagick, $file, $size, ColorInterface $color)
{
$this->gmagick = $gmagick;
parent::__construct($file, $size, $color);
}
/**
* {@inheritdoc}
*/
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 = new Box($info['textWidth'], $info['textHeight']);
return $box;
}
}

View File

@@ -0,0 +1,786 @@
<?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\Exception\OutOfBoundsException;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\AbstractImage;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\PaletteInterface;
use Imagine\Image\ImageInterface;
use Imagine\Image\Box;
use Imagine\Image\BoxInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Fill\FillInterface;
use Imagine\Image\Point;
use Imagine\Image\PointInterface;
use Imagine\Image\ProfileInterface;
/**
* Image implementation using the Gmagick PHP extension
*/
final class Image extends AbstractImage
{
/**
* @var \Gmagick
*/
private $gmagick;
/**
* @var Layers
*/
private $layers;
/**
* @var PaletteInterface
*/
private $palette;
private static $colorspaceMapping = array(
PaletteInterface::PALETTE_CMYK => \Gmagick::COLORSPACE_CMYK,
PaletteInterface::PALETTE_RGB => \Gmagick::COLORSPACE_RGB,
);
/**
* Constructs a new Image instance
*
* @param \Gmagick $gmagick
* @param PaletteInterface $palette
* @param MetadataBag $metadata
*/
public function __construct(\Gmagick $gmagick, PaletteInterface $palette, MetadataBag $metadata)
{
$this->metadata = $metadata;
$this->gmagick = $gmagick;
$this->setColorspace($palette);
$this->layers = new Layers($this, $this->palette, $this->gmagick);
}
/**
* Destroys allocated gmagick resources
*/
public function __destruct()
{
if ($this->gmagick instanceof \Gmagick) {
$this->gmagick->clear();
$this->gmagick->destroy();
}
}
/**
* Returns gmagick instance
*
* @return Gmagick
*/
public function getGmagick()
{
return $this->gmagick;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function copy()
{
return new self(clone $this->gmagick, $this->palette, clone $this->metadata);
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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}
*
* @return ImageInterface
*/
public function flipHorizontally()
{
try {
$this->gmagick->flopimage();
} catch (\GmagickException $e) {
throw new RuntimeException('Horizontal flip operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function flipVertically()
{
try {
$this->gmagick->flipimage();
} catch (\GmagickException $e) {
throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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}
*
* @return ImageInterface
*/
public function paste(ImageInterface $image, PointInterface $start)
{
if (!$image instanceof self) {
throw new InvalidArgumentException(sprintf('Gmagick\Image can only paste() Gmagick\Image instances, %s given', get_class($image)));
}
if (!$this->getSize()->contains($image->getSize(), $start)) {
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
}
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);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
{
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)) {
throw new InvalidArgumentException('Unsupported filter type');
}
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}
*
* @return ImageInterface
*/
public function rotate($angle, ColorInterface $background = null)
{
try {
$background = $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;
}
/**
* Internal
*
* Applies options before save or output
*
* @param \Gmagick $image
* @param array $options
* @param string $path
*
* @throws 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);
$options = $this->updateSaveOptions($options);
if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) {
$image->setCompressionQuality($options['jpeg_quality']);
}
if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') {
// first digit: compression level (default: 7)
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');
}
$compression = $options['png_compression_level'] * 10;
} else {
$compression = 70;
}
// second digit: compression filter (default: 5)
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');
}
$compression += $options['png_compression_filter'];
} else {
$compression += 5;
}
$image->setCompressionQuality($compression);
}
if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) {
if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) {
$image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERCENTIMETER);
} elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) {
$image->setimageunits(\Gmagick::RESOLUTION_PIXELSPERINCH);
} else {
throw new InvalidArgumentException('Unsupported image unit format');
}
$image->setimageresolution($options['resolution-x'], $options['resolution-y']);
}
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function save($path = null, array $options = array())
{
$path = null === $path ? $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']) || false === $options['animated'];
$this->gmagick->writeimage($path, $allFrames);
} catch (\GmagickException $e) {
throw new RuntimeException('Save operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function show($format, array $options = array())
{
header('Content-type: '.$this->getMimeType($format));
echo $this->get($format, $options);
return $this;
}
/**
* {@inheritdoc}
*/
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['format'])) {
$this->gmagick->setimageformat($options['format']);
}
if (isset($options['animated']) && true === $options['animated']) {
$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) && count($this->layers) > 1) {
$this->flatten();
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->get('png');
}
/**
* {@inheritdoc}
*/
public function draw()
{
return new Drawer($this->gmagick);
}
/**
* {@inheritdoc}
*/
public function effects()
{
return new Effects($this->gmagick);
}
/**
* {@inheritdoc}
*/
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 new Box($width, $height);
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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}
*/
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}
*
* @return ImageInterface
*/
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}
*/
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}
*/
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
->cropImage(1, 1, $point->getX(), $point->getY())
->getImageHistogram();
} catch (\GmagickException $e) {
throw new RuntimeException('Unable to get the pixel');
}
$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
*
* @return ColorInterface
*
* @throws InvalidArgumentException In case a unknown color is requested
*/
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,
);
if ($this->palette->supportsAlpha()) {
try {
$alpha = (int) round($pixel->getcolorvalue(\Gmagick::COLOR_ALPHA) * 100);
} catch (\GmagickPixelException $e) {
$alpha = null;
}
} else {
$alpha = null;
}
$palette = $this->palette();
return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) {
if (!isset($colorMapping[$color])) {
throw new InvalidArgumentException(sprintf('Color %s is not mapped in Gmagick', $color));
}
$multiplier = 255;
if ($palette->name() === PaletteInterface::PALETTE_CMYK) {
$multiplier = 100;
}
return $pixel->getcolorvalue($colorMapping[$color]) * $multiplier;
}, $this->palette->pixelDefinition()), $alpha);
}
/**
* {@inheritdoc}
*/
public function layers()
{
return $this->layers;
}
/**
* {@inheritdoc}
*/
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}
*/
public function usePalette(PaletteInterface $palette)
{
if (!isset(static::$colorspaceMapping[$palette->name()])) {
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver',$palette->name()));
}
if ($this->palette->name() === $palette->name()) {
return $this;
}
try {
try {
$hasICCProfile = (Boolean) $this->gmagick->getimageprofile('ICM');
} catch (\GmagickException $e) {
$hasICCProfile = false;
}
if (!$hasICCProfile) {
$this->profile($this->palette->profile());
}
$this->profile($palette->profile());
$this->setColorspace($palette);
$this->palette = $palette;
} catch (\GmagickException $e) {
throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function palette()
{
return $this->palette;
}
/**
* {@inheritdoc}
*/
public function profile(ProfileInterface $profile)
{
try {
$this->gmagick->profileimage('ICM', $profile->data());
} catch (\GmagickException $e) {
throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e);
}
return $this;
}
/**
* Internal
*
* 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 ColorInterface $color
*
* @return \GmagickPixel
*
* @throws InvalidArgumentException
*/
private function getColor(ColorInterface $color)
{
if (!$color->isOpaque()) {
throw new InvalidArgumentException('Gmagick doesn\'t support transparency');
}
return new \GmagickPixel((string) $color);
}
/**
* Internal
*
* Get the mime type based on format.
*
* @param string $format
*
* @return string mime-type
*
* @throws InvalidArgumentException
*/
private function getMimeType($format)
{
static $mimeTypes = array(
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'wbmp' => 'image/vnd.wap.wbmp',
'xbm' => 'image/xbm',
);
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 PaletteInterface $palette
*
* @throws InvalidArgumentException
*/
private function setColorspace(PaletteInterface $palette)
{
if (!isset(static::$colorspaceMapping[$palette->name()])) {
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Gmagick driver', $palette->name()));
}
$this->gmagick->setimagecolorspace(static::$colorspaceMapping[$palette->name()]);
$this->palette = $palette;
}
}

View File

@@ -0,0 +1,167 @@
<?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\Image\AbstractImagine;
use Imagine\Exception\NotSupportedException;
use Imagine\Image\BoxInterface;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Palette\Grayscale;
use Imagine\Image\Palette\CMYK;
use Imagine\Image\Palette\RGB;
use Imagine\Image\Palette\Color\CMYK as CMYKColor;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
/**
* Imagine implementation using the Gmagick PHP extension
*/
class Imagine extends AbstractImagine
{
/**
* @throws RuntimeException
*/
public function __construct()
{
if (!class_exists('Gmagick')) {
throw new RuntimeException('Gmagick not installed');
}
}
/**
* {@inheritdoc}
*/
public function open($path)
{
$path = $this->checkPath($path);
try {
$gmagick = new \Gmagick($path);
$image = new Image($gmagick, $this->createPalette($gmagick), $this->getMetadataReader()->readFile($path));
} catch (\GmagickException $e) {
throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e);
}
return $image;
}
/**
* {@inheritdoc}
*/
public function create(BoxInterface $size, ColorInterface $color = null)
{
$width = $size->getWidth();
$height = $size->getHeight();
$palette = null !== $color ? $color->getPalette() : new RGB();
$color = null !== $color ? $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() < 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 = new Image($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}
*/
public function load($string)
{
return $this->doLoad($string, $this->getMetadataReader()->readData($string));
}
/**
* {@inheritdoc}
*/
public function read($resource)
{
if (!is_resource($resource)) {
throw new InvalidArgumentException('Variable does not contain a stream resource');
}
$content = stream_get_contents($resource);
if (false === $content) {
throw new InvalidArgumentException('Couldn\'t read given resource');
}
return $this->doLoad($content, $this->getMetadataReader()->readStream($resource));
}
/**
* {@inheritdoc}
*/
public function font($file, $size, ColorInterface $color)
{
$gmagick = new \Gmagick();
$gmagick->newimage(1, 1, 'transparent');
return new Font($gmagick, $file, $size, $color);
}
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');
}
}
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 new Image($gmagick, $this->createPalette($gmagick), $metadata);
}
}

View File

@@ -0,0 +1,272 @@
<?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\Image\AbstractLayers;
use Imagine\Exception\RuntimeException;
use Imagine\Exception\NotSupportedException;
use Imagine\Exception\OutOfBoundsException;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\PaletteInterface;
class Layers extends AbstractLayers
{
/**
* @var Image
*/
private $image;
/**
* @var \Gmagick
*/
private $resource;
/**
* @var integer
*/
private $offset = 0;
/**
* @var array
*/
private $layers = array();
/**
* @var PaletteInterface
*/
private $palette;
public function __construct(Image $image, PaletteInterface $palette, \Gmagick $resource)
{
$this->image = $image;
$this->resource = $resource;
$this->palette = $palette;
}
/**
* {@inheritdoc}
*/
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}
*/
public function coalesce()
{
throw new NotSupportedException('Gmagick does not support coalescing');
}
/**
* {@inheritdoc}
*/
public function animate($format, $delay, $loops)
{
if ('gif' !== strtolower($format)) {
throw new NotSupportedException('Animated picture is currently only supported on gif');
}
if (!is_int($loops) || $loops < 0) {
throw new InvalidArgumentException('Loops must be a positive integer.');
}
if (null !== $delay && (!is_int($delay) || $delay < 0)) {
throw new InvalidArgumentException('Delay must be either null or a positive integer.');
}
try {
foreach ($this as $offset => $layer) {
$this->resource->setimageindex($offset);
$this->resource->setimageformat($format);
if (null !== $delay) {
$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}
*/
public function current()
{
return $this->extractAt($this->offset);
}
/**
* Tries to extract layer at given offset
*
* @param integer $offset
* @return Image
* @throws RuntimeException
*/
private function extractAt($offset)
{
if (!isset($this->layers[$offset])) {
try {
$this->resource->setimageindex($offset);
$this->layers[$offset] = new Image($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}
*/
public function key()
{
return $this->offset;
}
/**
* {@inheritdoc}
*/
public function next()
{
++$this->offset;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->offset = 0;
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->offset < count($this);
}
/**
* {@inheritdoc}
*/
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}
*/
public function offsetExists($offset)
{
return is_int($offset) && $offset >= 0 && $offset < count($this);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return $this->extractAt($offset);
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $image)
{
if (!$image instanceof Image) {
throw new InvalidArgumentException('Only a Gmagick Image can be used as layer');
}
if (null === $offset) {
$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 || 0 > $offset) {
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}
*/
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);
}
}
}

View File

@@ -0,0 +1,75 @@
<?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\Color\ColorInterface;
/**
* Abstract font base class
*/
abstract class AbstractFont implements FontInterface
{
/**
* @var string
*/
protected $file;
/**
* @var integer
*/
protected $size;
/**
* @var 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 integer $size
* @param ColorInterface $color
*/
public function __construct($file, $size, ColorInterface $color)
{
$this->file = $file;
$this->size = $size;
$this->color = $color;
}
/**
* {@inheritdoc}
*/
final public function getFile()
{
return $this->file;
}
/**
* {@inheritdoc}
*/
final public function getSize()
{
return $this->size;
}
/**
* {@inheritdoc}
*/
final public function getColor()
{
return $this->color;
}
}

View File

@@ -0,0 +1,120 @@
<?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\Image\Metadata\MetadataBag;
abstract class AbstractImage implements ImageInterface
{
/**
* @var MetadataBag
*/
protected $metadata;
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function thumbnail(BoxInterface $size, $mode = ImageInterface::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED)
{
if ($mode !== ImageInterface::THUMBNAIL_INSET &&
$mode !== ImageInterface::THUMBNAIL_OUTBOUND) {
throw new InvalidArgumentException('Invalid mode specified');
}
$imageSize = $this->getSize();
$ratios = array(
$size->getWidth() / $imageSize->getWidth(),
$size->getHeight() / $imageSize->getHeight()
);
$thumbnail = $this->copy();
$thumbnail->usePalette($this->palette());
$thumbnail->strip();
// if target width is larger than image width
// AND target height is longer than image height
if ($size->contains($imageSize)) {
return $thumbnail;
}
if ($mode === ImageInterface::THUMBNAIL_INSET) {
$ratio = min($ratios);
} else {
$ratio = max($ratios);
}
if ($mode === ImageInterface::THUMBNAIL_OUTBOUND) {
if (!$imageSize->contains($size)) {
$size = new Box(
min($imageSize->getWidth(), $size->getWidth()),
min($imageSize->getHeight(), $size->getHeight())
);
} else {
$imageSize = $thumbnail->getSize()->scale($ratio);
$thumbnail->resize($imageSize, $filter);
}
$thumbnail->crop(new Point(
max(0, round(($imageSize->getWidth() - $size->getWidth()) / 2)),
max(0, round(($imageSize->getHeight() - $size->getHeight()) / 2))
), $size);
} else {
if (!$imageSize->contains($size)) {
$imageSize = $imageSize->scale($ratio);
$thumbnail->resize($imageSize, $filter);
} else {
$imageSize = $thumbnail->getSize()->scale($ratio);
$thumbnail->resize($imageSize, $filter);
}
}
return $thumbnail;
}
/**
* 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;
}
/**
* {@inheritdoc}
*/
public function metadata()
{
return $this->metadata;
}
/**
* Assures the metadata instance will be cloned, too
*/
public function __clone()
{
if ($this->metadata !== null) {
$this->metadata = clone $this->metadata;
}
}
}

View File

@@ -0,0 +1,79 @@
<?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\Metadata\DefaultMetadataReader;
use Imagine\Image\Metadata\ExifMetadataReader;
use Imagine\Image\Metadata\MetadataReaderInterface;
use Imagine\Exception\InvalidArgumentException;
abstract class AbstractImagine implements ImagineInterface
{
/** @var MetadataReaderInterface */
private $metadataReader;
/**
* @param MetadataReaderInterface $metadataReader
*
* @return ImagineInterface
*/
public function setMetadataReader(MetadataReaderInterface $metadataReader)
{
$this->metadataReader = $metadataReader;
return $this;
}
/**
* @return MetadataReaderInterface
*/
public function getMetadataReader()
{
if (null === $this->metadataReader) {
if (ExifMetadataReader::isSupported()) {
$this->metadataReader = new ExifMetadataReader();
} else {
$this->metadataReader = new DefaultMetadataReader();
}
}
return $this->metadataReader;
}
/**
* Checks a path that could be used with ImagineInterface::open and returns
* a proper string.
*
* @param string|object $path
*
* @return string
*
* @throws InvalidArgumentException In case the given path is invalid.
*/
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 (false === $handle) {
throw new InvalidArgumentException(sprintf('File %s does not exist.', $path));
}
fclose($handle);
return $path;
}
}

View File

@@ -0,0 +1,61 @@
<?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;
abstract class AbstractLayers implements LayersInterface
{
/**
* {@inheritdoc}
*/
public function add(ImageInterface $image)
{
$this[] = $image;
return $this;
}
/**
* {@inheritdoc}
*/
public function set($offset, ImageInterface $image)
{
$this[$offset] = $image;
return $this;
}
/**
* {@inheritdoc}
*/
public function remove($offset)
{
unset($this[$offset]);
return $this;
}
/**
* {@inheritdoc}
*/
public function get($offset)
{
return $this[$offset];
}
/**
* {@inheritdoc}
*/
public function has($offset)
{
return isset($this[$offset]);
}
}

View File

@@ -0,0 +1,122 @@
<?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 integer
*/
private $width;
/**
* @var integer
*/
private $height;
/**
* Constructs the Size with given width and height
*
* @param integer $width
* @param integer $height
*
* @throws InvalidArgumentException
*/
public function __construct($width, $height)
{
if ($height < 1 || $width < 1) {
throw new InvalidArgumentException(sprintf('Length of either side cannot be 0 or negative, current size is %sx%s', $width, $height));
}
$this->width = (int) $width;
$this->height = (int) $height;
}
/**
* {@inheritdoc}
*/
public function getWidth()
{
return $this->width;
}
/**
* {@inheritdoc}
*/
public function getHeight()
{
return $this->height;
}
/**
* {@inheritdoc}
*/
public function scale($ratio)
{
return new Box(round($ratio * $this->width), round($ratio * $this->height));
}
/**
* {@inheritdoc}
*/
public function increase($size)
{
return new Box((int) $size + $this->width, (int) $size + $this->height);
}
/**
* {@inheritdoc}
*/
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}
*/
public function square()
{
return $this->width * $this->height;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return sprintf('%dx%d px', $this->width, $this->height);
}
/**
* {@inheritdoc}
*/
public function widen($width)
{
return $this->scale($width / $this->width);
}
/**
* {@inheritdoc}
*/
public function heighten($height)
{
return $this->scale($height / $this->height);
}
}

View File

@@ -0,0 +1,94 @@
<?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 current image height
*
* @return integer
*/
public function getHeight();
/**
* Gets current image width
*
* @return integer
*/
public function getWidth();
/**
* Creates new BoxInterface instance with ratios applied to both sides
*
* @param float $ratio
*
* @return BoxInterface
*/
public function scale($ratio);
/**
* Creates new BoxInterface, adding given size to both sides
*
* @param integer $size
*
* @return BoxInterface
*/
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 BoxInterface $box
* @param PointInterface $start
*
* @return Boolean
*/
public function contains(BoxInterface $box, PointInterface $start = null);
/**
* Gets current box square, useful for getting total number of pixels in a
* given box
*
* @return integer
*/
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 integer $width
*
* @return BoxInterface
*/
public function widen($width);
/**
* Resizes box to given height, constraining proportions and returns the new box
*
* @param integer $height
*
* @return BoxInterface
*/
public function heighten($height);
}

View File

@@ -0,0 +1,30 @@
<?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\Palette\Color\ColorInterface;
use Imagine\Image\PointInterface;
/**
* Interface for the fill
*/
interface FillInterface
{
/**
* Gets color of the fill for the given position
*
* @param PointInterface $position
*
* @return ColorInterface
*/
public function getColor(PointInterface $position);
}

View File

@@ -0,0 +1,28 @@
<?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}
*/
public function getDistance(PointInterface $position)
{
return $position->getX();
}
}

View File

@@ -0,0 +1,95 @@
<?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\Palette\Color\ColorInterface;
use Imagine\Image\Fill\FillInterface;
use Imagine\Image\PointInterface;
/**
* Linear gradient fill
*/
abstract class Linear implements FillInterface
{
/**
* @var integer
*/
private $length;
/**
* @var ColorInterface
*/
private $start;
/**
* @var ColorInterface
*/
private $end;
/**
* Constructs a linear gradient with overall gradient length, and start and
* end shades, which default to 0 and 255 accordingly
*
* @param integer $length
* @param ColorInterface $start
* @param ColorInterface $end
*/
final public function __construct($length, ColorInterface $start, ColorInterface $end)
{
$this->length = $length;
$this->start = $start;
$this->end = $end;
}
/**
* {@inheritdoc}
*/
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 ColorInterface
*/
final public function getStart()
{
return $this->start;
}
/**
* @return ColorInterface
*/
final public function getEnd()
{
return $this->end;
}
/**
* Get the distance of the position relative to the beginning of the gradient
*
* @param PointInterface $position
*
* @return integer
*/
abstract protected function getDistance(PointInterface $position);
}

View File

@@ -0,0 +1,28 @@
<?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}
*/
public function getDistance(PointInterface $position)
{
return $position->getY();
}
}

View File

@@ -0,0 +1,51 @@
<?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\Color\ColorInterface;
/**
* The font interface
*/
interface FontInterface
{
/**
* Gets the fontfile for current font
*
* @return string
*/
public function getFile();
/**
* Gets font's integer point size
*
* @return integer
*/
public function getSize();
/**
* Gets font's color
*
* @return ColorInterface
*/
public function getColor();
/**
* Gets BoxInterface of font size on the image based on string and angle
*
* @param string $string
* @param integer $angle
*
* @return BoxInterface
*/
public function box($string, $angle = 0);
}

View File

@@ -0,0 +1,56 @@
<?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 Range
*/
private $range;
/**
* @var integer
*/
private $count;
/**
* @param Range $range
* @param integer $count
*/
public function __construct(Range $range, $count = 0)
{
$this->range = $range;
$this->count = $count;
}
/**
* @param integer $value
*/
public function add($value)
{
if ($this->range->contains($value)) {
$this->count++;
}
}
/**
* @return integer The number of elements in the bucket.
*/
public function count()
{
return $this->count;
}
}

View File

@@ -0,0 +1,56 @@
<?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 integer
*/
private $start;
/**
* @var integer
*/
private $end;
/**
* @param integer $start
* @param integer $end
*
* @throws 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 integer $value
*
* @return Boolean
*/
public function contains($value)
{
return $value >= $this->start && $value < $this->end;
}
}

View File

@@ -0,0 +1,173 @@
<?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\Draw\DrawerInterface;
use Imagine\Effects\EffectsInterface;
use Imagine\Image\Palette\PaletteInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Exception\OutOfBoundsException;
/**
* The image interface
*/
interface ImageInterface extends ManipulatorInterface
{
const RESOLUTION_PIXELSPERINCH = 'ppi';
const RESOLUTION_PIXELSPERCENTIMETER = 'ppc';
const INTERLACE_NONE = 'none';
const INTERLACE_LINE = 'line';
const INTERLACE_PLANE = 'plane';
const INTERLACE_PARTITION = 'partition';
const FILTER_UNDEFINED = 'undefined';
const FILTER_POINT = 'point';
const FILTER_BOX = 'box';
const FILTER_TRIANGLE = 'triangle';
const FILTER_HERMITE = 'hermite';
const FILTER_HANNING = 'hanning';
const FILTER_HAMMING = 'hamming';
const FILTER_BLACKMAN = 'blackman';
const FILTER_GAUSSIAN = 'gaussian';
const FILTER_QUADRATIC = 'quadratic';
const FILTER_CUBIC = 'cubic';
const FILTER_CATROM = 'catrom';
const FILTER_MITCHELL = 'mitchell';
const FILTER_LANCZOS = 'lanczos';
const FILTER_BESSEL = 'bessel';
const FILTER_SINC = 'sinc';
/**
* Returns the image content as a binary string
*
* @param string $format
* @param array $options
*
* @throws RuntimeException
*
* @return string binary
*/
public function get($format, array $options = array());
/**
* Returns the image content as a PNG binary string
*
* @throws RuntimeException
*
* @return string binary
*/
public function __toString();
/**
* Instantiates and returns a DrawerInterface instance for image drawing
*
* @return DrawerInterface
*/
public function draw();
/**
* @return EffectsInterface
*/
public function effects();
/**
* Returns current image size
*
* @return BoxInterface
*/
public function getSize();
/**
* Transforms creates a grayscale mask from current image, returns a new
* image, while keeping the existing image unmodified
*
* @return ImageInterface
*/
public function mask();
/**
* Returns array of image colors as Imagine\Image\Palette\Color\ColorInterface instances
*
* @return array
*/
public function histogram();
/**
* Returns color at specified positions of current image
*
* @param PointInterface $point
*
* @throws RuntimeException
*
* @return ColorInterface
*/
public function getColorAt(PointInterface $point);
/**
* Returns the image layers when applicable.
*
* @throws RuntimeException In case the layer can not be returned
* @throws OutOfBoundsException In case the index is not a valid value
*
* @return LayersInterface
*/
public function layers();
/**
* Enables or disables interlacing
*
* @param string $scheme
*
* @throws InvalidArgumentException When an unsupported Interface type is supplied
*
* @return ImageInterface
*/
public function interlace($scheme);
/**
* Return the current color palette
*
* @return PaletteInterface
*/
public function palette();
/**
* Set a palette for the image. Useful to change colorspace.
*
* @param PaletteInterface $palette
*
* @return ImageInterface
*
* @throws RuntimeException
*/
public function usePalette(PaletteInterface $palette);
/**
* Applies a color profile on the Image
*
* @param ProfileInterface $profile
*
* @return ImageInterface
*
* @throws RuntimeException
*/
public function profile(ProfileInterface $profile);
/**
* Returns the Image's meta data
*
* @return Metadata\MetadataInterface
*/
public function metadata();
}

View File

@@ -0,0 +1,83 @@
<?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\Color\ColorInterface;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
/**
* The imagine interface
*/
interface ImagineInterface
{
const VERSION = '0.7-dev';
/**
* Creates a new empty image with an optional background color
*
* @param BoxInterface $size
* @param ColorInterface $color
*
* @throws InvalidArgumentException
* @throws RuntimeException
*
* @return ImageInterface
*/
public function create(BoxInterface $size, ColorInterface $color = null);
/**
* Opens an existing image from $path
*
* @param string $path
*
* @throws RuntimeException
*
* @return ImageInterface
*/
public function open($path);
/**
* Loads an image from a binary $string
*
* @param string $string
*
* @throws RuntimeException
*
* @return ImageInterface
*/
public function load($string);
/**
* Loads an image from a resource $resource
*
* @param resource $resource
*
* @throws RuntimeException
*
* @return 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 integer $size
* @param ColorInterface $color
*
* @return FontInterface
*/
public function font($file, $size, ColorInterface $color);
}

View File

@@ -0,0 +1,107 @@
<?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\RuntimeException;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\OutOfBoundsException;
/**
* The layers interface
*/
interface LayersInterface extends \Iterator, \Countable, \ArrayAccess
{
/**
* Merge layers into the original objects
*
* @throws RuntimeException
*/
public function merge();
/**
* Animates layers
*
* @param string $format The output output format
* @param integer $delay The delay in milliseconds between two frames
* @param integer $loops The number of loops, 0 means infinite
*
* @return LayersInterface
*
* @throws InvalidArgumentException In case an invalid argument is provided
* @throws RuntimeException In case the driver fails to animate
*/
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.
*/
public function coalesce();
/**
* Adds an image at the end of the layers stack
*
* @param ImageInterface $image
*
* @return LayersInterface
*
* @throws RuntimeException
*/
public function add(ImageInterface $image);
/**
* Set an image at offset
*
* @param integer $offset
* @param ImageInterface $image
*
* @return LayersInterface
*
* @throws RuntimeException
* @throws InvalidArgumentException
* @throws OutOfBoundsException
*/
public function set($offset, ImageInterface $image);
/**
* Removes the image at offset
*
* @param integer $offset
*
* @return LayersInterface
*
* @throws RuntimeException
* @throws InvalidArgumentException
*/
public function remove($offset);
/**
* Returns the image at offset
*
* @param integer $offset
*
* @return ImageInterface
*
* @throws RuntimeException
* @throws InvalidArgumentException
*/
public function get($offset);
/**
* Returns true if a layer at offset is preset
*
* @param integer $offset
*
* @return Boolean
*/
public function has($offset);
}

View File

@@ -0,0 +1,181 @@
<?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\Exception\OutOfBoundsException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Fill\FillInterface;
/**
* The manipulator interface
*/
interface ManipulatorInterface
{
const THUMBNAIL_INSET = 'inset';
const THUMBNAIL_OUTBOUND = 'outbound';
/**
* Copies current source image into a new ImageInterface instance
*
* @throws RuntimeException
*
* @return static
*/
public function copy();
/**
* Crops a specified box out of the source image (modifies the source image)
* Returns cropped self
*
* @param PointInterface $start
* @param BoxInterface $size
*
* @throws OutOfBoundsException
* @throws RuntimeException
*
* @return static
*/
public function crop(PointInterface $start, BoxInterface $size);
/**
* Resizes current image and returns self
*
* @param BoxInterface $size
* @param string $filter
*
* @throws RuntimeException
*
* @return static
*/
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 integer $angle
* @param ColorInterface $background
*
* @throws RuntimeException
*
* @return static
*/
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 ImageInterface $image
* @param PointInterface $start
*
* @throws InvalidArgumentException
* @throws OutOfBoundsException
* @throws RuntimeException
*
* @return static
*/
public function paste(ImageInterface $image, PointInterface $start);
/**
* Saves the image at a specified path, the target file extension is used
* to determine file format, only jpg, jpeg, gif, png, wbmp and xbm are
* supported
*
* @param string $path
* @param array $options
*
* @throws RuntimeException
*
* @return static
*/
public function save($path = null, array $options = array());
/**
* Outputs the image content
*
* @param string $format
* @param array $options
*
* @throws RuntimeException
*
* @return static
*/
public function show($format, array $options = array());
/**
* Flips current image using horizontal axis
*
* @throws RuntimeException
*
* @return static
*/
public function flipHorizontally();
/**
* Flips current image using vertical axis
*
* @throws RuntimeException
*
* @return static
*/
public function flipVertically();
/**
* Remove all profiles and comments
*
* @throws RuntimeException
*
* @return static
*/
public function strip();
/**
* Generates a thumbnail from a current image
* Returns it as a new image, doesn't modify the current image
*
* @param BoxInterface $size
* @param string $mode
* @param string $filter The filter to use for resizing, one of ImageInterface::FILTER_*
*
* @throws RuntimeException
*
* @return static
*/
public function thumbnail(BoxInterface $size, $mode = self::THUMBNAIL_INSET, $filter = ImageInterface::FILTER_UNDEFINED);
/**
* Applies a given mask to current image's alpha channel
*
* @param ImageInterface $mask
*
* @return static
*/
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 FillInterface $fill
*
* @return static
*/
public function fill(FillInterface $fill);
}

View File

@@ -0,0 +1,101 @@
<?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;
abstract class AbstractMetadataReader implements MetadataReaderInterface
{
/**
* {@inheritdoc}
*/
public function readFile($file)
{
if (stream_is_local($file)) {
if (!is_file($file)) {
throw new InvalidArgumentException(sprintf('File %s does not exist.', $file));
}
return new MetadataBag(array_merge(array('filepath' => realpath($file), 'uri' => $file), $this->extractFromFile($file)));
}
return new MetadataBag(array_merge(array('uri' => $file), $this->extractFromFile($file)));
}
/**
* {@inheritdoc}
*/
public function readData($data)
{
return new MetadataBag($this->extractFromData($data));
}
/**
* {@inheritdoc}
*/
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 $resource
*
* @return string|null The URI f ava
*/
private function getStreamMetadata($resource)
{
$metadata = array();
if (false !== $data = @stream_get_meta_data($resource)) {
$metadata['uri'] = $data['uri'];
if (stream_is_local($resource)) {
$metadata['filepath'] = realpath($data['uri']);
}
}
return $metadata;
}
/**
* Extracts metadata from a file
*
* @param $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);
}

View File

@@ -0,0 +1,42 @@
<?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;
/**
* Default metadata reader
*/
class DefaultMetadataReader extends AbstractMetadataReader
{
/**
* {@inheritdoc}
*/
protected function extractFromFile($file)
{
return array();
}
/**
* {@inheritdoc}
*/
protected function extractFromData($data)
{
return array();
}
/**
* {@inheritdoc}
*/
protected function extractFromStream($resource)
{
return array();
}
}

View File

@@ -0,0 +1,114 @@
<?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\Exception\NotSupportedException;
/**
* Metadata driven by Exif information
*/
class ExifMetadataReader extends AbstractMetadataReader
{
public function __construct()
{
if (!self::isSupported()) {
throw new NotSupportedException('PHP exif extension is required to use the ExifMetadataReader');
}
}
public static function isSupported()
{
return function_exists('exif_read_data');
}
/**
* {@inheritdoc}
*/
protected function extractFromFile($file)
{
if (false === $data = @file_get_contents($file)) {
throw new InvalidArgumentException(sprintf('File %s is not readable.', $file));
}
return $this->doReadData($data);
}
/**
* {@inheritdoc}
*/
protected function extractFromData($data)
{
return $this->doReadData($data);
}
/**
* {@inheritdoc}
*/
protected function extractFromStream($resource)
{
if (0 < ftell($resource)) {
$metadata = stream_get_meta_data($resource);
if ($metadata['seekable']) {
rewind($resource);
}
}
return $this->doReadData(stream_get_contents($resource));
}
/**
* Extracts metadata from raw data, merges with existing metadata
*
* @param string $data
*
* @return MetadataBag
*/
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 MetadataBag
*/
private function extract($path)
{
if (false === $exifData = @exif_read_data($path, null, true, false)) {
return array();
}
$metadata = array();
$sources = array('EXIF' => 'exif', 'IFD0' => 'ifd0');
foreach ($sources as $name => $prefix) {
if (!isset($exifData[$name])) {
continue;
}
foreach ($exifData[$name] as $prop => $value) {
$metadata[$prefix.'.'.$prop] = $value;
}
}
return $metadata;
}
}

View File

@@ -0,0 +1,97 @@
<?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;
/**
* An interface for Image Metadata
*/
class MetadataBag implements \ArrayAccess, \IteratorAggregate, \Countable
{
/** @var array */
private $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}
*/
public function count()
{
return count($this->data);
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new \ArrayIterator($this->data);
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
{
return array_key_exists($offset, $this->data);
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value)
{
$this->data[$offset] = $value;
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset)
{
unset($this->data[$offset]);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* Returns metadata as an array
*
* @return array An associative array
*/
public function toArray()
{
return $this->data;
}
}

View File

@@ -0,0 +1,48 @@
<?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;
interface MetadataReaderInterface
{
/**
* Reads metadata from a file.
*
* @param $file The path to the file where to read metadata.
*
* @throws InvalidArgumentException In case the file does not exist.
*
* @return MetadataBag
*/
public function readFile($file);
/**
* Reads metadata from a binary string.
*
* @param $data The binary string to read.
*
* @return MetadataBag
*/
public function readData($data);
/**
* Reads metadata from a stream.
*
* @param $resource The stream to read.
*
* @throws InvalidArgumentException In case the resource is not valid.
*
* @return MetadataBag
*/
public function readStream($resource);
}

View File

@@ -0,0 +1,118 @@
<?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\CMYK as CMYKColor;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Image\Profile;
use Imagine\Image\ProfileInterface;
class CMYK implements PaletteInterface
{
private $parser;
private $profile;
private static $colors = array();
public function __construct()
{
$this->parser = new ColorParser();
}
/**
* {@inheritdoc}
*/
public function name()
{
return PaletteInterface::PALETTE_CMYK;
}
/**
* {@inheritdoc}
*/
public function pixelDefinition()
{
return array(
ColorInterface::COLOR_CYAN,
ColorInterface::COLOR_MAGENTA,
ColorInterface::COLOR_YELLOW,
ColorInterface::COLOR_KEYLINE,
);
}
/**
* {@inheritdoc}
*/
public function supportsAlpha()
{
return false;
}
/**
* {@inheritdoc}
*/
public function color($color, $alpha = null)
{
if (null !== $alpha) {
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 (false === array_key_exists($index, self::$colors)) {
self::$colors[$index] = new CMYKColor($this, $color);
}
return self::$colors[$index];
}
/**
* {@inheritdoc}
*/
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');
}
return $this->color(array(
min(100, $color1->getCyan() + $color2->getCyan() * $amount),
min(100, $color1->getMagenta() + $color2->getMagenta() * $amount),
min(100, $color1->getYellow() + $color2->getYellow() * $amount),
min(100, $color1->getKeyline() + $color2->getKeyline() * $amount),
));
}
/**
* {@inheritdoc}
*/
public function useProfile(ProfileInterface $profile)
{
$this->profile = $profile;
return $this;
}
/**
* {@inheritdoc}
*/
public function profile()
{
if (!$this->profile) {
$this->profile = Profile::fromPath(__DIR__ . '/../../resources/Adobe/CMYK/USWebUncoated.icc');
}
return $this->profile;
}
}

View File

@@ -0,0 +1,219 @@
<?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\Image\Palette\CMYK as CMYKPalette;
use Imagine\Exception\RuntimeException;
use Imagine\Exception\InvalidArgumentException;
final class CMYK implements ColorInterface
{
/**
* @var integer
*/
private $c;
/**
* @var integer
*/
private $m;
/**
* @var integer
*/
private $y;
/**
* @var integer
*/
private $k;
/**
*
* @var CMYK
*/
private $palette;
public function __construct(CMYKPalette $palette, array $color)
{
$this->palette = $palette;
$this->setColor($color);
}
/**
* {@inheritdoc}
*/
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
*
* @return integer
*/
public function getCyan()
{
return $this->c;
}
/**
* Returns Magenta value of the color
*
* @return integer
*/
public function getMagenta()
{
return $this->m;
}
/**
* Returns Yellow value of the color
*
* @return integer
*/
public function getYellow()
{
return $this->y;
}
/**
* Returns Key value of the color
*
* @return integer
*/
public function getKeyline()
{
return $this->k;
}
/**
* {@inheritdoc}
*/
public function getPalette()
{
return $this->palette;
}
/**
* {@inheritdoc}
*/
public function getAlpha()
{
return null;
}
/**
* {@inheritdoc}
*/
public function dissolve($alpha)
{
throw new RuntimeException('CMYK does not support dissolution');
}
/**
* {@inheritdoc}
*/
public function lighten($shade)
{
return $this->palette->color(
array(
$this->c,
$this->m,
$this->y,
max(0, $this->k - $shade),
)
);
}
/**
* {@inheritdoc}
*/
public function darken($shade)
{
return $this->palette->color(
array(
$this->c,
$this->m,
$this->y,
min(100, $this->k + $shade),
)
);
}
/**
* {@inheritdoc}
*/
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}
*/
public function isOpaque()
{
return true;
}
/**
* Returns hex representation of the color
*
* @return string
*/
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 array $color
*
* @throws 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 255 for cyan, magenta, yellow and black color indexes accordingly');
}
$colors = array_values($color);
array_walk($colors, function ($color) {
return max(0, min(100, $color));
});
list($this->c, $this->m, $this->y, $this->k) = $colors;
}
}

View File

@@ -0,0 +1,95 @@
<?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\Image\Palette\PaletteInterface;
interface ColorInterface
{
const COLOR_RED = 'red';
const COLOR_GREEN = 'green';
const COLOR_BLUE = 'blue';
const COLOR_CYAN = 'cyan';
const COLOR_MAGENTA = 'magenta';
const COLOR_YELLOW = 'yellow';
const COLOR_KEYLINE = 'keyline';
const COLOR_GRAY = 'gray';
/**
* Return the value of one of the component.
*
* @param string $component One of the ColorInterface::COLOR_* component
*
* @return Integer
*/
public function getValue($component);
/**
* Returns percentage of transparency of the color
*
* @return integer
*/
public function getAlpha();
/**
* Returns the palette attached to the current color
*
* @return PaletteInterface
*/
public function getPalette();
/**
* Returns a copy of current color, incrementing the alpha channel by the
* given amount
*
* @param integer $alpha
*
* @return ColorInterface
*/
public function dissolve($alpha);
/**
* Returns a copy of the current color, lightened by the specified number
* of shades
*
* @param integer $shade
*
* @return ColorInterface
*/
public function lighten($shade);
/**
* Returns a copy of the current color, darkened by the specified number of
* shades
*
* @param integer $shade
*
* @return ColorInterface
*/
public function darken($shade);
/**
* Returns a gray related to the current color
*
* @return ColorInterface
*/
public function grayscale();
/**
* Checks if the current color is opaque
*
* @return Boolean
*/
public function isOpaque();
}

View File

@@ -0,0 +1,164 @@
<?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\Image\Palette\Grayscale;
use Imagine\Exception\InvalidArgumentException;
final class Gray implements ColorInterface
{
/**
* @var integer
*/
private $gray;
/**
* @var integer
*/
private $alpha;
/**
*
* @var Grayscale
*/
private $palette;
public function __construct(Grayscale $palette, array $color, $alpha)
{
$this->palette = $palette;
$this->setColor($color);
$this->setAlpha($alpha);
}
/**
* {@inheritdoc}
*/
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
*
* @return integer
*/
public function getGray()
{
return $this->gray;
}
/**
* {@inheritdoc}
*/
public function getPalette()
{
return $this->palette;
}
/**
* {@inheritdoc}
*/
public function getAlpha()
{
return $this->alpha;
}
/**
* {@inheritdoc}
*/
public function dissolve($alpha)
{
return $this->palette->color(
array($this->gray), $this->alpha + $alpha
);
}
/**
* {@inheritdoc}
*/
public function lighten($shade)
{
return $this->palette->color(array(min(255, $this->gray + $shade)), $this->alpha);
}
/**
* {@inheritdoc}
*/
public function darken($shade)
{
return $this->palette->color(array(max(0, $this->gray - $shade)), $this->alpha);
}
/**
* {@inheritdoc}
*/
public function grayscale()
{
return $this;
}
/**
* {@inheritdoc}
*/
public function isOpaque()
{
return 100 === $this->alpha;
}
/**
* Returns hex representation of the color
*
* @return string
*/
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 integer $alpha
*
* @throws 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 array $color
*
* @throws 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');
}
list($this->gray) = array_values($color);
}
}

View File

@@ -0,0 +1,214 @@
<?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\Image\Palette\RGB as RGBPalette;
use Imagine\Exception\InvalidArgumentException;
final class RGB implements ColorInterface
{
/**
* @var integer
*/
private $r;
/**
* @var integer
*/
private $g;
/**
* @var integer
*/
private $b;
/**
* @var integer
*/
private $alpha;
/**
*
* @var RGBPalette
*/
private $palette;
public function __construct(RGBPalette $palette, array $color, $alpha)
{
$this->palette = $palette;
$this->setColor($color);
$this->setAlpha($alpha);
}
/**
* {@inheritdoc}
*/
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
*
* @return integer
*/
public function getRed()
{
return $this->r;
}
/**
* Returns GREEN value of the color
*
* @return integer
*/
public function getGreen()
{
return $this->g;
}
/**
* Returns BLUE value of the color
*
* @return integer
*/
public function getBlue()
{
return $this->b;
}
/**
* {@inheritdoc}
*/
public function getPalette()
{
return $this->palette;
}
/**
* {@inheritdoc}
*/
public function getAlpha()
{
return $this->alpha;
}
/**
* {@inheritdoc}
*/
public function dissolve($alpha)
{
return $this->palette->color(array($this->r, $this->g, $this->b), $this->alpha + $alpha);
}
/**
* {@inheritdoc}
*/
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}
*/
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}
*/
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}
*/
public function isOpaque()
{
return 100 === $this->alpha;
}
/**
* Returns hex representation of the color
*
* @return string
*/
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 integer $alpha
*
* @throws 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 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');
}
list($this->r, $this->g, $this->b) = array_values($color);
}
}

View File

@@ -0,0 +1,153 @@
<?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|integer $color
*
* @return array
*
* @throws InvalidArgumentException
*/
public function parseToRGB($color)
{
$color = $this->parse($color);
if (4 === count($color)) {
$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|integer $color
*
* @return array
*
* @throws InvalidArgumentException
*/
public function parseToCMYK($color)
{
$color = $this->parse($color);
if (3 === count($color)) {
$r = $color[0] / 255;
$g = $color[1] / 255;
$b = $color[2] / 255;
$k = 1 - max($r, $g, $b);
$color = array(
1 === $k ? 0 : round((1 - $r - $k) / (1- $k) * 100),
1 === $k ? 0 : round((1 - $g - $k) / (1- $k) * 100),
1 === $k ? 0 : round((1 - $b - $k) / (1- $k) * 100),
round($k * 100)
);
}
return $color;
}
/**
* Parses a color to a grayscale value
*
* @param string|array|integer $color
*
* @return array
*
* @throws InvalidArgumentException
*/
public function parseToGrayscale($color)
{
if (is_array($color) && 1 === count($color)) {
return array_values($color);
}
$color = array_unique($this->parse($color));
if (1 !== count($color)) {
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|integer $color
*
* @return array
*
* @throws InvalidArgumentException
*/
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 (3 === count($color) || 4 === count($color)) {
return 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 (0 === strpos($color, 'cmyk(')) {
$substrColor = substr($color, 5, strlen($color) - 6);
$components = array_map(function ($component) {
return 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;
}
}

View File

@@ -0,0 +1,123 @@
<?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\Gray as GrayColor;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\ProfileInterface;
use Imagine\Image\Profile;
use Imagine\Exception\RuntimeException;
class Grayscale implements PaletteInterface
{
/**
* @var ColorParser
*/
private $parser;
/**
* @var ProfileInterface
*/
private $profile;
/**
* @var array
*/
protected static $colors = array();
public function __construct()
{
$this->parser = new ColorParser();
}
/**
* {@inheritdoc}
*/
public function name()
{
return PaletteInterface::PALETTE_GRAYSCALE;
}
/**
* {@inheritdoc}
*/
public function pixelDefinition()
{
return array(ColorInterface::COLOR_GRAY);
}
/**
* {@inheritdoc}
*/
public function supportsAlpha()
{
return true;
}
/**
* {@inheritdoc}
*/
public function useProfile(ProfileInterface $profile)
{
$this->profile = $profile;
return $this;
}
/**
* {@inheritdoc}
*/
public function profile()
{
if (!$this->profile) {
$this->profile = Profile::fromPath(__DIR__ . '/../../resources/colormanagement.org/ISOcoated_v2_grey1c_bas.ICC');
}
return $this->profile;
}
/**
* {@inheritdoc}
*/
public function color($color, $alpha = null)
{
if (null === $alpha) {
$alpha = 0;
}
$color = $this->parser->parseToGrayscale($color);
$index = sprintf('#%02x%02x%02x-%d', $color[0], $color[0], $color[0], $alpha);
if (false === array_key_exists($index, static::$colors)) {
static::$colors[$index] = new GrayColor($this, $color, $alpha);
}
return static::$colors[$index];
}
/**
* {@inheritdoc}
*/
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');
}
return $this->color(
array(
(int) min(255, 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))
);
}
}

View File

@@ -0,0 +1,87 @@
<?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\ProfileInterface;
use Imagine\Image\Palette\Color\ColorInterface;
interface PaletteInterface
{
const PALETTE_GRAYSCALE = 'gray';
const PALETTE_RGB = 'rgb';
const PALETTE_CMYK = 'cmyk';
/**
* Returns a color given some values
*
* @param string|array|integer $color A color
* @param integer|null $alpha Set alpha to null to disable it
*
* @return ColorInterface
*
* @throws InvalidArgumentException In case you pass an alpha value to a
* Palette that does not support alpha
*/
public function color($color, $alpha = null);
/**
* Blend two colors given an amount
*
* @param ColorInterface $color1
* @param ColorInterface $color2
* @param float $amount The amount of color2 in color1
*
* @return ColorInterface
*/
public function blend(ColorInterface $color1, ColorInterface $color2, $amount);
/**
* Attachs an ICC profile to this Palette.
*
* (A default profile is provided by default)
*
* @param ProfileInterface $profile
*
* @return PaletteInterface
*/
public function useProfile(ProfileInterface $profile);
/**
* Returns the ICC profile attached to this Palette.
*
* @return 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 array
*/
public function pixelDefinition();
/**
* Tells if alpha channel is supported in this palette
*
* @return Boolean
*/
public function supportsAlpha();
}

View File

@@ -0,0 +1,129 @@
<?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\RGB as RGBColor;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\ProfileInterface;
use Imagine\Image\Profile;
use Imagine\Exception\RuntimeException;
class RGB implements PaletteInterface
{
/**
* @var ColorParser
*/
private $parser;
/**
* @var ProfileInterface
*/
private $profile;
/**
* @var array
*/
protected static $colors = array();
public function __construct()
{
$this->parser = new ColorParser();
}
/**
* {@inheritdoc}
*/
public function name()
{
return PaletteInterface::PALETTE_RGB;
}
/**
* {@inheritdoc}
*/
public function pixelDefinition()
{
return array(
ColorInterface::COLOR_RED,
ColorInterface::COLOR_GREEN,
ColorInterface::COLOR_BLUE,
);
}
/**
* {@inheritdoc}
*/
public function supportsAlpha()
{
return true;
}
/**
* {@inheritdoc}
*/
public function useProfile(ProfileInterface $profile)
{
$this->profile = $profile;
return $this;
}
/**
* {@inheritdoc}
*/
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}
*/
public function color($color, $alpha = null)
{
if (null === $alpha) {
$alpha = 100;
}
$color = $this->parser->parseToRGB($color);
$index = sprintf('#%02x%02x%02x-%d', $color[0], $color[1], $color[2], $alpha);
if (false === array_key_exists($index, static::$colors)) {
static::$colors[$index] = new RGBColor($this, $color, $alpha);
}
return static::$colors[$index];
}
/**
* {@inheritdoc}
*/
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');
}
return $this->color(
array(
(int) min(255, min($color1->getRed(), $color2->getRed()) + round(abs($color2->getRed() - $color1->getRed()) * $amount)),
(int) min(255, min($color1->getGreen(), $color2->getGreen()) + round(abs($color2->getGreen() - $color1->getGreen()) * $amount)),
(int) min(255, min($color1->getBlue(), $color2->getBlue()) + round(abs($color2->getBlue() - $color1->getBlue()) * $amount)),
),
(int) min(100, min($color1->getAlpha(), $color2->getAlpha()) + round(abs($color2->getAlpha() - $color1->getAlpha()) * $amount))
);
}
}

View File

@@ -0,0 +1,88 @@
<?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 integer
*/
private $x;
/**
* @var integer
*/
private $y;
/**
* Constructs a point of coordinates
*
* @param integer $x
* @param integer $y
*
* @throws 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}
*/
public function getX()
{
return $this->x;
}
/**
* {@inheritdoc}
*/
public function getY()
{
return $this->y;
}
/**
* {@inheritdoc}
*/
public function in(BoxInterface $box)
{
return $this->x < $box->getWidth() && $this->y < $box->getHeight();
}
/**
* {@inheritdoc}
*/
public function move($amount)
{
return new Point($this->x + $amount, $this->y + $amount);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return sprintf('(%d, %d)', $this->x, $this->y);
}
}

View File

@@ -0,0 +1,77 @@
<?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;
/**
* Point center
*/
final class Center implements PointInterface
{
/**
* @var BoxInterface
*/
private $box;
/**
* Constructs coordinate with size instance, it needs to be relative to
*
* @param BoxInterface $box
*/
public function __construct(BoxInterface $box)
{
$this->box = $box;
}
/**
* {@inheritdoc}
*/
public function getX()
{
return ceil($this->box->getWidth() / 2);
}
/**
* {@inheritdoc}
*/
public function getY()
{
return ceil($this->box->getHeight() / 2);
}
/**
* {@inheritdoc}
*/
public function in(BoxInterface $box)
{
return $this->getX() < $box->getWidth() && $this->getY() < $box->getHeight();
}
/**
* {@inheritdoc}
*/
public function move($amount)
{
return new OriginalPoint($this->getX() + $amount, $this->getY() + $amount);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return sprintf('(%d, %d)', $this->getX(), $this->getY());
}
}

View File

@@ -0,0 +1,56 @@
<?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 integer
*/
public function getX();
/**
* Gets points y coordinate
*
* @return integer
*/
public function getY();
/**
* Checks if current coordinate is inside a given bo
*
* @param BoxInterface $box
*
* @return Boolean
*/
public function in(BoxInterface $box);
/**
* Returns another point, moved by a given amount from current coordinates
*
* @param integer $amount
* @return ImageInterface
*/
public function move($amount);
/**
* Gets a string representation for the current point
*
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,60 @@
<?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;
class Profile implements ProfileInterface
{
private $data;
private $name;
public function __construct($name, $data)
{
$this->name = $name;
$this->data = $data;
}
/**
* {@inheritdoc}
*/
public function name()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function data()
{
return $this->data;
}
/**
* Creates a profile from a path to a file
*
* @param String $path
*
* @return Profile
*
* @throws InvalidArgumentException In case the provided path is not valid
*/
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));
}
}

View File

@@ -0,0 +1,29 @@
<?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();
}

View File

@@ -0,0 +1,404 @@
<?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\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\AbstractFont;
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
{
/**
* @var Imagick
*/
private $imagick;
/**
* @param \Imagick $imagick
*/
public function __construct(\Imagick $imagick)
{
$this->imagick = $imagick;
}
/**
* {@inheritdoc}
*/
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
{
$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(max(1, (int) $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}
*/
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
{
$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(max(1, (int) $thickness));
if ($fill) {
$chord->setFillColor($pixel);
} else {
$this->line(
new Point(round($x + $width / 2 * cos(deg2rad($start))), round($y + $height / 2 * sin(deg2rad($start)))),
new Point(round($x + $width / 2 * cos(deg2rad($end))), round($y + $height / 2 * sin(deg2rad($end)))),
$color
);
$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}
*/
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
{
$width = $size->getWidth();
$height = $size->getHeight();
try {
$pixel = $this->getColor($color);
$ellipse = new \ImagickDraw();
$ellipse->setStrokeColor($pixel);
$ellipse->setStrokeWidth(max(1, (int) $thickness));
if ($fill) {
$ellipse->setFillColor($pixel);
} else {
$ellipse->setFillColor('transparent');
}
$ellipse->ellipse(
$center->getX(),
$center->getY(),
$width / 2,
$height / 2,
0, 360
);
if (false === $this->imagick->drawImage($ellipse)) {
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}
*/
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
{
try {
$pixel = $this->getColor($color);
$line = new \ImagickDraw();
$line->setStrokeColor($pixel);
$line->setStrokeWidth(max(1, (int) $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}
*/
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
{
$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}
*/
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}
*/
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)));
}
$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(max(1, (int) $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}
*/
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());
/**
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
*
* ensure font resolution is the same as GD's hard-coded 96
*/
if (version_compare(phpversion("imagick"), "3.0.2", ">=")) {
$text->setResolution(96, 96);
$text->setFontSize($font->getSize());
} else {
$text->setFontSize((int) ($font->getSize() * (96 / 72)));
}
$text->setFillColor($pixel);
$text->setTextAntialias(true);
$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);
if ($width !== null) {
$string = $this->wrapText($string, $text, $angle, $width);
}
$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 ColorInterface $color
*
* @return string
*/
private function getColor(ColorInterface $color)
{
$pixel = new \ImagickPixel((string) $color);
$pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100);
return $pixel;
}
/**
* Internal
*
* Fits a string into box with given width
*/
private function wrapText($string, $text, $angle, $width)
{
$result = '';
$words = explode(' ', $string);
foreach ($words as $word) {
$teststring = $result . ' ' . $word;
$testbox = $this->imagick->queryFontMetrics($text, $teststring, true);
if ($testbox['textWidth'] > $width) {
$result .= ($result == '' ? '' : "\n") . $word;
} else {
$result .= ($result == '' ? '' : ' ') . $word;
}
}
return $result;
}
}

View File

@@ -0,0 +1,113 @@
<?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\Effects\EffectsInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Palette\Color\ColorInterface;
/**
* Effects implementation using the Imagick PHP extension
*/
class Effects implements EffectsInterface
{
private $imagick;
public function __construct(\Imagick $imagick)
{
$this->imagick = $imagick;
}
/**
* {@inheritdoc}
*/
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');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function negative()
{
try {
$this->imagick->negateImage(false, \Imagick::CHANNEL_ALL);
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to negate the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function grayscale()
{
try {
$this->imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE);
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to grayscale the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function colorize(ColorInterface $color)
{
try {
$this->imagick->colorizeImage((string) $color, 1);
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to colorize the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
public function sharpen()
{
try {
$this->imagick->sharpenImage(2, 1);
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to sharpen the image');
}
return $this;
}
/**
* {@inheritdoc}
*/
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;
}
}

View File

@@ -0,0 +1,68 @@
<?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\Image\AbstractFont;
use Imagine\Image\Box;
use Imagine\Image\Palette\Color\ColorInterface;
/**
* Font implementation using the Imagick PHP extension
*/
final class Font extends AbstractFont
{
/**
* @var \Imagick
*/
private $imagick;
/**
* @param \Imagick $imagick
* @param string $file
* @param integer $size
* @param ColorInterface $color
*/
public function __construct(\Imagick $imagick, $file, $size, ColorInterface $color)
{
$this->imagick = $imagick;
parent::__construct($file, $size, $color);
}
/**
* {@inheritdoc}
*/
public function box($string, $angle = 0)
{
$text = new \ImagickDraw();
$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
*/
if (version_compare(phpversion("imagick"), "3.0.2", ">=")) {
$text->setResolution(96, 96);
$text->setFontSize($this->size);
} else {
$text->setFontSize((int) ($this->size * (96 / 72)));
}
$info = $this->imagick->queryFontMetrics($text, $string);
$box = new Box($info['textWidth'], $info['textHeight']);
return $box;
}
}

View File

@@ -0,0 +1,880 @@
<?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\Exception\OutOfBoundsException;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\AbstractImage;
use Imagine\Image\Box;
use Imagine\Image\BoxInterface;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Fill\FillInterface;
use Imagine\Image\Fill\Gradient\Horizontal;
use Imagine\Image\Fill\Gradient\Linear;
use Imagine\Image\Point;
use Imagine\Image\PointInterface;
use Imagine\Image\ProfileInterface;
use Imagine\Image\ImageInterface;
use Imagine\Image\Palette\PaletteInterface;
/**
* Image implementation using the Imagick PHP extension
*/
final class Image extends AbstractImage
{
/**
* @var \Imagick
*/
private $imagick;
/**
* @var Layers
*/
private $layers;
/**
* @var PaletteInterface
*/
private $palette;
/**
* @var Boolean
*/
private static $supportsColorspaceConversion;
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 PaletteInterface $palette
* @param MetadataBag $metadata
*/
public function __construct(\Imagick $imagick, PaletteInterface $palette, MetadataBag $metadata)
{
$this->metadata = $metadata;
$this->detectColorspaceConversionSupport();
$this->imagick = $imagick;
if (static::$supportsColorspaceConversion) {
$this->setColorspace($palette);
}
$this->palette = $palette;
$this->layers = new Layers($this, $this->palette, $this->imagick);
}
/**
* Destroys allocated imagick resources
*/
public function __destruct()
{
if ($this->imagick instanceof \Imagick) {
$this->imagick->clear();
$this->imagick->destroy();
}
}
/**
* Returns the underlying \Imagick instance
*
* @return \Imagick
*/
public function getImagick()
{
return $this->imagick;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function copy()
{
try {
if (version_compare(phpversion("imagick"), "3.1.0b1", ">=") || defined("HHVM_VERSION")) {
$clone = clone $this->imagick;
} else {
$clone = $this->imagick->clone();
}
} catch (\ImagickException $e) {
throw new RuntimeException('Copy operation failed', $e->getCode(), $e);
}
return new self($clone, $this->palette, clone $this->metadata);
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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->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}
*
* @return ImageInterface
*/
public function flipHorizontally()
{
try {
$this->imagick->flopImage();
} catch (\ImagickException $e) {
throw new RuntimeException('Horizontal Flip operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function flipVertically()
{
try {
$this->imagick->flipImage();
} catch (\ImagickException $e) {
throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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}
*
* @return ImageInterface
*/
public function paste(ImageInterface $image, PointInterface $start)
{
if (!$image instanceof self) {
throw new InvalidArgumentException(sprintf('Imagick\Image can only paste() Imagick\Image instances, %s given', get_class($image)));
}
if (!$this->getSize()->contains($image->getSize(), $start)) {
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
}
try {
$this->imagick->compositeImage($image->imagick, \Imagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY());
} catch (\ImagickException $e) {
throw new RuntimeException('Paste operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
{
try {
$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;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function rotate($angle, ColorInterface $background = null)
{
$color = $background ? $background : $this->palette->color('fff');
try {
$pixel = $this->getColor($color);
$this->imagick->rotateimage($pixel, $angle);
$pixel->clear();
$pixel->destroy();
} catch (\ImagickException $e) {
throw new RuntimeException('Rotate operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function save($path = null, array $options = array())
{
$path = null === $path ? $this->imagick->getImageFilename() : $path;
if (null === $path) {
throw new RuntimeException('You can omit save path only if image has been open from a file');
}
try {
$this->prepareOutput($options, $path);
$this->imagick->writeImages($path, true);
} catch (\ImagickException $e) {
throw new RuntimeException('Save operation failed', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
public function show($format, array $options = array())
{
header('Content-type: '.$this->getMimeType($format));
echo $this->get($format, $options);
return $this;
}
/**
* {@inheritdoc}
*/
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}
*/
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['format'])) {
$this->imagick->setImageFormat($options['format']);
}
if (isset($options['animated']) && true === $options['animated']) {
$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->imagick, $options, $path);
// flatten only if image has multiple layers
if ((!isset($options['flatten']) || $options['flatten'] === true) && count($this->layers) > 1) {
$this->flatten();
}
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->get('png');
}
/**
* {@inheritdoc}
*/
public function draw()
{
return new Drawer($this->imagick);
}
/**
* {@inheritdoc}
*/
public function effects()
{
return new Effects($this->imagick);
}
/**
* {@inheritdoc}
*/
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 new Box($width, $height);
}
/**
* {@inheritdoc}
*
* @return ImageInterface
*/
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}
*/
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}
*
* @return ImageInterface
*/
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}
*/
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_map(function (\ImagickPixel $pixel) use ($image) {
return $image->pixelToColor($pixel);
},$pixels);
}
/**
* {@inheritdoc}
*/
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
*
* @return ColorInterface
*
* @throws InvalidArgumentException In case a unknown color is requested
*/
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;
$palette = $this->palette();
return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) {
if (!isset($colorMapping[$color])) {
throw new InvalidArgumentException(sprintf('Color %s is not mapped in Imagick', $color));
}
$multiplier = 255;
if ($palette->name() === PaletteInterface::PALETTE_CMYK) {
$multiplier = 100;
}
return $pixel->getColorValue($colorMapping[$color]) * $multiplier;
}, $this->palette->pixelDefinition()), $alpha);
}
/**
* {@inheritdoc}
*/
public function layers()
{
return $this->layers;
}
/**
* {@inheritdoc}
*/
public function usePalette(PaletteInterface $palette)
{
if (!isset(static::$colorspaceMapping[$palette->name()])) {
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name()));
}
if ($this->palette->name() === $palette->name()) {
return $this;
}
if (!static::$supportsColorspaceConversion) {
throw new RuntimeException('Your version of Imagick does not support colorspace conversions.');
}
try {
try {
$hasICCProfile = (Boolean) $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}
*/
public function palette()
{
return $this->palette;
}
/**
* {@inheritdoc}
*/
public function profile(ProfileInterface $profile)
{
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;
}
/**
* Internal
*
* 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);
}
}
/**
* Internal
*
* Applies options before save or output
*
* @param \Imagick $image
* @param array $options
* @param string $path
*
* @throws InvalidArgumentException
* @throws RuntimeException
*/
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);
}
$format = strtolower($format);
$options = $this->updateSaveOptions($options);
if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) {
$image->setImageCompressionQuality($options['jpeg_quality']);
}
if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') {
// first digit: compression level (default: 7)
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');
}
$compression = $options['png_compression_level'] * 10;
} else {
$compression = 70;
}
// second digit: compression filter (default: 5)
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');
}
$compression += $options['png_compression_filter'];
} else {
$compression += 5;
}
$image->setImageCompressionQuality($compression);
}
if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) {
if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) {
$image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERCENTIMETER);
} elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) {
$image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH);
} else {
throw new RuntimeException('Unsupported image unit format');
}
$filter = ImageInterface::FILTER_UNDEFINED;
if (!empty($options['resampling-filter'])) {
$filter = $options['resampling-filter'];
}
$image->setImageResolution($options['resolution-x'], $options['resolution-y']);
$image->resampleImage($options['resolution-x'], $options['resolution-y'], $this->getFilter($filter), 0);
}
}
/**
* Gets specifically formatted color string from Color instance
*
* @param 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 FillInterface $fill
*
* @return Boolean
*/
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 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();
}
/**
* Internal
*
* Get the mime type based on format.
*
* @param string $format
*
* @return string mime-type
*
* @throws RuntimeException
*/
private function getMimeType($format)
{
static $mimeTypes = array(
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'png' => 'image/png',
'wbmp' => 'image/vnd.wap.wbmp',
'xbm' => 'image/xbm',
);
if (!isset($mimeTypes[$format])) {
throw new RuntimeException(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 PaletteInterface $palette
*
* @throws InvalidArgumentException
*/
private function setColorspace(PaletteInterface $palette)
{
static $typeMapping = array(
// We use Matte variants to preserve alpha
PaletteInterface::PALETTE_CMYK => \Imagick::IMGTYPE_TRUECOLORMATTE,
PaletteInterface::PALETTE_RGB => \Imagick::IMGTYPE_TRUECOLORMATTE,
PaletteInterface::PALETTE_GRAYSCALE => \Imagick::IMGTYPE_GRAYSCALEMATTE,
);
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()]);
$this->palette = $palette;
}
/**
* Older imagemagick versions does not support colorspace conversions.
* Let's detect if it is supported.
*
* @return Boolean
*/
private function detectColorspaceConversionSupport()
{
if (null !== static::$supportsColorspaceConversion) {
return static::$supportsColorspaceConversion;
}
return static::$supportsColorspaceConversion = method_exists('Imagick', 'setColorspace');
}
/**
* Returns the filter if it's supported.
*
* @param string $filter
*
* @return string
*
* @throws InvalidArgumentException If the filter is unsupported.
*/
private function getFilter($filter = ImageInterface::FILTER_UNDEFINED)
{
static $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 (!array_key_exists($filter, $supportedFilters)) {
throw new InvalidArgumentException(sprintf(
'The resampling filter "%s" is not supported by Imagick driver.',
$filter
));
}
return $supportedFilters[$filter];
}
}

View File

@@ -0,0 +1,176 @@
<?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\Exception\NotSupportedException;
use Imagine\Image\AbstractImagine;
use Imagine\Image\BoxInterface;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Palette\CMYK;
use Imagine\Image\Palette\RGB;
use Imagine\Image\Palette\Grayscale;
/**
* Imagine implementation using the Imagick PHP extension
*/
final class Imagine extends AbstractImagine
{
/**
* @throws RuntimeException
*/
public function __construct()
{
if (!class_exists('Imagick')) {
throw new RuntimeException('Imagick not installed');
}
if (version_compare('6.2.9', $this->getVersion(new \Imagick())) > 0) {
throw new RuntimeException('ImageMagick version 6.2.9 or higher is required');
}
}
/**
* {@inheritdoc}
*/
public function open($path)
{
$path = $this->checkPath($path);
try {
$imagick = new \Imagick($path);
$image = new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readFile($path));
} catch (\Exception $e) {
throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e);
}
return $image;
}
/**
* {@inheritdoc}
*/
public function create(BoxInterface $size, ColorInterface $color = null)
{
$width = $size->getWidth();
$height = $size->getHeight();
$palette = null !== $color ? $color->getPalette() : new RGB();
$color = null !== $color ? $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);
if (version_compare('6.3.1', $this->getVersion($imagick)) < 0) {
$imagick->setImageOpacity($pixel->getColorValue(\Imagick::COLOR_ALPHA));
}
$pixel->clear();
$pixel->destroy();
return new Image($imagick, $palette, new MetadataBag());
} catch (\ImagickException $e) {
throw new RuntimeException('Could not create empty image', $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function load($string)
{
try {
$imagick = new \Imagick();
$imagick->readImageBlob($string);
$imagick->setImageMatte(true);
return new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readData($string));
} catch (\ImagickException $e) {
throw new RuntimeException('Could not load image from string', $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
*/
public function read($resource)
{
if (!is_resource($resource)) {
throw new InvalidArgumentException('Variable does not contain a stream resource');
}
try {
$imagick = new \Imagick();
$imagick->readImageFile($resource);
} catch (\ImagickException $e) {
throw new RuntimeException('Could not read image from resource', $e->getCode(), $e);
}
return new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readStream($resource));
}
/**
* {@inheritdoc}
*/
public function font($file, $size, ColorInterface $color)
{
return new Font(new \Imagick(), $file, $size, $color);
}
/**
* Returns the palette corresponding to an \Imagick resource colorspace
*
* @param \Imagick $imagick
*
* @return CMYK|Grayscale|RGB
*
* @throws NotSupportedException
*/
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();
default:
throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported');
}
}
/**
* Returns ImageMagick version
*
* @param \Imagick $imagick
*
* @return string
*/
private function getVersion(\Imagick $imagick)
{
$v = $imagick->getVersion();
list($version) = sscanf($v['versionString'], 'ImageMagick %s %04d-%02d-%02d %s %s');
return $version;
}
}

View File

@@ -0,0 +1,271 @@
<?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\Image\AbstractLayers;
use Imagine\Image\Metadata\MetadataBag;
use Imagine\Exception\RuntimeException;
use Imagine\Exception\OutOfBoundsException;
use Imagine\Exception\InvalidArgumentException;
use Imagine\Image\Palette\PaletteInterface;
class Layers extends AbstractLayers
{
/**
* @var Image
*/
private $image;
/**
* @var \Imagick
*/
private $resource;
/**
* @var integer
*/
private $offset = 0;
/**
* @var array
*/
private $layers = array();
private $palette;
public function __construct(Image $image, PaletteInterface $palette, \Imagick $resource)
{
$this->image = $image;
$this->resource = $resource;
$this->palette = $palette;
}
/**
* {@inheritdoc}
*/
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}
*/
public function animate($format, $delay, $loops)
{
if ('gif' !== strtolower($format)) {
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 (null !== $delay && (!is_int($delay) || $delay < 0)) {
throw new InvalidArgumentException('Delay must be either null or a positive integer.');
}
try {
foreach ($this as $offset => $layer) {
$this->resource->setIteratorIndex($offset);
$this->resource->setFormat($format);
if (null !== $delay) {
$layer->getImagick()->setImageDelay($delay / 10);
$layer->getImagick()->setImageTicksPerSecond(100);
}
$layer->getImagick()->setImageIterations($loops);
$this->resource->setImage($layer->getImagick());
}
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to animate layers', $e->getCode(), $e);
}
return $this;
}
/**
* {@inheritdoc}
*/
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] = new Image($coalescedResource->getImage(), $this->palette, new MetadataBag());
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to retrieve layer', $e->getCode(), $e);
}
}
}
/**
* {@inheritdoc}
*/
public function current()
{
return $this->extractAt($this->offset);
}
/**
* Tries to extract layer at given offset
*
* @param integer $offset
*
* @return Image
* @throws RuntimeException
*/
private function extractAt($offset)
{
if (!isset($this->layers[$offset])) {
try {
$this->resource->setIteratorIndex($offset);
$this->layers[$offset] = new Image($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}
*/
public function key()
{
return $this->offset;
}
/**
* {@inheritdoc}
*/
public function next()
{
++$this->offset;
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->offset = 0;
}
/**
* {@inheritdoc}
*/
public function valid()
{
return $this->offset < count($this);
}
/**
* {@inheritdoc}
*/
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}
*/
public function offsetExists($offset)
{
return is_int($offset) && $offset >= 0 && $offset < count($this);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return $this->extractAt($offset);
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $image)
{
if (!$image instanceof Image) {
throw new InvalidArgumentException('Only an Imagick Image can be used as layer');
}
if (null === $offset) {
$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 || 0 > $offset) {
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}
*/
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);
}
}
}

Some files were not shown because too many files have changed in this diff Show More