QRInterventionImage.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. <?php
  2. /**
  3. * Class QRInterventionImage
  4. *
  5. * @created 21.01.2024
  6. * @author smiley <smiley@chillerlan.net>
  7. * @copyright 2024 smiley
  8. * @license MIT
  9. */
  10. declare(strict_types=1);
  11. namespace chillerlan\QRCode\Output;
  12. use chillerlan\QRCode\Data\QRMatrix;
  13. use chillerlan\QRCode\QROptions;
  14. use chillerlan\Settings\SettingsContainerInterface;
  15. use Intervention\Image\Drivers\Gd\Driver as GdDriver;
  16. use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
  17. use Intervention\Image\Geometry\Factories\CircleFactory;
  18. use Intervention\Image\Geometry\Factories\RectangleFactory;
  19. use Intervention\Image\ImageManager;
  20. use Intervention\Image\Interfaces\DriverInterface;
  21. use Intervention\Image\Interfaces\ImageInterface;
  22. use Intervention\Image\Interfaces\ImageManagerInterface;
  23. use UnhandledMatchError;
  24. use function class_exists;
  25. use function extension_loaded;
  26. use function intdiv;
  27. /**
  28. * intervention/image (GD/ImageMagick) output
  29. *
  30. * note: this output class works very slow compared to the native GD/Imagick output classes for obvious reasons.
  31. * use only if you must.
  32. *
  33. * @see https://github.com/Intervention/image
  34. * @see https://image.intervention.io/
  35. */
  36. class QRInterventionImage extends QROutputAbstract{
  37. use CssColorModuleValueTrait;
  38. protected DriverInterface $driver;
  39. protected ImageManagerInterface $manager;
  40. protected ImageInterface $image;
  41. /**
  42. * QRInterventionImage constructor.
  43. *
  44. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  45. */
  46. public function __construct(SettingsContainerInterface|QROptions|iterable $options, QRMatrix $matrix){
  47. if(!class_exists(ImageManager::class)){
  48. // @codeCoverageIgnoreStart
  49. throw new QRCodeOutputException(
  50. 'The QRInterventionImage output requires Intervention/image (https://github.com/Intervention/image)'.
  51. ' as dependency but the class "\\Intervention\\Image\\ImageManager" could not be found.',
  52. );
  53. // @codeCoverageIgnoreEnd
  54. }
  55. try{
  56. $this->driver = match(true){
  57. extension_loaded('gd') => new GdDriver,
  58. extension_loaded('imagick') => new ImagickDriver,
  59. };
  60. $this->setDriver($this->driver);
  61. }
  62. catch(UnhandledMatchError){
  63. throw new QRCodeOutputException('no image processing extension loaded (gd, imagick)'); // @codeCoverageIgnore
  64. }
  65. parent::__construct($options, $matrix);
  66. }
  67. /**
  68. * Sets a DriverInterface
  69. */
  70. public function setDriver(DriverInterface $driver):static{
  71. $this->manager = new ImageManager($driver);
  72. return $this;
  73. }
  74. public function dump(string|null $file = null):string|ImageInterface{
  75. [$width, $height] = $this->getOutputDimensions();
  76. $this->image = $this->manager->create($width, $height);
  77. $this->image->fill($this->getDefaultModuleValue(false));
  78. if($this->options->imageTransparent && $this::moduleValueIsValid($this->options->transparencyColor)){
  79. $this->manager
  80. ->driver()
  81. ->config()
  82. ->setOptions(blendingColor: $this->prepareModuleValue($this->options->transparencyColor))
  83. ;
  84. }
  85. if($this::moduleValueIsValid($this->options->bgColor)){
  86. $this->image->fill($this->prepareModuleValue($this->options->bgColor));
  87. }
  88. foreach($this->matrix->getMatrix() as $y => $row){
  89. foreach($row as $x => $M_TYPE){
  90. $this->module($x, $y, $M_TYPE);
  91. }
  92. }
  93. if($this->options->returnResource){
  94. return $this->image;
  95. }
  96. $image = $this->image->toPng();
  97. $imageData = $image->toString();
  98. $this->saveToFile($imageData, $file);
  99. if($this->options->outputBase64){
  100. return $image->toDataUri();
  101. }
  102. return $imageData;
  103. }
  104. /**
  105. * draws a single pixel at the given position
  106. */
  107. protected function module(int $x, int $y, int $M_TYPE):void{
  108. if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
  109. return;
  110. }
  111. $color = $this->getModuleValue($M_TYPE);
  112. if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
  113. $this->image->drawCircle(
  114. (($x * $this->scale) + intdiv($this->scale, 2)),
  115. (($y * $this->scale) + intdiv($this->scale, 2)),
  116. function(CircleFactory $circle) use ($color):void{
  117. $circle->radius((int)($this->circleRadius * $this->scale));
  118. $circle->background($color);
  119. },
  120. );
  121. return;
  122. }
  123. $this->image->drawRectangle(
  124. ($x * $this->scale),
  125. ($y * $this->scale),
  126. function(RectangleFactory $rectangle) use ($color):void{
  127. $rectangle->size($this->scale, $this->scale);
  128. $rectangle->background($color);
  129. },
  130. );
  131. }
  132. }