imageWithRoundedShapes.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. /**
  3. * php-gd realization of QR code with rounded modules
  4. *
  5. * @see https://github.com/chillerlan/php-qrcode/pull/215
  6. * @see https://github.com/chillerlan/php-qrcode/issues/127
  7. *
  8. * @created 17.09.2023
  9. * @author livingroot
  10. * @copyright 2023 livingroot
  11. * @license MIT
  12. *
  13. * @noinspection PhpComposerExtensionStubsInspection
  14. */
  15. declare(strict_types=1);
  16. use chillerlan\QRCode\{QRCode, QROptions};
  17. use chillerlan\QRCode\Common\EccLevel;
  18. use chillerlan\QRCode\Data\QRMatrix;
  19. use chillerlan\QRCode\Output\QRGdImagePNG;
  20. use chillerlan\Settings\SettingsContainerInterface;
  21. require_once __DIR__.'/../vendor/autoload.php';
  22. // --------------------
  23. // Class definition
  24. // --------------------
  25. class QRGdRounded extends QRGdImagePNG{
  26. public function __construct(SettingsContainerInterface|QROptions|iterable $options, QRMatrix $matrix){
  27. if(is_iterable($options)){
  28. $options = new QROptions($options);
  29. }
  30. // enable the internal scaling for better rounding results at scale < 20
  31. // we need to do this before calling the parent constructor as these values are used there
  32. $options->gdImageUseUpscale = true;
  33. $options->drawCircularModules = true;
  34. parent::__construct($options, $matrix);
  35. }
  36. protected function module(int $x, int $y, int $M_TYPE):void{
  37. /**
  38. * The bit order (starting from 0):
  39. *
  40. * 0 1 2
  41. * 7 # 3
  42. * 6 5 4
  43. */
  44. $neighbours = $this->matrix->checkNeighbours($x, $y);
  45. $x1 = ($x * $this->scale);
  46. $y1 = ($y * $this->scale);
  47. $x2 = (($x + 1) * $this->scale);
  48. $y2 = (($y + 1) * $this->scale);
  49. $rectsize = (int)($this->scale / 2);
  50. $light = $this->getModuleValue($M_TYPE);
  51. $dark = $this->getModuleValue($M_TYPE | QRMatrix::IS_DARK);
  52. // ------------------
  53. // Outer rounding
  54. // ------------------
  55. if(($neighbours & (1 << 7))){ // neighbour left
  56. // top left
  57. imagefilledrectangle($this->image, $x1, $y1, ($x1 + $rectsize), ($y1 + $rectsize), $light);
  58. // bottom left
  59. imagefilledrectangle($this->image, $x1, ($y2 - $rectsize), ($x1 + $rectsize), $y2, $light);
  60. }
  61. if(($neighbours & (1 << 3))){ // neighbour right
  62. // top right
  63. imagefilledrectangle($this->image, ($x2 - $rectsize), $y1, $x2, ($y1 + $rectsize), $light);
  64. // bottom right
  65. imagefilledrectangle($this->image, ($x2 - $rectsize), ($y2 - $rectsize), $x2, $y2, $light);
  66. }
  67. if(($neighbours & (1 << 1))){ // neighbour top
  68. // top left
  69. imagefilledrectangle($this->image, $x1, $y1, ($x1 + $rectsize), ($y1 + $rectsize), $light);
  70. // top right
  71. imagefilledrectangle($this->image, ($x2 - $rectsize), $y1, $x2, ($y1 + $rectsize), $light);
  72. }
  73. if(($neighbours & (1 << 5))){ // neighbour bottom
  74. // bottom left
  75. imagefilledrectangle($this->image, $x1, ($y2 - $rectsize), ($x1 + $rectsize), $y2, $light);
  76. // bottom right
  77. imagefilledrectangle($this->image, ($x2 - $rectsize), ($y2 - $rectsize), $x2, $y2, $light);
  78. }
  79. // ---------------------
  80. // inner rounding
  81. // ---------------------
  82. if(!$this->matrix->check($x, $y)){
  83. if(($neighbours & 1) && ($neighbours & (1 << 7)) && ($neighbours & (1 << 1))){
  84. // top left
  85. imagefilledrectangle($this->image, $x1, $y1, ($x1 + $rectsize), ($y1 + $rectsize), $dark);
  86. }
  87. if(($neighbours & (1 << 1)) && ($neighbours & (1 << 2)) && ($neighbours & (1 << 3))){
  88. // top right
  89. imagefilledrectangle($this->image, ($x2 - $rectsize), $y1, $x2, ($y1 + $rectsize), $dark);
  90. }
  91. if(($neighbours & (1 << 7)) && ($neighbours & (1 << 6)) && ($neighbours & (1 << 5))){
  92. // bottom left
  93. imagefilledrectangle($this->image, $x1, ($y2 - $rectsize), ($x1 + $rectsize), $y2, $dark);
  94. }
  95. if(($neighbours & (1 << 3)) && ($neighbours & (1 << 4)) && ($neighbours & (1 << 5))){
  96. // bottom right
  97. imagefilledrectangle($this->image, ($x2 - $rectsize), ($y2 - $rectsize), $x2, $y2, $dark);
  98. }
  99. }
  100. imagefilledellipse(
  101. $this->image,
  102. (int)($x * $this->scale + $this->scale / 2),
  103. (int)($y * $this->scale + $this->scale / 2),
  104. ($this->scale - 1),
  105. ($this->scale - 1),
  106. $light,
  107. );
  108. }
  109. }
  110. // --------------------
  111. // Example
  112. // --------------------
  113. $options = new QROptions([
  114. 'version' => 7,
  115. 'eccLevel' => EccLevel::H,
  116. 'outputInterface' => QRGdRounded::class,
  117. 'outputBase64' => false,
  118. 'scale' => 30,
  119. 'addLogoSpace' => true,
  120. 'logoSpaceWidth' => 13,
  121. 'logoSpaceHeight' => 13,
  122. ]);
  123. $img = (new QRCode($options))->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
  124. header('Content-type: image/png');
  125. echo $img;