From e3b62d24d863168c22efb2164f6cc362e3618e97 Mon Sep 17 00:00:00 2001 From: RainLoop Team Date: Thu, 29 Oct 2015 23:02:58 +0300 Subject: [PATCH] Added: Incorrect image orientation --- rainloop/v/0.0.0/app/handle.php | 2 +- .../Imagine/Draw/DrawerInterface.php | 149 +++ .../Imagine/Effects/EffectsInterface.php | 80 ++ .../libraries/Imagine/Exception/Exception.php | 19 + .../Exception/InvalidArgumentException.php | 19 + .../Exception/NotSupportedException.php | 19 + .../Exception/OutOfBoundsException.php | 19 + .../Imagine/Exception/RuntimeException.php | 19 + .../Imagine/Filter/Advanced/Border.php | 98 ++ .../Imagine/Filter/Advanced/Canvas.php | 74 ++ .../Imagine/Filter/Advanced/Grayscale.php | 30 + .../Imagine/Filter/Advanced/OnPixelBased.php | 57 ++ .../Filter/Advanced/RelativeResize.php | 50 + .../Imagine/Filter/Basic/ApplyMask.php | 42 + .../Imagine/Filter/Basic/Autorotate.php | 87 ++ .../libraries/Imagine/Filter/Basic/Copy.php | 29 + .../libraries/Imagine/Filter/Basic/Crop.php | 54 ++ .../libraries/Imagine/Filter/Basic/Fill.php | 43 + .../Imagine/Filter/Basic/FlipHorizontally.php | 29 + .../Imagine/Filter/Basic/FlipVertically.php | 29 + .../libraries/Imagine/Filter/Basic/Paste.php | 53 ++ .../libraries/Imagine/Filter/Basic/Resize.php | 48 + .../libraries/Imagine/Filter/Basic/Rotate.php | 52 ++ .../libraries/Imagine/Filter/Basic/Save.php | 51 + .../libraries/Imagine/Filter/Basic/Show.php | 51 + .../libraries/Imagine/Filter/Basic/Strip.php | 29 + .../Imagine/Filter/Basic/Thumbnail.php | 59 ++ .../Imagine/Filter/Basic/WebOptimization.php | 57 ++ .../Imagine/Filter/FilterInterface.php | 30 + .../libraries/Imagine/Filter/ImagineAware.php | 54 ++ .../Imagine/Filter/Transformation.php | 240 +++++ .../0.0.0/app/libraries/Imagine/Gd/Drawer.php | 333 +++++++ .../app/libraries/Imagine/Gd/Effects.php | 109 +++ .../v/0.0.0/app/libraries/Imagine/Gd/Font.php | 41 + .../0.0.0/app/libraries/Imagine/Gd/Image.php | 735 +++++++++++++++ .../app/libraries/Imagine/Gd/Imagine.php | 195 ++++ .../0.0.0/app/libraries/Imagine/Gd/Layers.php | 144 +++ .../app/libraries/Imagine/Gmagick/Drawer.php | 356 +++++++ .../app/libraries/Imagine/Gmagick/Effects.php | 106 +++ .../app/libraries/Imagine/Gmagick/Font.php | 63 ++ .../app/libraries/Imagine/Gmagick/Image.php | 786 ++++++++++++++++ .../app/libraries/Imagine/Gmagick/Imagine.php | 167 ++++ .../app/libraries/Imagine/Gmagick/Layers.php | 272 ++++++ .../libraries/Imagine/Image/AbstractFont.php | 75 ++ .../libraries/Imagine/Image/AbstractImage.php | 120 +++ .../Imagine/Image/AbstractImagine.php | 79 ++ .../Imagine/Image/AbstractLayers.php | 61 ++ .../0.0.0/app/libraries/Imagine/Image/Box.php | 122 +++ .../libraries/Imagine/Image/BoxInterface.php | 94 ++ .../Imagine/Image/Fill/FillInterface.php | 30 + .../Image/Fill/Gradient/Horizontal.php | 28 + .../Imagine/Image/Fill/Gradient/Linear.php | 95 ++ .../Imagine/Image/Fill/Gradient/Vertical.php | 28 + .../libraries/Imagine/Image/FontInterface.php | 51 + .../Imagine/Image/Histogram/Bucket.php | 56 ++ .../Imagine/Image/Histogram/Range.php | 56 ++ .../Imagine/Image/ImageInterface.php | 173 ++++ .../Imagine/Image/ImagineInterface.php | 83 ++ .../Imagine/Image/LayersInterface.php | 107 +++ .../Imagine/Image/ManipulatorInterface.php | 181 ++++ .../Image/Metadata/AbstractMetadataReader.php | 101 ++ .../Image/Metadata/DefaultMetadataReader.php | 42 + .../Image/Metadata/ExifMetadataReader.php | 114 +++ .../Imagine/Image/Metadata/MetadataBag.php | 97 ++ .../Metadata/MetadataReaderInterface.php | 48 + .../libraries/Imagine/Image/Palette/CMYK.php | 118 +++ .../Imagine/Image/Palette/Color/CMYK.php | 219 +++++ .../Image/Palette/Color/ColorInterface.php | 95 ++ .../Imagine/Image/Palette/Color/Gray.php | 164 ++++ .../Imagine/Image/Palette/Color/RGB.php | 214 +++++ .../Imagine/Image/Palette/ColorParser.php | 153 +++ .../Imagine/Image/Palette/Grayscale.php | 123 +++ .../Image/Palette/PaletteInterface.php | 87 ++ .../libraries/Imagine/Image/Palette/RGB.php | 129 +++ .../app/libraries/Imagine/Image/Point.php | 88 ++ .../libraries/Imagine/Image/Point/Center.php | 77 ++ .../Imagine/Image/PointInterface.php | 56 ++ .../app/libraries/Imagine/Image/Profile.php | 60 ++ .../Imagine/Image/ProfileInterface.php | 29 + .../app/libraries/Imagine/Imagick/Drawer.php | 404 ++++++++ .../app/libraries/Imagine/Imagick/Effects.php | 113 +++ .../app/libraries/Imagine/Imagick/Font.php | 68 ++ .../app/libraries/Imagine/Imagick/Image.php | 880 ++++++++++++++++++ .../app/libraries/Imagine/Imagick/Imagine.php | 176 ++++ .../app/libraries/Imagine/Imagick/Layers.php | 271 ++++++ .../0.0.0/app/libraries/RainLoop/Actions.php | 137 ++- .../libraries/RainLoop/Config/Application.php | 1 + 87 files changed, 10461 insertions(+), 21 deletions(-) create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Draw/DrawerInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Effects/EffectsInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Exception/Exception.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Exception/InvalidArgumentException.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Exception/NotSupportedException.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Exception/OutOfBoundsException.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Exception/RuntimeException.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Border.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Canvas.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Grayscale.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/OnPixelBased.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/RelativeResize.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/ApplyMask.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Autorotate.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Copy.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Crop.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Fill.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipHorizontally.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipVertically.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Paste.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Resize.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Rotate.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Save.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Show.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Strip.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Thumbnail.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/WebOptimization.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/FilterInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/ImagineAware.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Filter/Transformation.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gd/Drawer.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gd/Effects.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gd/Font.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gd/Image.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gd/Imagine.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gd/Layers.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Drawer.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Effects.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Font.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Image.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Imagine.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Layers.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractFont.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImage.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImagine.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractLayers.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Box.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/BoxInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/FillInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Linear.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Vertical.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/FontInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Bucket.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Range.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/ImageInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/ImagineInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/LayersInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/ManipulatorInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataBag.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/CMYK.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/CMYK.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/Gray.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/RGB.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/ColorParser.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Grayscale.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/PaletteInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/RGB.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Point.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Point/Center.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/PointInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/Profile.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Image/ProfileInterface.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Drawer.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Effects.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Font.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Image.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Imagine.php create mode 100644 rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Layers.php diff --git a/rainloop/v/0.0.0/app/handle.php b/rainloop/v/0.0.0/app/handle.php index 464a43e3d..b529c66e6 100644 --- a/rainloop/v/0.0.0/app/handle.php +++ b/rainloop/v/0.0.0/app/handle.php @@ -22,7 +22,7 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH')) function rainLoopSplAutoloadNamespaces() { return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop') : - array('RainLoop', 'Facebook', 'GuzzleHttp', 'PHPThumb', 'SabreForRainLoop'); + array('RainLoop', 'Facebook', 'GuzzleHttp', 'PHPThumb', 'SabreForRainLoop', 'Imagine'); } /** diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Draw/DrawerInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Draw/DrawerInterface.php new file mode 100644 index 000000000..c48dff876 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Draw/DrawerInterface.php @@ -0,0 +1,149 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Effects/EffectsInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Effects/EffectsInterface.php new file mode 100644 index 000000000..3593d75da --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Effects/EffectsInterface.php @@ -0,0 +1,80 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Exception/Exception.php b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/Exception.php new file mode 100644 index 000000000..39a67afed --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/Exception.php @@ -0,0 +1,19 @@ + + * + * 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 +{ +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Exception/InvalidArgumentException.php b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/InvalidArgumentException.php new file mode 100644 index 000000000..5ac139672 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * 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 +{ +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Exception/NotSupportedException.php b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/NotSupportedException.php new file mode 100644 index 000000000..fd68ce7c0 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/NotSupportedException.php @@ -0,0 +1,19 @@ + + * + * 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 +{ +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Exception/OutOfBoundsException.php b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/OutOfBoundsException.php new file mode 100644 index 000000000..f51cc9b4f --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/OutOfBoundsException.php @@ -0,0 +1,19 @@ + + * + * 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 +{ +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Exception/RuntimeException.php b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/RuntimeException.php new file mode 100644 index 000000000..205ad85e8 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * 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 +{ +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Border.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Border.php new file mode 100644 index 000000000..47a3ceb1f --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Border.php @@ -0,0 +1,98 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Canvas.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Canvas.php new file mode 100644 index 000000000..685b8ae73 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Canvas.php @@ -0,0 +1,74 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Grayscale.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Grayscale.php new file mode 100644 index 000000000..b3a08b403 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/Grayscale.php @@ -0,0 +1,30 @@ + + * + * 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()); + }); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/OnPixelBased.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/OnPixelBased.php new file mode 100644 index 000000000..ef297eaf6 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/OnPixelBased.php @@ -0,0 +1,57 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/RelativeResize.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/RelativeResize.php new file mode 100644 index 000000000..954587ad2 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Advanced/RelativeResize.php @@ -0,0 +1,50 @@ + + * + * 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)); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/ApplyMask.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/ApplyMask.php new file mode 100644 index 000000000..2be2786f9 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/ApplyMask.php @@ -0,0 +1,42 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Autorotate.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Autorotate.php new file mode 100644 index 000000000..3ef7cbec7 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Autorotate.php @@ -0,0 +1,87 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Copy.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Copy.php new file mode 100644 index 000000000..781b23a7e --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Copy.php @@ -0,0 +1,29 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Crop.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Crop.php new file mode 100644 index 000000000..6559e2298 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Crop.php @@ -0,0 +1,54 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Fill.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Fill.php new file mode 100644 index 000000000..4be0d0f2b --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Fill.php @@ -0,0 +1,43 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipHorizontally.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipHorizontally.php new file mode 100644 index 000000000..50203748d --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipHorizontally.php @@ -0,0 +1,29 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipVertically.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipVertically.php new file mode 100644 index 000000000..684c31b3d --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/FlipVertically.php @@ -0,0 +1,29 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Paste.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Paste.php new file mode 100644 index 000000000..bd274a179 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Paste.php @@ -0,0 +1,53 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Resize.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Resize.php new file mode 100644 index 000000000..934cfe2fd --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Resize.php @@ -0,0 +1,48 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Rotate.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Rotate.php new file mode 100644 index 000000000..7b5553ff9 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Rotate.php @@ -0,0 +1,52 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Save.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Save.php new file mode 100644 index 000000000..0f76625a1 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Save.php @@ -0,0 +1,51 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Show.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Show.php new file mode 100644 index 000000000..a7d4cbc44 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Show.php @@ -0,0 +1,51 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Strip.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Strip.php new file mode 100644 index 000000000..c096a51bc --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Strip.php @@ -0,0 +1,29 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Thumbnail.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Thumbnail.php new file mode 100644 index 000000000..3176f1c39 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/Thumbnail.php @@ -0,0 +1,59 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/WebOptimization.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/WebOptimization.php new file mode 100644 index 000000000..37f5f3f1a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Basic/WebOptimization.php @@ -0,0 +1,57 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/FilterInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/FilterInterface.php new file mode 100644 index 000000000..e23ab70e0 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/FilterInterface.php @@ -0,0 +1,30 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/ImagineAware.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/ImagineAware.php new file mode 100644 index 000000000..606c5254c --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/ImagineAware.php @@ -0,0 +1,54 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Transformation.php b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Transformation.php new file mode 100644 index 000000000..26c023a0f --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Filter/Transformation.php @@ -0,0 +1,240 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Drawer.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Drawer.php new file mode 100644 index 000000000..e5d74e0d9 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Drawer.php @@ -0,0 +1,333 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Effects.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Effects.php new file mode 100644 index 000000000..d86c4f8a2 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Effects.php @@ -0,0 +1,109 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Font.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Font.php new file mode 100644 index 000000000..8bc2b04d4 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Font.php @@ -0,0 +1,41 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Image.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Image.php new file mode 100644 index 000000000..73d7b9506 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Image.php @@ -0,0 +1,735 @@ + + * + * 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]; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Imagine.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Imagine.php new file mode 100644 index 000000000..f9e950876 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Imagine.php @@ -0,0 +1,195 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Layers.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Layers.php new file mode 100644 index 000000000..93c7a2695 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gd/Layers.php @@ -0,0 +1,144 @@ + + * + * 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'); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Drawer.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Drawer.php new file mode 100644 index 000000000..de6cc9048 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Drawer.php @@ -0,0 +1,356 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Effects.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Effects.php new file mode 100644 index 000000000..3f9c5cd08 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Effects.php @@ -0,0 +1,106 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Font.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Font.php new file mode 100644 index 000000000..ad67c5647 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Font.php @@ -0,0 +1,63 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Image.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Image.php new file mode 100644 index 000000000..61ef858c1 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Image.php @@ -0,0 +1,786 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Imagine.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Imagine.php new file mode 100644 index 000000000..ce4d4ed6c --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Imagine.php @@ -0,0 +1,167 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Layers.php b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Layers.php new file mode 100644 index 000000000..d708812a1 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Gmagick/Layers.php @@ -0,0 +1,272 @@ + + * + * 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); + } + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractFont.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractFont.php new file mode 100644 index 000000000..59e9a45ba --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractFont.php @@ -0,0 +1,75 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImage.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImage.php new file mode 100644 index 000000000..5c85769fd --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImage.php @@ -0,0 +1,120 @@ + + * + * 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; + } + } + +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImagine.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImagine.php new file mode 100644 index 000000000..fcb1a3d67 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractImagine.php @@ -0,0 +1,79 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractLayers.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractLayers.php new file mode 100644 index 000000000..486a72228 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/AbstractLayers.php @@ -0,0 +1,61 @@ + + * + * 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]); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Box.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Box.php new file mode 100644 index 000000000..37b85135a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Box.php @@ -0,0 +1,122 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/BoxInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/BoxInterface.php new file mode 100644 index 000000000..4a086c39f --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/BoxInterface.php @@ -0,0 +1,94 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/FillInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/FillInterface.php new file mode 100644 index 000000000..c8d69e5d4 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/FillInterface.php @@ -0,0 +1,30 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.php new file mode 100644 index 000000000..4a4230671 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Horizontal.php @@ -0,0 +1,28 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Linear.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Linear.php new file mode 100644 index 000000000..9bf2cf6cc --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Linear.php @@ -0,0 +1,95 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Vertical.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Vertical.php new file mode 100644 index 000000000..9a26b7789 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Fill/Gradient/Vertical.php @@ -0,0 +1,28 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/FontInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/FontInterface.php new file mode 100644 index 000000000..59d4a265a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/FontInterface.php @@ -0,0 +1,51 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Bucket.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Bucket.php new file mode 100644 index 000000000..34dad5eed --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Bucket.php @@ -0,0 +1,56 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Range.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Range.php new file mode 100644 index 000000000..9dc9f9928 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Histogram/Range.php @@ -0,0 +1,56 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/ImageInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ImageInterface.php new file mode 100644 index 000000000..6cf1dd87d --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ImageInterface.php @@ -0,0 +1,173 @@ + + * + * 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(); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/ImagineInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ImagineInterface.php new file mode 100644 index 000000000..b5c942c73 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ImagineInterface.php @@ -0,0 +1,83 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/LayersInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/LayersInterface.php new file mode 100644 index 000000000..44df42369 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/LayersInterface.php @@ -0,0 +1,107 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/ManipulatorInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ManipulatorInterface.php new file mode 100644 index 000000000..392b90828 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ManipulatorInterface.php @@ -0,0 +1,181 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php new file mode 100644 index 000000000..a709655be --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/AbstractMetadataReader.php @@ -0,0 +1,101 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.php new file mode 100644 index 000000000..349366e06 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/DefaultMetadataReader.php @@ -0,0 +1,42 @@ + + * + * 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(); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php new file mode 100644 index 000000000..dd8e7d093 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/ExifMetadataReader.php @@ -0,0 +1,114 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataBag.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataBag.php new file mode 100644 index 000000000..b7e917a38 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataBag.php @@ -0,0 +1,97 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.php new file mode 100644 index 000000000..62fcc8817 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Metadata/MetadataReaderInterface.php @@ -0,0 +1,48 @@ + + * + * 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); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/CMYK.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/CMYK.php new file mode 100644 index 000000000..2beecf28a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/CMYK.php @@ -0,0 +1,118 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/CMYK.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/CMYK.php new file mode 100644 index 000000000..316543355 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/CMYK.php @@ -0,0 +1,219 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php new file mode 100644 index 000000000..8784c4ed3 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/ColorInterface.php @@ -0,0 +1,95 @@ + + * + * 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(); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/Gray.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/Gray.php new file mode 100644 index 000000000..c8096450f --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/Gray.php @@ -0,0 +1,164 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/RGB.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/RGB.php new file mode 100644 index 000000000..a0b4f0d01 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Color/RGB.php @@ -0,0 +1,214 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/ColorParser.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/ColorParser.php new file mode 100644 index 000000000..35cf4e952 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/ColorParser.php @@ -0,0 +1,153 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Grayscale.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Grayscale.php new file mode 100644 index 000000000..088b7909a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/Grayscale.php @@ -0,0 +1,123 @@ + + * + * 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)) + ); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/PaletteInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/PaletteInterface.php new file mode 100644 index 000000000..855c244c4 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/PaletteInterface.php @@ -0,0 +1,87 @@ + + * + * 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(); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/RGB.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/RGB.php new file mode 100644 index 000000000..0462ca4da --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Palette/RGB.php @@ -0,0 +1,129 @@ + + * + * 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)) + ); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Point.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Point.php new file mode 100644 index 000000000..abfc7c3a8 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Point.php @@ -0,0 +1,88 @@ + + * + * 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); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Point/Center.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Point/Center.php new file mode 100644 index 000000000..0e60349e1 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Point/Center.php @@ -0,0 +1,77 @@ + + * + * 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()); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/PointInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/PointInterface.php new file mode 100644 index 000000000..f35fa80da --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/PointInterface.php @@ -0,0 +1,56 @@ + + * + * 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(); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/Profile.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Profile.php new file mode 100644 index 000000000..fda5415a6 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/Profile.php @@ -0,0 +1,60 @@ + + * + * 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)); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Image/ProfileInterface.php b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ProfileInterface.php new file mode 100644 index 000000000..b2caa9c9a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Image/ProfileInterface.php @@ -0,0 +1,29 @@ + + * + * 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(); +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Drawer.php b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Drawer.php new file mode 100644 index 000000000..2f8636491 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Drawer.php @@ -0,0 +1,404 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Effects.php b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Effects.php new file mode 100644 index 000000000..debe32a60 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Effects.php @@ -0,0 +1,113 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Font.php b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Font.php new file mode 100644 index 000000000..3fc41dd2f --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Font.php @@ -0,0 +1,68 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Image.php b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Image.php new file mode 100644 index 000000000..c94f605d5 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Image.php @@ -0,0 +1,880 @@ + + * + * 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]; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Imagine.php b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Imagine.php new file mode 100644 index 000000000..aa6e92646 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Imagine.php @@ -0,0 +1,176 @@ + + * + * 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; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Layers.php b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Layers.php new file mode 100644 index 000000000..7809447ad --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Layers.php @@ -0,0 +1,271 @@ + + * + * 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); + } + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 305165b74..285400197 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -8324,6 +8324,83 @@ class Actions return $aValues; } + /** + * @param \Imagine\Image\AbstractImage $oImage + * @param int $iOrientation + */ + private function rotateImageByOrientation(&$oImage, $iOrientation) + { + if (0 < $iOrientation) + { + switch ($iOrientation) + { + default: + case 1: + break; + + case 2: // horizontal flip + $oImage->flipHorizontally(); + break; + + case 3: // 180 rotate left + $oImage->rotate(180); + break; + + case 4: // vertical flip + $oImage->flipVertically(); + break; + + case 5: // vertical flip + 90 rotate right + $oImage->flipVertically(); + $oImage->rotate(-90); + break; + + case 6: // 90 rotate right + $oImage->rotate(-90); + break; + + case 7: // horizontal flip + 90 rotate right + $oImage->flipHorizontally(); + $oImage->rotate(-90); + break; + + case 8: // 90 rotate left + $oImage->rotate(90); + break; + } + } + } + + /** + * @param \Imagine\Image\AbstractImage $oImage + */ + private function correctImageOrientation($oImage, $bDetectImageOrientation = true, $iThumbnailBoxSize = null) + { + $iOrientation = 1; + if ($bDetectImageOrientation && \MailSo\Base\Utils::FunctionExistsAndEnabled('exif_read_data') && + \MailSo\Base\Utils::FunctionExistsAndEnabled('gd_info')) + { + $oMetadata = $oImage->metadata(new \Imagine\Image\Metadata\ExifMetadataReader()); + $iOrientation = isset($oMetadata['ifd0.Orientation']) && + is_numeric($oMetadata['ifd0.Orientation']) ? (int) $oMetadata['ifd0.Orientation'] : 1; + } + + if ($iThumbnailBoxSize && 0 < $iThumbnailBoxSize) + { + $oImage = $oImage->thumbnail( + new \Imagine\Image\Box($iThumbnailBoxSize, $iThumbnailBoxSize), + \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND); + + $this->rotateImageByOrientation($oImage, $iOrientation); + } + else + { + $this->rotateImageByOrientation($oImage, $iOrientation); + } + + return $oImage; + } + /** * @param bool $bDownload * @param bool $bThumbnail = false @@ -8343,6 +8420,8 @@ class Actions $sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : ''; $sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : ''; + $bDetectImageOrientation = !!$this->Config()->Get('labs', 'detect_image_exif_orientation', true); + if (!empty($sFileHashIn)) { $this->verifyCacheByKey($sRawKey); @@ -8386,7 +8465,7 @@ class Actions $self = $this; return $this->MailClient()->MessageMimeStream( - function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail) { + function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail, $bDetectImageOrientation) { if ($oAccount && \is_resource($rResource)) { $sContentTypeOut = $sContentTypeIn; @@ -8412,29 +8491,47 @@ class Actions $bDone = false; if ($bThumbnail && !$bDownload) { - $sFileName = ''; - $rTempResource = \MailSo\Base\StreamWrappers\TempFile::CreateStream( - \MailSo\Base\Utils::Md5Rand($sFileNameOut), $sFileName); - - if (@\is_resource($rTempResource)) + try { + $oImagine = new \Imagine\Gd\Imagine(); + $bDone = true; + $oImage = $oImagine->load(\stream_get_contents($rResource)); - \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource)); - @\fclose($rTempResource); + $oImage = $this->correctImageOrientation($oImage, $bDetectImageOrientation, 60); - try - { - $oThumb = new \PHPThumb\GD($sFileName); - if ($oThumb) - { - $oThumb->adaptiveResize(60, 60)->show(); - } - } - catch (\Exception $oException) - { - $self->Logger()->WriteExceptionShort($oException); - } + \header('Content-Disposition: inline; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut.'_thumb60x60.png')), true); + + $oImage->show('png'); + } + catch (\Exception $oException) + { + $self->Logger()->WriteExceptionShort($oException); + } + } + + if (!$bDone && !$bDownload && $bDetectImageOrientation && + \in_array($sContentTypeOut, array('image/png', 'image/jpeg', 'image/jpg')) && + \MailSo\Base\Utils::FunctionExistsAndEnabled('gd_info')) + { + try + { + $oImagine = new \Imagine\Gd\Imagine(); + + $bDone = true; + $oImage = $oImagine->load(\stream_get_contents($rResource)); + + $oImage = $this->correctImageOrientation($oImage, $bDetectImageOrientation); + + \header('Content-Disposition: inline; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); + + $oImage->show($sContentTypeOut === 'image/png' ? 'png' : 'jpg'); + } + catch (\Exception $oException) + { + $self->Logger()->WriteExceptionShort($oException); } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 2c432085a..d8d013910 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -352,6 +352,7 @@ Enables caching in the system'), 'fast_cache_memcache_port' => array(11211), 'fast_cache_memcache_expire' => array(43200), 'use_local_proxy_for_external_images' => array(false), + 'detect_image_exif_orientation' => array(true), 'cookie_default_path' => array(''), 'startup_url' => array(''), 'emogrifier' => array(true),