Skip to content

Commit b231433

Browse files
committed
✨ QRInterventionImage (intervention/image)
1 parent 81eb21a commit b231433

File tree

4 files changed

+314
-0
lines changed

4 files changed

+314
-0
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
},
5353
"require-dev": {
5454
"chillerlan/php-authenticator": "^5.1",
55+
"intervention/image": "^3.5",
5556
"phpbench/phpbench": "^1.2.15",
5657
"phan/phan": "^5.4",
5758
"phpunit/phpunit": "^11.0",
@@ -61,6 +62,7 @@
6162
},
6263
"suggest": {
6364
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
65+
"intervention/image": "More advanced GD and ImageMagick output.",
6466
"setasign/fpdf": "Required to use the QR FPDF output.",
6567
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
6668
},

examples/intervention-image.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
/**
3+
* intervention/image output example
4+
*
5+
* @created 04.05.2024
6+
* @author smiley <[email protected]>
7+
* @copyright 2024 smiley
8+
* @license MIT
9+
*/
10+
11+
use chillerlan\QRCode\Data\QRMatrix;
12+
use chillerlan\QRCode\Output\QRInterventionImage;
13+
use chillerlan\QRCode\QRCode;
14+
use chillerlan\QRCode\QROptions;
15+
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
16+
17+
require_once __DIR__.'/../vendor/autoload.php';
18+
19+
$options = new QROptions;
20+
21+
$options->version = 7;
22+
$options->outputInterface = QRInterventionImage::class;
23+
$options->scale = 20;
24+
$options->outputBase64 = false;
25+
$options->bgColor = '#cccccc';
26+
$options->imageTransparent = false;
27+
$options->transparencyColor = '#cccccc';
28+
$options->drawLightModules = true;
29+
$options->drawCircularModules = true;
30+
$options->circleRadius = 0.4;
31+
$options->keepAsSquare = [
32+
QRMatrix::M_FINDER_DARK,
33+
QRMatrix::M_FINDER_DOT,
34+
QRMatrix::M_ALIGNMENT_DARK,
35+
];
36+
$options->moduleValues = [
37+
// finder
38+
QRMatrix::M_FINDER_DARK => '#A71111', // dark (true)
39+
QRMatrix::M_FINDER_DOT => '#A71111', // finder dot, dark (true)
40+
QRMatrix::M_FINDER => '#FFBFBF', // light (false)
41+
// alignment
42+
QRMatrix::M_ALIGNMENT_DARK => '#A70364',
43+
QRMatrix::M_ALIGNMENT => '#FFC9C9',
44+
// timing
45+
QRMatrix::M_TIMING_DARK => '#98005D',
46+
QRMatrix::M_TIMING => '#FFB8E9',
47+
// format
48+
QRMatrix::M_FORMAT_DARK => '#003804',
49+
QRMatrix::M_FORMAT => '#CCFB12',
50+
// version
51+
QRMatrix::M_VERSION_DARK => '#650098',
52+
QRMatrix::M_VERSION => '#E0B8FF',
53+
// data
54+
QRMatrix::M_DATA_DARK => '#4A6000',
55+
QRMatrix::M_DATA => '#ECF9BE',
56+
// darkmodule
57+
QRMatrix::M_DARKMODULE => '#080063',
58+
// separator
59+
QRMatrix::M_SEPARATOR => '#DDDDDD',
60+
// quietzone
61+
QRMatrix::M_QUIETZONE => '#DDDDDD',
62+
];
63+
64+
$qrcode = new QRCode($options);
65+
$qrcode->addByteSegment('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
66+
67+
$qrOutputInterface = new QRInterventionImage($options, $qrcode->getQRMatrix());
68+
// set a different driver
69+
$qrOutputInterface->setDriver(new GdDriver);
70+
71+
$out = $qrOutputInterface->dump();
72+
73+
header('Content-type: image/png');
74+
75+
echo $out;
76+
77+
exit;
78+
79+
80+

src/Output/QRInterventionImage.php

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

0 commit comments

Comments
 (0)