pngWithRoundedShapes.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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. use chillerlan\QRCode\Common\EccLevel;
  14. use chillerlan\QRCode\Data\QRMatrix;
  15. use chillerlan\QRCode\Output\QRGdImage;
  16. use chillerlan\QRCode\Output\QROutputInterface;
  17. use chillerlan\QRCode\QRCode;
  18. use chillerlan\QRCode\QROptions;
  19. require_once __DIR__ . '/../vendor/autoload.php';
  20. // --------------------
  21. // Class definition
  22. // --------------------
  23. class QRGdRounded extends QRGdImage {
  24. protected function module(int $x, int $y, int $M_TYPE): void {
  25. $x1 = ($x * $this->scale);
  26. $y1 = ($y * $this->scale);
  27. $x2 = (($x + 1) * $this->scale);
  28. $y2 = (($y + 1) * $this->scale);
  29. $rectsize = ($this->scale / 2);
  30. /**
  31. * @var int $neighbours
  32. * The right bit order (starting from 0):
  33. * 0 1 2
  34. * 7 # 3
  35. * 6 5 4
  36. */
  37. $neighbours = $this->matrix->checkNeighbours($x, $y);
  38. // ------------------
  39. // Outer rounding
  40. // ------------------
  41. if ($neighbours & (1 << 7)) { // neighbour left
  42. // top left
  43. imagefilledrectangle(
  44. $this->image,
  45. $x1,
  46. $y1,
  47. ($x1 + $rectsize),
  48. ($y1 + $rectsize),
  49. $this->moduleValues[$M_TYPE]
  50. );
  51. // bottom left
  52. imagefilledrectangle(
  53. $this->image,
  54. $x1,
  55. ($y2 - $rectsize),
  56. ($x1 + $rectsize),
  57. $y2,
  58. $this->moduleValues[$M_TYPE]
  59. );
  60. }
  61. if ($neighbours & (1 << 3)) { // neighbour right
  62. // top right
  63. imagefilledrectangle(
  64. $this->image,
  65. ($x2 - $rectsize),
  66. $y1,
  67. $x2,
  68. ($y1 + $rectsize),
  69. $this->moduleValues[$M_TYPE]
  70. );
  71. // bottom right
  72. imagefilledrectangle(
  73. $this->image,
  74. ($x2 - $rectsize),
  75. ($y2 - $rectsize),
  76. $x2,
  77. $y2,
  78. $this->moduleValues[$M_TYPE]
  79. );
  80. }
  81. if ($neighbours & (1 << 1)) { // neighbour top
  82. // top left
  83. imagefilledrectangle(
  84. $this->image,
  85. $x1,
  86. $y1,
  87. ($x1 + $rectsize),
  88. ($y1 + $rectsize),
  89. $this->moduleValues[$M_TYPE]
  90. );
  91. // top right
  92. imagefilledrectangle(
  93. $this->image,
  94. ($x2 - $rectsize),
  95. $y1,
  96. $x2,
  97. ($y1 + $rectsize),
  98. $this->moduleValues[$M_TYPE]
  99. );
  100. }
  101. if ($neighbours & (1 << 5)) { // neighbour bottom
  102. // bottom left
  103. imagefilledrectangle(
  104. $this->image,
  105. $x1,
  106. ($y2 - $rectsize),
  107. ($x1 + $rectsize),
  108. $y2,
  109. $this->moduleValues[$M_TYPE]
  110. );
  111. // bottom right
  112. imagefilledrectangle(
  113. $this->image,
  114. ($x2 - $rectsize),
  115. ($y2 - $rectsize),
  116. $x2,
  117. $y2,
  118. $this->moduleValues[$M_TYPE]
  119. );
  120. }
  121. // ---------------------
  122. // inner rounding
  123. // ---------------------
  124. if (!$this->matrix->check($x, $y)) {
  125. if (($neighbours & 1) && ($neighbours & (1 << 7)) && ($neighbours & (1 << 1))) {
  126. // top left
  127. imagefilledrectangle(
  128. $this->image,
  129. $x1,
  130. $y1,
  131. ($x1 + $rectsize),
  132. ($y1 + $rectsize),
  133. $this->moduleValues[($M_TYPE | QRMatrix::IS_DARK)]
  134. );
  135. }
  136. if (($neighbours & (1 << 1)) && ($neighbours & (1 << 2)) && ($neighbours & (1 << 3))) {
  137. // top right
  138. imagefilledrectangle(
  139. $this->image,
  140. ($x2 - $rectsize),
  141. $y1,
  142. $x2,
  143. ($y1 + $rectsize),
  144. $this->moduleValues[($M_TYPE | QRMatrix::IS_DARK)]
  145. );
  146. }
  147. if (($neighbours & (1 << 7)) && ($neighbours & (1 << 6)) && ($neighbours & (1 << 5))) {
  148. // bottom left
  149. imagefilledrectangle(
  150. $this->image,
  151. $x1,
  152. ($y2 - $rectsize),
  153. ($x1 + $rectsize),
  154. $y2,
  155. $this->moduleValues[($M_TYPE | QRMatrix::IS_DARK)]
  156. );
  157. }
  158. if (($neighbours & (1 << 3)) && ($neighbours & (1 << 4)) && ($neighbours & (1 << 5))) {
  159. // bottom right
  160. imagefilledrectangle(
  161. $this->image,
  162. ($x2 - $rectsize),
  163. ($y2 - $rectsize),
  164. $x2,
  165. $y2,
  166. $this->moduleValues[($M_TYPE | QRMatrix::IS_DARK)]
  167. );
  168. }
  169. }
  170. imagefilledellipse(
  171. $this->image,
  172. (int)(($x * $this->scale) + ($this->scale / 2)),
  173. (int)(($y * $this->scale) + ($this->scale / 2)),
  174. (int)($this->scale - 1),
  175. (int)($this->scale - 1),
  176. $this->moduleValues[$M_TYPE]
  177. );
  178. }
  179. }
  180. // --------------------
  181. // Example
  182. // --------------------
  183. $options = new QROptions([
  184. 'outputType' => QROutputInterface::CUSTOM,
  185. 'outputInterface' => QRGdRounded::class,
  186. 'eccLevel' => EccLevel::M,
  187. 'imageTransparent' => false,
  188. 'imageBase64' => false,
  189. 'scale' => 30
  190. ]);
  191. $qrcode = new QRCode($options);
  192. $img = $qrcode->render('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
  193. header('Content-type: image/png');
  194. echo $img;