How to save/export a DOM element to an image? - javascript

I have a web page which has a form element (with its ID known) and
inside the form there are multiple DIVs, and the position of each div
may be changed.
What I'd like to do is:
a) Save the current state of this form
// var currentForm=document.forms['myFrm'].innerHTML;
would probably suffice...
b) Save or export the entire form with the most current position of each DIV
to an image file.
// how to save/export the javascript var of currentForm to an image
file is the key question.
Any help/pointer would be appreciated.

After hours of research, I finally found a solution to take a screenshot of an element, even if the origin-clean FLAG is set (to prevent XSS), that´s why you can even capture for example Google Maps (in my case). I wrote an universal function to get a screenshot. The only thing you need in addition is the html2canvas library (https://html2canvas.hertzen.com/).
Example:
getScreenshotOfElement($("div#toBeCaptured").get(0), 0, 0, 100, 100, function(data) {
// in the data variable there is the base64 image
// exmaple for displaying the image in an <img>
$("img#captured").attr("src", "data:image/png;base64,"+data);
}
Keep in mind console.log() and alert() won´t generate an output if the size of the image is great.
Function:
function getScreenshotOfElement(element, posX, posY, width, height, callback) {
html2canvas(element, {
onrendered: function (canvas) {
var context = canvas.getContext('2d');
var imageData = context.getImageData(posX, posY, width, height).data;
var outputCanvas = document.createElement('canvas');
var outputContext = outputCanvas.getContext('2d');
outputCanvas.width = width;
outputCanvas.height = height;
var idata = outputContext.createImageData(width, height);
idata.data.set(imageData);
outputContext.putImageData(idata, 0, 0);
callback(outputCanvas.toDataURL().replace("data:image/png;base64,", ""));
},
width: width,
height: height,
useCORS: true,
taintTest: false,
allowTaint: false
});
}

There is a library called Domvas that should do what you want.
It gives you the ability to take arbitrary DOM content and paint it to
a Canvas of your choice.
After that exporting an image from a canvas element should be pretty easy:
var canvas = document.getElementById("mycanvas");
var img = canvas.toDataURL("image/png");
document.write('<img src="'+img+'"/>');

Do you want to do it completely in JavaScript? If so, one possible solution could be to transform the HTML to an SVG. Or maybe you can use the <canvas> tag and draw it manually.

Related

HTML canvas saving on mysql database

I'm stuck with my code.
Problem: I have canvas and inside it I draw the lines. And after I finished I want that lines to stay in the right place where i left that(before reload website). So I need to send that canvas to mysql data base. But here I stuck. Did I first need to create .png image and then try to send that image information to database? or somehow I can send it right off from code to database by using AJAX? I read a lot of information and I am confused right now.
If I will use method HTMLgetImageData() and HTMLputImageData() then I need to create some real image in my server? or I can take straight from the canvas? and send to mysql databse? :)
so now I have Canvas in html and some script for drawing the lines:
$(".widget_body").on("mousedown", "canvas", function() {
var id = $(this).attr("id");
var canvas = document.getElementById(id);
var canvas,
context,
dragging = false,
dragStartLocation,
snapshot;
fitToContainer(canvas);
function fitToContainer(canvas){
// Make it visually fill the positioned parent
canvas.style.width ='100%';
canvas.style.height='100%';
// ...then set the internal size to match
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
}
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {x: x, y: y};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y);
context.stroke();
}
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
drawLine(position);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
drawLine(position);
}
function clearCanvas(event) {
context.clearRect(0, 0, canvas.width, canvas.height);
}
context = canvas.getContext('2d');
context.strokeStyle = 'purple';
context.lineWidth = 4;
context.lineCap = 'round';
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
canvas.addEventListener('dblclick', clearCanvas, false);
});
Maybe somebody can suggest something to me? Maybe something about next steps?What should I have to do from this moment?
Well, it depends on whether you're saving the Canvas as a single image or if you're saving each component of it (such as lines, squares, etc).
If you're saving it as a single image, it will be easier to just save the Data URL to your database. Otherwise, create JavaScript objects containing the properties and values of each shape, e.g.:
var line =
{
Name: "Line",
Color: "#3D4AEE",
Shadow: "NULL"
Length: "",
Point: "130, 120"
}
Then convert the object into a JSON String:
var JSONLine = JSON.stringify(line);
Now you have something you can insert into the database.
Now, when you need to retrieve this from the database, so you can redraw it in the browser, all you need to do is lookup the "design", get all the bits that make up that design and redraw them to the Canvas, using the properties of the shapes that you saved.
I'll leave it up to you to figure out how to structure your database to accommodate the different types of shapes, and their relationships to "designs" that are created.
1. You could save the coordinates in a database without reloading the page using AJAX and then fetch the coordinates via AJAX and set them dynamicly in the Javascript. If you want to use a JS Library that makes AJAX-requests easier to use, I recommend jQuery http://api.jquery.com/jquery.ajax/
2. You could convert the canvas to an image using something like
function convertCanvasToImage(canvas) {
var image = new Image();
image.src = canvas.toDataURL("image/png");
return image;
}
And then save the image in a database. However, you won't be able to change the canvas this way, it will be an image. The first way allows you to save the canvas as it is with it's information. Kind of like Photoshop and a .PSD file.
Firstly, you should use Canvas.toDataURL export the data. After that, you can send the data with a FormData via Fetch API.
var fd = new FormData();
fd.append('field', canvas.toDataURL('image/jpg'), 'sketch.jpg');
fetch('/saveSketch', {
method: 'POST',
body: fd,
});
On server side, you need to parse this FormData to retrieve the file. At this time, your files are already available for being saved into database or filesystem.

