From 90f4a4bc665b4a7551c915c61791dd3052509d42 Mon Sep 17 00:00:00 2001 From: smiley Date: Mon, 16 Oct 2023 18:27:45 +0200 Subject: [PATCH] :octocat: significant output performance improvements --- examples/svgMeltedModules.php | 12 ++--- examples/svgRandomColoredDots.php | 20 ++++---- examples/svgRoundQuietzone.php | 14 +++--- examples/svgWithLogoAndCustomShapes.php | 2 +- src/Data/QRMatrix.php | 14 +++++- src/Output/QREps.php | 2 +- src/Output/QRFpdf.php | 2 +- src/Output/QRGdImage.php | 11 +++-- src/Output/QRImagick.php | 6 +-- src/Output/QRMarkupHTML.php | 6 +-- src/Output/QRMarkupSVG.php | 20 ++++---- src/Output/QROutputAbstract.php | 62 ++++++++++++++++++++----- src/Output/QRString.php | 7 +-- 13 files changed, 116 insertions(+), 62 deletions(-) diff --git a/examples/svgMeltedModules.php b/examples/svgMeltedModules.php index 7eceaa873..3cc0ea139 100644 --- a/examples/svgMeltedModules.php +++ b/examples/svgMeltedModules.php @@ -39,25 +39,25 @@ class MeltedSVGQRCodeOutput extends QRMarkupSVG{ */ protected function collectModules(Closure $transform):array{ $paths = []; + $melt = $this->options->melt; // avoid magic getter in long loops // collect the modules for each type - for($y = 0; $y < $this->moduleCount; $y++){ - for($x = 0; $x < $this->moduleCount; $x++){ - $M_TYPE = $this->matrix->get($x, $y); + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ $M_TYPE_LAYER = $M_TYPE; - if($this->options->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->options->excludeFromConnect)){ + 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->check($x, $y)){ + 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($this->options->melt){ + if($melt){ $M_TYPE_LAYER |= QRMatrix::IS_DARK; } diff --git a/examples/svgRandomColoredDots.php b/examples/svgRandomColoredDots.php index 6c509cd4f..4e89746e8 100644 --- a/examples/svgRandomColoredDots.php +++ b/examples/svgRandomColoredDots.php @@ -40,26 +40,28 @@ class RandomDotsSVGOutput extends QRMarkupSVG{ * @inheritDoc */ protected function collectModules(Closure $transform):array{ - $paths = []; + $paths = []; + $dotColors = $this->options->dotColors; // avoid magic getter in long loops // collect the modules for each type - for($y = 0; $y < $this->moduleCount; $y++){ - for($x = 0; $x < $this->moduleCount; $x++){ - $M_TYPE = $this->matrix->get($x, $y); + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ $M_TYPE_LAYER = $M_TYPE; - if($this->options->connectPaths - && !$this->matrix->checkTypeIn($x, $y, $this->options->excludeFromConnect) - ){ + 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 = $this->matrix->check($x, $y) ? QRMatrix::M_DATA_DARK : QRMatrix::M_DATA; + $M_TYPE_LAYER = QRMatrix::M_DATA; + + if($this->matrix->isDark($M_TYPE)){ + $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; + } } // randomly assign another $M_TYPE_LAYER for the given types // note that the layer id has to be an integer value, // ideally outside the several bitmask values if($M_TYPE_LAYER === QRMatrix::M_DATA_DARK){ - $M_TYPE_LAYER = array_rand($this->options->dotColors); + $M_TYPE_LAYER = array_rand($dotColors); } // collect the modules per $M_TYPE diff --git a/examples/svgRoundQuietzone.php b/examples/svgRoundQuietzone.php index 307af8e51..1ca58d7c6 100644 --- a/examples/svgRoundQuietzone.php +++ b/examples/svgRoundQuietzone.php @@ -174,19 +174,19 @@ class RoundQuietzoneSVGoutput extends QRMarkupSVG{ * @inheritDoc */ protected function collectModules(Closure $transform):array{ - $paths = []; + $paths = []; + $dotColors = $this->options->dotColors; // avoid magic getter in long loops // collect the modules for each type - for($y = 0; $y < $this->moduleCount; $y++){ - for($x = 0; $x < $this->moduleCount; $x++){ - $M_TYPE = $this->matrix->get($x, $y); + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ $M_TYPE_LAYER = $M_TYPE; - if(!$this->matrix->checkTypeIn($x, $y, $this->options->excludeFromConnect)){ + 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->check($x, $y)){ + if($this->matrix->isDark($M_TYPE)){ $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; } } @@ -195,7 +195,7 @@ class RoundQuietzoneSVGoutput extends QRMarkupSVG{ // note that the layer id has to be an integer value, // ideally outside the several bitmask values if($M_TYPE_LAYER === QRMatrix::M_DATA_DARK){ - $M_TYPE_LAYER = array_rand($this->options->dotColors); + $M_TYPE_LAYER = array_rand($dotColors); } // collect the modules per $M_TYPE diff --git a/examples/svgWithLogoAndCustomShapes.php b/examples/svgWithLogoAndCustomShapes.php index 9e3c72c0a..d639257ba 100644 --- a/examples/svgWithLogoAndCustomShapes.php +++ b/examples/svgWithLogoAndCustomShapes.php @@ -64,7 +64,7 @@ class QRSvgWithLogoAndCustomShapes extends QRMarkupSVG{ protected function module(int $x, int $y, int $M_TYPE):string{ if( - !$this->matrix->check($x, $y) + !$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) diff --git a/src/Data/QRMatrix.php b/src/Data/QRMatrix.php index 04e9d0fb0..440db54cc 100755 --- a/src/Data/QRMatrix.php +++ b/src/Data/QRMatrix.php @@ -356,7 +356,19 @@ class QRMatrix{ * Checks whether the module at ($x, $y) is true (dark) or false (light) */ public function check(int $x, int $y):bool{ - return $this->checkType($x, $y, $this::IS_DARK); + + if(!isset($this->matrix[$y][$x])){ + return false; + } + + return $this->isDark($this->get($x, $y)); + } + + /** + * Checks whether the given $M_TYPE is a dark value + */ + public function isDark(int $M_TYPE):bool{ + return ($M_TYPE & $this::IS_DARK) === $this::IS_DARK; } /** diff --git a/src/Output/QREps.php b/src/Output/QREps.php index 9aad6716a..9702b9d06 100644 --- a/src/Output/QREps.php +++ b/src/Output/QREps.php @@ -155,7 +155,7 @@ class QREps extends QROutputAbstract{ */ protected function module(int $x, int $y, int $M_TYPE):string{ - if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){ + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ return ''; } diff --git a/src/Output/QRFpdf.php b/src/Output/QRFpdf.php index 4554033c8..6b3732056 100644 --- a/src/Output/QRFpdf.php +++ b/src/Output/QRFpdf.php @@ -157,7 +157,7 @@ class QRFpdf extends QROutputAbstract{ */ protected function module(int $x, int $y, int $M_TYPE):void{ - if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){ + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ return; } diff --git a/src/Output/QRGdImage.php b/src/Output/QRGdImage.php index bee05fb55..c5e74c74a 100644 --- a/src/Output/QRGdImage.php +++ b/src/Output/QRGdImage.php @@ -66,6 +66,7 @@ class QRGdImage extends QROutputAbstract{ $this->matrix->invert(); } + $this->copyVars(); $this->setMatrixDimensions(); } @@ -227,7 +228,7 @@ class QRGdImage extends QROutputAbstract{ */ protected function createImage(){ - if($this->options->drawCircularModules && $this->options->scale < 20){ + if($this->drawCircularModules && $this->options->scale < 20){ // increase the initial image size by 10 $this->length *= 10; $this->scale *= 10; @@ -289,19 +290,19 @@ class QRGdImage extends QROutputAbstract{ */ protected function module(int $x, int $y, int $M_TYPE):void{ - if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){ + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ return; } $color = $this->getModuleValue($M_TYPE); - if($this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->options->keepAsSquare)){ + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ imagefilledellipse( $this->image, (($x * $this->scale) + intdiv($this->scale, 2)), (($y * $this->scale) + intdiv($this->scale, 2)), - (int)(2 * $this->options->circleRadius * $this->scale), - (int)(2 * $this->options->circleRadius * $this->scale), + (int)(2 * $this->circleRadius * $this->scale), + (int)(2 * $this->circleRadius * $this->scale), $color ); diff --git a/src/Output/QRImagick.php b/src/Output/QRImagick.php index 4ebffea1d..b28c00abd 100644 --- a/src/Output/QRImagick.php +++ b/src/Output/QRImagick.php @@ -209,17 +209,17 @@ class QRImagick extends QROutputAbstract{ */ protected function module(int $x, int $y, int $M_TYPE):void{ - if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){ + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ return; } $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE)); - if($this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->options->keepAsSquare)){ + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ $this->imagickDraw->circle( (($x + 0.5) * $this->scale), (($y + 0.5) * $this->scale), - (($x + 0.5 + $this->options->circleRadius) * $this->scale), + (($x + 0.5 + $this->circleRadius) * $this->scale), (($y + 0.5) * $this->scale) ); diff --git a/src/Output/QRMarkupHTML.php b/src/Output/QRMarkupHTML.php index cf6f8b3bb..4c249b6d6 100644 --- a/src/Output/QRMarkupHTML.php +++ b/src/Output/QRMarkupHTML.php @@ -27,14 +27,14 @@ class QRMarkupHTML extends QRMarkup{ $element = ''; $modules = array_map(fn(int $M_TYPE):string => sprintf($element, $this->getModuleValue($M_TYPE)), $row); - $rows[] = sprintf('
%s
%s', implode('', $modules), $this->options->eol); + $rows[] = sprintf('
%s
%s', implode('', $modules), $this->eol); } $html = sprintf( '
%3$s%2$s
%3$s', $this->getCssClass(), implode('', $rows), - $this->options->eol + $this->eol ); // wrap the snippet into a body when saving to file @@ -43,7 +43,7 @@ class QRMarkupHTML extends QRMarkup{ '%2$s%2$s%2$s'. 'QR Code%2$s%1$s%2$s', $html, - $this->options->eol + $this->eol ); } diff --git a/src/Output/QRMarkupSVG.php b/src/Output/QRMarkupSVG.php index cda20e7c4..3fbb3f56d 100644 --- a/src/Output/QRMarkupSVG.php +++ b/src/Output/QRMarkupSVG.php @@ -60,13 +60,13 @@ class QRMarkupSVG extends QRMarkup{ $svg = $this->header(); if(!empty($this->options->svgDefs)){ - $svg .= sprintf('%1$s%2$s%2$s', $this->options->svgDefs, $this->options->eol); + $svg .= sprintf('%1$s%2$s%2$s', $this->options->svgDefs, $this->eol); } $svg .= $this->paths(); // close svg - $svg .= sprintf('%1$s%1$s', $this->options->eol); + $svg .= sprintf('%1$s%1$s', $this->eol); // transform to data URI only when not saving to file if(!$saveToFile && $this->options->outputBase64){ @@ -100,11 +100,11 @@ class QRMarkupSVG extends QRMarkup{ $this->options->cssClass, $this->getViewBox(), $this->options->svgPreserveAspectRatio, - $this->options->eol + $this->eol ); if($this->options->svgAddXmlHeader){ - $header = sprintf('%s%s', $this->options->eol, $header); + $header = sprintf('%s%s', $this->eol, $header); } return $header; @@ -127,7 +127,7 @@ class QRMarkupSVG extends QRMarkup{ $chonks[] = implode(' ', $chunk); } - $path = implode($this->options->eol, $chonks); + $path = implode($this->eol, $chonks); if(empty($path)){ continue; @@ -136,7 +136,7 @@ class QRMarkupSVG extends QRMarkup{ $svg[] = $this->path($path, $M_TYPE); } - return implode($this->options->eol, $svg); + return implode($this->eol, $svg); } /** @@ -164,7 +164,7 @@ class QRMarkupSVG extends QRMarkup{ protected function getCssClass(int $M_TYPE = 0):string{ return implode(' ', [ 'qr-'.($this::LAYERNAMES[$M_TYPE] ?? $M_TYPE), - (($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK) ? 'dark' : 'light', + $this->matrix->isDark($M_TYPE) ? 'dark' : 'light', $this->options->cssClass, ]); } @@ -176,12 +176,12 @@ class QRMarkupSVG extends QRMarkup{ */ protected function module(int $x, int $y, int $M_TYPE):string{ - if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){ + if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){ return ''; } - if($this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->options->keepAsSquare)){ - $r = $this->options->circleRadius; + if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){ + $r = $this->circleRadius; return sprintf( 'M%1$s %2$s a%3$s %3$s 0 1 0 %4$s 0 a%3$s %3$s 0 1 0 -%4$s 0Z', diff --git a/src/Output/QROutputAbstract.php b/src/Output/QROutputAbstract.php index d4d36162c..133bc843d 100644 --- a/src/Output/QROutputAbstract.php +++ b/src/Output/QROutputAbstract.php @@ -27,13 +27,6 @@ abstract class QROutputAbstract implements QROutputInterface{ */ protected int $moduleCount; - /** - * the current scaling for a QR pixel - * - * @see \chillerlan\QRCode\QROptions::$scale - */ - protected int $scale; - /** * the side length of the QR image (modules * scale) */ @@ -54,6 +47,23 @@ abstract class QROutputAbstract implements QROutputInterface{ */ protected SettingsContainerInterface $options; + /** @see \chillerlan\QRCode\QROptions::$scale */ + protected int $scale; + /** @see \chillerlan\QRCode\QROptions::$connectPaths */ + protected bool $connectPaths; + /** @see \chillerlan\QRCode\QROptions::$excludeFromConnect */ + protected array $excludeFromConnect; + /** @see \chillerlan\QRCode\QROptions::$eol */ + protected string $eol; + /** @see \chillerlan\QRCode\QROptions::$drawLightModules */ + protected bool $drawLightModules; + /** @see \chillerlan\QRCode\QROptions::$drawCircularModules */ + protected bool $drawCircularModules; + /** @see \chillerlan\QRCode\QROptions::$keepAsSquare */ + protected array $keepAsSquare; + /** @see \chillerlan\QRCode\QROptions::$circleRadius */ + protected float $circleRadius; + /** * QROutputAbstract constructor. */ @@ -65,10 +75,35 @@ abstract class QROutputAbstract implements QROutputInterface{ $this->matrix->invert(); } + $this->copyVars(); $this->setMatrixDimensions(); $this->setModuleValues(); } + /** + * Creates copies of several QROptions values to avoid calling the magic getters + * in long loops for a significant performance increase. + * + * These variables are usually used in the "module" methods and are called up to 31329 times (at version 40). + */ + protected function copyVars():void{ + + $vars = [ + 'connectPaths', + 'excludeFromConnect', + 'eol', + 'drawLightModules', + 'drawCircularModules', + 'keepAsSquare', + 'circleRadius', + ]; + + foreach($vars as $property){ + $this->{$property} = $this->options->{$property}; + } + + } + /** * Sets/updates the matrix dimensions * @@ -193,14 +228,17 @@ abstract class QROutputAbstract implements QROutputInterface{ $paths = []; // collect the modules for each type - for($y = 0; $y < $this->moduleCount; $y++){ - for($x = 0; $x < $this->moduleCount; $x++){ - $M_TYPE = $this->matrix->get($x, $y); + foreach($this->matrix->getMatrix() as $y => $row){ + foreach($row as $x => $M_TYPE){ $M_TYPE_LAYER = $M_TYPE; - if($this->options->connectPaths && !$this->matrix->checkTypeIn($x, $y, $this->options->excludeFromConnect)){ + 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 = $this->matrix->check($x, $y) ? QRMatrix::M_DATA_DARK : QRMatrix::M_DATA; + $M_TYPE_LAYER = QRMatrix::M_DATA; + + if($this->matrix->isDark($M_TYPE)){ + $M_TYPE_LAYER = QRMatrix::M_DATA_DARK; + } } // collect the modules per $M_TYPE diff --git a/src/Output/QRString.php b/src/Output/QRString.php index db73b5fa5..3da83d6e2 100644 --- a/src/Output/QRString.php +++ b/src/Output/QRString.php @@ -64,7 +64,8 @@ class QRString extends QROutputAbstract{ * string output */ protected function text():string{ - $lines = []; + $lines = []; + $linestart = $this->options->textLineStart; for($y = 0; $y < $this->moduleCount; $y++){ $r = []; @@ -73,10 +74,10 @@ class QRString extends QROutputAbstract{ $r[] = $this->getModuleValueAt($x, $y); } - $lines[] = $this->options->textLineStart.implode('', $r); + $lines[] = $linestart.implode('', $r); } - return implode($this->options->eol, $lines); + return implode($this->eol, $lines); } /**