| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 | <?phpdeclare(strict_types = 1);namespace BaconQrCode\Renderer\Image;use BaconQrCode\Exception\RuntimeException;use BaconQrCode\Renderer\Color\Alpha;use BaconQrCode\Renderer\Color\Cmyk;use BaconQrCode\Renderer\Color\ColorInterface;use BaconQrCode\Renderer\Color\Gray;use BaconQrCode\Renderer\Color\Rgb;use BaconQrCode\Renderer\Path\Close;use BaconQrCode\Renderer\Path\Curve;use BaconQrCode\Renderer\Path\EllipticArc;use BaconQrCode\Renderer\Path\Line;use BaconQrCode\Renderer\Path\Move;use BaconQrCode\Renderer\Path\Path;use BaconQrCode\Renderer\RendererStyle\Gradient;use BaconQrCode\Renderer\RendererStyle\GradientType;use Imagick;use ImagickDraw;use ImagickPixel;final class ImagickImageBackEnd implements ImageBackEndInterface{    /**     * @var string     */    private $imageFormat;    /**     * @var int     */    private $compressionQuality;    /**     * @var Imagick|null     */    private $image;    /**     * @var ImagickDraw|null     */    private $draw;    /**     * @var int|null     */    private $gradientCount;    /**     * @var TransformationMatrix[]|null     */    private $matrices;    /**     * @var int|null     */    private $matrixIndex;    public function __construct(string $imageFormat = 'png', int $compressionQuality = 100)    {        if (! class_exists(Imagick::class)) {            throw new RuntimeException('You need to install the imagick extension to use this back end');        }        $this->imageFormat = $imageFormat;        $this->compressionQuality = $compressionQuality;    }    public function new(int $size, ColorInterface $backgroundColor) : void    {        $this->image = new Imagick();        $this->image->newImage($size, $size, $this->getColorPixel($backgroundColor));        $this->image->setImageFormat($this->imageFormat);        $this->image->setCompressionQuality($this->compressionQuality);        $this->draw = new ImagickDraw();        $this->gradientCount = 0;        $this->matrices = [new TransformationMatrix()];        $this->matrixIndex = 0;    }    public function scale(float $size) : void    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->scale($size, $size);        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]            ->multiply(TransformationMatrix::scale($size));    }    public function translate(float $x, float $y) : void    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->translate($x, $y);        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]            ->multiply(TransformationMatrix::translate($x, $y));    }    public function rotate(int $degrees) : void    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->rotate($degrees);        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]            ->multiply(TransformationMatrix::rotate($degrees));    }    public function push() : void    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->push();        $this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1];    }    public function pop() : void    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->pop();        unset($this->matrices[$this->matrixIndex--]);    }    public function drawPathWithColor(Path $path, ColorInterface $color) : void    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->setFillColor($this->getColorPixel($color));        $this->drawPath($path);    }    public function drawPathWithGradient(        Path $path,        Gradient $gradient,        float $x,        float $y,        float $width,        float $height    ) : void {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height));        $this->drawPath($path);    }    public function done() : string    {        if (null === $this->draw) {            throw new RuntimeException('No image has been started');        }        $this->image->drawImage($this->draw);        $blob = $this->image->getImageBlob();        $this->draw->clear();        $this->image->clear();        $this->draw = null;        $this->image = null;        $this->gradientCount = null;        return $blob;    }    private function drawPath(Path $path) : void    {        $this->draw->pathStart();        foreach ($path as $op) {            switch (true) {                case $op instanceof Move:                    $this->draw->pathMoveToAbsolute($op->getX(), $op->getY());                    break;                case $op instanceof Line:                    $this->draw->pathLineToAbsolute($op->getX(), $op->getY());                    break;                case $op instanceof EllipticArc:                    $this->draw->pathEllipticArcAbsolute(                        $op->getXRadius(),                        $op->getYRadius(),                        $op->getXAxisAngle(),                        $op->isLargeArc(),                        $op->isSweep(),                        $op->getX(),                        $op->getY()                    );                    break;                case $op instanceof Curve:                    $this->draw->pathCurveToAbsolute(                        $op->getX1(),                        $op->getY1(),                        $op->getX2(),                        $op->getY2(),                        $op->getX3(),                        $op->getY3()                    );                    break;                case $op instanceof Close:                    $this->draw->pathClose();                    break;                default:                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));            }        }        $this->draw->pathFinish();    }    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string    {        list($width, $height) = $this->matrices[$this->matrixIndex]->apply($x + $width, $y + $height);        list($x, $y) = $this->matrices[$this->matrixIndex]->apply($x, $y);        $width -= $x;        $height -= $y;        $startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString();        $endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString();        $gradientImage = new Imagick();        switch ($gradient->getType()) {            case GradientType::HORIZONTAL():                $gradientImage->newPseudoImage((int) $height, (int) $width, sprintf(                    'gradient:%s-%s',                    $startColor,                    $endColor                ));                $gradientImage->rotateImage('transparent', -90);                break;            case GradientType::VERTICAL():                $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(                    'gradient:%s-%s',                    $startColor,                    $endColor                ));                break;            case GradientType::DIAGONAL():            case GradientType::INVERSE_DIAGONAL():                $gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf(                    'gradient:%s-%s',                    $startColor,                    $endColor                ));                if (GradientType::DIAGONAL() === $gradient->getType()) {                    $gradientImage->rotateImage('transparent', -45);                } else {                    $gradientImage->rotateImage('transparent', -135);                }                $rotatedWidth = $gradientImage->getImageWidth();                $rotatedHeight = $gradientImage->getImageHeight();                $gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0);                $gradientImage->cropImage(                    intdiv($rotatedWidth, 2) - 2,                    intdiv($rotatedHeight, 2) - 2,                    intdiv($rotatedWidth, 4) + 1,                    intdiv($rotatedWidth, 4) + 1                );                break;            case GradientType::RADIAL():                $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(                    'radial-gradient:%s-%s',                    $startColor,                    $endColor                ));                break;        }        $id = sprintf('g%d', ++$this->gradientCount);        $this->draw->pushPattern($id, 0, 0, $x + $width, $y + $height);        $this->draw->composite(Imagick::COMPOSITE_COPY, $x, $y, $width, $height, $gradientImage);        $this->draw->popPattern();        return $id;    }    private function getColorPixel(ColorInterface $color) : ImagickPixel    {        $alpha = 100;        if ($color instanceof Alpha) {            $alpha = $color->getAlpha();            $color = $color->getBaseColor();        }        if ($color instanceof Rgb) {            return new ImagickPixel(sprintf(                'rgba(%d, %d, %d, %F)',                $color->getRed(),                $color->getGreen(),                $color->getBlue(),                $alpha / 100            ));        }        if ($color instanceof Cmyk) {            return new ImagickPixel(sprintf(                'cmyka(%d, %d, %d, %d, %F)',                $color->getCyan(),                $color->getMagenta(),                $color->getYellow(),                $color->getBlack(),                $alpha / 100            ));        }        if ($color instanceof Gray) {            return new ImagickPixel(sprintf(                'graya(%d%%, %F)',                $color->getGray(),                $alpha / 100            ));        }        return $this->getColorPixel(new Alpha($alpha, $color->toRgb()));    }}
 |