SVG conversion in javascript example

This commit is contained in:
smiley
2023-10-03 18:34:59 +02:00
parent cf7e82ba7e
commit 8fb1e6a45c
3 changed files with 207 additions and 1 deletions
+2 -1
View File
@@ -22,7 +22,6 @@
- [GD Image with rounded modules](./imageWithRoundedShapes.php): similar to the SVG "melted" modules example ([#215](https://github.com/chillerlan/php-qrcode/pull/215))
- [ImageMagick with logo](./imagickWithLogo.php): a logo on top of the QR Code
- [ImageMagick with image as background](./imagickImageAsBackground.php): an image as full size background of the QR Code
- [ImageMagick SVG to raster conversion](./imagickConvertSVGtoPNG.php): use ImageMagick to convert SVG output to a raster image, e.g. PNG
- [SVG with logo](./svgWithLogo.php): an SVG QR Code with embedded logo (that is also SVG)
- [SVG with "melted" modules](./svgMeltedModules.php): an effect where the matrix appears to be like melted wax ([#127](https://github.com/chillerlan/php-qrcode/issues/127))
- [SVG with randomly colored modules](./svgRandomColoredDots.php): a visual effect using multiple colors for the matrix modules ([#136](https://github.com/chillerlan/php-qrcode/discussions/136))
@@ -35,6 +34,8 @@
- [Authenticator](./authenticator.php): create a QR Code that displays an URI for a mobile authenticator (featuring [`chillerlan/php-authenticator`](https://github.com/chillerlan/php-authenticator))
- [Interactive output](./qrcode-interactive.php): interactive demo (via [index.html](./index.html))
- [Custom module shapes](./shapes.svg): SVG paths to customize the module shapes ([#150](https://github.com/chillerlan/php-qrcode/discussions/150))
- [ImageMagick SVG to raster conversion](./imagickConvertSVGtoPNG.php): uses ImageMagick to convert SVG output to a raster image, e.g. PNG ([#216](https://github.com/chillerlan/php-qrcode/discussions/216))
- [HTML canvas SVG to PNG conversion](./svgConvertViaCanvas.php): converts an SVG element or a data URI fom and image element to a PNG image via the [HTML canvas element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas), includes [a javascript class](./SVGConvert.js) which handles the conversion.
Please note that the examples are self-contained, meaning that all custom classes are defined in an example file, so they don't necessarily respect the PSR-4 one file = one class principle.
+114
View File
@@ -0,0 +1,114 @@
/**
* @see https://stackoverflow.com/a/28226736
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
*
* @created 25.09.2023
* @author smiley <smiley@chillerlan.net>
* @copyright 2023 smiley
* @license MIT
*/
export default class SVGConvert{
/**
* Converts the SVG image from the source element into a raster image with the given size and format
* and inserts the resulting data URI into the src attribute of the given destination image element.
*
* @param {SVGElement|HTMLImageElement} source
* @param {HTMLImageElement} destination
* @param {Number<int>} width
* @param {Number<int>} height
* @param {String} format
* @return {void}
*/
static toDataURI(source, destination, width, height, format){
// format is whatever the fuck the specification allows:
// @see https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-todataurl-dev
format = (format || 'image/png');
if(!destination instanceof HTMLImageElement){
throw new Error('destination is not an instance of HTMLImageElement');
}
if(source instanceof HTMLImageElement){
source = SVGConvert.base64Decode(source);
}
SVGConvert.toCanvas(source, width, height)
.then(canvas => destination.src = canvas.toDataURL(format))
.catch(error => console.log('(╯°□°)╯彡┻━┻ ', error));
}
/**
* Draws the given SVG source on a canvas of the given size and returns the canvas element
*
* @param {SVGElement} source
* @param {Number<int>} width
* @param {Number<int>} height
* @return {Promise<HTMLCanvasElement>}
*/
static toCanvas(source, width, height){
return new Promise((resolve, reject) => {
if(!source instanceof SVGElement){
throw new Error('source is not an instance of SVGElement');
}
// the source SVG element needs to be visible
source.style.display = 'inline-block';
source.style.visibility = 'visible';
// add/fix the width/height of the SVG element
// @see https://bugzilla.mozilla.org/show_bug.cgi?id=700533#c39
source.width.baseVal.valueAsString = width;
source.height.baseVal.valueAsString = height;
try{
// stringify the SVG element and create an object URL for it
let svgString = new XMLSerializer().serializeToString(source);
let svgBlob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
let tempUrl = URL.createObjectURL(svgBlob);
// create a temporary image
let tempImg = new Image(width, height);
// trigger the onLoad event with the temporary blob URL
tempImg.src = tempUrl;
// process the conversion in the onLoad event
tempImg.onload = function(){
// create a canvas element to draw the SVG on
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(tempImg, 0, 0);
// revoke the temporary blob
URL.revokeObjectURL(tempUrl);
// return the PNG image as data URI
resolve(canvas);
};
}
catch(error){
reject(error.message);
}
});
}
/**
* Converts the given SVG base64 data URI into a DOM element
*
* @param {HTMLImageElement} image
* @return {SVGElement}
*/
static base64Decode(image){
if(!image instanceof HTMLImageElement){
throw new Error('image is not an instance of HTMLImageElement');
}
if(!image.src || !image.src.includes('base64,')){
throw new Error('invalid image source');
}
return new DOMParser().parseFromString(atob(image.src.split(',')[1]), 'image/svg+xml').firstChild;
}
}
+91
View File
@@ -0,0 +1,91 @@
<?php
/**
* SVG to PNG conversion via javascript canvas example
*
* @created 25.09.2023
* @author smiley <smiley@chillerlan.net>
* @copyright 2023 smiley
* @license MIT
*/
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\QROutputInterface;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
require_once __DIR__.'/../vendor/autoload.php';
$options = new QROptions;
$options->version = 7;
$options->outputType = QROutputInterface::MARKUP_SVG;
$options->outputBase64 = false;
$options->svgAddXmlHeader = false;
$options->drawLightModules = false;
$options->markupDark = '';
$options->markupLight = '';
$options->drawCircularModules = true;
$options->circleRadius = 0.4;
$options->connectPaths = true;
$options->keepAsSquare = [
QRMatrix::M_FINDER_DARK,
QRMatrix::M_FINDER_DOT,
QRMatrix::M_ALIGNMENT_DARK,
];
$options->svgDefs = '
<linearGradient id="rainbow" x1="1" y2="1">
<stop stop-color="#e2453c" offset="0"/>
<stop stop-color="#e07e39" offset="0.2"/>
<stop stop-color="#e5d667" offset="0.4"/>
<stop stop-color="#51b95b" offset="0.6"/>
<stop stop-color="#1e72b7" offset="0.8"/>
<stop stop-color="#6f5ba7" offset="1"/>
</linearGradient>
<style><![CDATA[
.dark{fill: url(#rainbow);}
]]></style>';
$qrcode = (new QRCode($options))->addByteSegment('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
// render the SVG
$svg_raw = $qrcode->render();
// switch to base64 output
$options->outputBase64 = true;
// render base64
$svg_base64 = $qrcode->render();
// dump the output
header('Content-type: text/html');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>QRCode javascript canvas conversion example</title>
</head>
<body>
<!-- raw SVG input -->
<div>
<?php echo $svg_raw ?>
<img id="qr-svg-dest" />
</div>
<!-- base64 encoded datd URI from img tag -->
<div>
<img id="qr-svg-base64" src="<?php echo $svg_base64 ?>" style="width: 300px; height: 300px;" />
<img id="qr-svg-base64-dest" />
</div>
<script type="module">
import SVGConvert from './SVGConvert.js';
// SVG DOM element
SVGConvert.toDataURI(document.querySelector('svg.qr-svg'), document.getElementById('qr-svg-dest'), 300, 300, 'image/jpeg');
// base64 data URI in image element
SVGConvert.toDataURI(document.getElementById('qr-svg-base64'), document.getElementById('qr-svg-base64-dest'), 300, 300);
</script>
</html>