Passed
Push — main ( 7963d9...f1858f )
by smiley
02:10
created

QRGdImage::getDefaultModuleValue()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

103
		$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...
104
105
		if($this->options->imageTransparent && $this->options->outputType !== QRCode::OUTPUT_IMAGE_JPG){
106
			imagecolortransparent($this->image, $background);
107
		}
108
109
		imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
110
111
		foreach($this->matrix->matrix() as $y => $row){
112
			foreach($row as $x => $M_TYPE){
113
				$this->setPixel($x, $y, $M_TYPE);
114
			}
115
		}
116
117
		// scale down to the expected size
118
		if($this->options->drawCircularModules && $this->options->scale <= 20){
119
			$this->image = imagescale($this->image, $this->length/10, $this->length/10, IMG_BICUBIC);
120
		}
121
122
		if($this->options->returnResource){
123
			restore_error_handler();
124
125
			return $this->image;
126
		}
127
128
		$imageData = $this->dumpImage();
129
130
		if($file !== null){
131
			$this->saveToFile($imageData, $file);
132
		}
133
134
		if($this->options->imageBase64){
135
			$imageData = $this->base64encode($imageData, 'image/'.$this->options->outputType);
136
		}
137
138
		restore_error_handler();
139
140
		return $imageData;
141
	}
142
143
	/**
144
	 * Creates a single QR pixel with the given settings
145
	 */
146
	protected function setPixel(int $x, int $y, int $M_TYPE):void{
147
		/** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
148
		$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

148
		$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...
149
150
		$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

150
		$this->options->drawCircularModules && !$this->matrix->checkTypes($x, $y, /** @scrutinizer ignore-type */ $this->options->keepAsSquare)
Loading history...
151
			? imagefilledellipse(
152
				$this->image,
153
				($x * $this->scale) + ($this->scale / 2),
154
				($y * $this->scale) + ($this->scale / 2),
155
				2 * $this->options->circleRadius * $this->scale,
156
				2 * $this->options->circleRadius * $this->scale,
157
				$color
158
			)
159
			: imagefilledrectangle(
160
				$this->image,
161
				$x * $this->scale,
162
				$y * $this->scale,
163
				($x + 1) * $this->scale,
164
				($y + 1) * $this->scale,
165
				$color
166
			);
167
	}
168
169
	/**
170
	 * Creates the final image by calling the desired GD output function
171
	 *
172
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
173
	 */
174
	protected function dumpImage():string{
175
		ob_start();
176
177
		try{
178
179
			switch($this->options->outputType){
180
				case QRCode::OUTPUT_IMAGE_GIF:
181
					imagegif($this->image);
182
					break;
183
				case QRCode::OUTPUT_IMAGE_JPG:
184
					imagejpeg($this->image, null, max(0, min(100, $this->options->jpegQuality)));
185
					break;
186
				// silently default to png output
187
				case QRCode::OUTPUT_IMAGE_PNG:
188
				default:
189
					imagepng($this->image, null, max(-1, min(9, $this->options->pngCompression)));
190
			}
191
192
		}
193
		// not going to cover edge cases
194
		// @codeCoverageIgnoreStart
195
		catch(Throwable $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