QRInterventionImage (intervention/image)

This commit is contained in:
smiley
2024-05-04 22:52:48 +02:00
parent 81eb21a7f8
commit b231433d56
4 changed files with 314 additions and 0 deletions
+2
View File
@@ -52,6 +52,7 @@
},
"require-dev": {
"chillerlan/php-authenticator": "^5.1",
"intervention/image": "^3.5",
"phpbench/phpbench": "^1.2.15",
"phan/phan": "^5.4",
"phpunit/phpunit": "^11.0",
@@ -61,6 +62,7 @@
},
"suggest": {
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
"intervention/image": "More advanced GD and ImageMagick output.",
"setasign/fpdf": "Required to use the QR FPDF output.",
"simple-icons/simple-icons": "SVG icons that you can use to embed as logos in the QR Code"
},
+80
View File
@@ -0,0 +1,80 @@
<?php
/**
* intervention/image output example
*
* @created 04.05.2024
* @author smiley <smiley@chillerlan.net>
* @copyright 2024 smiley
* @license MIT
*/
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\QRInterventionImage;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
require_once __DIR__.'/../vendor/autoload.php';
$options = new QROptions;
$options->version = 7;
$options->outputInterface = QRInterventionImage::class;
$options->scale = 20;
$options->outputBase64 = false;
$options->bgColor = '#cccccc';
$options->imageTransparent = false;
$options->transparencyColor = '#cccccc';
$options->drawLightModules = true;
$options->drawCircularModules = true;
$options->circleRadius = 0.4;
$options->keepAsSquare = [
QRMatrix::M_FINDER_DARK,
QRMatrix::M_FINDER_DOT,
QRMatrix::M_ALIGNMENT_DARK,
];
$options->moduleValues = [
// finder
QRMatrix::M_FINDER_DARK => '#A71111', // dark (true)
QRMatrix::M_FINDER_DOT => '#A71111', // finder dot, dark (true)
QRMatrix::M_FINDER => '#FFBFBF', // light (false)
// alignment
QRMatrix::M_ALIGNMENT_DARK => '#A70364',
QRMatrix::M_ALIGNMENT => '#FFC9C9',
// timing
QRMatrix::M_TIMING_DARK => '#98005D',
QRMatrix::M_TIMING => '#FFB8E9',
// format
QRMatrix::M_FORMAT_DARK => '#003804',
QRMatrix::M_FORMAT => '#CCFB12',
// version
QRMatrix::M_VERSION_DARK => '#650098',
QRMatrix::M_VERSION => '#E0B8FF',
// data
QRMatrix::M_DATA_DARK => '#4A6000',
QRMatrix::M_DATA => '#ECF9BE',
// darkmodule
QRMatrix::M_DARKMODULE => '#080063',
// separator
QRMatrix::M_SEPARATOR => '#DDDDDD',
// quietzone
QRMatrix::M_QUIETZONE => '#DDDDDD',
];
$qrcode = new QRCode($options);
$qrcode->addByteSegment('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
$qrOutputInterface = new QRInterventionImage($options, $qrcode->getQRMatrix());
// set a different driver
$qrOutputInterface->setDriver(new GdDriver);
$out = $qrOutputInterface->dump();
header('Content-type: image/png');
echo $out;
exit;
+162
View File
@@ -0,0 +1,162 @@
<?php
/**
* Class QRInterventionImage
*
* @created 21.01.2024
* @author smiley <smiley@chillerlan.net>
* @copyright 2024 smiley
* @license MIT
*/
namespace chillerlan\QRCode\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\QROptions;
use chillerlan\Settings\SettingsContainerInterface;
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
use Intervention\Image\Drivers\Imagick\Driver as ImagickDriver;
use Intervention\Image\Geometry\Factories\CircleFactory;
use Intervention\Image\Geometry\Factories\RectangleFactory;
use Intervention\Image\ImageManager;
use Intervention\Image\Interfaces\DriverInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ImageManagerInterface;
use UnhandledMatchError;
use function class_exists;
use function extension_loaded;
use function intdiv;
/**
* intervention/image (GD/ImageMagick) output
*
* note: this output class works very slow compared to the native GD/Imagick output classes for obvious reasons.
* use only if you must.
*
* @see https://github.com/Intervention/image
* @see https://image.intervention.io/
*/
class QRInterventionImage extends QROutputAbstract{
use CssColorModuleValueTrait;
protected DriverInterface $driver;
protected ImageManagerInterface $manager;
protected ImageInterface $image;
/**
* QRInterventionImage constructor.
*
* @throws \chillerlan\QRCode\Output\QRCodeOutputException
*/
public function __construct(SettingsContainerInterface|QROptions $options, QRMatrix $matrix){
if(!class_exists(ImageManager::class)){
// @codeCoverageIgnoreStart
throw new QRCodeOutputException(
'The QRInterventionImage output requires Intervention/image (https://github.com/Intervention/image)'.
' as dependency but the class "\\Intervention\\Image\\ImageManager" could not be found.',
);
// @codeCoverageIgnoreEnd
}
try{
$this->driver = match(true){
extension_loaded('gd') => new GdDriver,
extension_loaded('imagick') => new ImagickDriver,
};
$this->setDriver($this->driver);
}
catch(UnhandledMatchError){
throw new QRCodeOutputException('no image processing extension loaded (gd, imagick)'); // @codeCoverageIgnore
}
parent::__construct($options, $matrix);
}
/**
* Sets a DriverInterface
*/
public function setDriver(DriverInterface $driver):static{
$this->manager = new ImageManager($driver);
return $this;
}
/**
* @inheritDoc
*/
public function dump(string $file = null):string|ImageInterface{
[$width, $height] = $this->getOutputDimensions();
$this->image = $this->manager->create($width, $height);
$this->image->fill($this->getDefaultModuleValue(false));
if($this->options->imageTransparent && $this::moduleValueIsValid($this->options->transparencyColor)){
$this->image->setBlendingColor($this->prepareModuleValue($this->options->transparencyColor));
}
if($this::moduleValueIsValid($this->options->bgColor)){
$this->image->fill($this->prepareModuleValue($this->options->bgColor));
}
foreach($this->matrix->getMatrix() as $y => $row){
foreach($row as $x => $M_TYPE){
$this->module($x, $y, $M_TYPE);
}
}
if($this->options->returnResource){
return $this->image;
}
$image = $this->image->toPng();
$imageData = $image->toString();
$this->saveToFile($imageData, $file);
if($this->options->outputBase64){
return $image->toDataUri();
}
return $imageData;
}
/**
* draws a single pixel at the given position
*/
protected function module(int $x, int $y, int $M_TYPE):void{
if(!$this->drawLightModules && !$this->matrix->isDark($M_TYPE)){
return;
}
$color = $this->getModuleValue($M_TYPE);
if($this->drawCircularModules && !$this->matrix->checkTypeIn($x, $y, $this->keepAsSquare)){
$this->image->drawCircle(
(($x * $this->scale) + intdiv($this->scale, 2)),
(($y * $this->scale) + intdiv($this->scale, 2)),
function(CircleFactory $circle) use ($color):void{
$circle->radius((int)($this->circleRadius * $this->scale));
$circle->background($color);
}
);
return;
}
$this->image->drawRectangle(
($x * $this->scale),
($y * $this->scale),
function(RectangleFactory $rectangle) use ($color):void{
$rectangle->size($this->scale, $this->scale);
$rectangle->background($color);
}
);
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
/**
* Class QRInterventionImageTest
*
* @created 04.05.2024
* @author smiley <smiley@chillerlan.net>
* @copyright 2024 smiley
* @license MIT
*/
namespace chillerlan\QRCodeTest\Output;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\QRInterventionImage;
use chillerlan\QRCode\Output\QROutputInterface;
use chillerlan\QRCode\QROptions;
use chillerlan\Settings\SettingsContainerInterface;
use Intervention\Image\Interfaces\ImageInterface;
use function extension_loaded;
/**
* Tests the QRInterventionImage output module
*/
class QRInterventionImageTest extends QROutputTestAbstract{
use CssColorModuleValueProviderTrait;
/**
* @inheritDoc
*/
protected function setUp():void{
if(!extension_loaded('gd')){
$this::markTestSkipped('ext-gd not loaded');
}
parent::setUp();
}
protected function getOutputInterface(
SettingsContainerInterface|QROptions $options,
QRMatrix $matrix
):QROutputInterface{
return new QRInterventionImage($options, $matrix);
}
/**
* @inheritDoc
*/
public function testSetModuleValues():void{
$this->options->moduleValues = [
// data
QRMatrix::M_DATA_DARK => '#4A6000',
QRMatrix::M_DATA => '#ECF9BE',
];
$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
$this->outputInterface->dump();
$this::assertTrue(true); // tricking the code coverage
}
public function testOutputGetResource():void{
$this->options->returnResource = true;
$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
$this::assertInstanceOf(ImageInterface::class, $this->outputInterface->dump());
}
}