get image of canvas whenever there is change in canvas
var canvas = document.getElementById('myCanvas');
socket.emit('updateCanvasImage', canvas.toDataURL());
draw image on new canvas somewhere else
var canvas = document.getElementById('myCanvasImg');
var context = canvas.getContext('2d');
var image = new Image();
image.onload = function() {
context.drawImage(this, 0, 0, canvas.width, canvas.height);
};
socket.on('updateCanvasImage', function (img) {
image.src = img;
});
The canvas flickers when socket changes the image.src
There are lots of questions like these here, but none of the solutions seem to work for me.
How to solve this problem?
Do not use events to render content
Do not use events to redraw the canvas. Image content is presented to the display at a fixed rate, while most events are not synced to the display rate, the mismatch between display rate and event rates can cause flicker.
requestAnimationFrame
When you repeatedly update any visual content, be that the canvas or other DOM content, you should use requestAnimationFrame to call a render function. This function should then render all the content ready for the next display frame.
When the render function returns the changes will be held in a backbuffer until the display hardware is ready to display the next frame.
Removing flicker
Thus to fix your problem create a render function that is tied to the display rate.
var image = new Image();
var update = true; // if true redraw
function renderFunction(){
if(update){ // only raw if needed
update = false;
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
requestAnimationFrame(renderFunction);
}
requestAnimationFrame(renderFunction);
Then in the events just get the new image state and flag update when ready to draw
image.onload = () => update = true;
socket.on('updateCanvasImage', src => {update = false; image.src = src});
Do the same with the drag events
This will ensure you never have any flicker, and also you can check to see if the image updates are arriving faster than can be delayed and thus throttle back the image update rate.
Double buffering the canvas
There are many times where the canvas content is updated from one or more different sources, from a video, camera, a draw command (from mouse, touch, code), or from a stream of images.
In these cases it is best to use a second canvas that you keep offscreen (in RAM) and use as the source for display. This makes the display canvas just a view, that is independent of the content.
To create a second canvas;
function createCanvas(width, height){
const myOffScreenCanvas = document.createElement("canvas");
myOffScreenCanvas.width = width;
myOffScreenCanvas.height = height;
// attach the context to the canvas for easy access and to reduce complexity.
myOffScreenCanvas.ctx = myOffScreenCanvas.getContext("2d");
return myOffScreenCanvas;
}
Then in the render function you can display it
var background = createCanvas(1024,1024);
var scale = 1; // the current scale
var origin = {x : 0, y : 0}; // the current origin
function renderFunction(){
// set default transform
ctx.setTransform(1,0,0,1,0,0);
// clear
ctx.clearRect(0,0,canvas.width,canvas.height);
// set the current view
ctx.setTransform(scale,0,0,scale,origin.x,origin.y);
// draw the offscreen canvas
ctx.drawImage(background, 0, 0);
requestAnimationFrame(renderFunction);
}
requestAnimationFrame(renderFunction);
Thus your image load draws to the offscreen canvas
image.onload = () => background.ctx.drawImage(0, 0, background.width, background.height);
socket.on('updateCanvasImage', src => image.src = src);
And your mouse drag events need only update the canvas view. The render function will render the next frame using the updated view. You can also add zoom and rotation.
const mouse = {x : 0, y : 0, oldX : 0, oldY : 0, button : false}
function mouseEvents(e){
mouse.oldX = mouse.x;
mouse.oldY = mouse.y;
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
if(mouse.button){
origin.x += mouse.x - mouse.oldX;
origin.y += mouse.y - mouse.oldY;
}
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
Whenever you change the src of an HTMLImageElement, its content is cleared, and when the canvas tries to render it, it can't.
Because of this, you will experience frames without any image (flickers), until the newly set media is loaded and parsed (fiddle reproducing the issue).
Without seeing your code it's quite hard to offer you a correct solution, but a simple structure could be:
let current = the currently loaded image, accessible to animation-loop/drag-event.
on( socket.updateCanvasImage, let newImage = a new Image on which you set the new src).
on( newImage.load, current = new Image ).
With this simple structure, you will avoid the flickers.
var ctx = canvas.getContext('2d');
function animLoop(time){ // draws continously
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(current, 0,0, canvas.width, canvas.height);
ctx.fillText(time, 20,20);
requestAnimationFrame(animLoop);
}
var current = new Image();
function loadImage(){
var img = new Image(); // if you really want to optimize your code for memory impact, you could declare it only once out of the function...
img.onload = function(){
current = this; // update the image to be rendered with the new & loaded one
setTimeout(loadImage, 2000); // start loading a new one in 2 sec (will be rendered even later)
}
img.onerror = loadImage;
img.src = 'https://upload.wikimedia.org/wikipedia/commons' + urls[++url_index % urls.length]+'?'+Math.random();
}
var url_index = 0;
var urls = [
//Martin Falbisoner [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
'/2/2d/Okayama_Castle%2C_November_2016_-02.jpg',
//Diego Delso [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
'/9/9b/Gran_Mezquita_de_Isfah%C3%A1n%2C_Isfah%C3%A1n%2C_Ir%C3%A1n%2C_2016-09-20%2C_DD_34-36_HDR.jpg',
//Dietmar Rabich / Wikimedia Commons / “Münster, LVM, Skulptur -Körper und Seele- -- 2016 -- 5920-6” / CC BY-SA 4.0, via Wikimedia Commons
'/5/53/M%C3%BCnster%2C_LVM%2C_Skulptur_-K%C3%B6rper_und_Seele-_--_2016_--_5920-6.jpg',
//By Charlesjsharp (Own work, from Sharp Photography, sharpphotography) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
'/4/4b/Campo_flicker_(Colaptes_campestris)_female.JPG'
];
loadImage();
animLoop();
<canvas id="canvas" width="500" height="500"></canvas>
Edit:
This is only true for chrome, Firefox doesn't behave like that and actually only starts the parsing of the image when we call drawImage. This will hold the canvas' drawing during this time. If this is a problem, you can try to lower this with an ImageBitmap Object, but with the big images I used in demo, this halt is still there...
var ctx = canvas.getContext('2d');
function animLoop(time){ // draws continously
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(current, 0,0, canvas.width, canvas.height);
ctx.fillText(time, 20,20);
requestAnimationFrame(animLoop);
}
var current = new Image();
function loadImage(){
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function(){
createImageBitmap(this, 0,0,this.width, this.height).then(function(bmp){
current = bmp; // update the image to be rendered with an ImageBitmap
}).catch(e=>console.log(e))
setTimeout(loadImage, 2000); // start loading a new one in 2 sec (will be rendered even later)
}
img.onerror = loadImage;
img.src = 'https://upload.wikimedia.org/wikipedia/commons' + urls[++url_index % urls.length]+'?'+Math.random();
}
var url_index = 0;
var urls = [
//Martin Falbisoner [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
'/2/2d/Okayama_Castle%2C_November_2016_-02.jpg',
//Diego Delso [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
'/9/9b/Gran_Mezquita_de_Isfah%C3%A1n%2C_Isfah%C3%A1n%2C_Ir%C3%A1n%2C_2016-09-20%2C_DD_34-36_HDR.jpg',
//Dietmar Rabich / Wikimedia Commons / “Münster, LVM, Skulptur -Körper und Seele- -- 2016 -- 5920-6” / CC BY-SA 4.0, via Wikimedia Commons
'/5/53/M%C3%BCnster%2C_LVM%2C_Skulptur_-K%C3%B6rper_und_Seele-_--_2016_--_5920-6.jpg',
//By Charlesjsharp (Own work, from Sharp Photography, sharpphotography) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
'/4/4b/Campo_flicker_(Colaptes_campestris)_female.JPG'
];
loadImage();
animLoop();
<canvas id="canvas" width="500" height="500"></canvas>
Re-Edit:
Since what you do is screen sharing you might also want to consider WebRTC along with canvas.captureStream instead of sending still images.
Related
I have a project that has been written without a framework and works fine. Photos are displayed on a page. Clicking on one opens a modal and the image is displayed on a canvas in the modal.
This is the javascript used to open the modal imageModal and draw the photo onto the canvas called photoImage, with e being the image that has been clicked on.
var canvas = document.getElementById("photoImage");
var canvasCtx = canvas.getContext("2d");
function viewPhoto(e) {
$("#imageModal").modal("show");
var image = new Image();
image.src = e.src;
canvas.width = image.width; // Width does not get set
canvas.height = image.height; // Height does not get set
canvasCtx.drawImage(image, 0, 0); // Photo does not get drawn
}
I have now used the exact same code in a Laravel project but it does not draw the picture onto the canvas. I have checked the properties of the canvas in both projects and they are the same.
For example the canvas is picking up th height property correctly in both projects but it does not change the size of the canvas in the Laravel project.
What could be causing the problem?
var canvas = document.getElementById("photoImage")[0]; //it return an array of object
var canvasCtx = canvas.getContext("2d");
canvasCtx.drawImage(image, 6, 6); // Photo does not get drawn
also you can use typeo canvas.getContext('2d').drawImage(img, 6, 6)
I've looked through and tried many solutions to this issue and nothing has worked. I have an image on a website that needs to be updated 10-30x a second (live video feed) so I have the javascript request the image every 100ms. When the image stays the same, no flickering. When the image changes, I see flickering on the image for 2-3 seconds.
function initImg() {
var canvas = document.getElementById("diagimg");
var context = canvas.getContext("2d");
img = new Image();
img.onload = function() {
var scale = .73;
canvas.setAttribute("width", 640*scale);
canvas.setAttribute("height", 480*scale);
context.scale(scale, scale); //scale it to correct size
context.drawImage(this, 0, 0);
}
img.onerror = function() {
img.src="images/wait.jpeg"; //if error during loading, display this image
}
refreshImg();
}
function refreshImg() {
img.src = "images/IMAGE.png?time="+new Date().getTime();
window.setTimeout("refreshImg()", 100);
}
initImg();
I've turned your code into an example to test this behaviour, but I don't see any flickering at all.
Is it possible that the flickering is caused by server-side code?
let images = [
'https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Mercury_in_color_-_Prockter07-edit1.jpg/220px-Mercury_in_color_-_Prockter07-edit1.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/PIA23791-Venus-NewlyProcessedView-20200608.jpg/220px-PIA23791-Venus-NewlyProcessedView-20200608.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/The_Earth_seen_from_Apollo_17.jpg/220px-The_Earth_seen_from_Apollo_17.jpg',
'https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/OSIRIS_Mars_true_color.jpg/220px-OSIRIS_Mars_true_color.jpg'
];
let i = 0;
function getImage() {
i++;
if (i >= images.length)
i = 0;
return images[i];
}
//--------------------------------------
function initImg() {
var canvas = document.getElementById("diagimg");
var context = canvas.getContext("2d");
img = new Image();
img.onload = function() {
var scale = .73;
canvas.setAttribute("width", 640 * scale);
canvas.setAttribute("height", 480 * scale);
context.scale(scale, scale); //scale it to correct size
context.drawImage(this, 0, 0);
}
refreshImg();
}
function refreshImg() {
img.src = getImage() + "?time=" + new Date().getTime();
window.setTimeout("refreshImg()", 500);
}
initImg();
<canvas id="diagimg" />
Fixed the problem - turned out to be that I was trying to write an image at the same time that it was being read.
Quick summary of my setup: websocket connection between website and java program, the image being loaded onto the webpage is constantly being overwritten by an external program.
The fix was to have the website request the websocket server to copy the image. The server (java program) copies the image, checks if the copy is equal to the original, and sends a message to the website that the image is ready to be read. The device ID is also appended to the filepath so that each connected device (each instance of the website open) has its own image that will only be changed when it requests an update (it requests a new image once it's done loading).
This means that images are only overwritten when the client requests them, and the client only reads them when the java websocket says that it's done being copied.
I'm sure it's inefficient but it only needs to refresh at 10hz and the entire process only takes about 10ms on its own thread, so doesn't really matter.
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);
}
I am making a chrome extension that uses the chrome.tabs.captureVisibleTab method to grab a screencap of the current tab and then displays that in a popup from the chrome extension. If I use the img tag and use the data coming from the chrome function, such as data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwME…UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/2Q==, the <img> displays pixel perfect, it is crisp and that's what I want. Problem is that I need to make it into a canvas, and when I do that, it becomes blurry.
This is a screencap of the <img> that uses the link data provided by Google to be used as a comparison to the canvas which is below.
This is the blurry canvas.
This is the code that I am using to try to do this, but I can't figure out how to make the canvas crisp like the image.
chrome.runtime.sendMessage({msg: "capture"}, function(response) {
console.log(response.imgSrc);
var img = document.createElement('img');
var _canvas = document.createElement("canvas");
img.src = response.imgSrc;
img.height = 436;
img.width = 800;
document.getElementById('main-canvas').appendChild(img);
draw(response);
});
function draw(response) {
var canvas = document.getElementById('imageCanvas');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//Get a 2d context
var ctx = canvas.getContext('2d');
//use image to paint canvas
var _image = new Image();
_image.onload = function() {
ctx.drawImage(_image, 0, 0, 800, 436);
}
_image.src = response.imgSrc;
document.getElementById('main-canvas').appendChild(canvas);
}
This is what I used to fix my problem: High Resolution Canvas from HTML5Rocks
I write this html code :
<div id="container">
<canvas id="imageView" width="1181" height="1181">
<p>Unfortunately, your browser is currently unsupported by our web
application.</p>
</canvas>
<script type="text/javascript">
window.onload = function() {
var c = document.getElementById('imageView');
var cxt = c.getContext('2d');
var img = new Image();
img.src = "map.jpg";
cxt.drawImage(img, 0, 0);
};
</script>
</div>
And write this javascript :
this.mousemove = function(ev) {
if (!tool.started) {
return;
}
var x = Math.min(ev._x, tool.x0),
y = Math.min(ev._y, tool.y0),
w = Math.abs(ev._x - tool.x0),
h = Math.abs(ev._y - tool.y0);
context.clearRect(0, 0, canvas.width, canvas.height);
if (!w || !h) {
return;
}
context.clearRect(x, y, w, h);
context.strokeRect(x, y, w, h);
this code is make a rectangle . I want change this rectangle to a area map , that when I click on the area do something , (for example open google.com) .
If I understand you correctly you want to invoke a function when you hit a pixel on the actual map - not just in the map area.
Method 1
You can check a map click in more than one way. You can simply check for the pixel value at the click point to check if it is inside the area you want it to be by comparing the map color value.
I provided an example below for this method.
Method 2
You can pre-define a polygon which traces the outline of the map area you want to check.
Then build a path (ctx.beginPath(); and ctx.lineTo(..); etc.) to allow the use of the method:
if (ctx.isPointInPath(x, y)) { ... };
This is a good method if you have small regions to check.
Method 3
Store a separate image of the map containing only a matte (sort of an alpha map), That is usually black (or transparent) for non-clickable areas, white for clickable areas.
This is useful if your map is complex color-wise and a simple pixel value check is not trivial.
And speaking of which: you can even provide different solid color values for different areas so that you can define red color = USA, blue = Argentina, etc. As these are not visible to the user the only thing that matters is that the color value can be recognized (for this reason don't save images for this use with an ICC color profile).
Then project the mouse position from the click onto the matte image (which is basically an off-screen canvas where the matte image is drawn into) and check for the color (white or other color).
Example for method 1
This is a simple example, but in any case there are a couple of things you need to know in advance:
That the image is loaded from same server as the page or from a domain that allow cross-origin use. Or else you cannot grab a pixel from the map due to security reasons.
You need to know what color or alpha value to check for. If the map is solid and everything is transparent you just need to check for alpha value above zero (as in this example), and if not just check the RGB value of the region you want to trigger an action with.
ONLINE DEMO HERE
HTML:
<canvas width=725 height=420 id="demo"></canvas>
JavaScript:
var ctx = demo.getContext('2d'),
img = new Image();
/// we need to wait for the image to actually load:
img.onload = function() {
/// image is loaded and we can raw it onto canvas
ctx.drawImage(this, 0, 0);
/// enable mouse click
demo.onclick = function(e) {
/// adjust mouse position to be relative to canvas
var rect = demo.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
/// grab a pixel
var data = ctx.getImageData(x, y, 1, 1).data;
/// check it's alpha value to see if we're in a map point
/// this of course assumes the map has transparent areas.
/// if not just check for the color values instead.
if (data[3] > 0) alert('We hit map');
}
}
/// we need crossOrigin allowed image or we can't grab pixel later
img.crossOrigin = 'anonymous';
img.src = 'http://i.imgur.com/x8Ap3ij.png';
Just replace the alert with:
window.open('http://google.com/');
if you want it to open a new window/tab.
You can turn canvas into an anchor link by using addEventListener to listen for clicks on the canvas.
Then you can use window.open to open google in a new browser tab.
Also, you need to use image.onload to give your image time to load before using drawing it.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload=function(){
ctx.drawImage(img,0,0);
canvas.addEventListener("click",function(){
window.open("http://google.com");
});
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/google.jpg";