From 3991b8ee9697a905d8d7ca6050a6315c24087051 Mon Sep 17 00:00:00 2001 From: Zeno Zeng Date: Fri, 4 Feb 2022 13:50:27 +0800 Subject: [PATCH] feat: Context.prototype.getImageData (experimental), for https://github.com/gliffy/canvas2svg/issues/3, for https://github.com/zenozeng/p5.js-svg/issues/203 --- README.md | 8 +++++ context.js | 15 ++++++++- element.js | 49 ++++++------------------------ image.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 5 files changed, 119 insertions(+), 41 deletions(-) create mode 100644 image.js diff --git a/README.md b/README.md index b5081e8..7c9566f 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,16 @@ ctx.fillRect(100,100,100,100); const mySerializedSVG = ctx.getSerializedSvg(); ``` +## Tests + +https://zenozeng.github.io/p5.js-svg/test/ + ## CHANGELOG +## v2.2.0 + +- feat: Context.prototype.getImageData (experimental) for https://github.com/gliffy/canvas2svg/issues/3 and https://github.com/zenozeng/p5.js-svg/issues/203 + ## v2.1.0 - feat: SVGCanvasElement(options) diff --git a/context.js b/context.js index 04abf84..b1b801b 100644 --- a/context.js +++ b/context.js @@ -14,6 +14,7 @@ */ import * as utils from './utils'; +import imageUtils from './image'; export default (function () { "use strict"; @@ -1306,12 +1307,24 @@ export default (function () { return new DOMPoint(x, y).matrixTransform(this.__transformMatrix) } + /** + * + * @param {*} sx The x-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. + * @param {*} sy The y-axis coordinate of the top-left corner of the rectangle from which the ImageData will be extracted. + * @param {*} sw The width of the rectangle from which the ImageData will be extracted. Positive values are to the right, and negative to the left. + * @param {*} sh The height of the rectangle from which the ImageData will be extracted. Positive values are down, and negative are up. + * @param {Boolean} options.async Will return a Promise if true, must be set to true + * @returns An ImageData object containing the image data for the rectangle of the canvas specified. The coordinates of the rectangle's top-left corner are (sx, sy), while the coordinates of the bottom corner are (sx + sw, sy + sh). + */ + Context.prototype.getImageData = function(sx, sy, sw, sh, options) { + return imageUtils.getImageData(this.getSvg(), this.width, this.height, sx, sy, sw, sh, options); + }; + /** * Not yet implemented */ Context.prototype.drawFocusRing = function () {}; Context.prototype.createImageData = function () {}; - Context.prototype.getImageData = function () {}; Context.prototype.putImageData = function () {}; Context.prototype.globalCompositeOperation = function () {}; diff --git a/element.js b/element.js index 74d40a6..ba15a5d 100644 --- a/element.js +++ b/element.js @@ -1,4 +1,5 @@ import Context from './context'; +import imageUtils from './image'; function SVGCanvasElement(options) { @@ -74,45 +75,15 @@ SVGCanvasElement.prototype.toObjectURL = function() { return URL.createObjectURL(svg); }; -SVGCanvasElement.prototype.toDataURL = function(type, options) { - var xml = new XMLSerializer().serializeToString(this.svg); - - // documentMode is an IE-only property - // http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx - // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript - var isIE = document.documentMode; - - if (isIE) { - // This is patch from canvas2svg - // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly - var xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; - if(xmlns.test(xml)) { - xml = xml.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); - } - } - - var SVGDataURL = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(xml); - if (type === "image/svg+xml" || !type) { - return SVGDataURL; - } - if (type === "image/jpeg" || type === "image/png") { - var canvas = document.createElement('canvas'); - canvas.width = this.width; - canvas.height = this.height; - var ctx = canvas.getContext('2d'); - var img = new Image(); - img.src = SVGDataURL; - if (img.complete && img.width > 0 && img.height > 0) { - // for chrome, it's ready immediately - ctx.drawImage(img, 0, 0); - return canvas.toDataURL(type, options); - } else { - // for firefox, it's not possible to provide sync api in current thread - // and web worker doesn't provide canvas API, so - throw new Error('svgcanvas.toDataURL() for jpeg/png is only available in Chrome.'); - } - } - throw new Error('Unknown type for SVGCanvas.prototype.toDataURL, please use image/jpeg | image/png | image/svg+xml.'); +/** + * toDataURL returns a data URI containing a representation of the image in the format specified by the type parameter. + * + * @param {String} type A DOMString indicating the image format. The default type is image/svg+xml; this image format will be also used if the specified type is not supported. + * @param {Number} encoderOptions A Number between 0 and 1 indicating the image quality to be used when creating images using file formats that support lossy compression (such as image/jpeg or image/webp). A user agent will use its default quality value if this option is not specified, or if the number is outside the allowed range. + * @param {Boolean} options.async Will return a Promise if true, must be set to true if type is not image/svg+xml + */ +SVGCanvasElement.prototype.toDataURL = function(type, encoderOptions, options) { + return imageUtils.toDataURL(this.svg, this.width, this.height, type, encoderOptions, options) }; SVGCanvasElement.prototype.addEventListener = function() { diff --git a/image.js b/image.js new file mode 100644 index 0000000..468dad8 --- /dev/null +++ b/image.js @@ -0,0 +1,86 @@ +class ImageUtils { + + /** + * Convert svg dataurl to canvas element + * + * @private + */ + async svg2canvas(svgDataURL, width, height) { + const svgImage = await new Promise((resolve) => { + var svgImage = new Image(); + svgImage.onload = function() { + resolve(svgImage); + } + svgImage.src = svgDataURL; + }) + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + ctx.drawImage(svgImage, 0, 0); + return canvas; + } + + toDataURL(svgNode, width, height, type, encoderOptions, options) { + var xml = new XMLSerializer().serializeToString(svgNode); + + // documentMode is an IE-only property + // http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx + // http://stackoverflow.com/questions/10964966/detect-ie-version-prior-to-v9-in-javascript + var isIE = document.documentMode; + + if (isIE) { + // This is patch from canvas2svg + // IE search for a duplicate xmnls because they didn't implement setAttributeNS correctly + var xmlns = /xmlns="http:\/\/www\.w3\.org\/2000\/svg".+xmlns="http:\/\/www\.w3\.org\/2000\/svg/gi; + if(xmlns.test(xml)) { + xml = xml.replace('xmlns="http://www.w3.org/2000/svg','xmlns:xlink="http://www.w3.org/1999/xlink'); + } + } + + if (!options) { + options = {} + } + + var SVGDataURL = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(xml); + if (type === "image/svg+xml" || !type) { + if (options.async) { + return Promise.resolve(SVGDataURL) + } + return SVGDataURL; + } + if (type === "image/jpeg" || type === "image/png") { + if (!options.async) { + throw new Error('svgcanvas: options.async must be set to true if type is image/jpeg | image/png') + } + return (async () => { + const canvas = await this.svg2canvas(SVGDataURL, width, height); + const dataUrl = canvas.toDataURL(type, encoderOptions); + canvas.remove(); + return dataUrl; + })() + } + throw new Error('svgcanvas: Unknown type for toDataURL, please use image/jpeg | image/png | image/svg+xml.'); + } + + getImageData(svgNode, width, height, sx, sy, sw, sh, options) { + if (!options) { + options = {} + } + if (!options.async) { + throw new Error('svgcanvas: options.async must be set to true for getImageData') + } + const svgDataURL = this.toDataURL(svgNode, width, height, 'image/svg+xml'); + return (async () => { + const canvas = await this.svg2canvas(svgDataURL, width, height); + const ctx = canvas.getContext('2d') + const imageData = ctx.getImageData(sx, sy, sw, sh); + canvas.remove(); + return imageData; + })() + } +} + +const utils = new ImageUtils(); + +export default utils; \ No newline at end of file diff --git a/package.json b/package.json index 102e9ec..8333aed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svgcanvas", - "version": "2.1.0", + "version": "2.2.0", "description": "svgcanvas", "main": "dist/svgcanvas.js", "scripts": {