18
18
use function count , extension_loaded , imagecolorallocate , imagecolortransparent , imagecreatetruecolor ,
19
19
imagedestroy , imagefilledellipse , imagefilledrectangle , imagegif , imagejpeg , imagepng , imagescale , is_array , is_numeric ,
20
20
max , min , ob_end_clean , ob_get_contents , ob_start , restore_error_handler , set_error_handler ;
21
- use const IMG_BILINEAR_FIXED ;
22
21
23
22
/**
24
23
* Converts the matrix into GD images, raw or base64 output (requires ext-gd)
@@ -35,18 +34,49 @@ class QRGdImage extends QROutputAbstract{
35
34
*/
36
35
protected $ image ;
37
36
37
+ /**
38
+ * The allocated background color
39
+ *
40
+ * @see \imagecolorallocate()
41
+ */
42
+ protected int $ background ;
43
+
44
+ /**
45
+ * Whether we're running in upscale mode (scale < 20)
46
+ *
47
+ * @see \chillerlan\QRCode\QROptions::$drawCircularModules
48
+ */
49
+ protected bool $ upscaled = false ;
50
+
38
51
/**
39
52
* @inheritDoc
40
53
*
41
54
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
55
+ * @noinspection PhpMissingParentConstructorInspection
42
56
*/
43
57
public function __construct (SettingsContainerInterface $ options , QRMatrix $ matrix ){
44
58
45
59
if (!extension_loaded ('gd ' )){
46
60
throw new QRCodeOutputException ('ext-gd not loaded ' ); // @codeCoverageIgnore
47
61
}
48
62
49
- parent ::__construct ($ options , $ matrix );
63
+ $ this ->options = $ options ;
64
+ $ this ->matrix = $ matrix ;
65
+
66
+ $ this ->setMatrixDimensions ();
67
+
68
+ // we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
69
+ // @see https://github.com/chillerlan/php-qrcode/issues/23
70
+ if ($ this ->options ->drawCircularModules && $ this ->options ->scale < 20 ){
71
+ // increase the initial image size by 10
72
+ $ this ->length = (($ this ->length + 2 ) * 10 );
73
+ $ this ->scale *= 10 ;
74
+ $ this ->upscaled = true ;
75
+ }
76
+
77
+ $ this ->image = imagecreatetruecolor ($ this ->length , $ this ->length );
78
+ // set module values after image creation because we need the GdImage instance
79
+ $ this ->setModuleValues ();
50
80
}
51
81
52
82
/**
@@ -70,23 +100,31 @@ protected function moduleValueIsValid($value):bool{
70
100
71
101
/**
72
102
* @inheritDoc
103
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
73
104
*/
74
- protected function getModuleValue ($ value ):array {
105
+ protected function getModuleValue ($ value ):int {
75
106
$ v = [];
76
107
77
108
for ($ i = 0 ; $ i < 3 ; $ i ++){
78
109
// clamp value
79
110
$ v [] = (int )max (0 , min (255 , $ value [$ i ]));
80
111
}
81
112
82
- return $ v ;
113
+ /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
114
+ $ color = imagecolorallocate ($ this ->image , ...$ v );
115
+
116
+ if ($ color === false ){
117
+ throw new QRCodeOutputException ('could not set color: imagecolorallocate() error ' );
118
+ }
119
+
120
+ return $ color ;
83
121
}
84
122
85
123
/**
86
124
* @inheritDoc
87
125
*/
88
- protected function getDefaultModuleValue (bool $ isDark ):array {
89
- return ( $ isDark ) ? [0 , 0 , 0 ] : [255 , 255 , 255 ];
126
+ protected function getDefaultModuleValue (bool $ isDark ):int {
127
+ return $ this -> getModuleValue (( $ isDark ) ? [0 , 0 , 0 ] : [255 , 255 , 255 ]) ;
90
128
}
91
129
92
130
/**
@@ -104,47 +142,20 @@ public function dump(string $file = null){
104
142
throw new ErrorException ($ msg , 0 , $ severity , $ file , $ line );
105
143
});
106
144
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
- }
145
+ $ this ->setBgColor ();
112
146
113
- $ this ->image = imagecreatetruecolor ( $ this ->length , $ this ->length );
147
+ imagefilledrectangle ( $ this ->image , 0 , 0 , $ this ->length , $ this ->length , $ this -> background );
114
148
115
- // avoid: "Indirect modification of overloaded property $x has no effect"
116
- // https://stackoverflow.com/a/10455217
117
- $ bgColor = $ this ->options ->imageTransparencyBG ;
149
+ $ this ->drawImage ();
118
150
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 );
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
- }
151
+ if ($ this ->upscaled ){
152
+ // scale down to the expected size
153
+ $ this ->image = imagescale ($ this ->image , ($ this ->length / 10 ), ($ this ->length / 10 ));
154
+ $ this ->upscaled = false ;
142
155
}
143
156
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
- }
157
+ // set transparency after scaling, otherwise it would be undone
158
+ $ this ->setTransparencyColor ();
148
159
149
160
if ($ this ->options ->returnResource ){
150
161
restore_error_handler ();
@@ -165,12 +176,61 @@ public function dump(string $file = null){
165
176
return $ imageData ;
166
177
}
167
178
179
+ /**
180
+ * Sets the background color
181
+ */
182
+ protected function setBgColor ():void {
183
+
184
+ if (isset ($ this ->background )){
185
+ return ;
186
+ }
187
+
188
+ if ($ this ->moduleValueIsValid ($ this ->options ->bgColor )){
189
+ $ this ->background = $ this ->getModuleValue ($ this ->options ->bgColor );
190
+
191
+ return ;
192
+ }
193
+
194
+ $ this ->background = $ this ->getModuleValue ([255 , 255 , 255 ]);
195
+ }
196
+
197
+ /**
198
+ * Sets the transparency color
199
+ */
200
+ protected function setTransparencyColor ():void {
201
+
202
+ if ($ this ->options ->outputType === QROutputInterface::GDIMAGE_JPG || !$ this ->options ->imageTransparent ){
203
+ return ;
204
+ }
205
+
206
+ $ transparencyColor = $ this ->background ;
207
+
208
+ if ($ this ->moduleValueIsValid ($ this ->options ->transparencyColor )){
209
+ $ transparencyColor = $ this ->getModuleValue ($ this ->options ->transparencyColor );
210
+ }
211
+
212
+ imagecolortransparent ($ this ->image , $ transparencyColor );
213
+ }
214
+
215
+ /**
216
+ * Creates the QR image
217
+ */
218
+ protected function drawImage ():void {
219
+ foreach ($ this ->matrix ->matrix () as $ y => $ row ){
220
+ foreach ($ row as $ x => $ M_TYPE ){
221
+ $ this ->setPixel ($ x , $ y , $ M_TYPE );
222
+ }
223
+ }
224
+ }
225
+
168
226
/**
169
227
* Creates a single QR pixel with the given settings
170
228
*/
171
229
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 ]);
230
+
231
+ if (!$ this ->options ->drawLightModules && !$ this ->matrix ->check ($ x , $ y )){
232
+ return ;
233
+ }
174
234
175
235
$ this ->options ->drawCircularModules && !$ this ->matrix ->checkTypeIn ($ x , $ y , $ this ->options ->keepAsSquare )
176
236
? imagefilledellipse (
@@ -179,15 +239,15 @@ protected function setPixel(int $x, int $y, int $M_TYPE):void{
179
239
(int )(($ y * $ this ->scale ) + ($ this ->scale / 2 )),
180
240
(int )(2 * $ this ->options ->circleRadius * $ this ->scale ),
181
241
(int )(2 * $ this ->options ->circleRadius * $ this ->scale ),
182
- $ color
242
+ $ this -> moduleValues [ $ M_TYPE ]
183
243
)
184
244
: imagefilledrectangle (
185
245
$ this ->image ,
186
246
($ x * $ this ->scale ),
187
247
($ y * $ this ->scale ),
188
248
(($ x + 1 ) * $ this ->scale ),
189
249
(($ y + 1 ) * $ this ->scale ),
190
- $ color
250
+ $ this -> moduleValues [ $ M_TYPE ]
191
251
);
192
252
}
193
253
0 commit comments