Skip to content

Commit ced6668

Browse files
committed
✨ add module group shape example from #233
1 parent 92cf2cc commit ced6668

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed

examples/svgModuleGroupShape.php

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
<?php
2+
/**
3+
* Module group shape, by Rastian95
4+
*
5+
* This example works very similar to the "melted" modules, but creates a different effect
6+
*
7+
* @see https://github.com/chillerlan/php-qrcode/discussions/233
8+
*/
9+
declare(strict_types=1);
10+
11+
use chillerlan\QRCode\{QRCode, QROptions};
12+
use chillerlan\QRCode\Common\EccLevel;
13+
use chillerlan\QRCode\Data\QRMatrix;
14+
use chillerlan\QRCode\Output\QRMarkupSVG;
15+
16+
require_once __DIR__.'/../vendor/autoload.php';
17+
18+
/*
19+
* Class definition
20+
*/
21+
22+
/**
23+
* the extended SVG output module
24+
*/
25+
class GroupShapeSVGQRCodeOutput extends QRMarkupSVG{
26+
27+
protected function path(string $path, int $M_TYPE):string{
28+
// omit the "fill" and "opacity" attributes on the path element
29+
return sprintf('<path class="%s" d="%s"/>', $this->getCssClass($M_TYPE), $path);
30+
}
31+
32+
protected function collectModules():array{
33+
$paths = [];
34+
$melt = $this->options->melt; // avoid magic getter in long loops
35+
36+
// collect the modules for each type
37+
foreach($this->matrix->getMatrix() as $y => $row){
38+
foreach($row as $x => $M_TYPE){
39+
$M_TYPE_LAYER = $M_TYPE;
40+
41+
if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){
42+
// to connect paths we'll redeclare the $M_TYPE_LAYER to data only
43+
$M_TYPE_LAYER = QRMatrix::M_DATA;
44+
45+
if($this->matrix->isDark($M_TYPE)){
46+
$M_TYPE_LAYER = QRMatrix::M_DATA_DARK;
47+
}
48+
}
49+
50+
// if we're going to "melt" the matrix, we'll declare *all* modules as dark,
51+
// so that light modules with dark parts are rendered in the same path
52+
if($melt){
53+
$M_TYPE_LAYER |= QRMatrix::IS_DARK;
54+
}
55+
56+
// collect the modules per $M_TYPE
57+
$module = $this->moduleTransform($x, $y, $M_TYPE, $M_TYPE_LAYER);
58+
59+
if(!empty($module)){
60+
$paths[$M_TYPE_LAYER][] = $module;
61+
}
62+
}
63+
}
64+
65+
// beautify output
66+
ksort($paths);
67+
68+
return $paths;
69+
}
70+
71+
protected function moduleTransform(int $x, int $y, int $M_TYPE, int $M_TYPE_LAYER):string{
72+
$bits = $this->matrix->checkNeighbours($x, $y, null);
73+
$check = fn(int $all, int $any = 0):bool => ($bits & ($all | (~$any & 0xff))) === $all;
74+
75+
$template = ($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK
76+
? $this->darkModule($check, $this->options->inverseMelt)
77+
: $this->lightModule($check, $this->options->inverseMelt);
78+
79+
$r = $this->options->meltRadius;
80+
81+
return sprintf($template, $x, $y, $r, (1 - $r), (1 - 2 * $r));
82+
}
83+
84+
/**
85+
* returns a dark module for the given values
86+
*/
87+
protected function darkModule(Closure $check, bool $invert):string {
88+
return match (true) {
89+
!$invert && $check(0b00000000, 0b01010101),
90+
$invert && $check(0b00000000, 0b00000000)
91+
=> 'M%1$s %2$s m0.5,0 l0.5,0.5 l-0.5,0.5 l-0.5,-0.5Z',
92+
$invert && $check(0b01000000, 0b00000000)
93+
=> 'M%1$s,%2$s m0,1 h%4$s q%3$s,0 %3$s,-%3$s v-%5$s q0,-%3$s -%3$s,-%3$s h-%5$s q-%3$s,0 -%3$s,%3$sZ',
94+
$invert && $check(0b00000001, 0b00000000)
95+
=> 'M%1$s,%2$s v%4$s q0,%3$s %3$s,%3$s h%5$s q%3$s,0 %3$s,-%3$s v-%5$s q0,-%3$s -%3$s,-%3$sZ',
96+
$invert && $check(0b00000100, 0b00000000)
97+
=> 'M%1$s,%2$s m1,0 v%4$s q0,%3$s -%3$s,%3$s h-%5$s q-%3$s,0 -%3$s,-%3$s v-%5$s q0,-%3$s %3$s,-%3$sZ',
98+
$invert && $check(0b00010000, 0b00000000)
99+
=> 'M%1$s,%2$s m1,1 h-%4$s q-%3$s,0 -%3$s,-%3$s v-%5$s q0,-%3$s %3$s,-%3$s h%5$s q%3$s,0 %3$s,%3$sZ',
100+
!$invert && $check(0b00100000, 0b01010101),
101+
$invert && $check(0b00000000, 0b01110000)
102+
=> 'M%1$s,%2$s m0,1 h1 l-0.5,-1Z',
103+
!$invert && $check(0b10000000, 0b01010101),
104+
$invert && $check(0b00000000, 0b11000001)
105+
=> 'M%1$s,%2$s v1 l1,-0.5Z',
106+
!$invert && $check(0b00000010, 0b01010101),
107+
$invert && $check(0b00000000, 0b00000111)
108+
=> 'M%1$s,%2$s h1 l-0.5,1Z',
109+
!$invert && $check(0b00001000, 0b01010101),
110+
$invert && $check(0b00000000, 0b00011100)
111+
=> 'M%1$s,%2$s m1,1 v-1 l-1,0.5Z',
112+
$invert && $check(0b01000100, 0b00000000)
113+
=> 'M%1$s,%2$s m0,1 h%4$s q%3$s,0 %3$s,-%3$s v-%4$s h-%4$s q-%3$s,0 -%3$s,%3$sZ',
114+
$invert && $check(0b00010001, 0b00000000)
115+
=> 'M%1$s,%2$s h%4$s q%3$s,0 %3$s,%3$s v%4$s h-%4$s q-%3$s,0 -%3$s,-%3$sZ',
116+
!$invert && $check(0b00101000, 0b01010101),
117+
$invert && $check(0b00000000, 0b01111100)
118+
=> 'M%1$s,%2$s m0,1 h1 v-1 h-%4$s q-%3$s,0 -%3$s,%3$sZ',
119+
!$invert && $check(0b10100000, 0b01010101),
120+
$invert && $check(0b00000000, 0b11110001)
121+
=> 'M%1$s,%2$s h%4$s q%3$s,0 %3$s,%3$s v%4$s h-1Z',
122+
!$invert && $check(0b10000010, 0b01010101),
123+
$invert && $check(0b00000000, 0b11000111)
124+
=> 'M%1$s,%2$s h1 v%4$s q0,%3$s -%3$s,%3$s h-%4$sZ',
125+
!$invert && $check(0b00001010, 0b01010101),
126+
$invert && $check(0b00000000, 0b00011111)
127+
=> 'M%1$s,%2$s v%4$s q0,%3$s %3$s,%3$s h%4$s v-1Z',
128+
default
129+
=> 'M%1$s,%2$s h1 v1 h-1Z',
130+
};
131+
}
132+
133+
/**
134+
* returns a light module for the given values
135+
*/
136+
protected function lightModule(Closure $check, bool $invert):string {
137+
return match (true) {
138+
!$invert && $check(0b11111111, 0b01010101), $invert && $check(0b10101010, 0b01010101)
139+
=> 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sz m1,0 v%3$s q0,-%3$s -%3$s,-%3$sz m0,1 h-%3$s q%3$s,0 %3$s,-%3$sz m-1,0 v-%3$s q0,%3$s %3$s,%3$sZ',
140+
!$invert && $check(0b10111111, 0b00000000)
141+
=> 'M%1$s,%2$s h%3$sz m1,0 v%3$s q0,-%3$s -%3$s,-%3$sz m0,1 h-%3$s Z',
142+
!$invert && $check(0b11111110, 0b00000000)
143+
=> 'M%1$s,%2$s m1,0 v%3$s z m0,1 h-%3$s q%3$s,0 %3$s,-%3$sz m-1,0 v-%3$s Z',
144+
!$invert && $check(0b11111011, 0b00000000)
145+
=> 'M%1$s,%2$s h%3$s z m0,1 v-%3$s q0,%3$s %3$s,%3$sz m1,0 h-%3$s Z',
146+
!$invert && $check(0b11101111, 0b00000000)
147+
=> 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sz m0,1 v-%3$s z m1,-1 v%3$s Z',
148+
!$invert && $check(0b10001111, 0b01110000),
149+
$invert && $check(0b10001010, 0b01010101),
150+
!$invert && $check(0b00111110, 0b11000001),
151+
$invert && $check(0b00101010, 0b01010101),
152+
!$invert && $check(0b11111000, 0b00000111),
153+
$invert && $check(0b10101000, 0b01010101),
154+
!$invert && $check(0b11100011, 0b00011100),
155+
$invert && $check(0b10100010, 0b01010101),
156+
!$invert && $check(0b10111011, 0b00000000),
157+
!$invert && $check(0b11101110, 0b00000000),
158+
!$invert && $check(0b10000011, 0b01111100),
159+
$invert && $check(0b10000010, 0b01010101),
160+
!$invert && $check(0b00001110, 0b11110001),
161+
$invert && $check(0b00001010, 0b01010101),
162+
!$invert && $check(0b00111000, 0b11000111),
163+
$invert && $check(0b00101000, 0b01010101),
164+
!$invert && $check(0b11100000, 0b00011111),
165+
$invert && $check(0b10100000, 0b01010101)
166+
=> 'M%1$s,%2$s ',
167+
default => '',
168+
};
169+
}
170+
171+
}
172+
173+
174+
/**
175+
* the augmented options class
176+
*
177+
* @property bool $melt
178+
* @property bool $inverseMelt
179+
* @property float $meltRadius
180+
*/
181+
class GroupShapeOutputOptions extends QROptions{
182+
183+
/**
184+
* enable "melt" effect
185+
*/
186+
protected bool $melt = false;
187+
188+
/**
189+
* whether to let the melt effect flow along the dark or light modules
190+
*/
191+
protected bool $inverseMelt = false;
192+
193+
/**
194+
* the corner radius for melted modules
195+
*/
196+
protected float $meltRadius = 0.15;
197+
198+
/**
199+
* clamp/set melt corner radius
200+
*/
201+
protected function set_meltRadius(float $meltRadius):void{
202+
$this->meltRadius = max(0.01, min(0.5, $meltRadius));
203+
}
204+
205+
}
206+
207+
208+
/*
209+
* Runtime
210+
*/
211+
$options = new GroupShapeOutputOptions;
212+
213+
// settings from the custom options class
214+
$options->melt = true;
215+
$options->inverseMelt = true;
216+
$options->meltRadius = 0.5;
217+
218+
$options->version = 5;
219+
$options->outputInterface = GroupShapeSVGQRCodeOutput::class;
220+
$options->outputBase64 = false;
221+
$options->addQuietzone = true;
222+
$options->eccLevel = EccLevel::H;
223+
$options->addLogoSpace = true;
224+
$options->logoSpaceWidth = 13;
225+
$options->logoSpaceHeight = 13;
226+
$options->connectPaths = true;
227+
$options->excludeFromConnect = [
228+
QRMatrix::M_FINDER_DARK,
229+
QRMatrix::M_FINDER_DOT,
230+
];
231+
$options->svgDefs = '
232+
<linearGradient id="rainbow" x1="100%" y2="100%">
233+
<stop stop-color="#e2453c" offset="2.5%"/>
234+
<stop stop-color="#e07e39" offset="21.5%"/>
235+
<stop stop-color="#e5d667" offset="40.5%"/>
236+
<stop stop-color="#51b95b" offset="59.5%"/>
237+
<stop stop-color="#1e72b7" offset="78.5%"/>
238+
<stop stop-color="#6f5ba7" offset="97.5%"/>
239+
</linearGradient>
240+
<style><![CDATA[
241+
.light, .dark{fill: url(#rainbow);}
242+
]]></style>';
243+
244+
245+
$out = (new QRCode($options))->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
246+
247+
248+
if(PHP_SAPI !== 'cli'){
249+
header('Content-type: image/svg+xml');
250+
251+
if(extension_loaded('zlib')){
252+
header('Vary: Accept-Encoding');
253+
header('Content-Encoding: gzip');
254+
$out = gzencode($out, 9);
255+
}
256+
}
257+
258+
echo $out;
259+
260+
exit;

0 commit comments

Comments
 (0)