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

This commit is contained in:
smiley
2023-09-05 03:04:08 +02:00
parent c69531ca20
commit 6c135ba981
5 changed files with 195 additions and 41 deletions
+61 -17
View File
@@ -14,10 +14,12 @@ namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
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)
@@ -55,18 +57,56 @@ class QRGdImage extends QROutputAbstract{
* @noinspection PhpMissingParentConstructorInspection
*/
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
if(!extension_loaded('gd')){
throw new QRCodeOutputException('ext-gd not loaded'); // @codeCoverageIgnore
}
$this->options = $options;
$this->matrix = $matrix;
$this->checkGD();
$this->setMatrixDimensions();
// 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
$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')){
throw new QRCodeOutputException('ext-gd not loaded');
}
$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));
}
}
/**
* 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(){
if($this->options->drawCircularModules && $this->options->scale < 20){
// increase the initial image size by 10
$this->length = (($this->length + 2) * 10);
@@ -74,9 +114,7 @@ class QRGdImage extends QROutputAbstract{
$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{
foreach($this->matrix->getMatrix() as $y => $row){
@@ -282,16 +320,22 @@ class QRGdImage extends QROutputAbstract{
try{
switch($this->options->outputType){
case QROutputInterface::GDIMAGE_BMP:
imagebmp($this->image);
break;
case QROutputInterface::GDIMAGE_GIF:
imagegif($this->image);
break;
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;
// silently default to png output
case QROutputInterface::GDIMAGE_PNG:
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();
+27 -21
View File
@@ -18,27 +18,31 @@ use chillerlan\QRCode\Data\QRMatrix;
interface QROutputInterface{
/** @var string */
public const MARKUP_HTML = 'html';
public const MARKUP_HTML = 'html';
/** @var string */
public const MARKUP_SVG = 'svg';
public const MARKUP_SVG = 'svg';
/** @var string */
public const GDIMAGE_PNG = 'png';
public const GDIMAGE_BMP = 'bmp';
/** @var string */
public const GDIMAGE_JPG = 'jpg';
public const GDIMAGE_GIF = 'gif';
/** @var string */
public const GDIMAGE_GIF = 'gif';
public const GDIMAGE_JPG = 'jpg';
/** @var string */
public const STRING_JSON = 'json';
public const GDIMAGE_PNG = 'png';
/** @var string */
public const STRING_TEXT = 'text';
public const GDIMAGE_WEBP = 'webp';
/** @var string */
public const IMAGICK = 'imagick';
public const STRING_JSON = 'json';
/** @var string */
public const FPDF = 'fpdf';
public const STRING_TEXT = 'text';
/** @var string */
public const EPS = 'eps';
public const IMAGICK = 'imagick';
/** @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
@@ -46,16 +50,18 @@ interface QROutputInterface{
* @var string[]
*/
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
View File
@@ -89,7 +89,7 @@ trait QROptionsTrait{
* The built-in output type
*
* - 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::IMAGICK
* - QROutputInterface::EPS
@@ -268,18 +268,32 @@ trait QROptionsTrait{
*/
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
*/
/**
* @see imagepng()
* @deprecated 5.0.0 use QROptions::$quality instead
* @see \chillerlan\QRCode\QROptions::$quality
*/
protected int $pngCompression = -1;
/**
* @see imagejpeg()
* @deprecated 5.0.0 use QROptions::$quality instead
* @see \chillerlan\QRCode\QROptions::$quality
*/
protected int $jpegQuality = 85;
@@ -541,6 +555,7 @@ trait QROptionsTrait{
*
* @deprecated 5.0.0 use QROptions::$outputBase64 instead
* @see \chillerlan\QRCode\QROptions::$outputBase64
* @codeCoverageIgnore
*/
protected function set_imageBase64(bool $imageBase64):void{
$this->outputBase64 = $imageBase64;
@@ -551,9 +566,54 @@ trait QROptionsTrait{
*
* @deprecated 5.0.0 use QROptions::$outputBase64 instead
* @see \chillerlan\QRCode\QROptions::$outputBase64
* @codeCoverageIgnore
*/
protected function get_imageBase64():bool{
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
View File
@@ -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
View File
@@ -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;
}