Passed
Push — main ( 709a03...3e1426 )
by smiley
02:31
created

QRImage::gif()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class QRImage
4
 *
5
 * @created      05.12.2015
6
 * @author       Smiley <[email protected]>
7
 * @copyright    2015 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\QRCode\QRCode;
17
use chillerlan\Settings\SettingsContainerInterface;
18
use ErrorException, Exception;
19
20
use function array_values, count, extension_loaded, imagecolorallocate, imagecolortransparent, imagecreatetruecolor,
21
	 imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, imagescale, in_array,
22
	is_array, ob_end_clean, ob_get_contents, ob_start, range, restore_error_handler, set_error_handler;
23
use const IMG_BICUBIC;
24
25
/**
26
 * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
27
 *
28
 * @see http://php.net/manual/book.image.php
29
 */
30
class QRImage extends QROutputAbstract{
31
32
	/**
33
	 * GD image types that support transparency
34
	 *
35
	 * @var string[]
36
	 */
37
	protected const TRANSPARENCY_TYPES = [
38
		QRCode::OUTPUT_IMAGE_PNG,
39
		QRCode::OUTPUT_IMAGE_GIF,
40
	];
41
42
	protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
43
44
	/**
45
	 * The GD image resource
46
	 *
47
	 * @see imagecreatetruecolor()
48
	 * @var resource|\GdImage
49
	 */
50
	protected $image;
51
52
	/**
53
	 * @inheritDoc
54
	 *
55
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
56
	 */
57
	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
58
59
		if(!extension_loaded('gd')){
60
			throw new QRCodeOutputException('ext-gd not loaded'); // @codeCoverageIgnore
61
		}
62
63
		parent::__construct($options, $matrix);
64
	}
65
66
	/**
67
	 * @inheritDoc
68
	 */
69
	protected function moduleValueIsValid($value):bool{
70
		return is_array($value) && count($value) >= 3;
71
	}
72
73
	/**
74
	 * @inheritDoc
75
	 */
76
	protected function getModuleValue($value):array{
77
		return array_values($value);
78
	}
79
80
	/**
81
	 * @inheritDoc
82
	 */
83
	protected function getDefaultModuleValue(bool $isDark):array{
84
		return $isDark ? [0, 0, 0] : [255, 255, 255];
85
	}
86
87
	/**
88
	 * @inheritDoc
89
	 *
90
	 * @return string|resource|\GdImage
91
	 *
92
	 * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
93
	 * @throws \ErrorException
94
	 */
95
	public function dump(string $file = null){
96
97
		/** @phan-suppress-next-line PhanTypeMismatchArgumentInternal */
98
		set_error_handler(function(int $severity, string $msg, string $file, int $line):void{
99
			throw new ErrorException($msg, 0, $severity, $file, $line);
100
		});
101
102
		$file ??= $this->options->cachefile;
103
104
		// we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
105
		if($this->options->drawCircularModules && $this->options->scale <= 20){
106
			$this->length  = ($this->length + 2) * 10;
107
			$this->scale  *= 10;
108
		}
109
110
		$this->image = imagecreatetruecolor($this->length, $this->length);
111
112
		// avoid: "Indirect modification of overloaded property $imageTransparencyBG has no effect"
113
		// https://stackoverflow.com/a/10455217
114
		$tbg        = $this->options->imageTransparencyBG;
115
		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
116
		$background = imagecolorallocate($this->image, ...$tbg);
0 ignored issues
show
Bug introduced by
The call to imagecolorallocate() has too few arguments starting with green. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
		$background = /** @scrutinizer ignore-call */ imagecolorallocate($this->image, ...$tbg);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
117
118
		if($this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
119
			imagecolortransparent($this->image, $background);
120
		}
121
122
		imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
123
124
		foreach($this->matrix->matrix() as $y => $row){
125
			foreach($row as $x => $M_TYPE){
126
				$this->setPixel($x, $y, $M_TYPE);
127
			}
128
		}
129
130
		// scale down to the expected size
131
		if($this->options->drawCircularModules && $this->options->scale <= 20){
132
			$this->image = imagescale($this->image, $this->length/10, $this->length/10, IMG_BICUBIC);
133
		}
134
135
		if($this->options->returnResource){
136
			restore_error_handler();
137
138
			return $this->image;
139
		}
140
141
		$imageData = $this->dumpImage();
142
143
		if($file !== null){
144
			$this->saveToFile($imageData, $file);
145
		}
146
147
		if($this->options->imageBase64){
148
			$imageData = $this->base64encode($imageData, 'image/'.$this->options->outputType);
149
		}
150
151
		restore_error_handler();
152
153
		return $imageData;
154
	}
155
156
	/**
157
	 * Creates a single QR pixel with the given settings
158
	 */
159
	protected function setPixel(int $x, int $y, int $M_TYPE):void{
160
		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
161
		$color = imagecolorallocate($this->image, ...$this->moduleValues[$M_TYPE]);
0 ignored issues
show
Bug introduced by
The call to imagecolorallocate() has too few arguments starting with green. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

161
		$color = /** @scrutinizer ignore-call */ imagecolorallocate($this->image, ...$this->moduleValues[$M_TYPE]);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
162
163
		$this->options->drawCircularModules && !$this->matrix->checkTypes($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::checkTypes() 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

163
		$this->options->drawCircularModules && !$this->matrix->checkTypes($x, $y, /** @scrutinizer ignore-type */ $this->options->keepAsSquare)
Loading history...
164
			? imagefilledellipse(
165
				$this->image,
166
				($x * $this->scale) + ($this->scale / 2),
167
				($y * $this->scale) + ($this->scale / 2),
168
				2 * $this->options->circleRadius * $this->scale,
169
				2 * $this->options->circleRadius * $this->scale,
170
				$color
171
			)
172
			: imagefilledrectangle(
173
				$this->image,
174
				$x * $this->scale,
175
				$y * $this->scale,
176
				($x + 1) * $this->scale,
177
				($y + 1) * $this->scale,
178
				$color
179
			);
180
	}
181
182
	/**
183
	 * Creates the final image by calling the desired GD output function
184
	 *
185
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
186
	 */
187
	protected function dumpImage():string{
188
		ob_start();
189
190
		try{
191
			$this->{$this->outputMode ?? $this->defaultMode}();
192
		}
193
		// not going to cover edge cases
194
		// @codeCoverageIgnoreStart
195
		catch(Exception $e){
196
			throw new QRCodeOutputException($e->getMessage());
197
		}
198
		// @codeCoverageIgnoreEnd
199
200
		$imageData = ob_get_contents();
201
		imagedestroy($this->image);
202
203
		ob_end_clean();
204
205
		return $imageData;
206
	}
207
208
	/**
209
	 * PNG output
210
	 *
211
	 * @return void
212
	 */
213
	protected function png():void{
214
		imagepng(
215
			$this->image,
216
			null,
217
			in_array($this->options->pngCompression, range(-1, 9), true)
218
				? $this->options->pngCompression
219
				: -1
220
		);
221
	}
222
223
	/**
224
	 * Jiff - like... JitHub!
225
	 *
226
	 * @return void
227
	 */
228
	protected function gif():void{
229
		imagegif($this->image);
230
	}
231
232
	/**
233
	 * JPG output
234
	 *
235
	 * @return void
236
	 */
237
	protected function jpg():void{
238
		imagejpeg(
239
			$this->image,
240
			null,
241
			in_array($this->options->jpegQuality, range(0, 100), true)
242
				? $this->options->jpegQuality
243
				: 85
244
		);
245
	}
246
247
}
248