mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-07 16:37:02 +03:00
Added: Incorrect image orientation
This commit is contained in:
parent
217207883f
commit
e3b62d24d8
87 changed files with 10461 additions and 21 deletions
404
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Drawer.php
Normal file
404
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Drawer.php
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Imagick;
|
||||
|
||||
use Imagine\Draw\DrawerInterface;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Point;
|
||||
use Imagine\Image\PointInterface;
|
||||
|
||||
/**
|
||||
* Drawer implementation using the Imagick PHP extension
|
||||
*/
|
||||
final class Drawer implements DrawerInterface
|
||||
{
|
||||
/**
|
||||
* @var Imagick
|
||||
*/
|
||||
private $imagick;
|
||||
|
||||
/**
|
||||
* @param \Imagick $imagick
|
||||
*/
|
||||
public function __construct(\Imagick $imagick)
|
||||
{
|
||||
$this->imagick = $imagick;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function arc(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $thickness = 1)
|
||||
{
|
||||
$x = $center->getX();
|
||||
$y = $center->getY();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$arc = new \ImagickDraw();
|
||||
|
||||
$arc->setStrokeColor($pixel);
|
||||
$arc->setStrokeWidth(max(1, (int) $thickness));
|
||||
$arc->setFillColor('transparent');
|
||||
$arc->arc($x - $width / 2, $y - $height / 2, $x + $width / 2, $y + $height / 2, $start, $end);
|
||||
|
||||
$this->imagick->drawImage($arc);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$arc->clear();
|
||||
$arc->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw arc operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chord(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
$x = $center->getX();
|
||||
$y = $center->getY();
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$chord = new \ImagickDraw();
|
||||
|
||||
$chord->setStrokeColor($pixel);
|
||||
$chord->setStrokeWidth(max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$chord->setFillColor($pixel);
|
||||
} else {
|
||||
$this->line(
|
||||
new Point(round($x + $width / 2 * cos(deg2rad($start))), round($y + $height / 2 * sin(deg2rad($start)))),
|
||||
new Point(round($x + $width / 2 * cos(deg2rad($end))), round($y + $height / 2 * sin(deg2rad($end)))),
|
||||
$color
|
||||
);
|
||||
|
||||
$chord->setFillColor('transparent');
|
||||
}
|
||||
|
||||
$chord->arc(
|
||||
$x - $width / 2,
|
||||
$y - $height / 2,
|
||||
$x + $width / 2,
|
||||
$y + $height / 2,
|
||||
$start,
|
||||
$end
|
||||
);
|
||||
|
||||
$this->imagick->drawImage($chord);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$chord->clear();
|
||||
$chord->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw chord operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function ellipse(PointInterface $center, BoxInterface $size, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$ellipse = new \ImagickDraw();
|
||||
|
||||
$ellipse->setStrokeColor($pixel);
|
||||
$ellipse->setStrokeWidth(max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$ellipse->setFillColor($pixel);
|
||||
} else {
|
||||
$ellipse->setFillColor('transparent');
|
||||
}
|
||||
|
||||
$ellipse->ellipse(
|
||||
$center->getX(),
|
||||
$center->getY(),
|
||||
$width / 2,
|
||||
$height / 2,
|
||||
0, 360
|
||||
);
|
||||
|
||||
if (false === $this->imagick->drawImage($ellipse)) {
|
||||
throw new RuntimeException('Ellipse operation failed');
|
||||
}
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$ellipse->clear();
|
||||
$ellipse->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw ellipse operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function line(PointInterface $start, PointInterface $end, ColorInterface $color, $thickness = 1)
|
||||
{
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$line = new \ImagickDraw();
|
||||
|
||||
$line->setStrokeColor($pixel);
|
||||
$line->setStrokeWidth(max(1, (int) $thickness));
|
||||
$line->setFillColor($pixel);
|
||||
$line->line(
|
||||
$start->getX(),
|
||||
$start->getY(),
|
||||
$end->getX(),
|
||||
$end->getY()
|
||||
);
|
||||
|
||||
$this->imagick->drawImage($line);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$line->clear();
|
||||
$line->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw line operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pieSlice(PointInterface $center, BoxInterface $size, $start, $end, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$x1 = round($center->getX() + $width / 2 * cos(deg2rad($start)));
|
||||
$y1 = round($center->getY() + $height / 2 * sin(deg2rad($start)));
|
||||
$x2 = round($center->getX() + $width / 2 * cos(deg2rad($end)));
|
||||
$y2 = round($center->getY() + $height / 2 * sin(deg2rad($end)));
|
||||
|
||||
if ($fill) {
|
||||
$this->chord($center, $size, $start, $end, $color, true, $thickness);
|
||||
$this->polygon(
|
||||
array(
|
||||
$center,
|
||||
new Point($x1, $y1),
|
||||
new Point($x2, $y2),
|
||||
),
|
||||
$color,
|
||||
true,
|
||||
$thickness
|
||||
);
|
||||
} else {
|
||||
$this->arc($center, $size, $start, $end, $color, $thickness);
|
||||
$this->line($center, new Point($x1, $y1), $color, $thickness);
|
||||
$this->line($center, new Point($x2, $y2), $color, $thickness);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dot(PointInterface $position, ColorInterface $color)
|
||||
{
|
||||
$x = $position->getX();
|
||||
$y = $position->getY();
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$point = new \ImagickDraw();
|
||||
|
||||
$point->setFillColor($pixel);
|
||||
$point->point($x, $y);
|
||||
|
||||
$this->imagick->drawimage($point);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$point->clear();
|
||||
$point->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw point operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function polygon(array $coordinates, ColorInterface $color, $fill = false, $thickness = 1)
|
||||
{
|
||||
if (count($coordinates) < 3) {
|
||||
throw new InvalidArgumentException(sprintf('Polygon must consist of at least 3 coordinates, %d given', count($coordinates)));
|
||||
}
|
||||
|
||||
$points = array_map(function (PointInterface $p) {
|
||||
return array('x' => $p->getX(), 'y' => $p->getY());
|
||||
}, $coordinates);
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
$polygon = new \ImagickDraw();
|
||||
|
||||
$polygon->setStrokeColor($pixel);
|
||||
$polygon->setStrokeWidth(max(1, (int) $thickness));
|
||||
|
||||
if ($fill) {
|
||||
$polygon->setFillColor($pixel);
|
||||
} else {
|
||||
$polygon->setFillColor('transparent');
|
||||
}
|
||||
|
||||
$polygon->polygon($points);
|
||||
$this->imagick->drawImage($polygon);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$polygon->clear();
|
||||
$polygon->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw polygon operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function text($string, AbstractFont $font, PointInterface $position, $angle = 0, $width = null)
|
||||
{
|
||||
try {
|
||||
$pixel = $this->getColor($font->getColor());
|
||||
$text = new \ImagickDraw();
|
||||
|
||||
$text->setFont($font->getFile());
|
||||
/**
|
||||
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
|
||||
*
|
||||
* ensure font resolution is the same as GD's hard-coded 96
|
||||
*/
|
||||
if (version_compare(phpversion("imagick"), "3.0.2", ">=")) {
|
||||
$text->setResolution(96, 96);
|
||||
$text->setFontSize($font->getSize());
|
||||
} else {
|
||||
$text->setFontSize((int) ($font->getSize() * (96 / 72)));
|
||||
}
|
||||
$text->setFillColor($pixel);
|
||||
$text->setTextAntialias(true);
|
||||
|
||||
$info = $this->imagick->queryFontMetrics($text, $string);
|
||||
$rad = deg2rad($angle);
|
||||
$cos = cos($rad);
|
||||
$sin = sin($rad);
|
||||
|
||||
// round(0 * $cos - 0 * $sin)
|
||||
$x1 = 0;
|
||||
$x2 = round($info['characterWidth'] * $cos - $info['characterHeight'] * $sin);
|
||||
// round(0 * $sin + 0 * $cos)
|
||||
$y1 = 0;
|
||||
$y2 = round($info['characterWidth'] * $sin + $info['characterHeight'] * $cos);
|
||||
|
||||
$xdiff = 0 - min($x1, $x2);
|
||||
$ydiff = 0 - min($y1, $y2);
|
||||
|
||||
if ($width !== null) {
|
||||
$string = $this->wrapText($string, $text, $angle, $width);
|
||||
}
|
||||
|
||||
$this->imagick->annotateImage(
|
||||
$text, $position->getX() + $x1 + $xdiff,
|
||||
$position->getY() + $y2 + $ydiff, $angle, $string
|
||||
);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
$text->clear();
|
||||
$text->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Draw text operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets specifically formatted color string from ColorInterface instance
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getColor(ColorInterface $color)
|
||||
{
|
||||
$pixel = new \ImagickPixel((string) $color);
|
||||
$pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100);
|
||||
|
||||
return $pixel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Fits a string into box with given width
|
||||
*/
|
||||
private function wrapText($string, $text, $angle, $width)
|
||||
{
|
||||
$result = '';
|
||||
$words = explode(' ', $string);
|
||||
foreach ($words as $word) {
|
||||
$teststring = $result . ' ' . $word;
|
||||
$testbox = $this->imagick->queryFontMetrics($text, $teststring, true);
|
||||
if ($testbox['textWidth'] > $width) {
|
||||
$result .= ($result == '' ? '' : "\n") . $word;
|
||||
} else {
|
||||
$result .= ($result == '' ? '' : ' ') . $word;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
113
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Effects.php
Normal file
113
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Effects.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Imagick;
|
||||
|
||||
use Imagine\Effects\EffectsInterface;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* Effects implementation using the Imagick PHP extension
|
||||
*/
|
||||
class Effects implements EffectsInterface
|
||||
{
|
||||
private $imagick;
|
||||
|
||||
public function __construct(\Imagick $imagick)
|
||||
{
|
||||
$this->imagick = $imagick;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function gamma($correction)
|
||||
{
|
||||
try {
|
||||
$this->imagick->gammaImage($correction, \Imagick::CHANNEL_ALL);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to apply gamma correction to the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function negative()
|
||||
{
|
||||
try {
|
||||
$this->imagick->negateImage(false, \Imagick::CHANNEL_ALL);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to negate the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function grayscale()
|
||||
{
|
||||
try {
|
||||
$this->imagick->setImageType(\Imagick::IMGTYPE_GRAYSCALE);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to grayscale the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function colorize(ColorInterface $color)
|
||||
{
|
||||
try {
|
||||
$this->imagick->colorizeImage((string) $color, 1);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to colorize the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sharpen()
|
||||
{
|
||||
try {
|
||||
$this->imagick->sharpenImage(2, 1);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to sharpen the image');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blur($sigma = 1)
|
||||
{
|
||||
try {
|
||||
$this->imagick->gaussianBlurImage(0, $sigma);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to blur the image', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
68
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Font.php
Normal file
68
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Font.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Imagick;
|
||||
|
||||
use Imagine\Image\AbstractFont;
|
||||
use Imagine\Image\Box;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
|
||||
/**
|
||||
* Font implementation using the Imagick PHP extension
|
||||
*/
|
||||
final class Font extends AbstractFont
|
||||
{
|
||||
/**
|
||||
* @var \Imagick
|
||||
*/
|
||||
private $imagick;
|
||||
|
||||
/**
|
||||
* @param \Imagick $imagick
|
||||
* @param string $file
|
||||
* @param integer $size
|
||||
* @param ColorInterface $color
|
||||
*/
|
||||
public function __construct(\Imagick $imagick, $file, $size, ColorInterface $color)
|
||||
{
|
||||
$this->imagick = $imagick;
|
||||
|
||||
parent::__construct($file, $size, $color);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function box($string, $angle = 0)
|
||||
{
|
||||
$text = new \ImagickDraw();
|
||||
|
||||
$text->setFont($this->file);
|
||||
|
||||
/**
|
||||
* @see http://www.php.net/manual/en/imagick.queryfontmetrics.php#101027
|
||||
*
|
||||
* ensure font resolution is the same as GD's hard-coded 96
|
||||
*/
|
||||
if (version_compare(phpversion("imagick"), "3.0.2", ">=")) {
|
||||
$text->setResolution(96, 96);
|
||||
$text->setFontSize($this->size);
|
||||
} else {
|
||||
$text->setFontSize((int) ($this->size * (96 / 72)));
|
||||
}
|
||||
|
||||
$info = $this->imagick->queryFontMetrics($text, $string);
|
||||
|
||||
$box = new Box($info['textWidth'], $info['textHeight']);
|
||||
|
||||
return $box;
|
||||
}
|
||||
}
|
||||
880
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Image.php
Normal file
880
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Image.php
Normal file
|
|
@ -0,0 +1,880 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Imagick;
|
||||
|
||||
use Imagine\Exception\OutOfBoundsException;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\AbstractImage;
|
||||
use Imagine\Image\Box;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Image\Fill\FillInterface;
|
||||
use Imagine\Image\Fill\Gradient\Horizontal;
|
||||
use Imagine\Image\Fill\Gradient\Linear;
|
||||
use Imagine\Image\Point;
|
||||
use Imagine\Image\PointInterface;
|
||||
use Imagine\Image\ProfileInterface;
|
||||
use Imagine\Image\ImageInterface;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
|
||||
/**
|
||||
* Image implementation using the Imagick PHP extension
|
||||
*/
|
||||
final class Image extends AbstractImage
|
||||
{
|
||||
/**
|
||||
* @var \Imagick
|
||||
*/
|
||||
private $imagick;
|
||||
/**
|
||||
* @var Layers
|
||||
*/
|
||||
private $layers;
|
||||
/**
|
||||
* @var PaletteInterface
|
||||
*/
|
||||
private $palette;
|
||||
|
||||
/**
|
||||
* @var Boolean
|
||||
*/
|
||||
private static $supportsColorspaceConversion;
|
||||
|
||||
private static $colorspaceMapping = array(
|
||||
PaletteInterface::PALETTE_CMYK => \Imagick::COLORSPACE_CMYK,
|
||||
PaletteInterface::PALETTE_RGB => \Imagick::COLORSPACE_RGB,
|
||||
PaletteInterface::PALETTE_GRAYSCALE => \Imagick::COLORSPACE_GRAY,
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs a new Image instance
|
||||
*
|
||||
* @param \Imagick $imagick
|
||||
* @param PaletteInterface $palette
|
||||
* @param MetadataBag $metadata
|
||||
*/
|
||||
public function __construct(\Imagick $imagick, PaletteInterface $palette, MetadataBag $metadata)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->detectColorspaceConversionSupport();
|
||||
$this->imagick = $imagick;
|
||||
if (static::$supportsColorspaceConversion) {
|
||||
$this->setColorspace($palette);
|
||||
}
|
||||
$this->palette = $palette;
|
||||
$this->layers = new Layers($this, $this->palette, $this->imagick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys allocated imagick resources
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->imagick instanceof \Imagick) {
|
||||
$this->imagick->clear();
|
||||
$this->imagick->destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying \Imagick instance
|
||||
*
|
||||
* @return \Imagick
|
||||
*/
|
||||
public function getImagick()
|
||||
{
|
||||
return $this->imagick;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
try {
|
||||
if (version_compare(phpversion("imagick"), "3.1.0b1", ">=") || defined("HHVM_VERSION")) {
|
||||
$clone = clone $this->imagick;
|
||||
} else {
|
||||
$clone = $this->imagick->clone();
|
||||
}
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Copy operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return new self($clone, $this->palette, clone $this->metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function crop(PointInterface $start, BoxInterface $size)
|
||||
{
|
||||
if (!$start->in($this->getSize())) {
|
||||
throw new OutOfBoundsException('Crop coordinates must start at minimum 0, 0 position from top left corner, crop height and width must be positive integers and must not exceed the current image borders');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->imagick->cropImage($size->getWidth(), $size->getHeight(), $start->getX(), $start->getY());
|
||||
// Reset canvas for gif format
|
||||
$this->imagick->setImagePage(0, 0, 0, 0);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Crop operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function flipHorizontally()
|
||||
{
|
||||
try {
|
||||
$this->imagick->flopImage();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Horizontal Flip operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function flipVertically()
|
||||
{
|
||||
try {
|
||||
$this->imagick->flipImage();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Vertical flip operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function strip()
|
||||
{
|
||||
try {
|
||||
try {
|
||||
$this->profile($this->palette->profile());
|
||||
} catch (\Exception $e) {
|
||||
// here we discard setting the profile as the previous incorporated profile
|
||||
// is corrupted, let's now strip the image
|
||||
}
|
||||
$this->imagick->stripImage();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Strip operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function paste(ImageInterface $image, PointInterface $start)
|
||||
{
|
||||
if (!$image instanceof self) {
|
||||
throw new InvalidArgumentException(sprintf('Imagick\Image can only paste() Imagick\Image instances, %s given', get_class($image)));
|
||||
}
|
||||
|
||||
if (!$this->getSize()->contains($image->getSize(), $start)) {
|
||||
throw new OutOfBoundsException('Cannot paste image of the given size at the specified position, as it moves outside of the current image\'s box');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->imagick->compositeImage($image->imagick, \Imagick::COMPOSITE_DEFAULT, $start->getX(), $start->getY());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Paste operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function resize(BoxInterface $size, $filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
try {
|
||||
$this->imagick->resizeImage($size->getWidth(), $size->getHeight(), $this->getFilter($filter), 1);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Resize operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function rotate($angle, ColorInterface $background = null)
|
||||
{
|
||||
$color = $background ? $background : $this->palette->color('fff');
|
||||
|
||||
try {
|
||||
$pixel = $this->getColor($color);
|
||||
|
||||
$this->imagick->rotateimage($pixel, $angle);
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Rotate operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function save($path = null, array $options = array())
|
||||
{
|
||||
$path = null === $path ? $this->imagick->getImageFilename() : $path;
|
||||
if (null === $path) {
|
||||
throw new RuntimeException('You can omit save path only if image has been open from a file');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->prepareOutput($options, $path);
|
||||
$this->imagick->writeImages($path, true);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Save operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function show($format, array $options = array())
|
||||
{
|
||||
header('Content-type: '.$this->getMimeType($format));
|
||||
echo $this->get($format, $options);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($format, array $options = array())
|
||||
{
|
||||
try {
|
||||
$options['format'] = $format;
|
||||
$this->prepareOutput($options);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Get operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this->imagick->getImagesBlob();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function interlace($scheme)
|
||||
{
|
||||
static $supportedInterlaceSchemes = array(
|
||||
ImageInterface::INTERLACE_NONE => \Imagick::INTERLACE_NO,
|
||||
ImageInterface::INTERLACE_LINE => \Imagick::INTERLACE_LINE,
|
||||
ImageInterface::INTERLACE_PLANE => \Imagick::INTERLACE_PLANE,
|
||||
ImageInterface::INTERLACE_PARTITION => \Imagick::INTERLACE_PARTITION,
|
||||
);
|
||||
|
||||
if (!array_key_exists($scheme, $supportedInterlaceSchemes)) {
|
||||
throw new InvalidArgumentException('Unsupported interlace type');
|
||||
}
|
||||
|
||||
$this->imagick->setInterlaceScheme($supportedInterlaceSchemes[$scheme]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @param string $path
|
||||
*/
|
||||
private function prepareOutput(array $options, $path = null)
|
||||
{
|
||||
if (isset($options['format'])) {
|
||||
$this->imagick->setImageFormat($options['format']);
|
||||
}
|
||||
|
||||
if (isset($options['animated']) && true === $options['animated']) {
|
||||
$format = isset($options['format']) ? $options['format'] : 'gif';
|
||||
$delay = isset($options['animated.delay']) ? $options['animated.delay'] : null;
|
||||
$loops = isset($options['animated.loops']) ? $options['animated.loops'] : 0;
|
||||
|
||||
$options['flatten'] = false;
|
||||
|
||||
$this->layers->animate($format, $delay, $loops);
|
||||
} else {
|
||||
$this->layers->merge();
|
||||
}
|
||||
$this->applyImageOptions($this->imagick, $options, $path);
|
||||
|
||||
// flatten only if image has multiple layers
|
||||
if ((!isset($options['flatten']) || $options['flatten'] === true) && count($this->layers) > 1) {
|
||||
$this->flatten();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->get('png');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function draw()
|
||||
{
|
||||
return new Drawer($this->imagick);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function effects()
|
||||
{
|
||||
return new Effects($this->imagick);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSize()
|
||||
{
|
||||
try {
|
||||
$i = $this->imagick->getIteratorIndex();
|
||||
$this->imagick->rewind();
|
||||
$width = $this->imagick->getImageWidth();
|
||||
$height = $this->imagick->getImageHeight();
|
||||
$this->imagick->setIteratorIndex($i);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Could not get size', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return new Box($width, $height);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function applyMask(ImageInterface $mask)
|
||||
{
|
||||
if (!$mask instanceof self) {
|
||||
throw new InvalidArgumentException('Can only apply instances of Imagine\Imagick\Image as masks');
|
||||
}
|
||||
|
||||
$size = $this->getSize();
|
||||
$maskSize = $mask->getSize();
|
||||
|
||||
if ($size != $maskSize) {
|
||||
throw new InvalidArgumentException(sprintf('The given mask doesn\'t match current image\'s size, Current mask\'s dimensions are %s, while image\'s dimensions are %s', $maskSize, $size));
|
||||
}
|
||||
|
||||
$mask = $mask->mask();
|
||||
$mask->imagick->negateImage(true);
|
||||
|
||||
try {
|
||||
// remove transparent areas of the original from the mask
|
||||
$mask->imagick->compositeImage($this->imagick, \Imagick::COMPOSITE_DSTIN, 0, 0);
|
||||
$this->imagick->compositeImage($mask->imagick, \Imagick::COMPOSITE_COPYOPACITY, 0, 0);
|
||||
|
||||
$mask->imagick->clear();
|
||||
$mask->imagick->destroy();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Apply mask operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mask()
|
||||
{
|
||||
$mask = $this->copy();
|
||||
|
||||
try {
|
||||
$mask->imagick->modulateImage(100, 0, 100);
|
||||
$mask->imagick->setImageMatte(false);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Mask operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return ImageInterface
|
||||
*/
|
||||
public function fill(FillInterface $fill)
|
||||
{
|
||||
try {
|
||||
if ($this->isLinearOpaque($fill)) {
|
||||
$this->applyFastLinear($fill);
|
||||
} else {
|
||||
$iterator = $this->imagick->getPixelIterator();
|
||||
|
||||
foreach ($iterator as $y => $pixels) {
|
||||
foreach ($pixels as $x => $pixel) {
|
||||
$color = $fill->getColor(new Point($x, $y));
|
||||
|
||||
$pixel->setColor((string) $color);
|
||||
$pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100);
|
||||
}
|
||||
|
||||
$iterator->syncIterator();
|
||||
}
|
||||
}
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Fill operation failed', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function histogram()
|
||||
{
|
||||
try {
|
||||
$pixels = $this->imagick->getImageHistogram();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Error while fetching histogram', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$image = $this;
|
||||
|
||||
return array_map(function (\ImagickPixel $pixel) use ($image) {
|
||||
return $image->pixelToColor($pixel);
|
||||
},$pixels);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getColorAt(PointInterface $point)
|
||||
{
|
||||
if (!$point->in($this->getSize())) {
|
||||
throw new RuntimeException(sprintf('Error getting color at point [%s,%s]. The point must be inside the image of size [%s,%s]', $point->getX(), $point->getY(), $this->getSize()->getWidth(), $this->getSize()->getHeight()));
|
||||
}
|
||||
|
||||
try {
|
||||
$pixel = $this->imagick->getImagePixelColor($point->getX(), $point->getY());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Error while getting image pixel color', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this->pixelToColor($pixel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a color given a pixel, depending the Palette context
|
||||
*
|
||||
* Note : this method is public for PHP 5.3 compatibility
|
||||
*
|
||||
* @param \ImagickPixel $pixel
|
||||
*
|
||||
* @return ColorInterface
|
||||
*
|
||||
* @throws InvalidArgumentException In case a unknown color is requested
|
||||
*/
|
||||
public function pixelToColor(\ImagickPixel $pixel)
|
||||
{
|
||||
static $colorMapping = array(
|
||||
ColorInterface::COLOR_RED => \Imagick::COLOR_RED,
|
||||
ColorInterface::COLOR_GREEN => \Imagick::COLOR_GREEN,
|
||||
ColorInterface::COLOR_BLUE => \Imagick::COLOR_BLUE,
|
||||
ColorInterface::COLOR_CYAN => \Imagick::COLOR_CYAN,
|
||||
ColorInterface::COLOR_MAGENTA => \Imagick::COLOR_MAGENTA,
|
||||
ColorInterface::COLOR_YELLOW => \Imagick::COLOR_YELLOW,
|
||||
ColorInterface::COLOR_KEYLINE => \Imagick::COLOR_BLACK,
|
||||
// There is no gray component in \Imagick, let's use one of the RGB comp
|
||||
ColorInterface::COLOR_GRAY => \Imagick::COLOR_RED,
|
||||
);
|
||||
|
||||
$alpha = $this->palette->supportsAlpha() ? (int) round($pixel->getColorValue(\Imagick::COLOR_ALPHA) * 100) : null;
|
||||
$palette = $this->palette();
|
||||
|
||||
return $this->palette->color(array_map(function ($color) use ($palette, $pixel, $colorMapping) {
|
||||
if (!isset($colorMapping[$color])) {
|
||||
throw new InvalidArgumentException(sprintf('Color %s is not mapped in Imagick', $color));
|
||||
}
|
||||
$multiplier = 255;
|
||||
if ($palette->name() === PaletteInterface::PALETTE_CMYK) {
|
||||
$multiplier = 100;
|
||||
}
|
||||
|
||||
return $pixel->getColorValue($colorMapping[$color]) * $multiplier;
|
||||
}, $this->palette->pixelDefinition()), $alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function layers()
|
||||
{
|
||||
return $this->layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function usePalette(PaletteInterface $palette)
|
||||
{
|
||||
if (!isset(static::$colorspaceMapping[$palette->name()])) {
|
||||
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name()));
|
||||
}
|
||||
|
||||
if ($this->palette->name() === $palette->name()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!static::$supportsColorspaceConversion) {
|
||||
throw new RuntimeException('Your version of Imagick does not support colorspace conversions.');
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
$hasICCProfile = (Boolean) $this->imagick->getImageProfile('icc');
|
||||
} catch (\ImagickException $e) {
|
||||
$hasICCProfile = false;
|
||||
}
|
||||
|
||||
if (!$hasICCProfile) {
|
||||
$this->profile($this->palette->profile());
|
||||
}
|
||||
|
||||
$this->profile($palette->profile());
|
||||
$this->setColorspace($palette);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to set colorspace', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function palette()
|
||||
{
|
||||
return $this->palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function profile(ProfileInterface $profile)
|
||||
{
|
||||
try {
|
||||
$this->imagick->profileImage('icc', $profile->data());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException(sprintf('Unable to add profile %s to image', $profile->name()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Flatten the image.
|
||||
*/
|
||||
private function flatten()
|
||||
{
|
||||
/**
|
||||
* @see https://github.com/mkoppanen/imagick/issues/45
|
||||
*/
|
||||
try {
|
||||
if (method_exists($this->imagick, 'mergeImageLayers') && defined('Imagick::LAYERMETHOD_UNDEFINED')) {
|
||||
$this->imagick = $this->imagick->mergeImageLayers(\Imagick::LAYERMETHOD_UNDEFINED);
|
||||
} elseif (method_exists($this->imagick, 'flattenImages')) {
|
||||
$this->imagick = $this->imagick->flattenImages();
|
||||
}
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Flatten operation failed', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Applies options before save or output
|
||||
*
|
||||
* @param \Imagick $image
|
||||
* @param array $options
|
||||
* @param string $path
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function applyImageOptions(\Imagick $image, array $options, $path)
|
||||
{
|
||||
if (isset($options['format'])) {
|
||||
$format = $options['format'];
|
||||
} elseif ('' !== $extension = pathinfo($path, \PATHINFO_EXTENSION)) {
|
||||
$format = $extension;
|
||||
} else {
|
||||
$format = pathinfo($image->getImageFilename(), \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
$format = strtolower($format);
|
||||
|
||||
$options = $this->updateSaveOptions($options);
|
||||
|
||||
if (isset($options['jpeg_quality']) && in_array($format, array('jpeg', 'jpg', 'pjpeg'))) {
|
||||
$image->setImageCompressionQuality($options['jpeg_quality']);
|
||||
}
|
||||
|
||||
if ((isset($options['png_compression_level']) || isset($options['png_compression_filter'])) && $format === 'png') {
|
||||
// first digit: compression level (default: 7)
|
||||
if (isset($options['png_compression_level'])) {
|
||||
if ($options['png_compression_level'] < 0 || $options['png_compression_level'] > 9) {
|
||||
throw new InvalidArgumentException('png_compression_level option should be an integer from 0 to 9');
|
||||
}
|
||||
$compression = $options['png_compression_level'] * 10;
|
||||
} else {
|
||||
$compression = 70;
|
||||
}
|
||||
|
||||
// second digit: compression filter (default: 5)
|
||||
if (isset($options['png_compression_filter'])) {
|
||||
if ($options['png_compression_filter'] < 0 || $options['png_compression_filter'] > 9) {
|
||||
throw new InvalidArgumentException('png_compression_filter option should be an integer from 0 to 9');
|
||||
}
|
||||
$compression += $options['png_compression_filter'];
|
||||
} else {
|
||||
$compression += 5;
|
||||
}
|
||||
|
||||
$image->setImageCompressionQuality($compression);
|
||||
}
|
||||
|
||||
if (isset($options['resolution-units']) && isset($options['resolution-x']) && isset($options['resolution-y'])) {
|
||||
if ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERCENTIMETER) {
|
||||
$image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERCENTIMETER);
|
||||
} elseif ($options['resolution-units'] == ImageInterface::RESOLUTION_PIXELSPERINCH) {
|
||||
$image->setImageUnits(\Imagick::RESOLUTION_PIXELSPERINCH);
|
||||
} else {
|
||||
throw new RuntimeException('Unsupported image unit format');
|
||||
}
|
||||
|
||||
$filter = ImageInterface::FILTER_UNDEFINED;
|
||||
if (!empty($options['resampling-filter'])) {
|
||||
$filter = $options['resampling-filter'];
|
||||
}
|
||||
|
||||
$image->setImageResolution($options['resolution-x'], $options['resolution-y']);
|
||||
$image->resampleImage($options['resolution-x'], $options['resolution-y'], $this->getFilter($filter), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets specifically formatted color string from Color instance
|
||||
*
|
||||
* @param ColorInterface $color
|
||||
*
|
||||
* @return \ImagickPixel
|
||||
*/
|
||||
private function getColor(ColorInterface $color)
|
||||
{
|
||||
$pixel = new \ImagickPixel((string) $color);
|
||||
$pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100);
|
||||
|
||||
return $pixel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether given $fill is linear and opaque
|
||||
*
|
||||
* @param FillInterface $fill
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private function isLinearOpaque(FillInterface $fill)
|
||||
{
|
||||
return $fill instanceof Linear && $fill->getStart()->isOpaque() && $fill->getEnd()->isOpaque();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs optimized gradient fill for non-opaque linear gradients
|
||||
*
|
||||
* @param Linear $fill
|
||||
*/
|
||||
private function applyFastLinear(Linear $fill)
|
||||
{
|
||||
$gradient = new \Imagick();
|
||||
$size = $this->getSize();
|
||||
$color = sprintf('gradient:%s-%s', (string) $fill->getStart(), (string) $fill->getEnd());
|
||||
|
||||
if ($fill instanceof Horizontal) {
|
||||
$gradient->newPseudoImage($size->getHeight(), $size->getWidth(), $color);
|
||||
$gradient->rotateImage(new \ImagickPixel(), 90);
|
||||
} else {
|
||||
$gradient->newPseudoImage($size->getWidth(), $size->getHeight(), $color);
|
||||
}
|
||||
|
||||
$this->imagick->compositeImage($gradient, \Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$gradient->clear();
|
||||
$gradient->destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal
|
||||
*
|
||||
* Get the mime type based on format.
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return string mime-type
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function getMimeType($format)
|
||||
{
|
||||
static $mimeTypes = array(
|
||||
'jpeg' => 'image/jpeg',
|
||||
'jpg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'png' => 'image/png',
|
||||
'wbmp' => 'image/vnd.wap.wbmp',
|
||||
'xbm' => 'image/xbm',
|
||||
);
|
||||
|
||||
if (!isset($mimeTypes[$format])) {
|
||||
throw new RuntimeException(sprintf('Unsupported format given. Only %s are supported, %s given', implode(", ", array_keys($mimeTypes)), $format));
|
||||
}
|
||||
|
||||
return $mimeTypes[$format];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets colorspace and image type, assigns the palette.
|
||||
*
|
||||
* @param PaletteInterface $palette
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function setColorspace(PaletteInterface $palette)
|
||||
{
|
||||
static $typeMapping = array(
|
||||
// We use Matte variants to preserve alpha
|
||||
PaletteInterface::PALETTE_CMYK => \Imagick::IMGTYPE_TRUECOLORMATTE,
|
||||
PaletteInterface::PALETTE_RGB => \Imagick::IMGTYPE_TRUECOLORMATTE,
|
||||
PaletteInterface::PALETTE_GRAYSCALE => \Imagick::IMGTYPE_GRAYSCALEMATTE,
|
||||
);
|
||||
|
||||
if (!isset(static::$colorspaceMapping[$palette->name()])) {
|
||||
throw new InvalidArgumentException(sprintf('The palette %s is not supported by Imagick driver', $palette->name()));
|
||||
}
|
||||
|
||||
$this->imagick->setType($typeMapping[$palette->name()]);
|
||||
$this->imagick->setColorspace(static::$colorspaceMapping[$palette->name()]);
|
||||
$this->palette = $palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* Older imagemagick versions does not support colorspace conversions.
|
||||
* Let's detect if it is supported.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
private function detectColorspaceConversionSupport()
|
||||
{
|
||||
if (null !== static::$supportsColorspaceConversion) {
|
||||
return static::$supportsColorspaceConversion;
|
||||
}
|
||||
|
||||
return static::$supportsColorspaceConversion = method_exists('Imagick', 'setColorspace');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the filter if it's supported.
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws InvalidArgumentException If the filter is unsupported.
|
||||
*/
|
||||
private function getFilter($filter = ImageInterface::FILTER_UNDEFINED)
|
||||
{
|
||||
static $supportedFilters = array(
|
||||
ImageInterface::FILTER_UNDEFINED => \Imagick::FILTER_UNDEFINED,
|
||||
ImageInterface::FILTER_BESSEL => \Imagick::FILTER_BESSEL,
|
||||
ImageInterface::FILTER_BLACKMAN => \Imagick::FILTER_BLACKMAN,
|
||||
ImageInterface::FILTER_BOX => \Imagick::FILTER_BOX,
|
||||
ImageInterface::FILTER_CATROM => \Imagick::FILTER_CATROM,
|
||||
ImageInterface::FILTER_CUBIC => \Imagick::FILTER_CUBIC,
|
||||
ImageInterface::FILTER_GAUSSIAN => \Imagick::FILTER_GAUSSIAN,
|
||||
ImageInterface::FILTER_HANNING => \Imagick::FILTER_HANNING,
|
||||
ImageInterface::FILTER_HAMMING => \Imagick::FILTER_HAMMING,
|
||||
ImageInterface::FILTER_HERMITE => \Imagick::FILTER_HERMITE,
|
||||
ImageInterface::FILTER_LANCZOS => \Imagick::FILTER_LANCZOS,
|
||||
ImageInterface::FILTER_MITCHELL => \Imagick::FILTER_MITCHELL,
|
||||
ImageInterface::FILTER_POINT => \Imagick::FILTER_POINT,
|
||||
ImageInterface::FILTER_QUADRATIC => \Imagick::FILTER_QUADRATIC,
|
||||
ImageInterface::FILTER_SINC => \Imagick::FILTER_SINC,
|
||||
ImageInterface::FILTER_TRIANGLE => \Imagick::FILTER_TRIANGLE
|
||||
);
|
||||
|
||||
if (!array_key_exists($filter, $supportedFilters)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'The resampling filter "%s" is not supported by Imagick driver.',
|
||||
$filter
|
||||
));
|
||||
}
|
||||
|
||||
return $supportedFilters[$filter];
|
||||
}
|
||||
}
|
||||
176
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Imagine.php
Normal file
176
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Imagine.php
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Imagick;
|
||||
|
||||
use Imagine\Exception\NotSupportedException;
|
||||
use Imagine\Image\AbstractImagine;
|
||||
use Imagine\Image\BoxInterface;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Image\Palette\Color\ColorInterface;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Image\Palette\CMYK;
|
||||
use Imagine\Image\Palette\RGB;
|
||||
use Imagine\Image\Palette\Grayscale;
|
||||
|
||||
/**
|
||||
* Imagine implementation using the Imagick PHP extension
|
||||
*/
|
||||
final class Imagine extends AbstractImagine
|
||||
{
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if (!class_exists('Imagick')) {
|
||||
throw new RuntimeException('Imagick not installed');
|
||||
}
|
||||
|
||||
if (version_compare('6.2.9', $this->getVersion(new \Imagick())) > 0) {
|
||||
throw new RuntimeException('ImageMagick version 6.2.9 or higher is required');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open($path)
|
||||
{
|
||||
$path = $this->checkPath($path);
|
||||
|
||||
try {
|
||||
$imagick = new \Imagick($path);
|
||||
$image = new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readFile($path));
|
||||
} catch (\Exception $e) {
|
||||
throw new RuntimeException(sprintf('Unable to open image %s', $path), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(BoxInterface $size, ColorInterface $color = null)
|
||||
{
|
||||
$width = $size->getWidth();
|
||||
$height = $size->getHeight();
|
||||
|
||||
$palette = null !== $color ? $color->getPalette() : new RGB();
|
||||
$color = null !== $color ? $color : $palette->color('fff');
|
||||
|
||||
try {
|
||||
$pixel = new \ImagickPixel((string) $color);
|
||||
$pixel->setColorValue(\Imagick::COLOR_ALPHA, $color->getAlpha() / 100);
|
||||
|
||||
$imagick = new \Imagick();
|
||||
$imagick->newImage($width, $height, $pixel);
|
||||
$imagick->setImageMatte(true);
|
||||
$imagick->setImageBackgroundColor($pixel);
|
||||
|
||||
if (version_compare('6.3.1', $this->getVersion($imagick)) < 0) {
|
||||
$imagick->setImageOpacity($pixel->getColorValue(\Imagick::COLOR_ALPHA));
|
||||
}
|
||||
|
||||
$pixel->clear();
|
||||
$pixel->destroy();
|
||||
|
||||
return new Image($imagick, $palette, new MetadataBag());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Could not create empty image', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($string)
|
||||
{
|
||||
try {
|
||||
$imagick = new \Imagick();
|
||||
|
||||
$imagick->readImageBlob($string);
|
||||
$imagick->setImageMatte(true);
|
||||
|
||||
return new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readData($string));
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Could not load image from string', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read($resource)
|
||||
{
|
||||
if (!is_resource($resource)) {
|
||||
throw new InvalidArgumentException('Variable does not contain a stream resource');
|
||||
}
|
||||
|
||||
try {
|
||||
$imagick = new \Imagick();
|
||||
$imagick->readImageFile($resource);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Could not read image from resource', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return new Image($imagick, $this->createPalette($imagick), $this->getMetadataReader()->readStream($resource));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function font($file, $size, ColorInterface $color)
|
||||
{
|
||||
return new Font(new \Imagick(), $file, $size, $color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the palette corresponding to an \Imagick resource colorspace
|
||||
*
|
||||
* @param \Imagick $imagick
|
||||
*
|
||||
* @return CMYK|Grayscale|RGB
|
||||
*
|
||||
* @throws NotSupportedException
|
||||
*/
|
||||
private function createPalette(\Imagick $imagick)
|
||||
{
|
||||
switch ($imagick->getImageColorspace()) {
|
||||
case \Imagick::COLORSPACE_RGB:
|
||||
case \Imagick::COLORSPACE_SRGB:
|
||||
return new RGB();
|
||||
case \Imagick::COLORSPACE_CMYK:
|
||||
return new CMYK();
|
||||
case \Imagick::COLORSPACE_GRAY:
|
||||
return new Grayscale();
|
||||
default:
|
||||
throw new NotSupportedException('Only RGB and CMYK colorspace are currently supported');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns ImageMagick version
|
||||
*
|
||||
* @param \Imagick $imagick
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getVersion(\Imagick $imagick)
|
||||
{
|
||||
$v = $imagick->getVersion();
|
||||
list($version) = sscanf($v['versionString'], 'ImageMagick %s %04d-%02d-%02d %s %s');
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
271
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Layers.php
Normal file
271
rainloop/v/0.0.0/app/libraries/Imagine/Imagick/Layers.php
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Imagine package.
|
||||
*
|
||||
* (c) Bulat Shakirzyanov <mallluhuct@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Imagine\Imagick;
|
||||
|
||||
use Imagine\Image\AbstractLayers;
|
||||
use Imagine\Image\Metadata\MetadataBag;
|
||||
use Imagine\Exception\RuntimeException;
|
||||
use Imagine\Exception\OutOfBoundsException;
|
||||
use Imagine\Exception\InvalidArgumentException;
|
||||
use Imagine\Image\Palette\PaletteInterface;
|
||||
|
||||
class Layers extends AbstractLayers
|
||||
{
|
||||
/**
|
||||
* @var Image
|
||||
*/
|
||||
private $image;
|
||||
/**
|
||||
* @var \Imagick
|
||||
*/
|
||||
private $resource;
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $offset = 0;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $layers = array();
|
||||
|
||||
private $palette;
|
||||
|
||||
public function __construct(Image $image, PaletteInterface $palette, \Imagick $resource)
|
||||
{
|
||||
$this->image = $image;
|
||||
$this->resource = $resource;
|
||||
$this->palette = $palette;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function merge()
|
||||
{
|
||||
foreach ($this->layers as $offset => $image) {
|
||||
try {
|
||||
$this->resource->setIteratorIndex($offset);
|
||||
$this->resource->setImage($image->getImagick());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to substitute layer', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function animate($format, $delay, $loops)
|
||||
{
|
||||
if ('gif' !== strtolower($format)) {
|
||||
throw new InvalidArgumentException('Animated picture is currently only supported on gif');
|
||||
}
|
||||
|
||||
if (!is_int($loops) || $loops < 0) {
|
||||
throw new InvalidArgumentException('Loops must be a positive integer.');
|
||||
}
|
||||
|
||||
if (null !== $delay && (!is_int($delay) || $delay < 0)) {
|
||||
throw new InvalidArgumentException('Delay must be either null or a positive integer.');
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($this as $offset => $layer) {
|
||||
$this->resource->setIteratorIndex($offset);
|
||||
$this->resource->setFormat($format);
|
||||
|
||||
if (null !== $delay) {
|
||||
$layer->getImagick()->setImageDelay($delay / 10);
|
||||
$layer->getImagick()->setImageTicksPerSecond(100);
|
||||
}
|
||||
$layer->getImagick()->setImageIterations($loops);
|
||||
|
||||
$this->resource->setImage($layer->getImagick());
|
||||
}
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to animate layers', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function coalesce()
|
||||
{
|
||||
try {
|
||||
$coalescedResource = $this->resource->coalesceImages();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to coalesce layers', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$count = $coalescedResource->getNumberImages();
|
||||
for ($offset = 0; $offset < $count; $offset++) {
|
||||
try {
|
||||
$coalescedResource->setIteratorIndex($offset);
|
||||
$this->layers[$offset] = new Image($coalescedResource->getImage(), $this->palette, new MetadataBag());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to retrieve layer', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->extractAt($this->offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to extract layer at given offset
|
||||
*
|
||||
* @param integer $offset
|
||||
*
|
||||
* @return Image
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function extractAt($offset)
|
||||
{
|
||||
if (!isset($this->layers[$offset])) {
|
||||
try {
|
||||
$this->resource->setIteratorIndex($offset);
|
||||
$this->layers[$offset] = new Image($this->resource->getImage(), $this->palette, new MetadataBag());
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException(sprintf('Failed to extract layer %d', $offset), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->layers[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function key()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function next()
|
||||
{
|
||||
++$this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rewind()
|
||||
{
|
||||
$this->offset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->offset < count($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
try {
|
||||
return $this->resource->getNumberImages();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Failed to count the number of layers', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return is_int($offset) && $offset >= 0 && $offset < count($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->extractAt($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetSet($offset, $image)
|
||||
{
|
||||
if (!$image instanceof Image) {
|
||||
throw new InvalidArgumentException('Only an Imagick Image can be used as layer');
|
||||
}
|
||||
|
||||
if (null === $offset) {
|
||||
$offset = count($this) - 1;
|
||||
} else {
|
||||
if (!is_int($offset)) {
|
||||
throw new InvalidArgumentException('Invalid offset for layer, it must be an integer');
|
||||
}
|
||||
|
||||
if (count($this) < $offset || 0 > $offset) {
|
||||
throw new OutOfBoundsException(sprintf('Invalid offset for layer, it must be a value between 0 and %d, %d given', count($this), $offset));
|
||||
}
|
||||
|
||||
if (isset($this[$offset])) {
|
||||
unset($this[$offset]);
|
||||
$offset = $offset - 1;
|
||||
}
|
||||
}
|
||||
|
||||
$frame = $image->getImagick();
|
||||
|
||||
try {
|
||||
if (count($this) > 0) {
|
||||
$this->resource->setIteratorIndex($offset);
|
||||
}
|
||||
$this->resource->addImage($frame);
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Unable to set the layer', $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$this->layers = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
try {
|
||||
$this->extractAt($offset);
|
||||
} catch (RuntimeException $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->resource->setIteratorIndex($offset);
|
||||
$this->resource->removeImage();
|
||||
} catch (\ImagickException $e) {
|
||||
throw new RuntimeException('Unable to remove layer', $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue