Passed
Push — main ( 6b9f6f...a1f051 )
by smiley
02:03
created

QRImagick   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 65
dl 0
loc 194
rs 10
c 3
b 0
f 0
wmc 29

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 3
A dump() 0 26 3
A drawImage() 0 11 3
A moduleValueIsValid() 0 28 6
A setBgColor() 0 13 3
A setTransparencyColor() 0 13 3
A getDefaultModuleValue() 0 2 2
A setPixel() 0 20 5
A prepareModuleValue() 0 2 1
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
It seems like $this->getModuleValueAt($x, $y) can also be of type null; however, parameter $fill_pixel of ImagickDraw::setFillColor() does only seem to accept ImagickPixel, 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 ignore-type  annotation

207
		$this->imagickDraw->setFillColor(/** @scrutinizer ignore-type */ $this->getModuleValueAt($x, $y));
Loading history...
208
209
		$this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->options->keepAsSquare)
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

209
		$this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, /** @scrutinizer ignore-type */ $this->options->keepAsSquare)
Loading history...
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