1<?php 2declare(strict_types = 1); 3 4namespace BaconQrCode\Renderer; 5 6use BaconQrCode\Encoder\MatrixUtil; 7use BaconQrCode\Encoder\QrCode; 8use BaconQrCode\Exception\InvalidArgumentException; 9use BaconQrCode\Renderer\Image\ImageBackEndInterface; 10use BaconQrCode\Renderer\Path\Path; 11use BaconQrCode\Renderer\RendererStyle\EyeFill; 12use BaconQrCode\Renderer\RendererStyle\RendererStyle; 13 14final class ImageRenderer implements RendererInterface 15{ 16 /** 17 * @var RendererStyle 18 */ 19 private $rendererStyle; 20 21 /** 22 * @var ImageBackEndInterface 23 */ 24 private $imageBackEnd; 25 26 public function __construct(RendererStyle $rendererStyle, ImageBackEndInterface $imageBackEnd) 27 { 28 $this->rendererStyle = $rendererStyle; 29 $this->imageBackEnd = $imageBackEnd; 30 } 31 32 /** 33 * @throws InvalidArgumentException if matrix width doesn't match height 34 */ 35 public function render(QrCode $qrCode) : string 36 { 37 $size = $this->rendererStyle->getSize(); 38 $margin = $this->rendererStyle->getMargin(); 39 $matrix = $qrCode->getMatrix(); 40 $matrixSize = $matrix->getWidth(); 41 42 if ($matrixSize !== $matrix->getHeight()) { 43 throw new InvalidArgumentException('Matrix must have the same width and height'); 44 } 45 46 $totalSize = $matrixSize + ($margin * 2); 47 $moduleSize = $size / $totalSize; 48 $fill = $this->rendererStyle->getFill(); 49 50 $this->imageBackEnd->new($size, $fill->getBackgroundColor()); 51 $this->imageBackEnd->scale((float) $moduleSize); 52 $this->imageBackEnd->translate((float) $margin, (float) $margin); 53 54 $module = $this->rendererStyle->getModule(); 55 $moduleMatrix = clone $matrix; 56 MatrixUtil::removePositionDetectionPatterns($moduleMatrix); 57 $modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix)); 58 59 if ($fill->hasGradientFill()) { 60 $this->imageBackEnd->drawPathWithGradient( 61 $modulePath, 62 $fill->getForegroundGradient(), 63 0, 64 0, 65 $matrixSize, 66 $matrixSize 67 ); 68 } else { 69 $this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor()); 70 } 71 72 return $this->imageBackEnd->done(); 73 } 74 75 private function drawEyes(int $matrixSize, Path $modulePath) : Path 76 { 77 $fill = $this->rendererStyle->getFill(); 78 79 $eye = $this->rendererStyle->getEye(); 80 $externalPath = $eye->getExternalPath(); 81 $internalPath = $eye->getInternalPath(); 82 83 $modulePath = $this->drawEye( 84 $externalPath, 85 $internalPath, 86 $fill->getTopLeftEyeFill(), 87 3.5, 88 3.5, 89 0, 90 $modulePath 91 ); 92 $modulePath = $this->drawEye( 93 $externalPath, 94 $internalPath, 95 $fill->getTopRightEyeFill(), 96 $matrixSize - 3.5, 97 3.5, 98 90, 99 $modulePath 100 ); 101 $modulePath = $this->drawEye( 102 $externalPath, 103 $internalPath, 104 $fill->getBottomLeftEyeFill(), 105 3.5, 106 $matrixSize - 3.5, 107 -90, 108 $modulePath 109 ); 110 111 return $modulePath; 112 } 113 114 private function drawEye( 115 Path $externalPath, 116 Path $internalPath, 117 EyeFill $fill, 118 float $xTranslation, 119 float $yTranslation, 120 int $rotation, 121 Path $modulePath 122 ) : Path { 123 if ($fill->inheritsBothColors()) { 124 return $modulePath 125 ->append($externalPath->translate($xTranslation, $yTranslation)) 126 ->append($internalPath->translate($xTranslation, $yTranslation)); 127 } 128 129 $this->imageBackEnd->push(); 130 $this->imageBackEnd->translate($xTranslation, $yTranslation); 131 132 if (0 !== $rotation) { 133 $this->imageBackEnd->rotate($rotation); 134 } 135 136 if ($fill->inheritsExternalColor()) { 137 $modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation)); 138 } else { 139 $this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor()); 140 } 141 142 if ($fill->inheritsInternalColor()) { 143 $modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation)); 144 } else { 145 $this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor()); 146 } 147 148 $this->imageBackEnd->pop(); 149 150 return $modulePath; 151 } 152} 153