vendor/endroid/qr-code/src/Writer/PngWriter.php line 23

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Endroid\QrCode\Writer;
  4. use Endroid\QrCode\Bacon\MatrixFactory;
  5. use Endroid\QrCode\Exception\ValidationException;
  6. use Endroid\QrCode\ImageData\LabelImageData;
  7. use Endroid\QrCode\ImageData\LogoImageData;
  8. use Endroid\QrCode\Label\Alignment\LabelAlignmentLeft;
  9. use Endroid\QrCode\Label\Alignment\LabelAlignmentRight;
  10. use Endroid\QrCode\Label\LabelInterface;
  11. use Endroid\QrCode\Logo\LogoInterface;
  12. use Endroid\QrCode\QrCodeInterface;
  13. use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeNone;
  14. use Endroid\QrCode\Writer\Result\PngResult;
  15. use Endroid\QrCode\Writer\Result\ResultInterface;
  16. use Zxing\QrReader;
  17. final class PngWriter implements WriterInterface, ValidatingWriterInterface
  18. {
  19. public function write(QrCodeInterface $qrCode, LogoInterface $logo = null, LabelInterface $label = null, array $options = []): ResultInterface
  20. {
  21. if (!extension_loaded('gd')) {
  22. throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
  23. }
  24. $matrixFactory = new MatrixFactory();
  25. $matrix = $matrixFactory->create($qrCode);
  26. $baseBlockSize = $qrCode->getRoundBlockSizeMode() instanceof RoundBlockSizeModeNone ? 10 : intval($matrix->getBlockSize());
  27. $baseImage = imagecreatetruecolor($matrix->getBlockCount() * $baseBlockSize, $matrix->getBlockCount() * $baseBlockSize);
  28. if (!$baseImage) {
  29. throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
  30. }
  31. /** @var int $foregroundColor */
  32. $foregroundColor = imagecolorallocatealpha(
  33. $baseImage,
  34. $qrCode->getForegroundColor()->getRed(),
  35. $qrCode->getForegroundColor()->getGreen(),
  36. $qrCode->getForegroundColor()->getBlue(),
  37. $qrCode->getForegroundColor()->getAlpha()
  38. );
  39. /** @var int $transparentColor */
  40. $transparentColor = imagecolorallocatealpha($baseImage, 255, 255, 255, 127);
  41. imagefill($baseImage, 0, 0, $transparentColor);
  42. for ($rowIndex = 0; $rowIndex < $matrix->getBlockCount(); ++$rowIndex) {
  43. for ($columnIndex = 0; $columnIndex < $matrix->getBlockCount(); ++$columnIndex) {
  44. if (1 === $matrix->getBlockValue($rowIndex, $columnIndex)) {
  45. imagefilledrectangle(
  46. $baseImage,
  47. $columnIndex * $baseBlockSize,
  48. $rowIndex * $baseBlockSize,
  49. ($columnIndex + 1) * $baseBlockSize - 1,
  50. ($rowIndex + 1) * $baseBlockSize - 1,
  51. $foregroundColor
  52. );
  53. }
  54. }
  55. }
  56. $targetWidth = $matrix->getOuterSize();
  57. $targetHeight = $matrix->getOuterSize();
  58. if ($label instanceof LabelInterface) {
  59. $labelImageData = LabelImageData::createForLabel($label);
  60. $targetHeight += $labelImageData->getHeight() + $label->getMargin()->getTop() + $label->getMargin()->getBottom();
  61. }
  62. $targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
  63. if (!$targetImage) {
  64. throw new \Exception('Unable to generate image: please check if the GD extension is enabled and configured correctly');
  65. }
  66. /** @var int $backgroundColor */
  67. $backgroundColor = imagecolorallocatealpha(
  68. $targetImage,
  69. $qrCode->getBackgroundColor()->getRed(),
  70. $qrCode->getBackgroundColor()->getGreen(),
  71. $qrCode->getBackgroundColor()->getBlue(),
  72. $qrCode->getBackgroundColor()->getAlpha()
  73. );
  74. imagefill($targetImage, 0, 0, $backgroundColor);
  75. imagecopyresampled(
  76. $targetImage,
  77. $baseImage,
  78. $matrix->getMarginLeft(),
  79. $matrix->getMarginLeft(),
  80. 0,
  81. 0,
  82. $matrix->getInnerSize(),
  83. $matrix->getInnerSize(),
  84. imagesx($baseImage),
  85. imagesy($baseImage)
  86. );
  87. if (PHP_VERSION_ID < 80000) {
  88. imagedestroy($baseImage);
  89. }
  90. if ($qrCode->getBackgroundColor()->getAlpha() > 0) {
  91. imagesavealpha($targetImage, true);
  92. }
  93. $result = new PngResult($matrix, $targetImage);
  94. if ($logo instanceof LogoInterface) {
  95. $result = $this->addLogo($logo, $result);
  96. }
  97. if ($label instanceof LabelInterface) {
  98. $result = $this->addLabel($label, $result);
  99. }
  100. return $result;
  101. }
  102. private function addLogo(LogoInterface $logo, PngResult $result): PngResult
  103. {
  104. $logoImageData = LogoImageData::createForLogo($logo);
  105. if ('image/svg+xml' === $logoImageData->getMimeType()) {
  106. throw new \Exception('PNG Writer does not support SVG logo');
  107. }
  108. $targetImage = $result->getImage();
  109. $matrix = $result->getMatrix();
  110. if ($logoImageData->getPunchoutBackground()) {
  111. /** @var int $transparent */
  112. $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
  113. imagealphablending($targetImage, false);
  114. $xOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2);
  115. $yOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2);
  116. for ($xOffset = $xOffsetStart; $xOffset < $xOffsetStart + $logoImageData->getWidth(); ++$xOffset) {
  117. for ($yOffset = $yOffsetStart; $yOffset < $yOffsetStart + $logoImageData->getHeight(); ++$yOffset) {
  118. imagesetpixel($targetImage, $xOffset, $yOffset, $transparent);
  119. }
  120. }
  121. }
  122. imagecopyresampled(
  123. $targetImage,
  124. $logoImageData->getImage(),
  125. intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2),
  126. intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2),
  127. 0,
  128. 0,
  129. $logoImageData->getWidth(),
  130. $logoImageData->getHeight(),
  131. imagesx($logoImageData->getImage()),
  132. imagesy($logoImageData->getImage())
  133. );
  134. if (PHP_VERSION_ID < 80000) {
  135. imagedestroy($logoImageData->getImage());
  136. }
  137. return new PngResult($matrix, $targetImage);
  138. }
  139. private function addLabel(LabelInterface $label, PngResult $result): PngResult
  140. {
  141. $targetImage = $result->getImage();
  142. $labelImageData = LabelImageData::createForLabel($label);
  143. /** @var int $textColor */
  144. $textColor = imagecolorallocatealpha(
  145. $targetImage,
  146. $label->getTextColor()->getRed(),
  147. $label->getTextColor()->getGreen(),
  148. $label->getTextColor()->getBlue(),
  149. $label->getTextColor()->getAlpha()
  150. );
  151. $x = intval(imagesx($targetImage) / 2 - $labelImageData->getWidth() / 2);
  152. $y = imagesy($targetImage) - $label->getMargin()->getBottom();
  153. if ($label->getAlignment() instanceof LabelAlignmentLeft) {
  154. $x = $label->getMargin()->getLeft();
  155. } elseif ($label->getAlignment() instanceof LabelAlignmentRight) {
  156. $x = imagesx($targetImage) - $labelImageData->getWidth() - $label->getMargin()->getRight();
  157. }
  158. imagettftext($targetImage, $label->getFont()->getSize(), 0, $x, $y, $textColor, $label->getFont()->getPath(), $label->getText());
  159. return new PngResult($result->getMatrix(), $targetImage);
  160. }
  161. public function validateResult(ResultInterface $result, string $expectedData): void
  162. {
  163. $string = $result->getString();
  164. if (!class_exists(QrReader::class)) {
  165. throw ValidationException::createForMissingPackage('khanamiryan/qrcode-detector-decoder');
  166. }
  167. if (PHP_VERSION_ID >= 80000) {
  168. throw ValidationException::createForIncompatiblePhpVersion();
  169. }
  170. $reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
  171. if ($reader->text() !== $expectedData) {
  172. throw ValidationException::createForInvalidData($expectedData, strval($reader->text()));
  173. }
  174. }
  175. }