How to create an image generator for combining multiple images?

I am working on an image generator using HTML5 canvas and jQuery/JS. What I want to accomplish is the following.
The user can upload 2 or max 3 images (type should be png or jpg) to the canvas. The generated images should always be 1080x1920. If the hart uploads only 2 images, the images are 1080x960. If 3 images are uploaded, the size of each image should be 1080x640.
After they upload 2 or 3 images, the user can click on the download button to get the merged image, with a format of 1080x1920px.
It should make use of html canvas to get this done.
I came up with this:
HTML:
<canvas id="canvas">
Sorry, canvas not supported
</canvas><!-- /canvas.offers -->
<input id="fileInput" type="file" />
Generate
jQuery:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.height = 400;
canvas.width = 800;
var img1 = loadImage('http://www.shsu.edu/dotAsset/0e829093-971c-4037-9c1b-864a7be1dbe8.png', main);
var img2 = loadImage('https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Ikea_logo.svg/266px-Ikea_logo.svg.png', main);
var minImages = 2;
var imagesLoaded = 0;
function main() {
imagesLoaded += 1;
if(imagesLoaded >= minImages) {
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.save();
ctx.drawImage(img1, 0, 0);
// ctx.translate(canvas.height/2,canvas.width/2); // move to the center of the canvas
// ctx.rotate(270*Math.PI/180); // rotate the canvas to the specified degrees
// ctx.drawImage(img2,0,canvas.height/2);
ctx.translate(-canvas.height/2,canvas.width/2); // move to the center of the canvas
ctx.rotate(90*Math.PI/180); // rotate the canvas to the specified degrees
ctx.drawImage(img2,-img2.width/2,-img2.width/2);
ctx.restore(); // restore the unrotated context
}
}
function loadImage(src, onload) {
var img = new Image();
img.onload = onload;
img.src = src;
console.log(img);
return img;
}
Above code will create the canvas and place both images (that are now hard-coded in JS) to the created canvas. It will rotate 90 degrees, but it will not position to the right corner. Also the second image should be position beside the first one.
How can I do the rotation and positioning of each image side by side?
Working Fiddle: https://jsfiddle.net/8ww1x4eq/2/
Have a look at the updated jsFiddle, is that what you wanted?
Have a look here regarding image rotation
Updated jsFiddle, drawing multiple images.
Notice:
The save script was just a lazy way to make sure I've got the
external scripts loaded before I save the merged_image...
There is no synchornisation in the sample script, notice that addToCanvas
was called on image loaded event, there could be a race condition
here (but I doubt it, since the image is loaded to memory on
client-side)
function addToCanvas(img) {
// resize canvas to fit the image
// height should be the max width of the images added, since we rotate -90 degree
// width is just a sum of all images' height
canvas.height = max(lastHeight, img.width);
canvas.width = lastWidth + img.height;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (lastImage) {
ctx.drawImage(lastImage, 0, canvas.height - lastImage.height);
}
ctx.rotate(270 * Math.PI / 180); // rotate the canvas to the specified degrees
ctx.drawImage(img, -canvas.height, lastWidth);
lastImage = new Image();
lastImage.src = canvas.toDataURL();
lastWidth += img.height;
lastHeight = canvas.height;
imagesLoaded += 1;
}
PS: I've added some script to download the merged image, but it would fail. The error message was: "Uncaught SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported."
I've done a quick Google search and it seemed to be related to Cross-origin resources. I assumed that it wouldn't be an issue with FileReader. I haven't had time to test that so please test it (and please let me know :) It works with FileReader!
You can use toDataURL. But in this way user must do something like Save image as...
var img = canvas.toDataURL("image/png");
And then set for example img result src:
$("#result").attr("src",img);
Canvas is already an Image.
The canvas and img are interchangeable so there is no need to add the risky step of canvas.toDataURL which can fail depending on the image source domain. Just treat the canvas as if it were and img and put it in the DOM. Converting to a jpg does not save space (actually a resource hungry operation) as the an img needs to be decoded before it can be displayed.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.height = 400;
canvas.width = 800;
document.body.appendChild(canvas); // add to the end of the document
// or add it to a containing element
var container = document.getElementById("containerID"); // or use JQuery
if(container !== null){
container.appendChild(canvas);
}

