PHP Classes

File: src/Contracts/Customs/AbstractWriter.php

Recommend this page to a friend!
  Packages of Amirreza Ebrahimi   HeroQR Powerful PHP QR Code Library to generate PNG, SVG, PDF, Logos Ready to Use with Laravel   src/Contracts/Customs/AbstractWriter.php   Download  
File: src/Contracts/Customs/AbstractWriter.php
Role: Class source
Content type: text/plain
Description: Class source
Class: HeroQR Powerful PHP QR Code Library to generate PNG, SVG, PDF, Logos Ready to Use with Laravel
Generate QR code images in several formats
Author: By
Last change: Fix alpha conversion for GD: map 0–255 hex alpha to 0–127
Date: 6 months ago
Size: 10,675 bytes
 

Contents

Class file image Download
<?php declare(strict_types=1); namespace HeroQR\Contracts\Customs; use Endroid\QrCode\{QrCodeInterface, RoundBlockSizeMode}; use Endroid\QrCode\{Logo\LogoInterface, Matrix\MatrixInterface}; use Endroid\QrCode\ImageData\{LabelImageData, LogoImageData}; use Endroid\QrCode\Label\{LabelAlignment, LabelInterface}; use Endroid\QrCode\Writer\{AbstractGdWriter, WriterInterface}; use Endroid\QrCode\Writer\Result\{GdResult, ResultInterface}; use HeroQR\Customs\{ImageOverlay, ShapePaths}; /** * The AbstractWriter class is responsible for generating QR code images using the GD library * This class provides base functionality for creating QR code images with options like logo and label embedding * Subclasses should implement specific logic for rendering QR codes using the GD image processing library * * @package HeroQR\Contracts\Customs */ readonly abstract class AbstractWriter extends AbstractGdWriter implements WriterInterface { private const QUALITY_MULTIPLIER = 10; private ImageOverlay $imageOverlay; protected string $blockShape; public function __construct($background, $overlay, $blockShape) { $this->imageOverlay = new ImageOverlay($background, $overlay); $this->blockShape = ShapePaths::getValueByKey($blockShape) ?? 'drawSquare'; } /** * Writes a QR code image with optional logo and label * * @param QrCodeInterface $qrCode The QR code to be generated * @param LogoInterface|null $logo The logo to be embedded in the QR code (optional) * @param LabelInterface|null $label The label to be added to the QR code (optional) * @param array $options Additional options for the QR code generation * * @return ResultInterface The result containing the generated QR code image * * @throws \Exception If the GD extension is not loaded */ public function write( QrCodeInterface $qrCode, ?LogoInterface $logo = null, ?LabelInterface $label = null, array $options = [] ): ResultInterface { if (!extension_loaded('gd')) { throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly'); } $matrix = $this->getMatrix($qrCode); $baseBlockSize = (RoundBlockSizeMode::Margin === $qrCode->getRoundBlockSizeMode() ? 10 : intval($matrix->getBlockSize())) * self::QUALITY_MULTIPLIER; $baseImage = $this->createBaseImage($matrix, $baseBlockSize); $foregroundColor = $this->allocateForegroundColor($baseImage, $qrCode); Drawer::drawQrCode($baseImage, $matrix, $baseBlockSize, $foregroundColor, $qrCode, $this->blockShape, $logo, $this->imageOverlay); $targetImage = $this->createTargetImage($matrix, $qrCode, $label); Drawer::copyResampledImage( $targetImage, $baseImage, ['X' => $matrix->getMarginLeft(), 'Y' => $matrix->getMarginRight(), 'Width' => $matrix->getInnerSize(), 'Height' => $matrix->getInnerSize()], ['X' => 0, 'Y' => 0, 'Width' => imagesy($baseImage), 'Height' => imagesy($baseImage)] ); $this->destroyImages([$baseImage]); $result = new GdResult($matrix, $targetImage); if ($logo instanceof LogoInterface) { $result = $this->addLogoToResult($logo, $result); } if ($label instanceof LabelInterface) { $result = $this->addLabelToResult($label, $result); } return $result; } /** * Creates the base image for the QR code * * @param MatrixInterface $matrix The matrix that defines the block layout for the QR code * @param int $baseBlockSize The size of each block in the QR code * * @return \GdImage The created base image resource */ private function createBaseImage( MatrixInterface $matrix, int $baseBlockSize ): \GdImage { $baseImage = imagecreatetruecolor($matrix->getBlockCount() * $baseBlockSize, $matrix->getBlockCount() * $baseBlockSize); imageantialias($baseImage, true); imagesavealpha($baseImage, true); imagealphablending($baseImage, false); $transparentColor = imagecolorallocatealpha($baseImage, 0, 0, 0, 127); imagefill($baseImage, 0, 0, $transparentColor); return $baseImage; } /** * Allocates the foreground color for the base image * * @param \GdImage $baseImage The base image to apply the foreground color to * @param QrCodeInterface $qrCode The QR code object to get the foreground color from * * @return int The allocated color identifier */ private function allocateForegroundColor( \GdImage $baseImage, QrCodeInterface $qrCode ): int { return imagecolorallocatealpha( $baseImage, $qrCode->getForegroundColor()->getRed(), $qrCode->getForegroundColor()->getGreen(), $qrCode->getForegroundColor()->getBlue(), $qrCode->getForegroundColor()->getAlpha() ); } /** * Creates the target image for the QR code, including label if provided * * @param MatrixInterface $matrix The matrix representation of the QR code * @param QrCodeInterface $qrCode The QR code instance * @param LabelInterface|null $label The optional label to add below the QR code * * @return resource|\GdImage The created target image. * @throws \Exception */ private function createTargetImage( MatrixInterface $matrix, QrCodeInterface $qrCode, ?LabelInterface $label ): \GdImage { $targetWidth = $matrix->getOuterSize(); $targetHeight = $matrix->getOuterSize(); if ($label instanceof LabelInterface) { $labelImageData = LabelImageData::createForLabel($label); $targetHeight += $labelImageData->getHeight() + $label->getMargin()->getTop() + $label->getMargin()->getBottom(); } $targetImage = imagecreatetruecolor($targetWidth, $targetHeight); imageantialias($targetImage, true); imagesavealpha($targetImage, true); imagealphablending($targetImage, false); $backgroundColor = imagecolorallocatealpha( $targetImage, $qrCode->getBackgroundColor()->getRed(), $qrCode->getBackgroundColor()->getGreen(), $qrCode->getBackgroundColor()->getBlue(), $qrCode->getBackgroundColor()->getAlpha() ); imagefill($targetImage, 0, 0, $backgroundColor); imagealphablending($targetImage, true); return $targetImage; } /** * Adds a logo to the image, centered and with optional punch out background * Throws an exception if the logo is not in PNG format * * @param LogoInterface $logo The logo to add * @param GdResult $result The image to add the logo to * * @return GdResult The updated image with the logo * @throws \Exception */ private function addLogoToResult( LogoInterface $logo, GdResult $result ): GdResult { $logoImageData = LogoImageData::createForLogo($logo); if ('image/png' !== $logoImageData->getMimeType()) { throw new \Exception('PNG Writer does not support SVG logo'); } $targetImage = $result->getImage(); $matrix = $result->getMatrix(); if ($logoImageData->getPunchoutBackground()) { $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127); imagealphablending($targetImage, false); $xOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2); $yOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2); for ($xOffset = $xOffsetStart; $xOffset < $xOffsetStart + $logoImageData->getWidth(); ++$xOffset) { for ($yOffset = $yOffsetStart; $yOffset < $yOffsetStart + $logoImageData->getHeight(); ++$yOffset) { imagesetpixel($targetImage, $xOffset, $yOffset, $transparent); } } } Drawer::copyResampledImage( $targetImage, $logoImageData->getImage(), ['X' => intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2), 'Y' => intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2), 'Width' => $logoImageData->getWidth(), 'Height' => $logoImageData->getHeight()], ['X' => 0, 'Y' => 0, 'Width' => imagesy($logoImageData->getImage()), 'Height' => imagesy($logoImageData->getImage())] ); return new GdResult($matrix, $targetImage); } /** * Adds a label with text to the image * * The label's position is based on its alignment (left, center, right) and margin * The text is drawn using the specified font and color * * @param LabelInterface $label The label to add * @param GdResult $result The image to add the label to * * @return GdResult The updated image * @throws \Exception */ private function addLabelToResult( LabelInterface $label, GdResult $result ): GdResult { $targetImage = $result->getImage(); $labelImageData = LabelImageData::createForLabel($label); $textColor = imagecolorallocatealpha( $targetImage, $label->getTextColor()->getRed(), $label->getTextColor()->getGreen(), $label->getTextColor()->getBlue(), $label->getTextColor()->getAlpha() ); $x = intval(imagesx($targetImage) / 2 - $labelImageData->getWidth() / 2); $y = imagesy($targetImage) - $label->getMargin()->getBottom(); if (LabelAlignment::Left === $label->getAlignment()) { $x = $label->getMargin()->getLeft(); } elseif (LabelAlignment::Right === $label->getAlignment()) { $x = imagesx($targetImage) - $labelImageData->getWidth() - $label->getMargin()->getRight(); } imagettftext($targetImage, $label->getFont()->getSize(), 0, $x, $y, $textColor, $label->getFont()->getPath(), $label->getText()); return new GdResult($result->getMatrix(), $targetImage); } /** * Frees up memory by destroying image resources. * * @param array $images An array of image resources. * @return void */ private function destroyImages(array $images): void { foreach ($images as $image) { imagedestroy($image); } } }