Bläddra i källkod

:octocat: QRGdImage: add BMP and WEBP support, deprecate QROptionsTrait::$jpegQuality and QROptionsTrait::$pngCompression in favor of QROptionsTrait::$quality

smiley 2 år sedan
förälder
incheckning
6c135ba981

+ 60 - 16
src/Output/QRGdImage.php

@@ -14,10 +14,12 @@ namespace chillerlan\QRCode\Output;
 
 
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\QRCode\Data\QRMatrix;
 use chillerlan\Settings\SettingsContainerInterface;
 use chillerlan\Settings\SettingsContainerInterface;
-use ErrorException, Throwable;
-use function array_values, count, extension_loaded, imagecolorallocate, imagecolortransparent, imagecreatetruecolor,
-	imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng, imagescale, intdiv, intval,
-	is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start, restore_error_handler, set_error_handler;
+use ErrorException;
+use Throwable;
+use function array_values, count, extension_loaded, gd_info, imagebmp, imagecolorallocate, imagecolortransparent,
+	imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng,
+	imagescale, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start,
+	restore_error_handler, set_error_handler, sprintf;
 
 
 /**
 /**
  * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
  * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
@@ -55,18 +57,56 @@ class QRGdImage extends QROutputAbstract{
 	 * @noinspection PhpMissingParentConstructorInspection
 	 * @noinspection PhpMissingParentConstructorInspection
 	 */
 	 */
 	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
 	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
