Passed
Push — main ( c43715...22ad48 )
by smiley
12:03
created

QRGdImage::dump()   D

Complexity

Conditions 14
Paths 240

Size

Total Lines 70
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 33
nc 240
nop 1
dl 0
loc 70
rs 4.9333
c 1
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Class QRGdImage
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\Settings\SettingsContainerInterface;
17
use ErrorException, Throwable;
18
use function count, extension_loaded, imagecolorallocate, imagecolortransparent, imagecreatetruecolor,
19
	imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, imagescale, is_array, is_numeric,
20
	max, min, ob_end_clean, ob_get_contents, ob_start, restore_error_handler, set_error_handler;
21
use const IMG_BILINEAR_FIXED;
22
23
/**
24
 * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
25
 *
26
 * @see http://php.net/manual/book.image.php
27
 */
28
class QRGdImage extends QROutputAbstract{
29
30
	/**
31
	 * The GD image resource
32
	 *
33
	 * @see imagecreatetruecolor()
34
	 * @var resource|\GdImage
35
	 */
36
	protected $image;
37
38
	/**
39
	 * @inheritDoc
40
	 *
41
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
42
	 */
43
	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
44
45
		if(!extension_loaded('gd')){
46
			throw new QRCodeOutputException('ext-gd not loaded'); // @codeCoverageIgnore
47
		}
48
49
		parent::__construct($options, $matrix);
50
	}
51
52
	/**
53
	 * @inheritDoc
54
	 */
55
	protected function moduleValueIsValid($value):bool{
56
57
		if(!is_array($value) || count($value) < 3){
58
			return false;
59
		}
60
61
		// check the first 3 values of the array
62
		for($i = 0; $i < 3; $i++){
63
			if(!is_numeric($value[$i])){
64
				return false;
65
			}
66
		}
67
68
		return true;
69
	}
70
71
	/**
72
	 * @inheritDoc
73
	 */
74
	protected function getModuleValue($value):array{
75
		$v = [];
76
77
		for($i = 0; $i < 3; $i++){
78
			// clamp value
79
			$v[] = (int)max(0, min(255, $value[$i]));
80
		}
81
82
		return $v;
83
	}
84
85
	/**
86
	 * @inheritDoc
87
	 */
88
	protected function getDefaultModuleValue(bool $isDark):array{
89
		return $isDark ? [0, 0, 0] : [255, 255, 255];
90
	}
91
92
	/**
93
	 * @inheritDoc
94
	 *
95
	 * @return string|resource|\GdImage
96
	 *
97
	 * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
98
	 * @throws \ErrorException
99
	 */
100
	public function dump(string $file = null){
101
102
		/** @phan-suppress-next-line PhanTypeMismatchArgumentInternal */
103
		set_error_handler(function(int $severity, string $msg, string $file, int $line):void{
104
			throw new ErrorException($msg, 0, $severity, $file, $line);
105
		});
106
107
		$file ??= $this->options->cachefile;
108
109
		// we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
110
		if($this->options->drawCircularModules && $this->options->scale <= 20){
111
			$this->length  = ($this->length + 2) * 10;
112
			$this->scale  *= 10;
113
		}
114
115
		$this->image = imagecreatetruecolor($this->length, $this->length);
116
117
		// avoid: "Indirect modification of overloaded property $x has no effect"
118
		// https://stackoverflow.com/a/10455217
119
		$bgColor = $this->options->imageTransparencyBG;
120
121
		if($this->moduleValueIsValid($this->options->bgColor)){
122
			$bgColor = $this->getModuleValue($this->options->bgColor);
123
		}
124
125
		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
126
		$background = imagecolorallocate($this->image, ...$bgColor);
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

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

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...
127
128
		if(
129
			   $this->options->imageTransparent
130
			&& $this->options->outputType !== QROutputInterface::GDIMAGE_JPG
131
			&& $this->moduleValueIsValid($this->options->imageTransparencyBG)
132
		){
133
			$tbg = $this->getModuleValue($this->options->imageTransparencyBG);
134
			/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
135
			imagecolortransparent($this->image, imagecolorallocate($this->image, ...$tbg));
136
		}
137
138
		imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
139
140
		foreach($this->matrix->matrix() as $y => $row){
141
			foreach($row as $x => $M_TYPE){
142
				$this->setPixel($x, $y, $M_TYPE);
143
			}
144
		}
145
146
		// scale down to the expected size
147
		if($this->options->drawCircularModules && $this->options->scale <= 20){
148
			$this->image = imagescale($this->image, $this->length/10, $this->length/10, IMG_BILINEAR_FIXED);
149
		}
150
151
		if($this->options->returnResource){
152
			restore_error_handler();
153
154
			return $this->image;
155
		}
156
157
		$imageData = $this->dumpImage();
158
159
		if($file !== null){
160
			$this->saveToFile($imageData, $file);
161
		}
162
163
		if($this->options->imageBase64){
164
			$imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType);
165
		}
166
167
		restore_error_handler();
168
169
		return $imageData;
170
	}
171
172
	/**
173
	 * Creates a single QR pixel with the given settings
174
	 */
175
	protected function setPixel(int $x, int $y, int $M_TYPE):void{
176
		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
177
		$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

177
		$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...
178
179
		$this->options->drawCircularModules && $this->matrix->checkTypeNotIn($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::checkTypeNotIn() 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

179
		$this->options->drawCircularModules && $this->matrix->checkTypeNotIn($x, $y, /** @scrutinizer ignore-type */ $this->options->keepAsSquare)
Loading history...
180
			? imagefilledellipse(
181
				$this->image,
182
				(int)(($x * $this->scale) + ($this->scale / 2)),
183
				(int)(($y * $this->scale) + ($this->scale / 2)),
184
				(int)(2 * $this->options->circleRadius * $this->scale),
185
				(int)(2 * $this->options->circleRadius * $this->scale),
186
				$color
187
			)
188
			: imagefilledrectangle(
189
				$this->image,
190
				$x * $this->scale,
191
				$y * $this->scale,
192
				($x + 1) * $this->scale,
193
				($y + 1) * $this->scale,
194
				$color
195
			);
196
	}
197
198
	/**
199
	 * Creates the final image by calling the desired GD output function
200
	 *
201
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
202
	 */
203
	protected function dumpImage():string{
204
		ob_start();
205
206
		try{
207
208
			switch($this->options->outputType){
209
				case QROutputInterface::GDIMAGE_GIF:
210
					imagegif($this->image);
211
					break;
212
				case QROutputInterface::GDIMAGE_JPG:
213
					imagejpeg($this->image, null, max(0, min(100, $this->options->jpegQuality)));
214
					break;
215
				// silently default to png output
216
				case QROutputInterface::GDIMAGE_PNG:
217
				default:
218
					imagepng($this->image, null, max(-1, min(9, $this->options->pngCompression)));
219
			}
220
221
		}
222
		// not going to cover edge cases
223
		// @codeCoverageIgnoreStart
224
		catch(Throwable $e){
225
			throw new QRCodeOutputException($e->getMessage());
226
		}
227
		// @codeCoverageIgnoreEnd
228
229
		$imageData = ob_get_contents();
230
		imagedestroy($this->image);
231
232
		ob_end_clean();
233
234
		return $imageData;
235
	}
236
237
}
238