Rendering HTML elements to <canvas> - html

Is there a way to have an arbitrary HTML element rendered in a canvas (and then access its buffer...).

You won't get real HTML rendering to <canvas> per se currently, because canvas context does not have functions to render HTML elements.
There are some emulations:
html2canvas project http://html2canvas.hertzen.com/index.html (basically a HTML renderer attempt built on Javascript + canvas)
HTML to SVG to <canvas> might be possible depending on your use case:
https://github.com/miohtama/Krusovice/blob/master/src/tools/html2svg2canvas.js
Also if you are using Firefox you can hack some extended permissions and then render a DOM window to <canvas>
https://developer.mozilla.org/en-US/docs/HTML/Canvas/Drawing_Graphics_with_Canvas?redirectlocale=en-US&redirectslug=Drawing_Graphics_with_Canvas#Rendering_Web_Content_Into_A_Canvas

Take a look on MDN (archived)
It will render html element using creating SVG images.
For Example:
There is <em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span> HTML element. And I want to add it into <canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas> Canvas Element.
Here is Javascript Code to add HTML element to canvas.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var data = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
'<foreignObject width="100%" height="100%">' +
'<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px">' +
'<em>I</em> like <span style="color:white; text-shadow:0 0 2px blue;">cheese</span>' +
'</div>' +
'</foreignObject>' +
'</svg>';
var DOMURL = window.URL || window.webkitURL || window;
var img = new Image();
var svg = new Blob([data], {
type: 'image/svg+xml;charset=utf-8'
});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
DOMURL.revokeObjectURL(url);
}
img.src = url;
<canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>

Here is code to render arbitrary HTML into a canvas:
function render_html_to_canvas(html, ctx, x, y, width, height) {
var xml = html_to_xml(html);
xml = xml.replace(/\#/g, '%23');
var data = "data:image/svg+xml;charset=utf-8,"+'<svg xmlns="http://www.w3.org/2000/svg" width="'+width+'" height="'+height+'">' +
'<foreignObject width="100%" height="100%">' +
xml+
'</foreignObject>' +
'</svg>';
var img = new Image();
img.onload = function () {
ctx.drawImage(img, x, y);
}
img.src = data;
}
function html_to_xml(html) {
var doc = document.implementation.createHTMLDocument('');
doc.write(html);
// You must manually set the xmlns if you intend to immediately serialize
// the HTML document to a string as opposed to appending it to a
// <foreignObject> in the DOM
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc.body);
return html;
}
example:
const ctx = document.querySelector('canvas').getContext('2d');
const html = `
<p>this
<p>is <span style="color:red; font-weight: bold;">not</span>
<p><i>xml</i>!
<p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABWElEQVQ4jZ2Tu07DQBBFz9jjvEAQqAlQ0CHxERQ0/AItBV9Ew8dQUNBQIho6qCFE4Nhex4u85OHdWAKxzfWsx0d3HpazdGITA4kROjl0ckFrnYJmQlJrKsQZxFOIMyEqIMpADGhSZpikB1hAGsovdxABGuepC/4L0U7xRTG/riG3J8fuvdifPKnmasXp5c2TB1HNPl24gNTnpeqsgmj1eFgayoHvRDWbLBOKJbn9WLGYflCCpmM/2a4Au6/PTjdH+z9lCJQ9vyeq0w/ve2kA3vaOnI6k4Pz+0Y24yP3Gapy+Bw6qdfsCRZfWSWgclCCVXTZu5LZFXKJJ2sepW2KYNCENB3U5pw93zLoDjNK6E7rTFcgbkGYJtiLckxCiw4W1OURsxUE5BokQiQj3JIToVtKwlhsurq+YDYbMBjuU/W3KtT3xIbrpAD7E60lwQohuaMtP8ldI0uMbGfC1r1zyWPUAAAAASUVORK5CYII=">`;
render_html_to_canvas(html, ctx, 0, 0, 300, 150);
function render_html_to_canvas(html, ctx, x, y, width, height) {
var data = "data:image/svg+xml;charset=utf-8," + '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '">' +
'<foreignObject width="100%" height="100%">' +
html_to_xml(html) +
'</foreignObject>' +
'</svg>';
var img = new Image();
img.onload = function() {
ctx.drawImage(img, x, y);
}
img.src = data;
}
function html_to_xml(html) {
var doc = document.implementation.createHTMLDocument('');
doc.write(html);
// You must manually set the xmlns if you intend to immediately serialize
// the HTML document to a string as opposed to appending it to a
// <foreignObject> in the DOM
doc.documentElement.setAttribute('xmlns', doc.documentElement.namespaceURI);
// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc.body);
return html;
}
<canvas></canvas>

