1 | <?php |
||||
2 | /** |
||||
3 | * Class QRImagick |
||||
4 | * |
||||
5 | * @created 04.07.2018 |
||||
6 | * @author smiley <[email protected]> |
||||
7 | * @copyright 2018 smiley |
||||
8 | * @license MIT |
||||
9 | * |
||||
10 | * @noinspection PhpComposerExtensionStubsInspection |
||||
11 | */ |
||||
12 | |||||
13 | namespace chillerlan\QRCode\Output; |
||||
14 | |||||
15 | use chillerlan\QRCode\Data\QRMatrix; |
||||
16 | use chillerlan\Settings\SettingsContainerInterface; |
||||
17 | use finfo, Imagick, ImagickDraw, ImagickPixel; |
||||
18 | use function extension_loaded, in_array, is_string, preg_match, strlen; |
||||
19 | use const FILEINFO_MIME_TYPE; |
||||
20 | |||||
21 | /** |
||||
22 | * ImageMagick output module (requires ext-imagick) |
||||
23 | * |
||||
24 | * @see http://php.net/manual/book.imagick.php |
||||
25 | * @see http://phpimagick.com |
||||
26 | */ |
||||
27 | class QRImagick extends QROutputAbstract{ |
||||
28 | |||||
29 | /** |
||||
30 | * The main image instance |
||||
31 | */ |
||||
32 | protected Imagick $imagick; |
||||
33 | |||||
34 | /** |
||||
35 | * The main draw instance |
||||
36 | */ |
||||
37 | protected ImagickDraw $imagickDraw; |
||||
38 | |||||
39 | /** |
||||
40 | * The allocated background color |
||||
41 | */ |
||||
42 | protected ImagickPixel $background; |
||||
43 | |||||
44 | /** |
||||
45 | * @inheritDoc |
||||
46 | * |
||||
47 | * @throws \chillerlan\QRCode\Output\QRCodeOutputException |
||||
48 | */ |
||||
49 | public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){ |
||||
50 | |||||
51 | if(!extension_loaded('imagick')){ |
||||
52 | throw new QRCodeOutputException('ext-imagick not loaded'); // @codeCoverageIgnore |
||||
53 | } |
||||
54 | |||||
55 | if(!extension_loaded('fileinfo')){ |
||||
56 | throw new QRCodeOutputException('ext-fileinfo not loaded'); // @codeCoverageIgnore |
||||
57 | } |
||||
58 | |||||
59 | parent::__construct($options, $matrix); |
||||
60 | } |
||||
61 | |||||
62 | /** |
||||
63 | * note: we're not necessarily validating the several values, just checking the general syntax |
||||
64 | * |
||||
65 | * @see https://www.php.net/manual/imagickpixel.construct.php |
||||
66 | * @inheritDoc |
||||
67 | */ |
||||
68 | public static function moduleValueIsValid($value):bool{ |
||||
69 | |||||
70 | if(!is_string($value)){ |
||||
71 | return false; |
||||
72 | } |
||||
73 | |||||
74 | $value = trim($value); |
||||
75 | |||||
76 | // hex notation |
||||
77 | // #rgb(a) |
||||
78 | // #rrggbb(aa) |
||||
79 | // #rrrrggggbbbb(aaaa) |
||||
80 | // ... |
||||
81 | if(preg_match('/^#[a-f]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){ |
||||
82 | return true; |
||||
83 | } |
||||
84 | |||||
85 | // css (-like) func(...values) |
||||
86 | if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){ |
||||
87 | return true; |
||||
88 | } |
||||
89 | |||||
90 | // predefined css color |
||||
91 | if(preg_match('/^[a-z]+$/i', $value)){ |
||||
92 | return true; |
||||
93 | } |
||||
94 | |||||
95 | return false; |
||||
96 | } |
||||
97 | |||||
98 | /** |
||||
99 | * @inheritDoc |
||||
100 | * @throws \ImagickPixelException |
||||
101 | */ |
||||
102 | protected function prepareModuleValue($value):ImagickPixel{ |
||||
103 | return new ImagickPixel($value); |
||||
104 | } |
||||
105 | |||||
106 | /** |
||||
107 | * @inheritDoc |
||||
108 | */ |
||||
109 | protected function getDefaultModuleValue(bool $isDark):ImagickPixel{ |
||||
110 | return $this->prepareModuleValue(($isDark) ? $this->options->markupDark : $this->options->markupLight); |
||||
111 | } |
||||
112 | |||||
113 | /** |
||||
114 | * @inheritDoc |
||||
115 | * |
||||
116 | * @return string|\Imagick |
||||
117 | */ |
||||
118 | public function dump(string $file = null){ |
||||
119 | $this->imagick = new Imagick; |
||||
120 | |||||
121 | $this->setBgColor(); |
||||
122 | |||||
123 | $this->imagick->newImage($this->length, $this->length, $this->background, $this->options->imagickFormat); |
||||
124 | |||||
125 | $this->drawImage(); |
||||
126 | // set transparency color after all operations |
||||
127 | $this->setTransparencyColor(); |
||||
128 | |||||
129 | if($this->options->returnResource){ |
||||
130 | return $this->imagick; |
||||
131 | } |
||||
132 | |||||
133 | $imageData = $this->imagick->getImageBlob(); |
||||
134 | |||||
135 | $this->imagick->destroy(); |
||||
136 | |||||
137 | $this->saveToFile($imageData, $file); |
||||
138 | |||||
139 | if($this->options->imageBase64){ |
||||
140 | $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData)); |
||||
141 | } |
||||
142 | |||||
143 | return $imageData; |
||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * Sets the background color |
||||
148 | */ |
||||
149 | protected function setBgColor():void{ |
||||
150 | |||||
151 | if(isset($this->background)){ |
||||
152 | return; |
||||
153 | } |
||||
154 | |||||
155 | if($this::moduleValueIsValid($this->options->bgColor)){ |
||||
156 | $this->background = $this->prepareModuleValue($this->options->bgColor); |
||||
157 | |||||
158 | return; |
||||
159 | } |
||||
160 | |||||
161 | $this->background = $this->prepareModuleValue('white'); |
||||
162 | } |
||||
163 | |||||
164 | /** |
||||
165 | * Sets the transparency color |
||||
166 | */ |
||||
167 | protected function setTransparencyColor():void{ |
||||
168 | |||||
169 | if(!$this->options->imageTransparent){ |
||||
170 | return; |
||||
171 | } |
||||
172 | |||||
173 | $transparencyColor = $this->background; |
||||
174 | |||||
175 | if($this::moduleValueIsValid($this->options->transparencyColor)){ |
||||
176 | $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor); |
||||
177 | } |
||||
178 | |||||
179 | $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false); |
||||
180 | } |
||||
181 | |||||
182 | /** |
||||
183 | * Creates the QR image via ImagickDraw |
||||
184 | */ |
||||
185 | protected function drawImage():void{ |
||||
186 | $this->imagickDraw = new ImagickDraw; |
||||
187 | $this->imagickDraw->setStrokeWidth(0); |
||||
188 | |||||
189 | for($y = 0; $y < $this->moduleCount; $y++){ |
||||
190 | for($x = 0; $x < $this->moduleCount; $x++){ |
||||
191 | $this->setPixel($x, $y); |
||||
192 | } |
||||
193 | } |
||||
194 | |||||
195 | $this->imagick->drawImage($this->imagickDraw); |
||||
196 | } |
||||
197 | |||||
198 | /** |
||||
199 | * draws a single pixel at the given position |
||||
200 | */ |
||||
201 | protected function setPixel(int $x, int $y):void{ |
||||
202 | |||||
203 | if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){ |
||||
204 | return; |
||||
205 | } |
||||
206 | |||||
207 | $this->imagickDraw->setFillColor($this->getModuleValueAt($x, $y)); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
208 | |||||
209 | $this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->options->keepAsSquare) |
||||
0 ignored issues
–
show
It seems like
$this->options->keepAsSquare can also be of type null ; however, parameter $M_TYPES of chillerlan\QRCode\Data\QRMatrix::checkTypeIn() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
210 | ? $this->imagickDraw->circle( |
||||
211 | (($x + 0.5) * $this->scale), |
||||
212 | (($y + 0.5) * $this->scale), |
||||
213 | (($x + 0.5 + $this->options->circleRadius) * $this->scale), |
||||
214 | (($y + 0.5) * $this->scale) |
||||
215 | ) |
||||
216 | : $this->imagickDraw->rectangle( |
||||
217 | ($x * $this->scale), |
||||
218 | ($y * $this->scale), |
||||
219 | (($x + 1) * $this->scale), |
||||
220 | (($y + 1) * $this->scale) |
||||
221 | ); |
||||
222 | } |
||||
223 | |||||
224 | } |
||||
225 |