1818use function count , extension_loaded , imagecolorallocate , imagecolortransparent , imagecreatetruecolor ,
1919 imagedestroy , imagefilledellipse , imagefilledrectangle , imagegif , imagejpeg , imagepng , imagescale , is_array , is_numeric ,
2020 max , min , ob_end_clean , ob_get_contents , ob_start , restore_error_handler , set_error_handler ;
21- use const IMG_BILINEAR_FIXED ;
2221
2322/**
2423 * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
@@ -35,18 +34,49 @@ class QRGdImage extends QROutputAbstract{
3534 */
3635 protected $ image ;
3736
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+
3851 /**
3952 * @inheritDoc
4053 *
4154 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
55+ * @noinspection PhpMissingParentConstructorInspection
4256 */
4357 public function __construct (SettingsContainerInterface $ options , QRMatrix $ matrix ){
4458
4559 if (!extension_loaded ('gd ' )){
4660 throw new QRCodeOutputException ('ext-gd not loaded ' ); // @codeCoverageIgnore
4761 }
4862
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 ();
5080 }
5181
5282 /**
@@ -70,23 +100,31 @@ protected function moduleValueIsValid($value):bool{
70100
71101 /**
72102 * @inheritDoc
103+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
73104 */
74- protected function getModuleValue ($ value ):array {
105+ protected function getModuleValue ($ value ):int {
75106 $ v = [];
76107
77108 for ($ i = 0 ; $ i < 3 ; $ i ++){
78109 // clamp value
79110 $ v [] = (int )max (0 , min (255 , $ value [$ i ]));
80111 }
81112
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 ;
83121 }
84122
85123 /**
86124 * @inheritDoc
87125 */
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 ]) ;
90128 }
91129
92130 /**
@@ -104,47 +142,20 @@ public function dump(string $file = null){
104142 throw new ErrorException ($ msg , 0 , $ severity , $ file , $ line );
105143 });
106144
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 ();
112146
113- $ this ->image = imagecreatetruecolor ( $ this ->length , $ this ->length );
147+ imagefilledrectangle ( $ this ->image , 0 , 0 , $ this ->length , $ this ->length , $ this -> background );
114148
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 ();
118150
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 ;
142155 }
143156
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 ();
148159
149160 if ($ this ->options ->returnResource ){
150161 restore_error_handler ();
@@ -165,12 +176,61 @@ public function dump(string $file = null){
165176 return $ imageData ;
166177 }
167178
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+
168226 /**
169227 * Creates a single QR pixel with the given settings
170228 */
171229 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+ }
174234
175235 $ this ->options ->drawCircularModules && !$ this ->matrix ->checkTypeIn ($ x , $ y , $ this ->options ->keepAsSquare )
176236 ? imagefilledellipse (
@@ -179,15 +239,15 @@ protected function setPixel(int $x, int $y, int $M_TYPE):void{
179239 (int )(($ y * $ this ->scale ) + ($ this ->scale / 2 )),
180240 (int )(2 * $ this ->options ->circleRadius * $ this ->scale ),
181241 (int )(2 * $ this ->options ->circleRadius * $ this ->scale ),
182- $ color
242+ $ this -> moduleValues [ $ M_TYPE ]
183243 )
184244 : imagefilledrectangle (
185245 $ this ->image ,
186246 ($ x * $ this ->scale ),
187247 ($ y * $ this ->scale ),
188248 (($ x + 1 ) * $ this ->scale ),
189249 (($ y + 1 ) * $ this ->scale ),
190- $ color
250+ $ this -> moduleValues [ $ M_TYPE ]
191251 );
192252 }
193253
0 commit comments