+		$this->options = $options;
+		$this->matrix  = $matrix;
+
+		$this->checkGD();
+		$this->setMatrixDimensions();
+
+		$this->image = $this->createImage();
+		// set module values after image creation because we need the GdImage instance
+		$this->setModuleValues();
+	}
+
+	/**
+	 * Checks whether GD is installed and if the given mode is supported
+	 *
+	 * @return void
+	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+	 * @codeCoverageIgnore
+	 */
+	protected function checkGD():void{
 
 
 		if(!extension_loaded('gd')){
 		if(!extension_loaded('gd')){
-			throw new QRCodeOutputException('ext-gd not loaded'); // @codeCoverageIgnore
+			throw new QRCodeOutputException('ext-gd not loaded');
 		}
 		}
 
 
-		$this->options = $options;
-		$this->matrix  = $matrix;
+		$info = gd_info();
+		$mode = [
+			        self::GDIMAGE_BMP  => 'BMP Support',
+			        self::GDIMAGE_GIF  => 'GIF Create Support',
+			        self::GDIMAGE_JPG  => 'JPEG Support',
+			        self::GDIMAGE_PNG  => 'PNG Support',
+			        self::GDIMAGE_WEBP => 'WebP Support',
+		        ][$this->options->outputType];
+
+		if(!isset($info[$mode]) || $info[$mode] !== true){
+			throw new QRCodeOutputException(sprintf('output mode "%s" not supported', $this->options->outputType));
+		}
 
 
-		$this->setMatrixDimensions();
+	}
+
+	/**
+	 * Creates a new GdImage resource and scales it if necessary
+	 *
+	 * we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
+	 *
+	 * @see https://github.com/chillerlan/php-qrcode/issues/23
+	 *
+	 * @return \GdImage|resource
+	 */
+	protected function createImage(){
 
 
-		// we're scaling the image up in order to draw crisp round circles, otherwise they appear square-y on small scales
-		// @see https://github.com/chillerlan/php-qrcode/issues/23
 		if($this->options->drawCircularModules && $this->options->scale < 20){
 		if($this->options->drawCircularModules && $this->options->scale < 20){
 			// increase the initial image size by 10
 			// increase the initial image size by 10
 			$this->length    = (($this->length + 2) * 10);
 			$this->length    = (($this->length + 2) * 10);
@@ -74,9 +114,7 @@ class QRGdImage extends QROutputAbstract{
 			$this->upscaled  = true;
 			$this->upscaled  = true;
 		}
 		}
 
 
-		$this->image = imagecreatetruecolor($this->length, $this->length);
-		// set module values after image creation because we need the GdImage instance
-		$this->setModuleValues();
+		return imagecreatetruecolor($this->length, $this->length);
 	}
 	}
 
 
 	/**
 	/**
@@ -225,7 +263,7 @@ class QRGdImage extends QROutputAbstract{
 	}
 	}
 
 
 	/**
 	/**
-	 * Creates the QR image
+	 * Draws the QR image
 	 */
 	 */
 	protected function drawImage():void{
 	protected function drawImage():void{
 		foreach($this->matrix->getMatrix() as $y => $row){
 		foreach($this->matrix->getMatrix() as $y => $row){
@@ -282,16 +320,22 @@ class QRGdImage extends QROutputAbstract{
 		try{
 		try{
 
 
 			switch($this->options->outputType){
 			switch($this->options->outputType){
+				case QROutputInterface::GDIMAGE_BMP:
+					imagebmp($this->image);
+					break;
 				case QROutputInterface::GDIMAGE_GIF:
 				case QROutputInterface::GDIMAGE_GIF:
 					imagegif($this->image);
 					imagegif($this->image);
 					break;
 					break;
 				case QROutputInterface::GDIMAGE_JPG:
 				case QROutputInterface::GDIMAGE_JPG:
-					imagejpeg($this->image, null, max(0, min(100, $this->options->jpegQuality)));
+					imagejpeg($this->image, null, max(-1, min(100, $this->options->quality)));
+					break;
+				case QROutputInterface::GDIMAGE_WEBP:
+					imagewebp($this->image, null, max(-1, min(100, $this->options->quality)));
 					break;
 					break;
 				// silently default to png output
 				// silently default to png output
 				case QROutputInterface::GDIMAGE_PNG:
 				case QROutputInterface::GDIMAGE_PNG:
 				default:
 				default:
-					imagepng($this->image, null, max(-1, min(9, $this->options->pngCompression)));
+					imagepng($this->image, null, max(-1, min(9, $this->options->quality)));
 			}
 			}
 
 
 			$imageData = ob_get_contents();
 			$imageData = ob_get_contents();

+ 27 - 21
src/Output/QROutputInterface.php

@@ -18,27 +18,31 @@ use chillerlan\QRCode\Data\QRMatrix;
 interface QROutputInterface{
 interface QROutputInterface{
 
 
 	/** @var string */
 	/** @var string */
-	public const MARKUP_HTML = 'html';
+	public const MARKUP_HTML  = 'html';
 	/** @var string */
 	/** @var string */
-	public const MARKUP_SVG  = 'svg';
+	public const MARKUP_SVG   = 'svg';
 	/** @var string */
 	/** @var string */
-	public const GDIMAGE_PNG = 'png';
+	public const GDIMAGE_BMP  = 'bmp';
 	/** @var string */
 	/** @var string */
-	public const GDIMAGE_JPG = 'jpg';
+	public const GDIMAGE_GIF  = 'gif';
 	/** @var string */
 	/** @var string */
-	public const GDIMAGE_GIF = 'gif';
+	public const GDIMAGE_JPG  = 'jpg';
 	/** @var string */
 	/** @var string */
-	public const STRING_JSON = 'json';
+	public const GDIMAGE_PNG  = 'png';
 	/** @var string */
 	/** @var string */
-	public const STRING_TEXT = 'text';
+	public const GDIMAGE_WEBP = 'webp';
 	/** @var string */
 	/** @var string */
-	public const IMAGICK     = 'imagick';
+	public const STRING_JSON  = 'json';
 	/** @var string */
 	/** @var string */
-	public const FPDF        = 'fpdf';
+	public const STRING_TEXT  = 'text';
 	/** @var string */
 	/** @var string */
-	public const EPS         = 'eps';
+	public const IMAGICK      = 'imagick';
 	/** @var string */
 	/** @var string */
-	public const CUSTOM      = 'custom';
+	public const FPDF         = 'fpdf';
+	/** @var string */
+	public const EPS          = 'eps';
+	/** @var string */
+	public const CUSTOM       = 'custom';
 
 
 	/**
 	/**
 	 * Map of built-in output modes => class FQN
 	 * Map of built-in output modes => class FQN
@@ -46,16 +50,18 @@ interface QROutputInterface{
 	 * @var string[]
 	 * @var string[]
 	 */
 	 */
 	public const MODES = [
 	public const MODES = [
-		self::MARKUP_SVG  => QRMarkupSVG::class,
-		self::MARKUP_HTML => QRMarkupHTML::class,
-		self::GDIMAGE_PNG => QRGdImage::class,
-		self::GDIMAGE_GIF => QRGdImage::class,
-		self::GDIMAGE_JPG => QRGdImage::class,
-		self::STRING_JSON => QRString::class,
-		self::STRING_TEXT => QRString::class,
-		self::IMAGICK     => QRImagick::class,
-		self::FPDF        => QRFpdf::class,
-		self::EPS         => QREps::class,
+		self::MARKUP_SVG   => QRMarkupSVG::class,
+		self::MARKUP_HTML  => QRMarkupHTML::class,
+		self::GDIMAGE_BMP  => QRGdImage::class,
+		self::GDIMAGE_GIF  => QRGdImage::class,
+		self::GDIMAGE_JPG  => QRGdImage::class,
+		self::GDIMAGE_PNG  => QRGdImage::class,
+		self::GDIMAGE_WEBP => QRGdImage::class,
+		self::STRING_JSON  => QRString::class,
+		self::STRING_TEXT  => QRString::class,
+		self::IMAGICK      => QRImagick::class,
+		self::FPDF         => QRFpdf::class,
+		self::EPS          => QREps::class,
 	];
 	];
 
 
 	/**
 	/**

+ 63 - 3
src/QROptionsTrait.php

@@ -89,7 +89,7 @@ trait QROptionsTrait{
 	 * The built-in output type
 	 * The built-in output type
 	 *
 	 *
 	 *   - QROutputInterface::MARKUP_XXXX where XXXX = HTML, SVG
 	 *   - QROutputInterface::MARKUP_XXXX where XXXX = HTML, SVG
-	 *   - QROutputInterface::GDIMAGE_XXX where XXX = PNG, GIF, JPG
+	 *   - QROutputInterface::GDIMAGE_XXX where XXX = BMP, GIF, JPG, PNG, WEBP
 	 *   - QROutputInterface::STRING_XXXX where XXXX = TEXT, JSON
 	 *   - QROutputInterface::STRING_XXXX where XXXX = TEXT, JSON
 	 *   - QROutputInterface::IMAGICK
 	 *   - QROutputInterface::IMAGICK
 	 *   - QROutputInterface::EPS
 	 *   - QROutputInterface::EPS
@@ -268,18 +268,32 @@ trait QROptionsTrait{
 	 */
 	 */
 	protected $transparencyColor = null;
 	protected $transparencyColor = null;
 
 
+	/**
+	 * Compression quality
+	 *
+	 * The given value depends on the used output type:
+	 *
+	 * @see \imagejpeg()
+	 * @see \imagepng()
+	 * @see \imagewebp()
+	 * @see \Imagick::setImageCompressionQuality()
+	 */
+	protected int $quality = -1;
+
 
 
 	/*
 	/*
 	 * QRGdImage settings
 	 * QRGdImage settings
 	 */
 	 */
 
 
 	/**
 	/**
-	 * @see imagepng()
+	 * @deprecated 5.0.0 use QROptions::$quality instead
+	 * @see        \chillerlan\QRCode\QROptions::$quality
 	 */
 	 */
 	protected int $pngCompression = -1;
 	protected int $pngCompression = -1;
 
 
 	/**
 	/**
-	 * @see imagejpeg()
+	 * @deprecated 5.0.0 use QROptions::$quality instead
+	 * @see        \chillerlan\QRCode\QROptions::$quality
 	 */
 	 */
 	protected int $jpegQuality = 85;
 	protected int $jpegQuality = 85;
 
 
@@ -541,6 +555,7 @@ trait QROptionsTrait{
 	 *
 	 *
 	 * @deprecated 5.0.0 use QROptions::$outputBase64 instead
 	 * @deprecated 5.0.0 use QROptions::$outputBase64 instead
 	 * @see        \chillerlan\QRCode\QROptions::$outputBase64
 	 * @see        \chillerlan\QRCode\QROptions::$outputBase64
+	 * @codeCoverageIgnore
 	 */
 	 */
 	protected function set_imageBase64(bool $imageBase64):void{
 	protected function set_imageBase64(bool $imageBase64):void{
 		$this->outputBase64 = $imageBase64;
 		$this->outputBase64 = $imageBase64;
@@ -551,9 +566,54 @@ trait QROptionsTrait{
 	 *
 	 *
 	 * @deprecated 5.0.0 use QROptions::$outputBase64 instead
 	 * @deprecated 5.0.0 use QROptions::$outputBase64 instead
 	 * @see        \chillerlan\QRCode\QROptions::$outputBase64
 	 * @see        \chillerlan\QRCode\QROptions::$outputBase64
+	 * @codeCoverageIgnore
 	 */
 	 */
 	protected function get_imageBase64():bool{
 	protected function get_imageBase64():bool{
 		return $this->outputBase64;
 		return $this->outputBase64;
 	}
 	}
 
 
+	/**
+	 * redirect call to the new variable
+	 *
+	 * @deprecated 5.0.0 use QROptions::$quality instead
+	 * @see        \chillerlan\QRCode\QROptions::$quality
+	 * @codeCoverageIgnore
+	 */
+	protected function set_jpegQuality(bool $jpegQuality):void{
+		$this->quality = $jpegQuality;
+	}
+
+	/**
+	 * redirect call to the new variable
+	 *
+	 * @deprecated 5.0.0 use QROptions::$quality instead
+	 * @see        \chillerlan\QRCode\QROptions::$quality
+	 * @codeCoverageIgnore
+	 */
+	protected function get_jpegQuality():bool{
+		return $this->quality;
+	}
+
+	/**
+	 * redirect call to the new variable
+	 *
+	 * @deprecated 5.0.0 use QROptions::$quality instead
+	 * @see        \chillerlan\QRCode\QROptions::$quality
+	 * @codeCoverageIgnore
+	 */
+	protected function set_pngCompression(bool $pngCompression):void{
+		$this->quality = $pngCompression;
+	}
+
+	/**
+	 * redirect call to the new variable
+	 *
+	 * @deprecated 5.0.0 use QROptions::$quality instead
+	 * @see        \chillerlan\QRCode\QROptions::$quality
+	 * @codeCoverageIgnore
+	 */
+	protected function get_pngCompression():bool{
+		return $this->quality;
+	}
+
 }
 }

+ 22 - 0
tests/Output/QRGdImageBMPTest.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * Class QRGdImageBMPTest
+ *
+ * @created      05.09.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Output;
+
+use chillerlan\QRCode\Output\QROutputInterface;
+
+/**
+ *
+ */
+final class QRGdImageBMPTest extends QRGdImageTestAbstract{
+
+	protected string $type = QROutputInterface::GDIMAGE_BMP;
+
+}

+ 22 - 0
tests/Output/QRGdImageWEBPTest.php

@@ -0,0 +1,22 @@
+<?php
+/**
+ * Class QRGdImageWEBPTest
+ *
+ * @created      05.09.2023
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2023 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Output;
+
+use chillerlan\QRCode\Output\QROutputInterface;
+
+/**
+ *
+ */
+final class QRGdImageWEBPTest extends QRGdImageTestAbstract{
+
+	protected string $type = QROutputInterface::GDIMAGE_WEBP;
+
+}