QRImagick.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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\QROptions;
  14. use chillerlan\QRCode\Data\QRMatrix;
  15. use chillerlan\Settings\SettingsContainerInterface;
  16. use finfo, Imagick, ImagickDraw, ImagickPixel;
  17. use function extension_loaded, in_array, is_string, max, min, preg_match, sprintf, strlen;
  18. use const FILEINFO_MIME_TYPE;
  19. /**
  20. * ImageMagick output module (requires ext-imagick)
  21. *
  22. * @see https://php.net/manual/book.imagick.php
  23. * @see https://phpimagick.com
  24. */
  25. class QRImagick extends QROutputAbstract{
  26. /**
  27. * The main image instance
  28. */
  29. protected Imagick $imagick;
  30. /**
  31. * The main draw instance
  32. */
  33. protected ImagickDraw $imagickDraw;
  34. /**
  35. * The allocated background color
  36. */
  37. protected ImagickPixel $backgroundColor;
  38. /**
  39. * @inheritDoc
  40. *
  41. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  42. */
  43. public function __construct(SettingsContainerInterface|QROptions $options, QRMatrix $matrix){
  44. foreach(['fileinfo', 'imagick'] as $ext){
  45. if(!extension_loaded($ext)){
  46. throw new QRCodeOutputException(sprintf('ext-%s not loaded', $ext)); // @codeCoverageIgnore
  47. }
  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(mixed $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(mixed $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. public function dump(string|null $file = null):string|Imagick{
  96. $this->setBgColor();
  97. $this->imagick = $this->createImage();
  98. $this->drawImage();
  99. // set transparency color after all operations
  100. $this->setTransparencyColor();
  101. if($this->options->returnResource){
  102. return $this->imagick;
  103. }
  104. $imageData = $this->imagick->getImageBlob();
  105. $this->imagick->destroy();
  106. $this->saveToFile($imageData, $file);
  107. if($this->options->outputBase64){
  108. $imageData = $this->toBase64DataURI($imageData, (new finfo(FILEINFO_MIME_TYPE))->buffer($imageData));
  109. }
  110. return $imageData;
  111. }
  112. /**
  113. * Sets the background color
  114. */
  115. protected function setBgColor():void{
  116. if($this::moduleValueIsValid($this->options->bgColor)){
  117. $this->backgroundColor = $this->prepareModuleValue($this->options->bgColor);
  118. return;
  119. }
  120. $this->backgroundColor = $this->prepareModuleValue('white');
  121. }
  122. /**
  123. * Creates a new Imagick instance
  124. */
  125. protected function createImage():Imagick{
  126. $imagick = new Imagick;
  127. [$width, $height] = $this->getOutputDimensions();
  128. $imagick->newImage($width, $height, $this->backgroundColor, $this->options->imagickFormat);
  129. if($this->options->quality > -1){
  130. $imagick->setImageCompressionQuality(max(0, min(100, $this->options->quality)));
  131. }
  132. return $imagick;
  133. }
  134. /**
  135. * Sets the transparency color
  136. */
  137. protected function setTransparencyColor():void{
  138. if(!$this->options->imageTransparent){
  139. return;
  140. }
  141. $transparencyColor = $this->backgroundColor;
  142. if($this::moduleValueIsValid($this->options->transparencyColor)){
  143. $transparencyColor = $this->prepareModuleValue($this->options->transparencyColor);
  144. }
  145. $this->imagick->transparentPaintImage($transparencyColor, 0.0, 10, false);
  146. }
  147. /**
  148. * Creates the QR image via ImagickDraw
  149. */
  150. protected function drawImage():void{
  151. $this->imagickDraw = new ImagickDraw;
  152. $this->imagickDraw->setStrokeWidth(0);
  153. foreach($this->matrix->getMatrix() as $y => $row){
  154. foreach($row as $x => $M_TYPE){
  155. $this->module($x, $y, $M_TYPE);
  156. }
  157. }
  158. $this->imagick->drawImage($this->imagickDraw);
  159. }
  160. /**
  161. * draws a single pixel at the given position
  162. */
  163. protected function module(int $x, int $y, int $M_TYPE):void{
  164. if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
  165. return;
  166. }
  167. $this->imagickDraw->setFillColor($this->getModuleValue($M_TYPE));
  168. if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
  169. $this->imagickDraw->circle(
  170. (($x + 0.5) * $this->scale),
  171. (($y + 0.5) * $this->scale),
  172. (($x + 0.5 + $this->circleRadius) * $this->scale),
  173. (($y + 0.5) * $this->scale)
  174. );
  175. return;
  176. }
  177. $this->imagickDraw->rectangle(
  178. ($x * $this->scale),
  179. ($y * $this->scale),
  180. ((($x + 1) * $this->scale) - 1),
  181. ((($y + 1) * $this->scale) - 1)
  182. );
  183. }
  184. }