The CSS element() function may eventually help some people here, even though it's not a direct answer to the question. It allows you to use an element (and all children, including videos, cross-domain iframes, etc.) as a background image (and anywhere else that you'd normally use url(...) in your CSS code). Here's a blog post that shows what you can do with it.
It has been implemented in Firefox since 2011, and is being considered in Chromium/Chrome (don't forget to give the issue a star if you care about this functionality).

RasterizeHTML is a very good project, but if you need to access the canvas it wont work on chrome. due to the use of <foreignObject>.
If you need to access the canvas then you can use html2canvas
I am trying to find another project as html2canvas is very slow in performance

According to the HTML specification you can't access the elements of the Canvas. You can get its context, and draw in it manipulate it, but that is all.
BUT, you can put both the Canvas and the html element in the same div with a aposition: relative and then set the canvas and the other element to position: absolute.
This ways they will be on the top of each other. Then you can use the left and right CSS properties to position the html element.
If the element doesn't shows up, maybe the canvas is before it, so use the z-index CSS property to bring it before the canvas.

Related

Html2Canvas does not render my svg element (lastest version)

I have a roadmap with svg elements to represent links between two items like this :
the svg code looks like :
each time that i try to print to png, my links are not rendered:
My generation code is :
html2canvas(
document.getElementById("roadmapPrint"),
{
backgroundColor:null,
}
).then(function(canvas) {
var image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
var a = document.createElement('a');
a.href = image ;
a.id = "tempAtoDelete"
a.download = "filename.png";
document.body.appendChild(a);
a.click();
let aToDelete = document.getElementById("tempAtoDelete");
document.body.removeChild(aToDelete);})
Do you have some advices ? i read that the latest version of html2canvas library is able to manage svg elements, is it the case ?
Thank you a lot for your help,
I have fixed my issue converting all svg elements to canvas before use html2canvas librairy.
This was an old solution (6 years old) but it works fine.
See my steps below:
install the version 1.5.3 of canvg librairy and import the canvg function (for information this version is dowloaded more than 140000 per week..) (there is no dependence vulnerability with the last version of node/npm/CRA)
npm install canvg#1.5.3
import canvg from 'canvg'
get all your svg element and create a canvas for each (track them to an array to delete them after)
//FOR EACH SVG ELEMENT, A CANVAS WILL BE CREATED => AT THE END WE NEED TO DELETE THEM
var svgNodesToRemove = [];
//GET ALL SVG ELEMENT
let svgElements = document.body.querySelectorAll('svg');
//LOOP ON EACH ELEMENT
svgElements.forEach(function(item) {
//YOU CAN PROCEED TO ELEMENT TREATMENT BEFORE CONVERT TO CANVAS
let children = item.childNodes
children.forEach(function(child) {
//IN MY CASE I USE ANIMATION FOR MY SVG PATH AND NEED TO FIX THE OPACITY STYLE
child.style.opacity = 1;
})
//TRIM EACH ITEM IN THE LOOP
let svg = item.outerHTML.trim();
//CREATE CANVAS FOR YOUR CURRENT SVG
var canvas = document.createElement('canvas');
//UPDATE THE NEEDED ATTRIBUTE WITH AND HEIGHT
canvas.width = item.getBoundingClientRect().width;
canvas.height = item.getBoundingClientRect().height;
//CONVERT SVG TO CANVAS
canvg(canvas, svg);
//UPDATE THE CANVAS POSITION TO THE SVG ELEMENT
if (item.style.position) {
canvas.style.position += item.style.position;
canvas.style.left += item.style.left;
canvas.style.top += item.style.top;
}
//APPEND THE CANVAS TO THE DOM
item.parentNode.appendChild(canvas);
//TRACK CANVAS TO BE DELTED AFTER
svgNodesToRemove.push(canvas)
});
I did not hidde my current svg element in the dom because there are not rendered...
now you are able to use html2canvas :
html2canvas(
document.getElementById("roadmapPrint"),
{
backgroundColor:null,
}
).then(function(canvas){
//YOUR LOGIC
//....
//DELETE SVG CANVAS
svgNodesToRemove.forEach(function(element){
element.remove();
})
}).catch((err)=>{
//DISPLAY ERROR
console.log(err)
//DELETE SVG CANVAS
svgNodesToRemove.forEach(function(element){
element.remove();
})
});

Passing a variable from Jquery into HTML

I have a jquery function that works out the width and height of an image I upload through umbraco. How can I call this.width and this.height into my img src in html at the bottom of the page?
<script>
function getMeta(varA, varB) {
if (typeof varB !== 'undefined') {
alert(varA + ' width ' + varB + ' height');
} else {
var img = new Image();
img.src = varA;
img.onload = function () {
getMeta(this.width, this.height);
}
}
}
getMeta("#image.Url()")
</script>
<img src="#image.Url()" width="" height="" border="0" alt="#Model.Value("mapImage")
mapImage" usemap="#Map" />
I've left the width and height empty but that's where I want it to equal this.width and this.height. I need it to automatically call it for other functions I have as it needs to change depending on the size of the image uploaded.
Thanks in advance for any help :)
Well.. good news - this is native JS - no jquery used the code you've posted
Second - you don't need to send the width and height to your data model - just update your image element. so...
img.onload = function () {
const imgElement = document.querySelector('img'); // select the image element in you HTML page
imgElement.style.width = `${this.width}px`; // set a proper with using pixels as unit
imgElement.style.height = `${this.height}px`; // set height the same way
}
This should update your image proportions to be an exact fit to the image provided
Assuming that I understood you well and you have an uploaded pic (that you get its source by calling #image.Url()), and you want to apply its dimensions of another pic (with id templateImg), this is the code:
<img id="templateImg" src="https://cdn.vox-cdn.com/thumbor/prZDWmD_4e4Js7KCRJ2JDrJOqO0=/0x0:939x704/1400x1050/filters:focal(0x0:939x704):format(jpeg)/cdn.vox-cdn.com/uploads/chorus_image/image/49610677/homersimpson.0.0.jpg" />
<script type = "text/javascript">
var uploadedPic = 'https://i.pinimg.com/736x/dd/50/38/dd5038cdbfde2a8f1583bd84f992f862.jpg';
function getMeta(src) {
return new Promise(resolve => {
var img = new Image();
img.src = src;
img.onload = function () {
resolve({
width: this.width,
height: this.height
});
}
})
}
getMeta(uploadedPic)
.then(dimensions => {
document.getElementById('templateImg').style.width = `${dimensions.width}px`;
document.getElementById('templateImg').style.height = `${dimensions.height}px`;
});
</script>
It was working example with real pictures, now you just replace the line var uploadedPic... with this Razor code:
<text>
var uploadedPic = '#image.Url()';
</text>
(assuming this is the path to your uploaded picture) and you'll be fine.

how to render offscreen canvas properly

I'm trying to create a basic example of offscreen rendering canvas but I'm error in js "cannot read property of context". actually my idea is to create a demo like I saw in https://yalantis.com/ I want to create my name initial. If there is any better idea to achieve this then please enlighten me.
Thanks here is my basic attempt before the actual implementation :)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Off Screen Canvas</title>
<script>
function createOffscreenCanvas() {
var offScreenCanvas= document.createElement('canvas');
offScreenCanvas.width= '1360px';
offScreenCanvas.height= '400px';
var context= offScreenCanvas.getContext("2d");
context.fillRect(10,10,200,200);
}
function copyToOnScreen(offScreenCanvas) {
var onScreenContext=document.getElementById('onScreen').getContext('2d');
var offScreenContext=offScreenCanvas.getContext('2d');
var image=offScreenCanvas.getImageData(10,10,200,200);
onScreenContext.putImageData(image,0,0);
}
function main() {
copyToOnScreen(createOffscreenCanvas());
}
</script>
<style>
#onScreen {
width:1360px;
height: 400px;
}
</style>
</head>
<body onload="main()">
<canvas id="onScreen"></canvas>
</body>
</html>
You could achieve this in the following way ...
function createOffscreenCanvas() {
var offScreenCanvas = document.createElement('canvas');
offScreenCanvas.width = '1360';
offScreenCanvas.height = '400';
var context = offScreenCanvas.getContext("2d");
context.fillStyle = 'orange'; //set fill color
context.fillRect(10, 10, 200, 200);
return offScreenCanvas; //return canvas element
}
function copyToOnScreen(offScreenCanvas) {
var onScreenContext = document.getElementById('onScreen').getContext('2d');
onScreenContext.drawImage(offScreenCanvas, 0, 0);
}
function main() {
copyToOnScreen(createOffscreenCanvas());
}
canvas {
border: 1px solid black;
}
<body onload="main()">
<canvas id="onScreen" width="1360" height="400"></canvas>
note : never set canvas's width and height using css. instead use the native width and height property of the canvas.
You can try to do that with experimental OffScreenCanvas API. I'm leaving the link here.
So you don't actually need a canvas element attached to DOM with this experimental API, furthermore you can perform your drawing in webworkers, so it will not block the browser's main thread if you're drawing thousands of objects.
const offscreen = new OffscreenCanvas(256, 256);
offscreen.getContext("2d") // or webgl, etc.
Please beware that it's experimental. You can try to check like "OffscreenCanvas" in window and use that, if it's not available, you can fallback to document.createElement("canvas").
Return offScreenCanvas in your function createOffscreenCanvas
function createOffscreenCanvas() {
var offScreenCanvas= document.createElement('canvas');
offScreenCanvas.width= '1360px';
offScreenCanvas.height= '400px';
var context= offScreenCanvas.getContext("2d");
context.fillRect(10,10,200,200);
return offScreenCanvas;
}
Edit
You were getting image date from canvas not context.
function copyToOnScreen(offScreenCanvas) {
var onScreenContext=document.getElementById('onScreen').getContext('2d');
var offScreenContext = offScreenCanvas.getContext('2d');
var image=offScreenContext.getImageData(10,10,200,200);
onScreenContext.putImageData(image,0,0);
}

