Driver/Gmagick/Image.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

/*
 * This File is part of the Thapp\Image\Driver\Gmagick 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\Gmagick;

use Gmagick;
use GmagickPixel;
use GmagickException;
use GmagickPixelException;
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\Color\Palette\Rgb;
use Thapp\Image\Color\Palette\PaletterInterface;
use Thapp\Image\Color\ColorInterface;
use Thapp\Image\Filter\FilterInterface;
use Thapp\Image\Filter\GmagickFilter;
use Thapp\Image\Color\Palette\PaletteInterface;
use Thapp\Image\Info\MetaDataInterface;
use Thapp\Image\Info\MetaData;
use Thapp\Image\Exception\ImageException;

/**
 * @class Image
 *
 * @package Thapp\Image\Driver\Gmagick
 * @version $Id$
 * @author iwyg <mail@thomas-appel.com>
 */
class Image extends AbstractImage
{
    /**
     * The Gmagick instance
     *
     * @var Gmagick
     */
    private $gmagick;

    /**
     * Documentation at http://php.net/manual/en/gmagickpixel.getcolorvalue.php
     * is wrong. Gmagick::getcolorvalue needs a Gmagick::COLOR_* constant, not
     * a Gmagick::CHANNEL_* constant.
     *
     * Gmagick::COLOR_ALPHA will error. Use Gmagick::COLOR_OPACITY and reverse results.
     *
     * @var array
     */
    private static $colorMap = [
        ColorInterface::CHANNEL_RED     => Gmagick::COLOR_RED,
        ColorInterface::CHANNEL_GREEN   => Gmagick::COLOR_GREEN,
        ColorInterface::CHANNEL_BLUE    => Gmagick::COLOR_BLUE,
        ColorInterface::CHANNEL_ALPHA   => Gmagick::COLOR_OPACITY,
        ColorInterface::CHANNEL_CYAN    => Gmagick::COLOR_CYAN,
        ColorInterface::CHANNEL_MAGENTA => Gmagick::COLOR_MAGENTA,
        ColorInterface::CHANNEL_YELLOW  => Gmagick::COLOR_YELLOW,
        ColorInterface::CHANNEL_KEY     => Gmagick::COLOR_BLACK,
        ColorInterface::CHANNEL_GRAY    => Gmagick::COLOR_RED
    ];

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

    public function __clone()
    {
        $this->gmagick = clone $this->gmagick;
        $this->meta = clone $this->meta;
        $this->frames = new Frames($this);
    }

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

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

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function getGmagick()
    {
        return $this->gmagick;
    }

    /**
     * {@inheritdoc}
     */
    public function swapGmagick(Gmagick $gmagick)
    {
        $this->gmagick = $gmagick;
    }

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

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

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

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

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

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

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

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

        return parent::getFormat();
    }

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

        // Gmagick does not have a dedicated method for retreiving pixel colors
        // at a given point:
        try {
            $magick = clone $this->gmagick;
            $magick->cropImage(1, 1, $pixel->getX(), $pixel->getY());
            $colors = $magick->getImageHistogram();
        } catch (GmagickException $e) {
            throw new ImageException('Unable to retrive color sample', $e->getCode(), $e);
        }

        $px = array_shift($colors);

        $magick->clear();
        $magick->destroy();
        unset($magick, $colors);

        return $this->colorFromPixel($px);
    }

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

        $size = $this->getSize();
        $background = $this->gmagick->getImageBackGroundColor()->getColor();

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

        if ($this->hasFrames()) {
            $this->frames()->merge();
            // always flatten images:

            if ($this->getOption($options, 'flatten', false)) {
                $this->gmagick->flattenImages();
            } elseif (in_array($format, ['gif'])) {
                //return $this->gmagick->getImagesBlob();
            }
        } else {
            $this->gmagick->setImageFormat($format);
        }

        $this->gmagick->flattenImages();

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

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

    /**
     * pixelToColor
     *
     * @param ImagickPixel $px
     *
     * @return void
     */
    private function colorFromPixel(GmagickPixel $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;
            }

            // GmagickPixel will throw an exception when using Gmagick::COLOR_ALPHA, instead use color
            // opacity and reverse result.
            $value = $px->getColorValue($colorMap[$color]);

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

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

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

    /**
     * getNumberImages
     *
     * @return void
     */
    private function getNumberImages()
    {
        try {
            return $this->getGmagick()->getNumberImages();
        } catch (GmagickException $e) {
            throw $e;
        }
    }
}