QRImagick.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. /**
  3. * Class QRImagick
  4. *
  5. * @created 04.07.2018
  6. * @author smiley <smiley@chillerlan.net>
  7. * @copyright 2018 smiley
  8. * @license MIT
  9. *
  10. * @noinspection PhpComposerExtensionStubsInspection
  11. */
  12. namespace chillerlan\QRCode\Output;
  13. use chillerlan\QRCode\Data\QRMatrix;
  14. use chillerlan\Settings\SettingsContainerInterface;
  15. use finfo, Imagick, ImagickDraw, ImagickPixel;
  16. use function extension_loaded, in_array, is_string, max, min, preg_match, strlen;
  17. use const FILEINFO_MIME_TYPE;
  18. /**
  19. * ImageMagick output module (requires ext-imagick)
  20. *
  21. * @see https://php.net/manual/book.imagick.php
  22. * @see https://phpimagick.com
  23. */
  24. class QRImagick extends QROutputAbstract{
  25. /**
  26. * The main image instance
  27. */
  28. protected Imagick $imagick;
  29. /**
  30. * The main draw instance
  31. */
  32. protected ImagickDraw $imagickDraw;
  33. /**
  34. * The allocated background color
  35. */
  36. protected ImagickPixel $backgroundColor;
  37. /**
  38. * @inheritDoc
  39. *
  40. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  41. */
  42. public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
  43. if(!extension_loaded('imagick')){
  44. throw new QRCodeOutputException('ext-imagick not loaded'); // @codeCoverageIgnore
  45. }
  46. if(!extension_loaded('fileinfo')){
  47. throw new QRCodeOutputException('ext-fileinfo not loaded'); // @codeCoverageIgnore
  48. }
  49. parent::__construct($options, $matrix);
  50. }
  51. /**
  52. * note: we're not necessarily validating the several values, just checking the general syntax
  53. *
  54. * @see https://www.php.net/manual/imagickpixel.construct.php
  55. * @inheritDoc
  56. */
  57. public static function moduleValueIsValid($value):bool{
  58. if(!is_string($value)){
  59. return false;
  60. }
  61. $value = trim($value);
  62. // hex notation
  63. // #rgb(a)
  64. // #rrggbb(aa)
  65. // #rrrrggggbbbb(aaaa)
  66. // ...
  67. if(preg_match('/^#[a-f\d]+$/i', $value) && in_array((strlen($value) - 1), [3, 4, 6, 8, 9, 12, 16, 24, 32], true)){
  68. return true;
  69. }
  70. // css (-like) func(...values)
  71. if(preg_match('#^(graya?|hs(b|la?)|rgba?)\([\d .,%]+\)$#i', $value)){
  72. return true;
  73. }
  74. // predefined css color
  75. if(preg_match('/^[a-z]+$/i', $value)){
  76. return true;
  77. }
  78. return false;
  79. }
  80. /**
  81. * @inheritDoc
  82. */
  83. protected function prepareModuleValue($value):ImagickPixel{
  84. return new ImagickPixel($value);
  85. }
  86. /**
  87. * @inheritDoc
  88. */
  89. protected function getDefaultModuleValue(bool $isDark):ImagickPixel{
  90. return $this->prepareModuleValue(($isDark) ? '#000' : '#fff');
  91. }
  92. /**
  93. * @inheritDoc
  94. *
  95. * @return string|\Imagick
  96. */
  97. public function dump(string $file = null){
  98. $this->setBgColor();
  99. $this->imagick = $this->createImage();
  100. $this->drawImage();
  101. // set transparency color after all operations
  102. $this->setTransparencyColor();
  103. if($this->options->returnResource){
  104. return $this->imagick;
  105. }
  106. $imageData = $this->imagick->getImageBlob();
  107. $this->imagick->destroy();
  108. $this->saveToFile($imageData, $file);
  109. if($this->options->outputBase64){
  110. $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData));
  111. }
  112. return $imageData;
  113. }
  114. /**
  115. * Sets the background color
  116. */
  117. protected function setBgColor():void{
  118. if($this::moduleValueIsValid($this->options->bgColor)){
  119. $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor);
  120. return;
  121. }
  122. $this->backgroundColor = $this->prepareModuleValue('white');
  123. }
  124. /**
  125. * Creates a new Imagick instance
  126. */
  127. protected function createImage():Imagick{
  128. $imagick = new Imagick;
  129. [$width, $height] = $this->getOutputDimensions();
  130. $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat);
  131. if($this->options->quality > -1){
  132. $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality)));
  133. }
  134. return $imagick;
  135. }
  136. /**
  137. * Sets the transparency color
  138. */
  139. protected function setTransparencyColor():void{
  140. if(!$this->options->imageTransparent){
  141. return;
  142. }
  143. $transparencyColor = $this->backgroundColor;
  144. if($this::moduleValueIsValid($this->options->transparencyColor)){
  145. $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor);
  146. }
  147. $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false);
  148. }
  149. /**
  150. * Creates the QR image via ImagickDraw
  151. */
  152. protected function drawImage():void{
  153. $this->imagickDraw = new ImagickDraw;
  154. $this->imagickDraw->setStrokeWidth(0);
  155. foreach($this->matrix->getMatrix() as $y => $row){
  156. foreach($row as $x => $M_TYPE){
  157. $this->module($x, $y, $M_TYPE);
  158. }
  159. }
  160. $this->imagick->drawImage($this->imagickDraw);
  161. }
  162. /**
  163. * draws a single pixel at the given position
  164. */
  165. protected function module(int $x, int $y, int $M_TYPE):void{
  166. if(!$this->options->drawLightModules && !$this->matrix->check($x, $y)){
  167. return;
  168. }
  169. $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE));
  170. if($this->options->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->options->keepAsSquare)){
  171. $this->imagickDraw->circle(
  172. (($x + 0.5) * $this->scale),
  173. (($y + 0.5) * $this->scale),
  174. (($x + 0.5 + $this->options->circleRadius) * $this->scale),
  175. (($y + 0.5) * $this->scale)
  176. );
  177. return;
  178. }
  179. $this->imagickDraw->rectangle(
  180. ($x * $this->scale),
  181. ($y * $this->scale),
  182. ((($x + 1) * $this->scale) - 1),
  183. ((($y + 1) * $this->scale) - 1)
  184. );
  185. }
  186. }