Adding image in the body of the email using href

I have a link in my page that once click it will open the email client.
My code is below:
<a href="mailto:?subject=Testing&body=AttachImage">
My question is how can I add an image in the body of the email. I tried image src but it does not work. Any suggestions?
Suppose you have your image defined like :
<img id="myimage" src="http://www.myserver.com/images/myimage"></img>
And its equivalent base64 encoded here that you would like to send by mail :
<img id="b64" src=""></img>
You can use base64 encoded form, such as :
function getBase64Image() {
//return LOGO;
var dataURL = '';
try {
var img = document.getElementById('myimage');
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
// Copy the image contents to the canvas
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Get the data-URL formatted image
// Firefox supports PNG and JPEG. You could check img.src to guess the
// original format, but be aware the using "image/jpg" will re-encode the image.
dataURL = canvas.toDataURL("image/jpg");
}
catch(e) {
console.log('Could not retrieve Base64 image with userAgent : ' + navigator.userAgent + ' \nException : ' + e);
}
return dataURL;
}
var base64Image = getBase64Image();
And then set your img src :
document["b64"].src = base64Image ;
Add that image inside <a> tag.
<a href="mailto:?subject=Testing">
<img src="your image path">
</a>
Hope it will help.
It's working, you might have not defined img width and height.
img{
width:100px;
height:100px;
}
<a href="mailto:?subject=Testing&body=AttachImage">
<img src = "https://source.unsplash.com/random">
</a>

Why my canvas getImageData doesn't work?

I am trying to pick up a color from a image pixel with that simple code :
<body>
<img src="image.png" id="monImage"/>
<canvas id="myCanvas"></canvas>
<script src="jquery.js"></script>
<script>
$(function() {
var img = $('#monImage')[0];
var canvas = $('#myCanvas')[0];
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height);
var pixelData = canvas.getContext('2d').getImageData(0, 0, 1, 1);
alert("test");
});
</script>
</body>
I am using jquery so it's easier to select element. If I comment this line :
var pixelData = canvas.getContext('2d').getImageData(1, 1, 1, 1);
Then the alert() works and apears on my screen. But if I don't comment it doesn't, so that line doesn't works. Why ?
Thanks
At the point your function executes the image is not done loading (image data is not available).
The image element is valid however so you can read its properties. This is why it doesn't fail before, but its width and height properties will be 0.
A little bitsurprising is that you can execute the line with drawImage. However at the point you get to getImageData there is no data to retrieve and therefor it will fail.
You need to either put your code inside a window load event handler or add an event listener to the image element.
For example:
window.onload = function() {
/// your function goes here
}