<canvas id=canvas width=200 height=200></canvas><script>window.onload = function() {var canvas = document.getElementById("canvas");var context = canvas.getContext("2d");context.fillStyle = "green";context.fillRect(50, 50, 100, 100);// no argument defaults to image/png; image/jpeg, etc also work on some// implementations -- image/png is the only one that must be supported per spec.window.location = canvas.toDataURL("image/png");}</script>
export function canvasToArrayBuffer(canvas: HTMLCanvasElement, mime: string): Promise<ArrayBuffer> {return new Promise((resolve, reject) => canvas.toBlob(async (d) => {if (d) {const r = new FileReader();r.addEventListener('loadend', e => {const ab = r.result;if (ab) {resolve(ab as ArrayBuffer);}else {reject(new Error('Expected FileReader result'));}}); r.addEventListener('error', e => {reject(e)});r.readAsArrayBuffer(d);}else {reject(new Error('Expected toBlob() to be defined'));}}, mime));}
Alternatively, if you wanted to work with low-level byte data, you can get the raw bytes of the canvas, then, depending on the file spec, write the raw image data into the necessary bytes of the data. you just need to call ctx.getImageData(0, 0, ctx.canvas.widht, ctx.canvas.height) to get the raw image data, then based on the file specification, write it to that
<script>(() => {window.onload = () => {// Test 1: SVG from Onlineconst canvas = new Canvas(650, 500)// canvas.DrawGrid() // If you want to show grid, you can use it.const svg2img = new SVG2IMG(canvas.canvas, "https://upload.wikimedia.org/wikipedia/commons/b/bd/Test.svg")svg2img.AddText("Hello", 100, 250, {mode: "fill", color: "yellow", alpha: 0.8})svg2img.AddText("world", 200, 250, {mode: "stroke", color: "red"})svg2img.AddText("!", 280, 250, {color: "#f700ff", size: "72px"})svg2img.Build("Test.png")
// Test 2: URI.dataconst canvas2 = new Canvas(180, 180)const uriData = "data:image/svg+xml;base64,PHN2ZyBjbGFzcz0ic3ZnLWlubGluZS0tZmEgZmEtc21pbGUtd2luayBmYS13LTE2IiBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhciIgZGF0YS1pY29uPSJzbWlsZS13aW5rIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDQ5NiA1MTIiIGRhdGEtZmEtaTJzdmc9IiI+PHBhdGggZmlsbD0iY3VycmVudENvbG9yIiBkPSJNMjQ4IDhDMTExIDggMCAxMTkgMCAyNTZzMTExIDI0OCAyNDggMjQ4IDI0OC0xMTEgMjQ4LTI0OFMzODUgOCAyNDggOHptMCA0NDhjLTExMC4zIDAtMjAwLTg5LjctMjAwLTIwMFMxMzcuNyA1NiAyNDggNTZzMjAwIDg5LjcgMjAwIDIwMC04OS43IDIwMC0yMDAgMjAwem0xMTcuOC0xNDYuNGMtMTAuMi04LjUtMjUuMy03LjEtMzMuOCAzLjEtMjAuOCAyNS01MS41IDM5LjQtODQgMzkuNHMtNjMuMi0xNC4zLTg0LTM5LjRjLTguNS0xMC4yLTIzLjctMTEuNS0zMy44LTMuMS0xMC4yIDguNS0xMS41IDIzLjYtMy4xIDMzLjggMzAgMzYgNzQuMSA1Ni42IDEyMC45IDU2LjZzOTAuOS0yMC42IDEyMC45LTU2LjZjOC41LTEwLjIgNy4xLTI1LjMtMy4xLTMzLjh6TTE2OCAyNDBjMTcuNyAwIDMyLTE0LjMgMzItMzJzLTE0LjMtMzItMzItMzItMzIgMTQuMy0zMiAzMiAxNC4zIDMyIDMyIDMyem0xNjAtNjBjLTI1LjcgMC01NS45IDE2LjktNTkuOSA0Mi4xLTEuNyAxMS4yIDExLjUgMTguMiAxOS44IDEwLjhsOS41LTguNWMxNC44LTEzLjIgNDYuMi0xMy4yIDYxIDBsOS41IDguNWM4LjUgNy40IDIxLjYuMyAxOS44LTEwLjgtMy44LTI1LjItMzQtNDIuMS01OS43LTQyLjF6Ij48L3BhdGg+PC9zdmc+"const svg2img2 = new SVG2IMG(canvas2.canvas, uriData)svg2img2.Build("SmileWink.png")
// Test 3: Exists SVGImportFontAwesome()const range = document.createRange()const fragSmile = range.createContextualFragment(`<i class="far fa-smile" style="background-color:black;color:yellow"></i>`)document.querySelector(`body`).append(fragSmile)
// use MutationObserver wait the fontawesome convert ``<i class="far fa-smile"></i>`` to SVG. If you write the element in the HTML, then you can skip this hassle way.const observer = new MutationObserver((mutationRecordList, observer) => {for (const mutation of mutationRecordList) {switch (mutation.type) {case "childList":const targetSVG = mutation.target.querySelector(`svg`)if (targetSVG !== null) {const canvas3 = new Canvas(64, 64) // 👈 Focus here. The part of the observer is not important.const svg2img3 = new SVG2IMG(canvas3.canvas, SVG2IMG.Convert2URIData(targetSVG))svg2img3.Build("Smile.png")targetSVG.remove() // This SVG is created by font-awesome, and it's an extra element. I don't want to see it.observer.disconnect()return}}}})observer.observe(document.querySelector(`body`), {childList: true})}})()
class SVG2IMG {/*** @param {HTMLCanvasElement} canvas* @param {string} src "http://.../xxx.svg" or "data:image/svg+xml;base64,${base64}"* */constructor(canvas, src) {this.canvas = canvas;this.context = this.canvas.getContext("2d")this.src = srcthis.addTextList = []}
/*** @param {HTMLElement} node* @param {string} mediaType: https://en.wikipedia.org/wiki/Media_type#Common_examples_%5B10%5D* @see https://en.wikipedia.org/wiki/List_of_URI_schemes* */static Convert2URIData(node, mediaType = 'data:image/svg+xml') {const base64 = btoa(node.outerHTML)return `${mediaType};base64,${base64}`}
/*** @param {string} text* @param {int} x* @param {int} y* @param {"stroke"|"fill"} mode* @param {string} size, "30px"* @param {string} font, example: "Arial"* @param {string} color, example: "#3ae016" or "yellow"* @param {int} alpha, 0.0 (fully transparent) to 1.0 (fully opaque) // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#transparency* */AddText(text, x, y, {mode = "fill", size = "32px", font = "Arial", color = "black", alpha = 1.0}) {const drawFunc = (text, x, y, mode, font) => {return () => {// https://www.w3schools.com/graphics/canvas_text.asp// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillTextconst context = this.contextconst originAlpha = context.globalAlphacontext.globalAlpha = alphacontext.font = `${size} ${font}`
switch (mode) {case "fill":context.fillStyle = colorcontext.fillText(text, x, y)breakcase "stroke":context.strokeStyle = colorcontext.strokeText(text, x, y)breakdefault:throw Error(`Unknown mode:${mode}`)}context.globalAlpha = originAlpha}}this.addTextList.push(drawFunc(text, x, y, mode, font))}
/*** @description When the build is finished, you can click the filename to download the PNG or mouse enters to copy PNG to the clipboard.* */Build(filename = "download.png") {const img = new Image()img.src = this.srcimg.crossOrigin = "anonymous" // Fixes: Tainted canvases may not be exported
img.onload = (event) => {this.context.drawImage(event.target, 0, 0)for (const drawTextFunc of this.addTextList) {drawTextFunc()}
// create a "a" node for downloadconst a = document.createElement('a')document.querySelector('body').append(a)a.innerText = filenamea.download = filename
const quality = 1.0// a.target = "_blank"a.href = this.canvas.toDataURL("image/png", quality)a.append(this.canvas)}
this.canvas.onmouseenter = (event) => {// set background to white. Otherwise, background-color is black.this.context.globalCompositeOperation = "destination-over" // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation // https://www.w3schools.com/tags/canvas_globalcompositeoperation.aspthis.context.fillStyle = "rgb(255,255,255)"this.context.fillRect(0, 0, this.canvas.width, this.canvas.height)this.canvas.toBlob(blob => navigator.clipboard.write([new ClipboardItem({'image/png': blob})])) // copy to clipboard}}}
class Canvas {
/*** @description for do something like that: ``<canvas width="" height=""></>canvas>``**/constructor(w, h) {const canvas = document.createElement("canvas")document.querySelector(`body`).append(canvas)this.canvas = canvas;[this.canvas.width, this.canvas.height] = [w, h]}
/*** @description If your SVG is large, you may want to know which part is what you wanted.* */DrawGrid(step = 100) {const ctx = this.canvas.getContext('2d')const w = this.canvas.widthconst h = this.canvas.height
// Draw the vertical line.ctx.beginPath();for (let x = 0; x <= w; x += step) {ctx.moveTo(x, 0);ctx.lineTo(x, h);}// set the color of the linectx.strokeStyle = 'rgba(255,0,0, 0.5)'ctx.lineWidth = 1ctx.stroke();
// Draw the horizontal line.ctx.beginPath();for (let y = 0; y <= h; y += step) {ctx.moveTo(0, y)ctx.lineTo(w, y)}ctx.strokeStyle = 'rgba(128, 128, 128, 0.5)'ctx.lineWidth = 5ctx.stroke()}}
function ImportFontAwesome() {const range = document.createRange()const frag = range.createContextualFragment(`<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" /><script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/js/all.min.js" integrity="sha512-UwcC/iaz5ziHX7V6LjSKaXgCuRRqbTp1QHpbOJ4l1nw2/boCfZ2KlFIqBUA/uRVF0onbREnY9do8rM/uT/ilqw==" crossorigin="anonymous"/>`)document.querySelector("head").append(frag)}</script>