Driver/Imagick/Image.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php

/*
 * This File is part of the Thapp\Image\Driver\Imagick package
 *
 * (c) iwyg <mail@thomas-appel.com>
 *
 * For full copyright and license information, please refer to the LICENSE file
 * that was distributed with this package.
 */

namespace Thapp\Image\Driver\Imagick;

use Imagick;
use ImagickPixel;
use ImagickException;
use Thapp\Image\Geometry\Size;
use Thapp\Image\Geometry\Point;
use Thapp\Image\Geometry\SizeInterface;
use Thapp\Image\Geometry\PointInterface;
use Thapp\Image\Geometry\GravityInterface;
use Thapp\Image\Driver\AbstractImage;
use Thapp\Image\Filter\FilterInterface;
use Thapp\Image\Filter\ImagickFilter;
use Thapp\Image\Color\ColorInterface;
use Thapp\Image\Color\Palette\PaletteInterface;
use Thapp\Image\Color\Palette\RgbPaletteInterface;
use Thapp\Image\Color\Palette\CmykPaletteInterface;
use Thapp\Image\Color\Palette\GrayscalePaletteInterface;
use Thapp\Image\Info\MetaData;
use Thapp\Image\Info\MetaDataInterface;

/**
 * @class Image
 *
 * @package Thapp\Image\Driver\Imagick
 * @version $Id$
 * @author iwyg <mail@thomas-appel.com>
 */
class Image extends AbstractImage
{
    private $imagick;
    private static $orientMap = [
        Imagick::ORIENTATION_UNDEFINED   => self::ORIENT_UNDEFINED,
        Imagick::ORIENTATION_TOPLEFT     => self::ORIENT_TOPLEFT,
        Imagick::ORIENTATION_TOPRIGHT    => self::ORIENT_TOPRIGHT,
        Imagick::ORIENTATION_BOTTOMRIGHT => self::ORIENT_BOTTOMRIGHT,
        Imagick::ORIENTATION_BOTTOMLEFT  => self::ORIENT_BOTTOMLEFT,
        Imagick::ORIENTATION_LEFTTOP     => self::ORIENT_LEFTTOP,
        Imagick::ORIENTATION_RIGHTTOP    => self::ORIENT_RIGHTTOP,
        Imagick::ORIENTATION_RIGHTBOTTOM => self::ORIENT_RIGHTBOTTOM,
        Imagick::ORIENTATION_LEFTBOTTOM  => self::ORIENT_LEFTBOTTOM
    ];

    private static $colorMap = [
        ColorInterface::CHANNEL_RED     => Imagick::COLOR_RED,
        ColorInterface::CHANNEL_GREEN   => Imagick::COLOR_GREEN,
        ColorInterface::CHANNEL_BLUE    => Imagick::COLOR_BLUE,
        ColorInterface::CHANNEL_ALPHA   => Imagick::COLOR_ALPHA,
        ColorInterface::CHANNEL_CYAN    => Imagick::COLOR_CYAN,
        ColorInterface::CHANNEL_MAGENTA => Imagick::COLOR_MAGENTA,
        ColorInterface::CHANNEL_YELLOW  => Imagick::COLOR_YELLOW,
        ColorInterface::CHANNEL_KEY     => Imagick::COLOR_BLACK,
        ColorInterface::CHANNEL_GRAY    => Imagick::COLOR_RED
    ];

    /**
     * Constructor.
     *
     * @param Imagick $imagick
     *
     * @return void
     */
    public function __construct(Imagick $imagick, PaletteInterface $palette, MetaDataInterface $meta = null)
    {
        $this->imagick = $imagick;
        $this->palette  = $palette;
        $this->meta  = $meta ?: new MetaData([]);
        $this->frames  = new Frames($this);
    }

    /**
     * Returns a copy of this image.
     *
     * @return void
     */
    public function __clone()
    {
        $this->imagick  = $this->cloneImagick();
        $this->frames   = new Frames($this);
        $this->meta     = clone $this->meta;
    }

    /**
     * {@inheritdoc}
     */
    public function copy()
    {
        return clone $this;
    }

