Passed
Push — main ( 0538d1...ccad9c )
by smiley
01:52
created

QRGdImage::dump()   C

Complexity

Conditions 13
Paths 144

Size

Total Lines 66
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 31
nc 144
nop 1
dl 0
loc 66
rs 6.25
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
		// we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
108
		if($this->options->drawCircularModules && $this->options->scale <= 20){
109
			$this->length  = ($this->length + 2) * 10;
110
			$this->scale  *= 10;
111
		}
112
113
		$this->image = imagecreatetruecolor($this->length, $this->length);
114
115
		// avoid: "Indirect modification of overloaded property $x has no effect"
116
		// https://stackoverflow.com/a/10455217
117
		$bgColor = $this->options->imageTransparencyBG;
118
119
		if($this->moduleValueIsValid($this->options->bgColor)){
120
			$bgColor = $this->getModuleValue($this->options->bgColor);
121
		}
122
123
		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
124
		$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

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

173
		$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...
174
175
		$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

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