PngWriter.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * (c) Jeroen van den Enden <info@endroid.nl>
  5. *
  6. * This source file is subject to the MIT license that is bundled
  7. * with this source code in the file LICENSE.
  8. */
  9. namespace Endroid\QrCode\Writer;
  10. use Endroid\QrCode\Exception\GenerateImageException;
  11. use Endroid\QrCode\Exception\MissingFunctionException;
  12. use Endroid\QrCode\Exception\MissingLogoHeightException;
  13. use Endroid\QrCode\Exception\ValidationException;
  14. use Endroid\QrCode\LabelAlignment;
  15. use Endroid\QrCode\QrCodeInterface;
  16. use Zxing\QrReader;
  17. class PngWriter extends AbstractWriter
  18. {
  19. public function writeString(QrCodeInterface $qrCode): string
  20. {
  21. $image = $this->createImage($qrCode->getData(), $qrCode);
  22. $logoPath = $qrCode->getLogoPath();
  23. if (null !== $logoPath) {
  24. $image = $this->addLogo($image, $logoPath, $qrCode->getLogoWidth(), $qrCode->getLogoHeight());
  25. }
  26. $label = $qrCode->getLabel();
  27. if (null !== $label) {
  28. $image = $this->addLabel($image, $label, $qrCode->getLabelFontPath(), $qrCode->getLabelFontSize(), $qrCode->getLabelAlignment(), $qrCode->getLabelMargin(), $qrCode->getForegroundColor(), $qrCode->getBackgroundColor());
  29. }
  30. $string = $this->imageToString($image);
  31. imagedestroy($image);
  32. if ($qrCode->getValidateResult()) {
  33. $reader = new QrReader($string, QrReader::SOURCE_TYPE_BLOB);
  34. if ($reader->text() !== $qrCode->getText()) {
  35. throw new ValidationException('Built-in validation reader read "'.$reader->text().'" instead of "'.$qrCode->getText().'".
  36. Adjust your parameters to increase readability or disable built-in validation.');
  37. }
  38. }
  39. return $string;
  40. }
  41. private function createImage(array $data, QrCodeInterface $qrCode)
  42. {
  43. $baseSize = $qrCode->getRoundBlockSize() ? $data['block_size'] : 25;
  44. $baseImage = $this->createBaseImage($baseSize, $data, $qrCode);
  45. $interpolatedImage = $this->createInterpolatedImage($baseImage, $data, $qrCode);
  46. imagedestroy($baseImage);
  47. return $interpolatedImage;
  48. }
  49. private function createBaseImage(int $baseSize, array $data, QrCodeInterface $qrCode)
  50. {
  51. $image = imagecreatetruecolor($data['block_count'] * $baseSize, $data['block_count'] * $baseSize);
  52. if (!is_resource($image)) {
  53. throw new GenerateImageException('Unable to generate image: check your GD installation');
  54. }
  55. $foregroundColor = imagecolorallocatealpha($image, $qrCode->getForegroundColor()['r'], $qrCode->getForegroundColor()['g'], $qrCode->getForegroundColor()['b'], $qrCode->getForegroundColor()['a']);
  56. $backgroundColor = imagecolorallocatealpha($image, $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b'], $qrCode->getBackgroundColor()['a']);
  57. imagefill($image, 0, 0, $backgroundColor);
  58. foreach ($data['matrix'] as $row => $values) {
  59. foreach ($values as $column => $value) {
  60. if (1 === $value) {
  61. imagefilledrectangle($image, $column * $baseSize, $row * $baseSize, intval(($column + 1) * $baseSize), intval(($row + 1) * $baseSize), $foregroundColor);
  62. }
  63. }
  64. }
  65. return $image;
  66. }
  67. private function createInterpolatedImage($baseImage, array $data, QrCodeInterface $qrCode)
  68. {
  69. $image = imagecreatetruecolor($data['outer_width'], $data['outer_height']);
  70. if (!is_resource($image)) {
  71. throw new GenerateImageException('Unable to generate image: check your GD installation');
  72. }
  73. $backgroundColor = imagecolorallocatealpha($image, $qrCode->getBackgroundColor()['r'], $qrCode->getBackgroundColor()['g'], $qrCode->getBackgroundColor()['b'], $qrCode->getBackgroundColor()['a']);
  74. imagefill($image, 0, 0, $backgroundColor);
  75. 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));
  76. imagesavealpha($image, true);
  77. return $image;
  78. }
  79. private function addLogo($sourceImage, string $logoPath, int $logoWidth = null, int $logoHeight = null)
  80. {
  81. $mimeType = $this->getMimeType($logoPath);
  82. $logoImage = imagecreatefromstring(strval(file_get_contents($logoPath)));
  83. if ('image/svg+xml' === $mimeType && (null === $logoHeight || null === $logoWidth)) {
  84. throw new MissingLogoHeightException('SVG Logos require an explicit height set via setLogoSize($width, $height)');
  85. }
  86. if (!is_resource($logoImage)) {
  87. throw new GenerateImageException('Unable to generate image: check your GD installation or logo path');
  88. }
  89. $logoSourceWidth = imagesx($logoImage);
  90. $logoSourceHeight = imagesy($logoImage);
  91. if (null === $logoWidth) {
  92. $logoWidth = $logoSourceWidth;
  93. }
  94. if (null === $logoHeight) {
  95. $aspectRatio = $logoWidth / $logoSourceWidth;
  96. $logoHeight = intval($logoSourceHeight * $aspectRatio);
  97. }
  98. $logoX = imagesx($sourceImage) / 2 - $logoWidth / 2;
  99. $logoY = imagesy($sourceImage) / 2 - $logoHeight / 2;
  100. imagecopyresampled($sourceImage, $logoImage, intval($logoX), intval($logoY), 0, 0, $logoWidth, $logoHeight, $logoSourceWidth, $logoSourceHeight);
  101. imagedestroy($logoImage);
  102. return $sourceImage;
  103. }
  104. private function addLabel($sourceImage, string $label, string $labelFontPath, int $labelFontSize, string $labelAlignment, array $labelMargin, array $foregroundColor, array $backgroundColor)
  105. {
  106. if (!function_exists('imagettfbbox')) {
  107. throw new MissingFunctionException('Missing function "imagettfbbox", please make sure you installed the FreeType library');
  108. }
  109. $labelBox = imagettfbbox($labelFontSize, 0, $labelFontPath, $label);
  110. $labelBoxWidth = intval($labelBox[2] - $labelBox[0]);
  111. $labelBoxHeight = intval($labelBox[0] - $labelBox[7]);
  112. $sourceWidth = imagesx($sourceImage);
  113. $sourceHeight = imagesy($sourceImage);
  114. $targetWidth = $sourceWidth;
  115. $targetHeight = $sourceHeight + $labelBoxHeight + $labelMargin['t'] + $labelMargin['b'];
  116. // Create empty target image
  117. $targetImage = imagecreatetruecolor($targetWidth, $targetHeight);
  118. if (!is_resource($targetImage)) {
  119. throw new GenerateImageException('Unable to generate image: check your GD installation');
  120. }
  121. $foregroundColor = imagecolorallocate($targetImage, $foregroundColor['r'], $foregroundColor['g'], $foregroundColor['b']);
  122. $backgroundColor = imagecolorallocate($targetImage, $backgroundColor['r'], $backgroundColor['g'], $backgroundColor['b']);
  123. imagefill($targetImage, 0, 0, $backgroundColor);
  124. // Copy source image to target image
  125. imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $sourceWidth, $sourceHeight, $sourceWidth, $sourceHeight);
  126. imagedestroy($sourceImage);
  127. switch ($labelAlignment) {
  128. case LabelAlignment::LEFT:
  129. $labelX = $labelMargin['l'];
  130. break;
  131. case LabelAlignment::RIGHT:
  132. $labelX = $targetWidth - $labelBoxWidth - $labelMargin['r'];
  133. break;
  134. default:
  135. $labelX = intval($targetWidth / 2 - $labelBoxWidth / 2);
  136. break;
  137. }
  138. $labelY = $targetHeight - $labelMargin['b'];
  139. imagettftext($targetImage, $labelFontSize, 0, $labelX, $labelY, $foregroundColor, $labelFontPath, $label);
  140. return $targetImage;
  141. }
  142. private function imageToString($image): string
  143. {
  144. ob_start();
  145. imagepng($image);
  146. return (string) ob_get_clean();
  147. }
  148. public static function getContentType(): string
  149. {
  150. return 'image/png';
  151. }
  152. public static function getSupportedExtensions(): array
  153. {
  154. return ['png'];
  155. }
  156. public function getName(): string
  157. {
  158. return 'png';
  159. }
  160. }