vendor/endroid/qr-code/src/Writer/AbstractGdWriter.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\GdResult;
  15. use Endroid\QrCode\Writer\Result\ResultInterface;
  16. use Zxing\QrReader;
  17. abstract class AbstractGdWriter 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 ($qrCode->getBackgroundColor()->getAlpha() > 0) {
  88. imagesavealpha($targetImage, true);
  89. }
  90. $result = new GdResult($matrix, $targetImage);
  91. if ($logo instanceof LogoInterface) {
  92. $result = $this->addLogo($logo, $result);
  93. }
  94. if ($label instanceof LabelInterface) {
  95. $result = $this->addLabel($label, $result);
  96. }
  97. return $result;
  98. }
  99. private function addLogo(LogoInterface $logo, GdResult $result): GdResult
  100. {
  101. $logoImageData = LogoImageData::createForLogo($logo);
  102. if ('image/svg+xml' === $logoImageData->getMimeType()) {
  103. throw new \Exception('PNG Writer does not support SVG logo');
  104. }
  105. $targetImage = $result->getImage();
  106. $matrix = $result->getMatrix();
  107. if ($logoImageData->getPunchoutBackground()) {
  108. /** @var int $transparent */
  109. $transparent = imagecolorallocatealpha($targetImage, 255, 255, 255, 127);
  110. imagealphablending($targetImage, false);
  111. $xOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2);
  112. $yOffsetStart = intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2);
  113. for ($xOffset = $xOffsetStart; $xOffset < $xOffsetStart + $logoImageData->getWidth(); ++$xOffset) {
  114. for ($yOffset = $yOffsetStart; $yOffset < $yOffsetStart + $logoImageData->getHeight(); ++$yOffset) {
  115. imagesetpixel($targetImage, $xOffset, $yOffset, $transparent);
  116. }
  117. }
  118. }
  119. imagecopyresampled(
  120. $targetImage,
  121. $logoImageData->getImage(),
  122. intval($matrix->getOuterSize() / 2 - $logoImageData->getWidth() / 2),
  123. intval($matrix->getOuterSize() / 2 - $logoImageData->getHeight() / 2),
  124. 0,
  125. 0,
  126. $logoImageData->getWidth(),
  127. $logoImageData->getHeight(),
  128. imagesx($logoImageData->getImage()),
  129. imagesy($logoImageData->getImage())
  130. );
  131. return new GdResult($matrix, $targetImage);
  132. }
  133. private function addLabel(LabelInterface $label, GdResult $result): GdResult
  134. {
  135. $targetImage = $result->getImage();
  136. $labelImageData = LabelImageData::createForLabel($label);
  137. /** @var int $textColor */
  138. $textColor = imagecolorallocatealpha(
  139. $targetImage,
  140. $label->getTextColor()->getRed(),
  141. $label->getTextColor()->getGreen(),
  142. $label->getTextColor()->getBlue(),
  143. $label->getTextColor()->getAlpha()
  144. );
  145. $x = intval(imagesx($targetImage) / 2 - $labelImageData->getWidth() / 2);
  146. $y = imagesy($targetImage) - $label->getMargin()->getBottom();
  147. if ($label->getAlignment() instanceof LabelAlignmentLeft) {
  148. $x = $label->getMargin()->getLeft();
  149. } elseif ($label->getAlignment() instanceof LabelAlignmentRight) {
  150. $x = imagesx($targetImage) - $labelImageData->getWidth() - $label->getMargin()->getRight();
  151. }
  152. imagettftext($targetImage, $label->getFont()->getSize(), 0, $x, $y, $textColor, $label->getFont()->getPath(), $label->getText());
  153. return new GdResult($result->getMatrix(), $targetImage);
  154. }
  155. public function validateResult(ResultInterface $result, string $expectedData): void
  156. {
  157. $string = $result->getString();
  158. if (!class_exists(QrReader::class)) {
  159. throw ValidationException::createForMissingPackage('khanamiryan/qrcode-detector-decoder');
  160. }
  161. $reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
  162. if ($reader->text() !== $expectedData) {
  163. throw ValidationException::createForInvalidData($expectedData, strval($reader->text()));
  164. }
  165. }
  166. }