Passed
Push — master ( 552d15...d4c1db )
by smiley
02:51
created

QRImage::setModuleValues()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 7
nc 4
nop 0
dl 0
loc 12
rs 9.6111
c 1
b 0
f 0
1
<?php
2
/**
3
 * Class QRImage
4
 *
5
 * @filesource   QRImage.php
6
 * @created      05.12.2015
7
 * @package      chillerlan\QRCode\Output
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2015 Smiley
10
 * @license      MIT
11
 *
12
 * @noinspection PhpComposerExtensionStubsInspection
13
 * @noinspection PhpUnused
14
 */
15
16
namespace chillerlan\QRCode\Output;
17
18
use chillerlan\QRCode\Data\QRMatrix;
0 ignored issues
show
Bug introduced by
The type chillerlan\QRCode\Data\QRMatrix was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use chillerlan\QRCode\{QRCode, QRCodeException};
20
use chillerlan\Settings\SettingsContainerInterface;
21
use Exception;
22
23
use function array_values, base64_encode, call_user_func, count, extension_loaded, imagecolorallocate, imagecolortransparent,
24
	imagecreatetruecolor, imagedestroy, imagefilledrectangle, imagegif, imagejpeg, imagepng, in_array,
25
	is_array, ob_end_clean, ob_get_contents, ob_start, range, sprintf;
26
27
/**
28
 * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
29
 *
30
 * @see http://php.net/manual/book.image.php
31
 */
32
class QRImage extends QROutputAbstract{
33
34
	/**
35
	 * @inheritDoc
36
	 */
37
	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
38
39
		if(!extension_loaded('gd')){
40
			throw new QRCodeException('ext-gd not loaded'); // @codeCoverageIgnore
41
		}
42
43
		parent::__construct($options, $matrix);
44
	}
45
46
	/**
47
	 * GD image types that support transparency
48
	 *
49
	 * @var string[]
50
	 */
51
	protected const TRANSPARENCY_TYPES = [
52
		QRCode::OUTPUT_IMAGE_PNG,
53
		QRCode::OUTPUT_IMAGE_GIF,
54
	];
55
56
	protected string $defaultMode = QRCode::OUTPUT_IMAGE_PNG;
57
58
	/**
59
	 * The GD image resource
60
	 *
61
	 * @see imagecreatetruecolor()
62
	 * @var resource
63
	 */
64
	protected $image;
65
66
	/**
67
	 * @inheritDoc
68
	 */
69
	protected function setModuleValues():void{
70
71
		foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
72
			$v = $this->options->moduleValues[$M_TYPE] ?? null;
73
74
			if(!is_array($v) || count($v) < 3){
75
				$this->moduleValues[$M_TYPE] = $defaultValue
76
					? [0, 0, 0]
77
					: [255, 255, 255];
78
			}
79
			else{
80
				$this->moduleValues[$M_TYPE] = array_values($v);
81
			}
82
83
		}
84
85
	}
86
87
	/**
88
	 * @inheritDoc
89
	 */
90
	public function dump(string $file = null):string{
91
		$file ??= $this->options->cachefile;
92
93
		$this->image = imagecreatetruecolor($this->length, $this->length);
0 ignored issues
show
Documentation Bug introduced by
It seems like imagecreatetruecolor($th...>length, $this->length) can also be of type false. However, the property $image is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
94
95
		// avoid: Indirect modification of overloaded property $imageTransparencyBG has no effect
96
		// https://stackoverflow.com/a/10455217
97
		$tbg        = $this->options->imageTransparencyBG;
98
		$background = imagecolorallocate($this->image, ...$tbg);
0 ignored issues
show
Bug introduced by
It seems like $this->image can also be of type false; however, parameter $image of imagecolorallocate() does only seem to accept resource, 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

98
		$background = imagecolorallocate(/** @scrutinizer ignore-type */ $this->image, ...$tbg);
Loading history...
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

98
		$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...
99
100
		if((bool)$this->options->imageTransparent && in_array($this->options->outputType, $this::TRANSPARENCY_TYPES, true)){
101
			imagecolortransparent($this->image, $background);
0 ignored issues
show
Bug introduced by
It seems like $this->image can also be of type false; however, parameter $image of imagecolortransparent() does only seem to accept resource, 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

101
			imagecolortransparent(/** @scrutinizer ignore-type */ $this->image, $background);
Loading history...
102
		}
103
104
		imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $background);
0 ignored issues
show
Bug introduced by
It seems like $this->image can also be of type false; however, parameter $image of imagefilledrectangle() does only seem to accept resource, 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

104
		imagefilledrectangle(/** @scrutinizer ignore-type */ $this->image, 0, 0, $this->length, $this->length, $background);
Loading history...
105
106
		foreach($this->matrix->matrix() as $y => $row){
107
			foreach($row as $x => $M_TYPE){
108
				$this->setPixel($x, $y, $this->moduleValues[$M_TYPE]);
109
			}
110
		}
111
112
		$imageData = $this->dumpImage();
113
114
		if($file !== null){
115
			$this->saveToFile($imageData, $file);
116
		}
117
118
		if((bool)$this->options->imageBase64){
119
			$imageData = sprintf('data:image/%s;base64,%s', $this->options->outputType, base64_encode($imageData));
120
		}
121
122
		return $imageData;
123
	}
124
125
	/**
126
	 * Creates a single QR pixel with the given settings
127
	 */
128
	protected function setPixel(int $x, int $y, array $rgb):void{
129
		imagefilledrectangle(
130
			$this->image,
131
			$x * $this->scale,
132
			$y * $this->scale,
133
			($x + 1) * $this->scale,
134
			($y + 1) * $this->scale,
135
			imagecolorallocate($this->image, ...$rgb)
136
		);
137
	}
138
139
	/**
140
	 * Creates the final image by calling the desired GD output function
141
	 *
142
	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
143
	 */
144
	protected function dumpImage():string{
145
		ob_start();
146
147
		try{
148
			call_user_func([$this, $this->outputMode ?? $this->defaultMode]);
149
		}
150
		// not going to cover edge cases
151
		// @codeCoverageIgnoreStart
152
		catch(Exception $e){
153
			throw new QRCodeOutputException($e->getMessage());
154
		}
155
		// @codeCoverageIgnoreEnd
156
157
		$imageData = ob_get_contents();
158
		imagedestroy($this->image);
159
160
		ob_end_clean();
161
162
		return $imageData;
163
	}
164
165
	/**
166
	 * PNG output
167
	 *
168
	 * @return void
169
	 */
170
	protected function png():void{
171
		imagepng(
172
			$this->image,
173
			null,
174
			in_array($this->options->pngCompression, range(-1, 9), true)
175
				? $this->options->pngCompression
176
				: -1
177
		);
178
	}
179
180
	/**
181
	 * Jiff - like... JitHub!
182
	 *
183
	 * @return void
184
	 */
185
	protected function gif():void{
186
		imagegif($this->image);
187
	}
188
189
	/**
190
	 * JPG output
191
	 *
192
	 * @return void
193
	 */
194
	protected function jpg():void{
195
		imagejpeg(
196
			$this->image,
197
			null,
198
			in_array($this->options->jpegQuality, range(0, 100), true)
199
				? $this->options->jpegQuality
200
				: 85
201
		);
202
	}
203
204
}
205