is there a way to add white spaces to an image in javascript ?
I this the image 1 and I want to edit or create a new image, to add white spaces and the result would be the image 2.
The following code achieves this task. Basically, we create a canvas and set it to the size of the desired output. We then fill it with white before drawing the original image at (0,250) This centers the image (I should have done this with code, but instead looked at your output image in an image editor. (OutputHeight-InputHeight)/2 = Y offset to draw image at.
Since you're not actually adding any detail, it's possible this isn't the best way to go about what you're trying to achieve. It's possible that you should use margin/padding to expand the room the image appears to occupy.
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
let img = document.querySelector('img');
let canvas = document.querySelector('#output');
let ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0,0,759,759);
ctx.drawImage(img, 0, 250);
}
body{
background-color: #ddd;
}
<img src='https://i.stack.imgur.com/bN5hp.jpg'/>
<hr>
<canvas id='output' width=760 height=760/></canvas>
You can do it using the canvas object.. if you have the image for instance
<img src="some.jpeg" id="myImg">
You can copy it to a new canvas and play with the drawImage properties
var img = document.getElementById('myImg');
var canvas = document.createElement('canvas');
canvas.width = img.width; // add here the extra width you want
canvas.height = img.height; // add here the extra height you want
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); // play here with the position, 0, 0 are the top x,y axis
Here is the link to mozilla documentation:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
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.
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'm having some issues using the drawImage method to place a pre-loaded image larger then 250PX width and height onto a canvas.
//Canvas
var canvas = document.createElement("canvas");
var contex = canvas_image.getContext('2d');
canvas.width = 350;
canvas.height = 350;
canvas.id = 'canvas'
$('.canvas').append(canvas);
//Draw Image to canvas
var imageObj = new Image();
imageObj.onload = new function() {
contex.drawImage(imageObj, 0, 0);
};
imageObj.src = $('img').attr('src');
I can't seem to get it to work with an image larger then 250PX Width or Height. But under 250 the image shows... It's really odd and frustrating.
You must get the context from the canvas element. The code you are showing in the post (not sure if it's a typo that happen when posting the question or not? though you shouldn't be able to draw anything if it's not a typo :-) ) has the following error:
This line:
var contex = canvas_image.getContext('2d');
should be:
var contex = canvas.getContext('2d');
as canvas_image does not seem to exist.
If you already have an image loaded you can draw that directly onto canvas instead - there is no need to do a second load of the image:
contex.drawImage($('img')[0], 0, 0);
just make sure you tap into its load event first as you do with the off-screen image.
var img = $('img');
img.on('load', function(e) {
contex.drawImage(img[0], 0, 0);
}
or call it on window's load event.
Other things to look out for is if the image actually has data in the 350x350 pixel area in top left corner (in case the image is very large). You can test by drawing it scaled to see if there is information there:
contex.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
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.