瀏覽代碼

:sparkles: SVG conversion in javascript example

smiley 2 年之前
父節點
當前提交
8fb1e6a45c
共有 3 個文件被更改,包括 207 次插入1 次删除
  1. 2 1
      examples/Readme.md
  2. 114 0
      examples/SVGConvert.js
  3. 91 0
      examples/svgConvertViaCanvas.php

+ 2 - 1
examples/Readme.md

@@ -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 - 0
examples/SVGConvert.js

@@ -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 - 0
examples/svgConvertViaCanvas.php

@@ -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>