    /**
     * {@inheritdoc}
     */
    public function destroy()
    {
        if (null === $this->imagick) {
            return false;
        }

        $this->imagick->clear();
        $this->imagick->destroy();
        $this->imagick = null;

        $this->frames = null;

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function getColorAt(PointInterface $pixel)
    {
        if (!$this->getSize()->has($pixel)) {
            throw new \OutOfBoundsException('Sample is outside of image.');
        }

        return $this->colorFromPixel($this->imagick->getImagePixelColor($pixel->getX(), $pixel->getY()));
    }

    /**
     * {@inheritdoc}
     */
    public function hasFrames()
    {
        return 1 < $this->imagick->getNumberImages();
    }

    /**
     * {@inheritdoc}
     */
    public function getWidth()
    {
        return $this->imagick->current()->getImageWidth();
    }

    /**
     * {@inheritdoc}
     */
    public function getHeight()
    {
        return $this->imagick->current()->getImageHeight();
    }

    /**
     * {@inheritdoc}
     */
    public function getFormat()
    {
        if (null === $this->format) {
            $this->format = $this->imagick->getImageFormat();
        }

        return parent::getFormat();
    }

    /**
     * {@inheritdoc}
     */
    public function getOrientation()
    {
        if (!$orient = $this->meta->get($key = 'ifd0.Orentation')) {
            $this->meta->set($key, $orient = static::$orientMap[$this->imagick->getImageOrientation()]);
        }

        return $this->mapOrientation($orient);
    }

    /**
     * {@inheritdoc}
     */
    public function newImage($format = null, ColorInterface $color = null)
    {
        $imagick = new Imagick;
        $imagick->newImage($this->getWidth(), $this->getHeight(), $this->imagick->getImageBackgroundColor());

        if (null === $format && $fmt = $this->getFormat()) {
            $format = $fmt;
        }

        if (null !== $format) {
            $imagick->setImageFormat($fmt);
        }

        return new static($imagick, clone $this->palette);
    }

    /**
     * Get the current imagick resource
     *
     * @return Imagick
     */
    public function getImagick()
    {
        return $this->imagick;
    }

    /**
     * Swaps the current imagick resource.
     *
     * @param Imagick $imagick
     *
     * @return void
     */
    public function swapImagick(Imagick $imagick)
    {
        if (null !== $this->imagick) {

            $map = array_flip(static::$orientMap);
            $orient = $map[$this->getOrientation()];

            $this->imagick->clear();
            $this->imagick->destroy();

            try {
                $imagick->setImageOrientation($orient);
            } catch (ImagickException $orient) {
            }
        }

        $this->imagick = $imagick;
    }

    /**
     * {@inheritdoc}
     */
    public function getBlob($format = null, array $options = [])
    {
        if (null === $format) {
            $format = isset($options['format']) ? $options['format'] : $this->getFormat();
        }

        $background = $this->imagick->getImageBackGroundColor()->getColor();

        if (!in_array($format, ['png', 'gif', 'tiff']) ) {
            // preserve color apearance when flatten images
            if (Imagick::ALPHACHANNEL_ACTIVATE === $this->imagick->getImageAlphaChannel()) {
                $this->edit()->canvas($this->getSize(), new Point(0, 0), $this->palette->getColor([255, 255, 255, 1]));
            }
            //$this->imagick->flattenImages();
        }

        if ($this->hasFrames()) {
            $this->frames()->merge();

            if ($this->getOption($options, 'flatten', false)) {
                $this->imagick->flattenImages();
            }
        }

        $this->imagick->setImageFormat($format);

        if ($interlace = $this->getOption($options, 'interlace', false)) {
        }

        $this->imagick->setImageCompressionQuality($this->getOption($options, 'quality', 80));

        return $this->imagick->getImagesBlob();
    }

    /**
     * {@inheritdoc}
     */
    protected function newEdit()
    {
        return new Edit($this);
    }

    /**
     * cloneImagick
     *
     * @return void
     */
    private function cloneImagick()
    {
        if (version_compare(phpversion('imagick'), '3.1.0b1', '>=') || defined('HHVM_VERSION')) {
            return clone $this->imagick;
        }

        return $this->imagick->clone();
    }

    /**
     * pixelToColor
     *
     * @param ImagickPixel $px
     *
     * @return void
     */
    private function colorFromPixel(ImagickPixel $px)
    {
        $colorMap =& static::$colorMap;
        $multiply = $this->palette instanceof CmykPaletteInterface ? 100 : 255;

        $colors = array_map(function ($color) use ($colorMap, $px, $multiply) {
            if (!isset($colorMap[$color])) {
                throw new \RuntimeException;
            }

            $value = $px->getColorValue($colorMap[$color]);

            return ColorInterface::CHANNEL_ALPHA === $color ? (float)$value : ($value * $multiply);

        }, $keys = $this->palette->getDefinition());

        return $this->palette->getColor(array_combine($keys, $colors));
    }
}