-
-
Notifications
You must be signed in to change notification settings - Fork 319
How do I replicate a QR Code with rounded modules and pointy rounded control points with a PNG image overlay? #279
-
How would I replicate this QR code using php-qrcode?
The center Image is a PNG file.
In the generation of the QR code the PNG will be substituted for a PNG image of the avatar of the person who is creating the QR code. Also I would need a High level of error correction.
In the iQR MacOS app they call the control points "centered" and I am using a corner radius of about 80%.
Any help or pointers in how to recreate this QR code is appreciated.
I am using the 5.0.x branch.
I have seen these examples:
- examples/svgWithLogo.php
- examples/svgMeltedModules.php
And I have found an ImageMagick Function to convert the PNG image into an SVG.
I am using the Laravel Framework.
protected function pngToSVG()
{
// Assuming the logged-in user
$user = Auth::user();
$pngBlob = $user->avatar;
// Create a new Imagick instance from the PNG blob
$imagick = new Imagick();
$imagick->readImageBlob($pngBlob);
// Set the image format to SVG
$imagick->setImageFormat("svg");
// Get the SVG content
$svgContent = $imagick->getImageBlob();
// Generate a unique ID for the filename
$uniqueId = Str::uuid()->toString();
$fileName = $uniqueId . '.svg';
// Define the path where the file will be stored
$filePath = 'tmp/' . $fileName;
// Save the SVG file to the storage/tmp directory
Storage::disk('local')->put($filePath, $svgContent);
return $filePath;
}
Beta Was this translation helpful? Give feedback.
All reactions
I finally got the logo to appear correctly, I set the png image to be 300x300 pixels as a standard size then reduced the size of the png by 13% and then set the height and width on the tag.
use Intervention\Image\Laravel\Facades\Image;
$imgScale = 0.13;
$image = Image::read($logoBlob);
$pngScaledWidth = $image->width() * $imgScale;
$pngScaledHeight = $image->height() * $imgScale;
$embedPng = '<image width="' . $pngScaledWidth . '" height="' . $pngScaledHeight . '" href="data:image/png;base64,' . $logoBlob . '" />';
and this is my getFinderPatterns function
protected function getFinderPatterns():string{
$qz = ($this->options->ad...Replies: 1 comment 7 replies
-
Hey, I assume the desired output format is SVG? You can simply embed an image into an SVG document via the <image> element (see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image) - similar to the svgWithLogo example, where you'd replace the whole logo SVG string with the <image> tag over here:
php-qrcode/examples/svgWithLogo.php
Line 65 in 9b71dad
Alternatively, you can append the <g ...><image ... /></g> after rendering the SVG (replace the closing </svg> with your image embed and close the SVG again).
Beta Was this translation helpful? Give feedback.
All reactions
-
The relative coordinates of the finder patterns look very large, they should be around 0...7 (absolute size) each.
Beta Was this translation helpful? Give feedback.
All reactions
-
I was able to get the Finder patterns to position correctly. Now I am having issues with the SVG Logo. It is not scaling and positioning properly. is it possible to fill the quietzone area? The SVG Logo is an embedded png in base64. Here is my code so far
namespace App\Services; use Closure; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; use chillerlan\QRCode\Common\EccLevel; use chillerlan\QRCode\Data\QRMatrix; use chillerlan\QRCode\Output\QRMarkupSVG; use chillerlan\QRCode\Output\QROutputInterface; class QRSvgMeltedWithCustomFinder extends QRMarkupSVG{ protected function paths():string{ // make sure connect paths is enabled $this->options->connectPaths = true; // we're calling QRMatrix::setLogoSpace() manually, so QROptions::$addLogoSpace has no effect here $this->matrix->setLogoSpace((int)ceil($this->moduleCount * $this->options->svgLogoScale)); // generate the path element(s) - in this case it's just one element as we've "disabled" several options $svg = parent::paths(); // add the custom shapes for the finder patterns $svg .= $this->getFinderPatterns(); // and add the custom logo $svg .= $this->getLogo(); return $svg; } protected function path(string $path, int $M_TYPE):string{ // omit the "fill" and "opacity" attributes on the path element return sprintf('<path class="%s" d="%s"/>', $this->getCssClass($M_TYPE), $path); } protected function collectModules(Closure $transform):array{ $paths = []; $melt = $this->options->melt; // avoid magic getter in long loops // collect the modules for each type foreach($this->matrix->getMatrix() as $y => $row){ foreach($row as $x => $M_TYPE){ $M_TYPE_LAYER = $M_TYPE; if($this->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->excludeFromConnect)){ // to connect paths we'll redeclare the $M_TYPE_LAYER to data only $M_TYPE_LAYER = QRMatrix::M_DATA; if($this->matrix->isDark($M_TYPE)){ $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; } } // if we're going to "melt" the matrix, we'll declare *all* modules as dark, // so that light modules with dark parts are rendered in the same path if($melt){ $M_TYPE_LAYER |= QRMatrix::IS_DARK; } // collect the modules per $M_TYPE $module = $transform($x, $y, $M_TYPE, $M_TYPE_LAYER); if(!empty($module)){ $paths[$M_TYPE_LAYER][] = $module; } } } // beautify output ksort($paths); return $paths; } protected function module(int $x, int $y, int $M_TYPE):string{ $bits = $this->matrix->checkNeighbours($x, $y, null); $check = fn(int $all, int $any = 0):bool => ($bits & ($all | (~$any & 0xff))) === $all; $template = ($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK ? $this->darkModule($check, $this->options->inverseMelt) : $this->lightModule($check, $this->options->inverseMelt); $r = $this->options->meltRadius; if( !$this->matrix->isDark($M_TYPE) // we're skipping the finder patterns here || $this->matrix->checkType($x, $y, QRMatrix::M_FINDER) || $this->matrix->checkType($x, $y, QRMatrix::M_FINDER_DOT) ){ return ''; } return sprintf($template, $x, $y, $r, (1 - $r), (1 - 2 * $r)); } protected function darkModule(Closure $check, bool $invert):string{ switch(true){ // 4 rounded case !$invert && $check(0b00000000, 0b01010101): case $invert && $check(0b00000000, 0b00000000): return 'M%1$s,%2$s m0,%3$s v%5$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$s h-%5$s q-%3$s,0 -%3$s,%3$sZ'; // 3 rounded case $invert && $check(0b01000000, 0b00000000): // 135 return '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'; case $invert && $check(0b00000001, 0b00000000): // 357 return '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'; case $invert && $check(0b00000100, 0b00000000): // 571 return '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'; case $invert && $check(0b00010000, 0b00000000): // 713 return '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'; // 2 rounded case !$invert && $check(0b00100000, 0b01010101): // 13 case $invert && $check(0b00000000, 0b01110000): return 'M%1$s,%2$s m0,1 h1 v-%4$s q0,-%3$s -%3$s,-%3$s h-%5$s q-%3$s,0 -%3$s,%3$sZ'; case !$invert && $check(0b10000000, 0b01010101): // 35 case $invert && $check(0b00000000, 0b11000001): return 'M%1$s,%2$s v1 h%4$s q%3$s,0 %3$s,-%3$s v-%5$s q0,-%3$s -%3$s,-%3$sZ'; case !$invert && $check(0b00000010, 0b01010101): // 57 case $invert && $check(0b00000000, 0b00000111): return '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-%4$sZ'; case !$invert && $check(0b00001000, 0b01010101): // 71 case $invert && $check(0b00000000, 0b00011100): return 'M%1$s,%2$s m1,1 v-1 h-%4$s q-%3$s,0 -%3$s,%3$s v%5$s q0,%3$s %3$s,%3$sZ'; // diagonal case $invert && $check(0b01000100, 0b00000000): // 15 return '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'; case $invert && $check(0b00010001, 0b00000000): // 37 return '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'; // 1 rounded case !$invert && $check(0b00101000, 0b01010101): // 1 case $invert && $check(0b00000000, 0b01111100): return 'M%1$s,%2$s m0,1 h1 v-1 h-%4$s q-%3$s,0 -%3$s,%3$sZ'; case !$invert && $check(0b10100000, 0b01010101): // 3 case $invert && $check(0b00000000, 0b11110001): return 'M%1$s,%2$s h%4$s q%3$s,0 %3$s,%3$s v%4$s h-1Z'; case !$invert && $check(0b10000010, 0b01010101): // 5 case $invert && $check(0b00000000, 0b11000111): return 'M%1$s,%2$s h1 v%4$s q0,%3$s -%3$s,%3$s h-%4$sZ'; case !$invert && $check(0b00001010, 0b01010101): // 7 case $invert && $check(0b00000000, 0b00011111): return 'M%1$s,%2$s v%4$s q0,%3$s %3$s,%3$s h%4$s v-1Z'; default: // full square return 'M%1$s,%2$s h1 v1 h-1Z'; } } protected function lightModule(Closure $check, bool $invert):string{ switch(true){ // 4 rounded case !$invert && $check(0b11111111, 0b01010101): case $invert && $check(0b10101010, 0b01010101): return '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'; // 3 rounded case !$invert && $check(0b10111111, 0b00000000): // 135 return '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'; case !$invert && $check(0b11111110, 0b00000000): // 357 return 'M%1$s,%2$s 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'; case !$invert && $check(0b11111011, 0b00000000): // 571 return 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sz m0,1 v-%3$s q0,%3$s %3$s,%3$sz m1,0 h-%3$s q%3$s,0 %3$s,-%3$sZ'; case !$invert && $check(0b11101111, 0b00000000): // 713 return 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sz m0,1 v-%3$s q0,%3$s %3$s,%3$sz m1,-1 v%3$s q0,-%3$s -%3$s,-%3$sZ'; // 2 rounded case !$invert && $check(0b10001111, 0b01110000): // 13 case $invert && $check(0b10001010, 0b01010101): return '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'; case !$invert && $check(0b00111110, 0b11000001): // 35 case $invert && $check(0b00101010, 0b01010101): return 'M%1$s,%2$s 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'; case !$invert && $check(0b11111000, 0b00000111): // 57 case $invert && $check(0b10101000, 0b01010101): return 'M%1$s,%2$s m1,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'; case !$invert && $check(0b11100011, 0b00011100): // 71 case $invert && $check(0b10100010, 0b01010101): return 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sz m0,1 v-%3$s q0,%3$s %3$s,%3$sZ'; // diagonal case !$invert && $check(0b10111011, 0b00000000): // 15 return 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sz m1,1 h-%3$s q%3$s,0 %3$s,-%3$sZ'; case !$invert && $check(0b11101110, 0b00000000): // 37 return 'M%1$s,%2$s m1,0 v%3$s q0,-%3$s -%3$s,-%3$sz m-1,1 v-%3$s q0,%3$s %3$s,%3$sZ'; // 1 rounded case !$invert && $check(0b10000011, 0b01111100): // 1 case $invert && $check(0b10000010, 0b01010101): return 'M%1$s,%2$s h%3$s q-%3$s,0 -%3$s,%3$sZ'; case !$invert && $check(0b00001110, 0b11110001): // 3 case $invert && $check(0b00001010, 0b01010101): return 'M%1$s,%2$s m1,0 v%3$s q0,-%3$s -%3$s,-%3$sZ'; case !$invert && $check(0b00111000, 0b11000111): // 5 case $invert && $check(0b00101000, 0b01010101): return 'M%1$s,%2$s m1,1 h-%3$s q%3$s,0 %3$s,-%3$sZ'; case !$invert && $check(0b11100000, 0b00011111): // 7 case $invert && $check(0b10100000, 0b01010101): return 'M%1$s,%2$s m0,1 v-%3$s q0,%3$s %3$s,%3$sZ'; default: // empty block return ''; } } protected function getFinderPatterns():string{ $qz = ($this->options->addQuietzone) ? $this->options->quietzoneSize : 0; // the positions for the finder patterns (top left corner) // $this->moduleCount includes 2* the quiet zone size already, so we need to take this into account $pos = [ [(0 + $qz), (0 + $qz)], [(0 + $qz), ($this->moduleCount - $qz - 7)], [($this->moduleCount - $qz - 7), (0 + $qz)], ]; // the custom paths for the finder pattern - the first move (M) is parametrized, the rest are relative coordinates. This is in order of top left, top right, bottom left. // Bottom Left $path[1] = 'M%1$s,%2$s m2,0 c-0.96,0,-1.73,0.77,-1.73,1.73v3.54c0,0.96,0.77,1.73,1.73,1.73h5.27v-5.27c0,-0.96,-0.77,-1.73,-1.73,-1.73zm4.27,1.73v4.27h-4.27c-0.4,0,-0.73,-0.33,-0.73,-0.73v-3.54c0,-0.4,0.33,-0.73,0.73,-0.73h3.54c0.4,0,0.73,0.33,0.73,0.73zm-3.4,0.27c-0.33,0,-0.6,0.27,-0.6,0.6v1.8c0,0.33,0.27,0.6,0.6,0.6h2.4v-2.4c0,-0.33,-0.27,-0.6,-0.6,-0.6z'; // Top Left $path[2] = 'M%1$s,%2$s m2,7 c-0.96,0,-1.73,-0.77,-1.73,-1.73v-3.54c0,-0.96,0.77,-1.73,1.73,-1.73h5.27v5.27c0,0.96,-0.77,1.73,-1.73,1.73zm4.27,-1.73v-4.27h-4.27c-0.4,0,-0.73,0.33,-0.73,0.73v3.54c0,0.4,0.33,0.73,0.73,0.73h3.54c0.4,0,0.73,-0.33,0.73,-0.73zm-3.4,-0.27c-0.33,0,-0.6,-0.27,-0.6,-0.6v-1.8c0,-0.33,0.27,-0.6,0.6,-0.6h2.4v2.4c0,0.33,-0.27,0.6,-0.6,0.6z'; // Top Right $path[3] = 'M%1$s,%2$s m5,0 c0.96,0,1.73,0.77,1.73,1.73v3.54c0,0.96,-0.77,1.73,-1.73,1.73h-5.27v-5.27c0,-0.96,0.77,-1.73,1.73,-1.73zm-4.27,1.73v4.27h4.27c0.4,0,0.73,-0.33,0.73,-0.73v-3.54c0,-0.4,-0.33,-0.73,-0.73,-0.73h-3.54c-0.4,0,-0.73,0.33,-0.73,0.73zm3.4,0.27c0.33,0,0.6,0.27,0.6,0.6v1.8c0,0.33,-0.27,0.6,-0.6,0.6h-2.4v-2.4c0,-0.33,0.27,-0.6,0.6,-0.6z'; $finder = []; $z = 1; foreach($pos as [$ix, $iy]){ $finder[] = sprintf($path[$z], $ix, $iy); $z += 1; } return sprintf( '%s<path class="%s" d="%s"/>', $this->options->eol, $this->getCssClass(QRMatrix::M_FINDER_DARK), implode('', $finder) ); } protected function getLogo():string{ // @todo: customize the <g> element to your liking (css class, style...) return sprintf( '%5$s<g transform="translate(%1$s %1$s) scale(%2$s)" class="%3$s">%5$s %4$s%5$s</g>', (($this->moduleCount - ($this->moduleCount * $this->options->svgLogoScale)) / 2), $this->options->svgLogoScale, $this->options->svgLogoCssClass, $this->options->svgLogo, $this->options->eol ); } } class QRSvgMeltedWithCustomFinderOptions extends QROptions{ // path to svg logo protected string $svgLogo; // logo scale in % of QR Code size, clamped to 10%-30% protected float $svgLogoScale = 0.20; // css class for the logo (defined in $svgDefs) protected string $svgLogoCssClass = ''; // enable the melt effect protected bool $melt = false; // whether to let the melt effect flow along the dark or light modules protected bool $inverseMelt = false; // the corner radius for melted modules protected float $meltRadius = 0.15; // clamp/set melt corner radius protected function set_meltRadius(float $meltRadius):void{ $this->meltRadius = max(0.01, min(0.5, $meltRadius)); } // check logo protected function set_svgLogo(string $svgLogo):void{ #if(!file_exists($svgLogo) || !is_readable($svgLogo)){ # throw new QRCodeException('invalid svg logo'); #} // @todo: validate svg $this->svgLogo = $svgLogo; } // clamp logo scale protected function set_svgLogoScale(float $svgLogoScale):void{ $this->svgLogoScale = max(0.05, min(0.3, $svgLogoScale)); } } class QRCodeService { public function generateQRCode(string $data, string $logoBlob = null): string { $options = new QRSvgMeltedWithCustomFinderOptions; // SVG Logo Options $options->svgLogo = '<image href="data:image/png;base64,'.$logoBlob.'" />'; $options->svgLogoScale = 0.3; $options->svgLogoCssClass = 'qr-logo dark'; // settings from the custom options class $options->version = 5; $options->quietzoneSize = 4; $options->melt = true; $options->inverseMelt = false; $options->meltRadius = 0.4; $options->outputType = QROutputInterface::CUSTOM; $options->outputInterface = QRSvgMeltedWithCustomFinder::class; $options->outputBase64 = false; $options->eccLevel = EccLevel::H; $options->addQuietzone = true; $options->svgDefs = ' <linearGradient id="rainbow" x1="100%" y2="100%"> <stop stop-color="#e2453c" offset="2.5%"/> <stop stop-color="#e07e39" offset="21.5%"/> <stop stop-color="#e5d667" offset="40.5%"/> <stop stop-color="#51b95b" offset="59.5%"/> <stop stop-color="#1e72b7" offset="78.5%"/> <stop stop-color="#6f5ba7" offset="97.5%"/> </linearGradient> <style><![CDATA[ .light, .dark{fill: url(#rainbow);} ]]></style>'; $svg = (new QRCode($options))->render($data); return $svg; } }
Here is my latest QR code SVG:
Beta Was this translation helpful? Give feedback.
All reactions
-
I think the <image> element needs the width and height attributes (I'm not an SVG expert though, you might need to experiment a bit with these attributes and translate/scale).
Beta Was this translation helpful? Give feedback.
All reactions
-
I finally got the logo to appear correctly, I set the png image to be 300x300 pixels as a standard size then reduced the size of the png by 13% and then set the height and width on the tag.
use Intervention\Image\Laravel\Facades\Image;
$imgScale = 0.13;
$image = Image::read($logoBlob);
$pngScaledWidth = $image->width() * $imgScale;
$pngScaledHeight = $image->height() * $imgScale;
$embedPng = '<image width="' . $pngScaledWidth . '" height="' . $pngScaledHeight . '" href="data:image/png;base64,' . $logoBlob . '" />';
and this is my getFinderPatterns function
protected function getFinderPatterns():string{
$qz = ($this->options->addQuietzone) ? $this->options->quietzoneSize : 0;
// the positions for the finder patterns (top left corner)
// $this->moduleCount includes 2* the quiet zone size already, so we need to take this into account
$pos = [
[(0 + $qz), (0 + $qz)],
[(0 + $qz), ($this->moduleCount - $qz - 7)],
[($this->moduleCount - $qz - 7), (0 + $qz)],
];
// the custom paths for the finder pattern - the first move (M) is parametrized, the rest are relative coordinates. This is in order of top left, top right, bottom left.
// Bottom Left
$path[1] = 'M%1$s,%2$s m2,0 c-0.96,0,-1.73,0.77,-1.73,1.73v3.54c0,0.96,0.77,1.73,1.73,1.73h5.27v-5.27c0,-0.96,-0.77,-1.73,-1.73,-1.73zm4.27,1.73v4.27h-4.27c-0.4,0,-0.73,-0.33,-0.73,-0.73v-3.54c0,-0.4,0.33,-0.73,0.73,-0.73h3.54c0.4,0,0.73,0.33,0.73,0.73zm-3.4,0.27c-0.33,0,-0.6,0.27,-0.6,0.6v1.8c0,0.33,0.27,0.6,0.6,0.6h2.4v-2.4c0,-0.33,-0.27,-0.6,-0.6,-0.6z';
// Top Left
$path[2] = 'M%1$s,%2$s m2,7 c-0.96,0,-1.73,-0.77,-1.73,-1.73v-3.54c0,-0.96,0.77,-1.73,1.73,-1.73h5.27v5.27c0,0.96,-0.77,1.73,-1.73,1.73zm4.27,-1.73v-4.27h-4.27c-0.4,0,-0.73,0.33,-0.73,0.73v3.54c0,0.4,0.33,0.73,0.73,0.73h3.54c0.4,0,0.73,-0.33,0.73,-0.73zm-3.4,-0.27c-0.33,0,-0.6,-0.27,-0.6,-0.6v-1.8c0,-0.33,0.27,-0.6,0.6,-0.6h2.4v2.4c0,0.33,-0.27,0.6,-0.6,0.6z';
// Top Right
$path[3] = 'M%1$s,%2$s m5,0 c0.96,0,1.73,0.77,1.73,1.73v3.54c0,0.96,-0.77,1.73,-1.73,1.73h-5.27v-5.27c0,-0.96,0.77,-1.73,1.73,-1.73zm-4.27,1.73v4.27h4.27c0.4,0,0.73,-0.33,0.73,-0.73v-3.54c0,-0.4,-0.33,-0.73,-0.73,-0.73h-3.54c-0.4,0,-0.73,0.33,-0.73,0.73zm3.4,0.27c0.33,0,0.6,0.27,0.6,0.6v1.8c0,0.33,-0.27,0.6,-0.6,0.6h-2.4v-2.4c0,-0.33,0.27,-0.6,0.6,-0.6z';
$finder = [];
$z = 1;
foreach($pos as [$ix, $iy]){
$finder[] = sprintf($path[$z], $ix, $iy);
$z += 1;
}
return sprintf(
'%s<path class="%s" d="%s"/>',
$this->options->eol,
$this->getCssClass(QRMatrix::M_FINDER_DARK),
implode(' ', $finder)
);
}
Beta Was this translation helpful? Give feedback.
All reactions
-
🎉 1