123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- <?php
- declare(strict_types=1);
- /*
- * (c) Jeroen van den Enden <info@endroid.nl>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace Endroid\QrCode\Writer;
- use Endroid\QrCode\Exception\GenerateImageException;
- use Endroid\QrCode\Exception\MissingFunctionException;
- use Endroid\QrCode\Exception\MissingLogoHeightException;
- use Endroid\QrCode\Exception\ValidationException;
- use Endroid\QrCode\LabelAlignment;
- use Endroid\QrCode\QrCodeInterface;
- use Zxing\QrReader;
- class PngWriter extends AbstractWriter
- {
- public function writeString(QrCodeInterface $qrCode): string
- {
- if (!extension_loaded('gd')) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
- }
- $image = $this->createImage($qrCode->getData(), $qrCode);
- $logoPath = $qrCode->getLogoPath();
- if (null !== $logoPath) {
- $image = $this->addLogo($image, $logoPath, $qrCode->getLogoWidth(), $qrCode->getLogoHeight());
- }
- $label = $qrCode->getLabel();
- if (null !== $label) {
- $image = $this->addLabel($image, $label, $qrCode->getLabelFontPath(), $qrCode->getLabelFontSize(), $qrCode->getLabelAlignment(), $qrCode->getLabelMargin(), $qrCode->getForegroundColor(), $qrCode->getBackgroundColor());
- }
- $string = $this->imageToString($image);
- if (PHP_VERSION_ID < 80000) {
- imagedestroy($image);
- }
- if ($qrCode->getValidateResult()) {
- $reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
- if ($reader->text() !== $qrCode->getText()) {
- throw new ValidationException('Built-in validation reader read "'.$reader->text().'" instead of "'.$qrCode->getText().'".
- Adjust your parameters to increase readability or disable built-in validation.');
- }
- }
- return $string;
- }
- /**
- * @param array<mixed> $data
- *
- * @return mixed
- */
- private function createImage(array $data, QrCodeInterface $qrCode)
- {
- $baseSize = $qrCode->getRoundBlockSize() ? $data['block_size'] : 25;
- $baseImage = $this->createBaseImage($baseSize, $data, $qrCode);
- $interpolatedImage = $this->createInterpolatedImage($baseImage, $data, $qrCode);
- if (PHP_VERSION_ID < 80000) {
- imagedestroy($baseImage);
- }
- return $interpolatedImage;
- }
- /**
- * @param array<mixed> $data
- *
- * @return mixed
- */
- private function createBaseImage(int $baseSize, array $data, QrCodeInterface $qrCode)
- {
- $image = imagecreatetruecolor($data['block_count'] * $baseSize, $data['block_count'] * $baseSize);
- if (!$image) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
- }
- $foregroundColor = imagecolorallocatealpha($image, $qrCode->getForegroundColor()['r'], $qrCode->getForegroundColor()['g'], $qrCode->getForegroundColor()['b'], $qrCode->getForegroundColor()['a']);
- if (!is_int($foregroundColor)) {
- throw new GenerateImageException('Foreground color could not be allocated');
- }
- $backgroundColor = imagecolorallocatealpha($image, $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b'], $qrCode->getBackgroundColor()['a']);
- if (!is_int($backgroundColor)) {
- throw new GenerateImageException('Background color could not be allocated');
- }
- imagefill($image, 0, 0, $backgroundColor);
- foreach ($data['matrix'] as $row => $values) {
- foreach ($values as $column => $value) {
- if (1 === $value) {
- imagefilledrectangle($image, $column * $baseSize, $row * $baseSize, intval(($column + 1) * $baseSize), intval(($row + 1) * $baseSize), $foregroundColor);
- }
- }
- }
- return $image;
- }
- /**
- * @param mixed $baseImage
- * @param array<mixed> $data
- *
- * @return mixed
- */
- private function createInterpolatedImage($baseImage, array $data, QrCodeInterface $qrCode)
- {
- $image = imagecreatetruecolor($data['outer_width'], $data['outer_height']);
- if (!$image) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
- }
- $backgroundColor = imagecolorallocatealpha($image, $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b'], $qrCode->getBackgroundColor()['a']);
- if (!is_int($backgroundColor)) {
- throw new GenerateImageException('Background color could not be allocated');
- }
- imagefill($image, 0, 0, $backgroundColor);
- imagecopyresampled($image, $baseImage, (int) $data['margin_left'], (int) $data['margin_left'], 0, 0, (int) $data['inner_width'], (int) $data['inner_height'], imagesx($baseImage), imagesy($baseImage));
- if ($qrCode->getBackgroundColor()['a'] > 0) {
- imagesavealpha($image, true);
- }
- return $image;
- }
- /**
- * @param mixed $sourceImage
- *
- * @return mixed
- */
- private function addLogo($sourceImage, string $logoPath, int $logoWidth = null, int $logoHeight = null)
- {
- $mimeType = $this->getMimeType($logoPath);
- $logoImage = imagecreatefromstring(strval(file_get_contents($logoPath)));
- if ('image/svg+xml' === $mimeType && (null === $logoHeight || null === $logoWidth)) {
- throw new MissingLogoHeightException('SVG Logos require an explicit height set via setLogoSize($width, $height)');
- }
- if (!$logoImage) {
- throw new GenerateImageException('Unable to generate image: check your GD installation or logo path');
- }
- $logoSourceWidth = imagesx($logoImage);
- $logoSourceHeight = imagesy($logoImage);
- if (null === $logoWidth) {
- $logoWidth = $logoSourceWidth;
- }
- if (null === $logoHeight) {
- $aspectRatio = $logoWidth / $logoSourceWidth;
- $logoHeight = intval($logoSourceHeight * $aspectRatio);
- }
- $logoX = imagesx($sourceImage) / 2 - $logoWidth / 2;
- $logoY = imagesy($sourceImage) / 2 - $logoHeight / 2;
- imagecopyresampled($sourceImage, $logoImage, intval($logoX), intval($logoY), 0, 0, $logoWidth, $logoHeight, $logoSourceWidth, $logoSourceHeight);
- if (PHP_VERSION_ID < 80000) {
- imagedestroy($logoImage);
- }
- return $sourceImage;
- }
- /**
- * @param mixed $sourceImage
- * @param array<int> $labelMargin
- * @param array<int> $foregroundColor
- * @param array<int> $backgroundColor
- *
- * @return mixed
- */
- private function addLabel($sourceImage, string $label, string $labelFontPath, int $labelFontSize, string $labelAlignment, array $labelMargin, array $foregroundColor, array $backgroundColor)
- {
- if (!function_exists('imagettfbbox')) {
- throw new MissingFunctionException('Missing function "imagettfbbox", please make sure you installed the FreeType library');
- }
- $labelBox = imagettfbbox($labelFontSize, 0, $labelFontPath, $label);
- if (!$labelBox) {
- throw new GenerateImageException('Unable to add label: check your GD installation');
- }
- $labelBoxWidth = intval($labelBox[2] - $labelBox[0]);
- $labelBoxHeight = intval($labelBox[0] - $labelBox[7]);
- $sourceWidth = imagesx($sourceImage);
- $sourceHeight = imagesy($sourceImage);
- $targetWidth = $sourceWidth;
- $targetHeight = $sourceHeight + $labelBoxHeight + $labelMargin['t'] + $labelMargin['b'];
- // Create empty target image
- $targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
- if (!$targetImage) {
- throw new GenerateImageException('Unable to generate image: check your GD installation');
- }
- $foregroundColor = imagecolorallocate($targetImage, $foregroundColor['r'], $foregroundColor['g'], $foregroundColor['b']);
- if (!is_int($foregroundColor)) {
- throw new GenerateImageException('Foreground color could not be allocated');
- }
- $backgroundColor = imagecolorallocate($targetImage, $backgroundColor['r'], $backgroundColor['g'], $backgroundColor['b']);
- if (!is_int($backgroundColor)) {
- throw new GenerateImageException('Background color could not be allocated');
- }
- imagefill($targetImage, 0, 0, $backgroundColor);
- // Copy source image to target image
- imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $sourceWidth, $sourceHeight, $sourceWidth, $sourceHeight);
- if (PHP_VERSION_ID < 80000) {
- imagedestroy($sourceImage);
- }
- switch ($labelAlignment) {
- case LabelAlignment::LEFT:
- $labelX = $labelMargin['l'];
- break;
- case LabelAlignment::RIGHT:
- $labelX = $targetWidth - $labelBoxWidth - $labelMargin['r'];
- break;
- default:
- $labelX = intval($targetWidth / 2 - $labelBoxWidth / 2);
- break;
- }
- $labelY = $targetHeight - $labelMargin['b'];
- imagettftext($targetImage, $labelFontSize, 0, $labelX, $labelY, $foregroundColor, $labelFontPath, $label);
- return $targetImage;
- }
- /**
- * @param mixed $image
- */
- private function imageToString($image): string
- {
- ob_start();
- imagepng($image);
- return (string) ob_get_clean();
- }
- public static function getContentType(): string
- {
- return 'image/png';
- }
- public static function getSupportedExtensions(): array
- {
- return ['png'];
- }
- public function getName(): string
- {
- return 'png';
- }
- }
|