I have a canvas element with some doodling in it.
I am using the following to convert the canvas to a jpeg:
var data = canvas.toDataURL( "image/jpeg", 0.5 );
var img = new Image();
img.src = data;
$( "body" ).append( img );
However instead of my doodle, I get a solid black jpeg.
Can anyone tell me what I'm doing wrong?
Thanks!
Thats happening because the JPEG does not support a transparent background.. if you want that to be supported use png (the default extension) else you can set a non transparent fill color to the canvas using .fillStyle and .fillRect
Image created with a "image/jpeg" type have a default black background. Consider the snippet below in which the canvas is on the left and the captured image is on the right:
var canvas = $("#c").get(0), ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect(40,60,20,20);
var data = canvas.toDataURL("image/jpeg");
var img = new Image();
img.src = data;
$( "body" ).append( img );
var data = canvas.toDataURL("image/png");
var img = new Image();
img.src = data;
$( "body" ).append( img );
canvas { border: thin solid green; }
img { border: thin solid black; margin-right: 5px; }
body { background-color: #DFF; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="c" width="100"></canvas>
If you've only drawn black shapes on the canvas, you won't see them against a default black JPEG background.
If you instead use the type image/png, the background will be transparent by default.
Related
so I have a style in the svg image and I am using that to change the fill using javascript with a random colors function and that works fine. but when I need the svg to be in png format after the color is changed, the changed colors colors are not retained for the PNG but the original colors are kept instead
<style type="text/css">
:root{
--back:#662D91;
--shadow:0.28;
--highlight:#F3B4AE;
--stomach:#EA8A82;
--lightHigh:#FEE9E5;
--lasso:#9B5D12;
--gloves:#29A6DE;--glovesStroke:#000674;--gloves2:#29A6DE;--gloves3:#1C7FC9;--gloves4:#1D7DC6;--gloves5:#1C7DC4;
--hatBelow:#DB8556;
--hatBelow2:#8A3C13;
--hatInner:#8C4017;
--hatInner2:#934A24;
--hatInner3:#9D5C3A;
--stripe:#8A7454;
--stripeEnd:#382000;
}
.st0{fill:var(--back);}
.st1{opacity:var(--shadow);}
.st2{fill:var(--highlight);}
.st3{fill:var(--stomach);}
.st4{fill:var(--lightHigh);}
.st5{fill:var(--lasso);}
.st6{filter:url(#Adobe_OpacityMaskFilter);}
.st7{filter:url(#Adobe_OpacityMaskFilter_1_);}
.st8{clip-path:url(#SVGID_3_);mask:url(#SVGID_4_);fill:url(#SVGID_5_);}
.st9{opacity:0.7;clip-path:url(#SVGID_3_);fill:url(#SVGID_6_);}
.st10{fill:url(#SVGID_10_);}
.st11{fill:#FFD2B3;}
.st12{fill:#EEBD9C;}
.st13{fill:#FFE3CE;}
.st14{fill:#AF7B6E;}
.st15{fill:var(--gloves);stroke:var(--glovesStroke);stroke-miterlimit:10;}
.st16{fill:var(--gloves2);}
.st17{fill:var(--gloves3);}
.st18{fill:var(--gloves4);}
.st19{fill:var(--gloves5);}
</style>
this is the style element in my svg and I am changing it using this
root.style.setProperty('--back',colors[0]);
the colors is an array of colors that I get my randomly making a color using a script in the svg
help plz
so this is how i am converting svg to png
<script>
var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = self.URL||self.webkitURL||self;
var img = new Image();
var svg = new Blob([svgString],{type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function(){
ctx.drawImage(img,0,0);
var png = canvas.toDataURL("image/png");
document.querySelector('#png-container').innerHTML = '<img src="'+png+'"/>';
DOMURL.revokeObjectURL(png);
};
img.src = url;
</script>
Edit: Took a minute to thing about it, and it makes sense that it doesn't work. svg blob only includes svg tag contents and hence has no idea there's a CSS variable set on html tag outside of it. Styles applied with :root are defined within SVG, so when you yank it out of HTML context, I assume :root points to the svg tag itself.
Not sure why it works this way, but when using canvas CSS variables set on ancestors of svg tag seem to be ignored. What you can do to fix this is set inline styles on either svg tag itself or its children.
Here's a reproduction with a fix, don't mind changes to the JS code, it does the exact same thing yours does just but sets CSS variable on svg tag instead of html.
<!DOCTYPE html>
<html lang="en">
<head>
<title>SVG thingy</title>
</head>
<body>
<svg
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
>
<style>
:root {
--color: red;
}
circle {
fill: var(--color);
}
</style>
<circle
cx="5"
cy="5"
r="4"
/>
</svg>
<canvas></canvas>
<div id="png-container"></div>
<script>
const root = document.documentElement; // html tag
// root.style.setProperty('--color', 'black'); // Doesn't work
const svgElement = document.querySelector('svg');
svgElement.style.setProperty('--color', 'black'); // Works
const svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));
const svgBlob = new Blob([svgElement.outerHTML], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(svgBlob);
const canvasElement = document.querySelector('canvas');
const ctx = canvasElement.getContext('2d');
const img = new Image();
img.src = url;
img.onload = () => {
ctx.drawImage(img, 0, 0);
const png = canvasElement.toDataURL('image/png');
document.querySelector('#png-container').innerHTML = '<img src="' + png + '"/>';
URL.revokeObjectURL(png);
};
</script>
</body>
</html>
I'm using fabricjs to place an image on a canvas. I'd like to learn what I need to do to to replace the already drawn image with another one from a url onclick.
My limited understanding has me thinking I need to maybe set a var for each image but I'm not sure how to connect that with the fabricjs canvas. I've spent the last few hours trying different things without any luck.
I tried submitting a similar question but deleted it because I thought maybe it was too complicated an ask. Any help would be greatly appreciated. Here's my code now:
var canvas = new fabric.Canvas('c');
fabric.Image.fromURL('http://lorempixel.com/400/400/', function(img) {
var oImg = img.set({
selectable: false
})
canvas.add(oImg).renderAll();
});
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
This is how you could achieve that :
var canvas = new fabric.Canvas('c');
var image, isImageLoaded;
fabric.Image.fromURL('//lorempixel.com/200/200/', function(img) {
isImageLoaded = true;
image = img.set({
selectable: false
})
canvas.add(image).renderAll();
});
function replaceImage(imgUrl) {
if (!isImageLoaded) return; //return if initial image not loaded
var imgElem = image._element; //reference to actual image element
imgElem.src = imgUrl; //set image source
imgElem.onload = () => canvas.renderAll(); //render on image load
}
canvas.on('mouse:down', function() {
replaceImage('//lorempixel.com/200/200/');
});
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<canvas id="c" width="200" height="200"></canvas>
PS: apology for not giving that much explanation.
You can do this much simpler. Don't see why you'd use an extra lib for something like this. I wrote something small in plain JS which hopefully helps you further.
document.getElementById("image").onclick = function(){
document.getElementById("image").src = "http://lorempixel.com/400/400/";
};
<img id="image" src="http://lorempixel.com/400/400/">
var canvas = new fabric.Canvas('c');
var oImg;
loadImage();
function loadImage(){
fabric.Image.fromURL('https://lorempixel.com/400/400/', function(img) {
oImg && canvas.remove(oImg); //If image element alread added to canvas remove it.
oImg = img.set({
selectable: false
})
canvas.add(oImg); //add new image to canvas
oImg.on('mousedown',onImgMouseDown); //add mouse down lisner to image
});
}
function onImgMouseDown(){
loadImage();
}
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
Alternative you add mousedown listener to image , and check if element added ,then remove it from canvas. then add new one to canvas.
i want to know that, how to put the div tag generated with ajax dynamically onto the canvas.
I just create the div tage with ajax and append the generated divs to the another div tag on the jsp page. and i want to add main div on the canvas.
Thanks in advance.
You can't just render HTML into a canvas. Instead, one approach would be to use an SVG image containing the content you want to render.
To draw HTML content, you'd use a <foreignObject> element containing the HTML, then draw that SVG image into your 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;
You can find more information here.
Limitations
This approach has a lot of limitations :
IE below Edge just doesn't support <foreignObject>, so this won't work at all + it will taint the canvas, even if the result is a blank rect, since these browser do so as soon as any svg images as been drawn on it, for security reasons.
Some browsers (at least Safari 9) do taint the canvas when a <foreignObject> is drawn on it, for security reasons.
Some elements do render weirdly (<button> doesn't have any style in FF, <video> is just a weird thing on some UA ...).
No external resource will be loaded into the <img> element, images will have to be converted to dataURL first, styles will have to be appended directly into the svg, or inline in the HTML tags, fonts will have to be dataURL encoded.
So the best approach, if you don't mind using a library, is still to use html2canvas.
If you do mind using a library, then you can try to do what it does :
use the native canvas drawing methods to draw each of the HTML elements and its styles.
canvas is an HTML element which can be used to draw graphics using scripting (usually JavaScript), so It's impossible to add an element inside canvas element, but you can play with css position to move the div inside canvas
<div class="container">
<canvas id="myCanvas" width="200" height="100"></canvas>
<div id="myDiv"></div>
</div>
css:
.container{postion: relative;}
#myCanvas{background: red;}
#myDiv{height: 50px;width: 50px;background: blue;
position: absolute; top:10px;
}
see JSFIDLE
A canvas can't actually contain a div (or any other HTML tag), it's a drawing surface you can draw on.
But you can position the div on top of the canvas in the z-order, e.g.:
// Lower
var ctx = document.getElementById("lower-canvas").getContext('2d');
var path = new Path2D();
path.arc(100, 100, 100, 0, Math.PI * 2, true);
ctx.fillStyle = ctx.strokeStyle = "blue";
ctx.fill(path);
// Upper
ctx = document.getElementById("upper-canvas").getContext('2d');
path = new Path2D();
path.arc(75, 75, 50, 0, Math.PI * 2, true);
ctx.fillStyle = ctx.strokeStyle = "green";
ctx.fill(path);
#lower-canvas, #upper-canvas {
width: 300px;
height: 300px;
position: absolute;
left: 20px;
top: 20px;
}
#upper-canvas {
z-index: 2;
}
#the-div {
position: absolute;
left: 20px;
top: 50px;
z-index: 1;
background-color: white;
}
<canvas id="lower-canvas" width="300" height="300"></canvas>
<canvas id="upper-canvas" width="300" height="300"></canvas>
<div id="the-div">This is the div</div>
In that example, we don't even need z-index because the div is absolutely positioned but the canvas isn't, and by default positioned elements are on a layer "nearer" the viewer than non-positioned elements. If you are positioning the canvas, you'd add something like z-index: 10 (using whatever value was appropriate) to ensure the div was "nearer" the viewer in the order for the positioned layer.
I understand that Canvas is as ink dries and each item that is draw is on top.
I'm having a little problem, I have a list of sources of background images
var sources = {
bg: 'images/room-1/bg.png',
rightWall: 'images/room-1/wall-right.png',
leftWall: 'images/room-1/wall-left.png',
beam: 'images/room-1/beam-left.png',
sofa: 'images/room-1/sofa.png'
};
loadImages(sources, function(images) {
context.drawImage(images.bg, 0, 0, 760, 500);
context.drawImage(images.rightWall, 714, 0, 46, 392);
context.drawImage(images.leftWall, 0, 160, 194,322);
context.drawImage(images.beam, 0, 45, 143,110);
context.drawImage(images.sofa, 194, 280, 436,140);
});
This is fine and they order how I like.
My issue is I have an image upload for a user to upload their image into the Canvas.
I am just using an upload box to test the theory, but the idea is the user will upload their image, crop, scale it, using JQuery / PHP and this saves the image. I will then grab this manipulated URL and pull this in, however the problem is that the Canvas is loaded so when i upload an image, it goes on top off the sources image.
I do not want to use multiple Canvas as I need to save this canvas as an image as a whole.
var imageLoader = document.getElementById('imageLoader');
imageLoader.addEventListener('change', handleImage, false);
function handleImage(e){
var reader = new FileReader();
reader.onload = function(event){
var img = new Image();
img.onload = function(){
canvas.width = 760;
canvas.height = 300;
}
img.src = event.target.result;
img.onload = function(){
context.drawImage(img, 0, 0);
};
}
reader.readAsDataURL(e.target.files[0]);
}
Canvas does not have layers.
But you can still get 1 final image with both your background and your user's scaled/rotated foreground.
Since you want to let the user rotate/scale their image but you don't want to affect the background, you will need 2 canvases -- a background canvas and a user canvas.
// HTML
<div id="container">
<canvas id="background" class="subcanvs" width=760 height=300></canvas>
<canvas id="canvas" class="subcanvs" width=760 height=300></canvas>
</div>
// CSS
#container{
position:relative;
border:1px solid blue;
width:760px;
height:300px;
}
.subcanvs{
position:absolute;
}
// JavaScript
// a background canvas
var background=document.getElementById("background");
var bkCtx=background.getContext("2d");
// a foreground canvas the user can rotate/scale
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
Then when the user is done rotating/scaling, drawImage the users canvas to the background canvas.
// combine the users finished canvas with the background canvas
bkCtx.drawImage(canvas,0,0);
// and save the background canvas (now it's combined) as an image
var imageURL=background.toDataURL();
I am currently using http://paperjs.org to create an HTML5 canvas drawing app. I want to let users upload images into the canvas. I know I need to make a login and signup but is there an easier way? I have seen the HTML5 drag and drop upload.
I assume you mean, to load an image into the canvas and not uploading the image from the canvas.
It'd probably be a good idea to read through all the canvas articles they have over here
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Using_images
But basically what you want to do is create an image in javascript, and set the image.src = to whatever the file location is. In the case of loading images from the user on their end, you're going to want to use the File System API.
Threw together a brief example here: http://jsfiddle.net/influenztial/qy7h5/
function handleImage(e){
var reader = new FileReader();
reader.onload = function(event){
var img = new Image();
img.onload = function(){
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img,0,0);
}
img.src = event.target.result;
}
reader.readAsDataURL(e.target.files[0]);
}
One doesn't need a FileReader*, it is better to use the URL.createObjectURL method, which will create a symlink directly to the File on disk. This will incur less memory usage, and will have the added benefit to have only one async event to wait for (the one of the img.onload).
document.getElementById('inp').onchange = function(e) {
var img = new Image();
img.onload = draw;
img.onerror = failed;
img.src = URL.createObjectURL(this.files[0]);
};
function draw() {
var canvas = document.getElementById('canvas');
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(this, 0,0);
}
function failed() {
console.error("The provided file couldn't be loaded as an Image media");
}
<input type="file" id="inp">
<canvas id="canvas"></canvas>
*IIRC only a few versions of Chrome did support FileReader while not yet supporting URL.createObejctURL, so if you target these very versions, you might need FileReader..
Modified answer by #kaiido to create a new canvas element each time and append it to a wrapper. Useful when you don't know how many canvases you may need.
Note: There is no new Canvas() constructor, therefore we must use createElement().
document.getElementById('inp').onchange = function(e) {
let img = new Image();
img.onload = draw;
img.onerror = failed;
img.src = URL.createObjectURL(this.files[0]);
};
function draw() {
let canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
ctx.drawImage(this, 0, 0);
document.getElementById('gallery').append(canvas);
}
function failed() {
console.error("The provided file couldn't be loaded as an Image media");
}
/* entirely decorative */
#gallery {
display: flex;
gap: 1em;
margin: 1em;
}
#gallery canvas {
height: 100px;
border-radius: .5em;
aspect-ratio: 1/1;
object-fit: cover;
}
<input type="file" id="inp">
<div id='gallery'></div>
The most optimal way of creating an image consumable by the canvas is to create an ImageBitmap out of the File you get from the input.
This will use an optimized path to produce just what the browser needs to render that image, and will store the bitmap data in the GPU, allowing for fast drawing when asked to.
Given this is a quite recent feature (Safari added support only last year), you may want to use a polyfill like this one of mine.
document.querySelector("input").oninput = async (evt) => {
try {
const file = evt.target.files[0];
const bitmap = await createImageBitmap(file);
const canvas = document.querySelector("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(bitmap, 0, 0);
}
catch(err) {
console.error(err);
}
};
<!-- createImageBitmap polyfill for old browsers --> <script src="https://cdn.jsdelivr.net/gh/Kaiido/createImageBitmap/dist/createImageBitmap.js"></script>
<input type="file">
<canvas></canvas>
<script>
window.onload = function() {
var canvas=document.getElementById(“drawing”); // grabs the canvas element
var context=canvas.getContext(“2d”); // returns the 2d context object
var img=new Image() //creates a variable for a new image
img.src= “images/vft.jpg” // specifies the location of the image
context.drawImage(img,20,20); // draws the image at the specified x and y location
}
</script>
Check Demo