aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php')
-rw-r--r--vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php400
1 files changed, 400 insertions, 0 deletions
diff --git a/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
new file mode 100644
index 000000000..25db1c902
--- /dev/null
+++ b/vendor/chillerlan/php-qrcode/src/Output/QRGdImage.php
@@ -0,0 +1,400 @@
+<?php
+/**
+ * Class QRGdImage
+ *
+ * @created 05.12.2015
+ * @author Smiley <smiley@chillerlan.net>
+ * @copyright 2015 Smiley
+ * @license MIT
+ *
+ * @noinspection PhpComposerExtensionStubsInspection
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\Settings\SettingsContainerInterface;
+use ErrorException;
+use Throwable;
+use function array_values, count, extension_loaded, imagebmp, imagecolorallocate, imagecolortransparent,
+ imagecreatetruecolor, imagedestroy, imagefilledellipse, imagefilledrectangle, imagegif, imagejpeg, imagepng,
+ imagescale, imagetypes, imagewebp, intdiv, intval, is_array, is_numeric, max, min, ob_end_clean, ob_get_contents, ob_start,
+ restore_error_handler, set_error_handler, sprintf;
+use const IMG_BMP, IMG_GIF, IMG_JPG, IMG_PNG, IMG_WEBP;
+
+/**
+ * Converts the matrix into GD images, raw or base64 output (requires ext-gd)
+ *
+ * @see https://php.net/manual/book.image.php
+ *
+ * @deprecated 5.0.0 this class will be made abstract in future versions,
+ * calling it directly is deprecated - use one of the child classes instead
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ */
+class QRGdImage extends QROutputAbstract{
+
+ /**
+ * The GD image resource
+ *
+ * @see imagecreatetruecolor()
+ * @var resource|\GdImage
+ *
+ * @todo: add \GdImage type in v6
+ */
+ protected $image;
+
+ /**
+ * The allocated background color
+ *
+ * @see \imagecolorallocate()
+ */
+ protected int $background;
+
+ /**
+ * Whether we're running in upscale mode (scale < 20)
+ *
+ * @see \chillerlan\QRCode\QROptions::$drawCircularModules
+ */
+ protected bool $upscaled = false;
+
+ /**
+ * @inheritDoc
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ * @noinspection PhpMissingParentConstructorInspection
+ */
+ public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
+ $this->options = $options;
+ $this->matrix = $matrix;
+
+ $this->checkGD();
+
+ if($this->options->invertMatrix){
+ $this->matrix->invert();
+ }
+
+ $this->copyVars();
+ $this->setMatrixDimensions();
+ }
+
+ /**
+ * 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');
+ }
+
+ $modes = [
+ self::GDIMAGE_BMP => IMG_BMP,
+ self::GDIMAGE_GIF => IMG_GIF,
+ self::GDIMAGE_JPG => IMG_JPG,
+ self::GDIMAGE_PNG => IMG_PNG,
+ self::GDIMAGE_WEBP => IMG_WEBP,
+ ];
+
+ // likely using default or custom output
+ if(!isset($modes[$this->options->outputType])){
+ return;
+ }
+
+ $mode = $modes[$this->options->outputType];
+
+ if((imagetypes() & $mode) !== $mode){
+ throw new QRCodeOutputException(sprintf('output mode "%s" not supported', $this->options->outputType));
+ }
+
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function moduleValueIsValid($value):bool{
+
+ if(!is_array($value) || count($value) < 3){
+ return false;
+ }
+
+ // check the first 3 values of the array
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 2){
+ break;
+ }
+
+ if(!is_numeric($val)){
+ return false;
+ }
+
+ }
+
+ return true;
+ }
+
+ /**
+ * @param array $value
+ *
+ * @inheritDoc
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
+ protected function prepareModuleValue($value):int{
+ $values = [];
+
+ foreach(array_values($value) as $i => $val){
+
+ if($i > 2){
+ break;
+ }
+
+ $values[] = max(0, min(255, intval($val)));
+ }
+
+ /** @phan-suppress-next-line PhanParamTooFewInternalUnpack */
+ $color = imagecolorallocate($this->image, ...$values);
+
+ if($color === false){
+ throw new QRCodeOutputException('could not set color: imagecolorallocate() error');
+ }
+
+ return $color;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getDefaultModuleValue(bool $isDark):int{
+ return $this->prepareModuleValue(($isDark) ? [0, 0, 0] : [255, 255, 255]);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return string|resource|\GdImage
+ *
+ * @phan-suppress PhanUndeclaredTypeReturnType, PhanTypeMismatchReturn
+ * @throws \ErrorException
+ */
+ public function dump(?string $file = null){
+
+ set_error_handler(function(int $errno, string $errstr):bool{
+ throw new ErrorException($errstr, $errno);
+ });
+
+ $this->image = $this->createImage();
+ // set module values after image creation because we need the GdImage instance
+ $this->setModuleValues();
+ $this->setBgColor();
+
+ imagefilledrectangle($this->image, 0, 0, $this->length, $this->length, $this->background);
+
+ $this->drawImage();
+
+ if($this->upscaled){
+ // scale down to the expected size
+ $this->image = imagescale($this->image, ($this->length / 10), ($this->length / 10));
+ $this->upscaled = false;
+ }
+
+ // set transparency after scaling, otherwise it would be undone
+ // @see https://www.php.net/manual/en/function.imagecolortransparent.php#77099
+ $this->setTransparencyColor();
+
+ if($this->options->returnResource){
+ restore_error_handler();
+
+ return $this->image;
+ }
+
+ $imageData = $this->dumpImage();
+
+ $this->saveToFile($imageData, $file);
+
+ if($this->options->outputBase64){
+ // @todo: remove mime parameter in v6
+ $imageData = $this->toBase64DataURI($imageData, 'image/'.$this->options->outputType);
+ }
+
+ restore_error_handler();
+
+ return $imageData;
+ }
+
+ /**
+ * 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->drawCircularModules && $this->options->gdImageUseUpscale && $this->options->scale < 20){
+ // increase the initial image size by 10
+ $this->length *= 10;
+ $this->scale *= 10;
+ $this->upscaled = true;
+ }
+
+ return imagecreatetruecolor($this->length, $this->length);
+ }
+
+ /**
+ * Sets the background color
+ */
+ protected function setBgColor():void{
+
+ if(isset($this->background)){
+ return;
+ }
+
+ if($this::moduleValueIsValid($this->options->bgColor)){
+ $this->background = $this->prepareModuleValue($this->options->bgColor);
+
+ return;
+ }
+
+ $this->background = $this->prepareModuleValue([255, 255, 255]);
+ }
+
+ /**
+ * Sets the transparency color
+ */
+ protected function setTransparencyColor():void{
+
+ // @todo: the jpg skip can be removed in v6
+ if($this->options->outputType === QROutputInterface::GDIMAGE_JPG || !$this->options->imageTransparent){
+ return;
+ }
+
+ $transparencyColor = $this->background;
+
+ if($this::moduleValueIsValid($this->options->transparencyColor)){
+ $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor);
+ }
+
+ imagecolortransparent($this->image, $transparencyColor);
+ }
+
+ /**
+ * Draws the QR image
+ */
+ protected function drawImage():void{
+ foreach($this->matrix->getMatrix() as $y => $row){
+ foreach($row as $x => $M_TYPE){
+ $this->module($x, $y, $M_TYPE);
+ }
+ }
+ }
+
+ /**
+ * Creates a single QR pixel with the given settings
+ */
+ protected function module(int $x, int $y, int $M_TYPE):void{
+
+ if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
+ return;
+ }
+
+ $color = $this->getModuleValue($M_TYPE);
+
+ 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)($this->circleDiameter * $this->scale),
+ (int)($this->circleDiameter * $this->scale),
+ $color
+ );
+
+ return;
+ }
+
+ imagefilledrectangle(
+ $this->image,
+ ($x * $this->scale),
+ ($y * $this->scale),
+ (($x + 1) * $this->scale),
+ (($y + 1) * $this->scale),
+ $color
+ );
+ }
+
+ /**
+ * Renders the image with the gdimage function for the desired output
+ *
+ * @see \imagebmp()
+ * @see \imagegif()
+ * @see \imagejpeg()
+ * @see \imagepng()
+ * @see \imagewebp()
+ *
+ * @todo: v6.0: make abstract and call from child classes
+ * @see https://github.com/chillerlan/php-qrcode/issues/223
+ * @codeCoverageIgnore
+ */
+ protected function renderImage():void{
+
+ switch($this->options->outputType){
+ case QROutputInterface::GDIMAGE_BMP:
+ imagebmp($this->image, null, ($this->options->quality > 0));
+ break;
+ case QROutputInterface::GDIMAGE_GIF:
+ imagegif($this->image);
+ break;
+ case QROutputInterface::GDIMAGE_JPG:
+ 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->quality)));
+ }
+
+ }
+
+ /**
+ * Creates the final image by calling the desired GD output function
+ *
+ * @throws \chillerlan\QRCode\Output\QRCodeOutputException
+ */
+ protected function dumpImage():string{
+ $exception = null;
+ $imageData = null;
+
+ ob_start();
+
+ try{
+ $this->renderImage();
+
+ $imageData = ob_get_contents();
+ imagedestroy($this->image);
+ }
+ // not going to cover edge cases
+ // @codeCoverageIgnoreStart
+ catch(Throwable $e){
+ $exception = $e;
+ }
+ // @codeCoverageIgnoreEnd
+
+ ob_end_clean();
+
+ // throw here in case an exception happened within the output buffer
+ if($exception instanceof Throwable){
+ throw new QRCodeOutputException($exception->getMessage());
+ }
+
+ return $imageData;
+ }
+
+}