correct canvas height and width not being passed to a function

Canvas element not being passed with correct height and width to a function, for now I fixed the problem by again assigning the height and width of the canvas after it has been passed to the accepting function. As I am new to this so I want to know if this is a problem with canvas or not?.
$(document).ready(function(){
calling_function = function(eventObj){
//some code
ajaxOptsFtn = {
url: '/xyz_data/',
dataType: 'json',
data: form_vals,
success: function(resp){
//initialisation for function.
if(resp.var_ready === true){
//dynamically adding canvas element.
var canvas_obj = $('<canvas/>').css({width:160, height:240});
$(clicked_element).children('canvas').remove();
$(clicked_element).append(canvas_obj);
//intilise other arguments with some values
var x = 30;
var y= http://abcs.com/dds.jpg;
var z = resp.apparel_img_url;
var nl = gamma_value;
var wD = 23;
var wU = 26;
acceptingFunction(canvas_obj[0],y,z,x,n,wU,wD);
}
else{
console.log('some other message');
}
},
};
if (data.var_ready) {
$.ajax(ajaxOptsFtn);
}
else{
console.log('some message');
};
};
acceptingFunction = function(canvas_obj,y,z,x,n,wU,wD){
canvas = canvas_obj;
console.log("canvas passed height and width:"+ canvas.height +","+canvas.width);
console.log("re assigning expected values");
canvas.width = 160;
canvas.height = 240;
var context = canvas.getContext('2d');
//some code
AimageObj.onload = function () {
//some code
};
BimageObj.onload = function () {
//some code
};
};
You must set the size of the canvas using its attributes, not CSS - for example:
var canvas_obj = $('<canvas/>').attr({'width': 160, 'height': 240});
If you don't do this the canvas element will default to size 300 x 150 pixels which is only stretched by CSS (like an image).
And likewise you also read the same attributes/properties when you want to get the canvas' size.
I have written a blog post that explains this in details - it's too long for SO but here is the essential part:
If we don’t set any actual size for the canvas’ source bitmap it will
default to 300 x 150 pixels as per specification. If you now set the
CSS size of the element to lets say 900 x 450 pixels what happens is
that those 300 x 150 pixels are simply scaled to the new size as the
CSS applies to the element while the default 300 x 150 applies to the
source bitmap (ie. the image). The applied CSS rule doesn’t do
anything with the actual bitmap size.
It would be exactly the same if the canvas was an image, which works
in a similar fashion: it has the image element and then the source
bitmap – the image itself. If you choose to use a different size for
the element than what the image is, the image is simply stretched but
its original data stays the same. There are no more or less pixels in
the original image.
It's also very easy to do it from an HTML5 standpoint. As Simple as:
<canvas id="canvasName" width="160" height="240">
<p>Sorry, The Canvas element is not supported in this browser :(</p>
</canvas>
Adding the paragraph makes it so that a browser that doesn't support the Canvas (IE6 for example) makes this line pop up instead of the Canvas. But putting this into the HTML5 file makes it simple for drawing the Canvas which can be worked with with Javascript. Just make sure you load the Canvas before you load the Javascript, otherwise it will crash
You would then have to declare it in Javascript with
var canvas = document.getElementById("mCanvas");
var context = canvas.getContext("2d");
And you can Console.log it to make sure it is connected properly

Replacing divs with background images with canvas elements

I am trying to replace any divs that have background images with canvas elements with those background images drawn onto them.
I've got the basics working but I am slightly stumped by the difference in image quality between the background-image on a div and the same image drawn onto a canvas.
Here is the code I am using to do this:
$('#container div').each(function(){
if($(this).css('background-image') != 'none'){
var bgImage = $(this).css('background-image').replace(/^url|[\(\)]/g, '');
var image = new Image();
var attrs = $(this)[0].attributes;
var dimensions = new Array();
var canvas = document.createElement('canvas');
dimensions.push($(this).height())
dimensions.push($(this).width());
$(canvas).attr('width',dimensions[0]);
$(canvas).attr('height',dimensions[1]);
$(canvas).css('background-image', 'none');
for(var i = 0; i < attrs.length; i++){
$(canvas).attr(attrs[i].nodeName,attrs[i].nodeValue);
}
var ctx = canvas.getContext('2d');
image.onload = function () {
ctx.drawImage(image, 0, 0, image.height, image.width);
}
image.src = bgImage;
$(this).replaceWith(canvas);
}
});
Here are the results:
It looks like the image is being stretched for some reason but I've tried to console.log the width/height of the image that I am using in drawImage and the values match up to the image dimensions. The results show just a crop of the image - the real one is 900x4000ish pixels.
Here is a jsfiddle link showing the problem in action:
http://jsfiddle.net/xRKJt/
What is causing this odd behaviour?
Ha! (took some seconds to figure out)
Image has naturalWidth and naturalHeight attributes which reflect its pixel dimensions. Change your code
image.onload = function () {
ctx.drawImage(image, 0, 0, image.naturalWidth, image.naturalHeight);
}
Because the image is so large, if you open the image in the browser it zooms out it by default. I think you'll get those zoomed out width and height attributes if you try to access image.width and image.height. Or something along the lines.

Removing an image from a canvas in HTML5

there's an example, which loads 2 images:
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var img1 = new Image();
img.src = "/path/to/image/img1.png";
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
var img2 = new Image();
img2.src = "/path/to/image/img2.png";
img2.onload = function() {
ctx.drawImage(img2, 100, 100);
};
I need to remove(replace) img2 from canvas. What is the best way to do it?
I think maybe you misunderstand what a Canvas is.
A canvas is essentially a 2 dimensional grid of pixels along an 'X' axis and a 'Y' axis. You use the API to draw pixels onto that canvas, so when you draw an image you're basically drawing the pixels that make up that image onto your canvas. The reason there is NO method that lets you just remove an image, is because the Canvas doesn't know there's an image there in the first place, it just see pixels.
This is unlike the HTML DOM (Document Object Model) where everything is a HTML element, or an actual 'thing' you can interact with, hook-up script events to etc. this isn't the case with stuff you draw onto a Canvas. When draw a 'thing' onto a Canvas, that thing doesn't become something you can target or hook into, it's just pixels. To get a 'thing' you need to represent your 'thing' in some way such as a JavaScript object, and maintain a collection of these JS objects somewhere. This how how Canvas games work. This lack of a DOM-like structure for Canvas makes rendering very fast, but can be a pain for implementing UI elements that you can easily hook into and interact with, remove etc. For that you might want to try SVG.
To answer your question, simply paint a rectangle onto your Canvas that covers up your image by using the same X/Y coords and dimensions you used for your original image, or try Pointy's solution. 'Cover-up' is probably the wrong terminology, since you're actually replacing the pixels (there are no layers in Canvas).
It's not clear what you want the canvas to show when the image is gone. If you want it to be transparent, you could get the image data and fill it with transparent pixels:
var img = ctx.createImageData(w, h);
for (var i = img.data.length; --i >= 0; )
img.data[i] = 0;
ctx.putImageData(img, 100, 100);
where "w" and "h" would be the width and height of your original image.
edit — if you just want another image there, why not just put one there? It will overwrite whatever pixels are there on the canvas.
You can use clearRect() function to clear the image area.Rather then clearing whole context you can clear only the image area using this:
ctx.clearRect(xcoordinate_of_img1,ycoordinate_of_img1,xcoordinate_of_img1 + img1.width ,ycoord_of_img1 +img1.height );
If what "Sunday Ironfoot" said is right, then the best way to remove an image is by drawing the images once again from scratch. For this, you need to have an array of images and draw only the ones you use. For example,
function EmptyClass{};
var img=new Array();
img[0]=new EmptyClass;
img[0].i=new Image();
img[0].src="yourfile1.jpg";
img[0].enabled=true;
img[1]=new EmptyClass;
img[1].i=new Image();
img[1].src="yourfile2.jpg";
img[1].enabled=false;// <-------- not enabled, should not be drawn equivalent to removing
img[2]=new EmptyClass;
img[2].i=new Image();
img[2].src="yourfile3.jpg";
img[2].enabled=true;
for(var i=0;i<3;i++){
if(img[i].enabled)ctx.drawImage(img[i], 100, 100);
}
P.S. I am creating an engine for javascript canvas. Will post it within a week
Peace
You can erase an image by drawing the same image again, using a different globalCompositeOperation
ctx.globalCompositeOperation ="xor"
ctx.drawImage(img2, 100, 100);
See https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
Unlike drawing things yourself, if you 'replace' THE image on a canvas, the old one is still there.
Canvas c2;
...
if (null != Image2) {
var ctx = c2.getContext("2d");
ctx.clearRect(0, 0, c2.width, c2.height);
}
Can you overlay canvas objects (I guess I should try before asking, you can -1 one me for being lazy). I guess I'd be interested in have one canvas element as a background, and then another for a layer objects that pop in and out of view. Might be a little more efficient then having to redraw every image if one gets deleted or moved. I'll play around and see what I can find.
There is ``ctx.clearRect(x, y, w, h)'' but this is not a good way to remove the shape, because it will remove any full or partial shapes in the same area of the removed shape. This shouldn't happen, and may remove one or more shapes, I've found it's best to save all your shapes in a list that usually comes from the database using backend language or ajax request, and add for it's shape object an identifier, when you need to remove a shape just remove that shape from the list using the id or the index, then Redraw the canvas with this new array of shapes without a deleted shape, the next time the page loads, this shape will not be added to this list, because it should be deleted from database.
const projectStamps = [{image_id: 'scream', x: 100, y: 100, id: 1}, {image_id: 'scream', x: 100, y: 100, id: 2}, {image_id: 'scream', x: 50, y: 0, id: 3}, {image_id: 'scream', x: 150, y: 0, id: 4}];
let currentShapes = [];
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
function validStampObj(stamp){
if (typeof(stamp.x) !== 'number' || typeof(stamp.y) !== 'number' || typeof(stamp.image_id) === 'undefined' || !document.getElementById(stamp.image_id)){
return false;
} else {
return true;
}
}
function addStamp(stamp){
if (!validStampObj(stamp)){
console.log("can not add stamp, invalid object");
return false;
}
const image = document.getElementById(stamp.image_id);
stamp['w'] = image.getBoundingClientRect().width;
stamp['h'] = image.getBoundingClientRect().height;
ctx.drawImage(image, stamp.x, stamp.y, stamp.w, stamp.h);
currentShapes.push(stamp);
return stamp;
}
let id = 1;
window.onload = function() {
drawProject();
};
function clearCanvas(){
currentShapes = [];
ctx.clearRect(0, 0, canvas.width, canvas.height);
return true;
}
const projectImage = document.getElementById("project_image");
function drawProject(){
if (!projectImage){console.log('missing project image element');return false;}
clearCanvas();
ctx.drawImage(projectImage,0,0);
projectStamps.forEach( (stamp)=>{
addStamp(stamp);
});
}
function removeStamp(targetId){
let targetI = false;
for (let i=0; i<projectStamps.length; i++){
if (projectStamps[i].id == targetId){
targetI = i;
break;
}
}
if (targetI !== false){
/* remove the stamp from drawing stamps list and redraw the data */
projectStamps.splice(targetI,1);
drawProject();
}
}
setTimeout( ()=>{
removeStamp(3);
console.log("removed icon with id 3");
}, 2500 );
<p>Image to use:</p>
<img id="scream" width="35" height="35"
src="https://i.ibb.co/wYyc259/iconimage.png" alt="The Scream">
<img id="project_image" width="450" height="300"
src="https://i.ibb.co/sK5HtQy/bulding-image.png" style="position:absolute;left:-15455px;">
<p>Canvas:</p>
<button onclick="drawProject()">Redraw things</button>
<canvas id="myCanvas" width="450" height="300"
style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
notes if you used clearRect in this example it will remove the part of main image of the canvas not just the icon with id 3 like this code does hope it helps.

Categories