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.
Related
I can't understand why when I'm creating an image from a base64 source (coming from another canvas cnvBase) it 'flashes' : the real image imgBW is OK and remains in place until a new base64 image is sended by cnvBase, but in cnvBW it appears for a fraction of a second and disappears immediately.
HTML
<canvas id="cnvBase"></canvas>
<img id="imgBW">
<canvas id="cnvBW"></canvas>
JS
function createBWImg(){
console.log('image '+imageNB.substr(0,50)) // data:image/png;base64,iVBORw0KG....
document.getElementById('imgBW').style.width = w+'px';
document.getElementById('imgBW').style.height = y+'px';
document.getElementById('imgBW').src = imageNB;
let newimage = new Image();
newimage.onload = function() {
ctxBW.drawImage(newimage, 0, 0);
};
newimage.src = imageNB;
}
Note: cnvBase sends a new image every second when User moves his smartphone (beta value):
window.addEventListener('deviceorientation', function(event) {
alpha= Math.round(event.alpha);
beta = Math.round(event.beta);
gamma= Math.round(event.gamma);
setTimeout(function() {
if (beta >= 0 && beta <= 90) {
getValues(alpha,beta,gamma);
}
}, 1000);
});
The getValues(a,b,g) function retrieves a part of the camera stream and places it in cnvBase according to the value of b
EDIT
OK, I think that image 'seems' to remain the same but in fact not. With this code, canvas keeps the right color for one second without flashing... I think something happens (but what?) during image production: something is sent to canvas (but what?).
let color;
if(b>=0 &&b<34) color='red';
if(b>=34 &&b<67) color='green';
if(b>=67 &&b<90) color='blue';
ctxBW.fillStyle = color;
ctxBW.fillRect(0, 0, w, y);
EDIT 2
More and more strange. When adding this part of code for PCs (because changing beta orientation is hard to obtain) it works perfectly: image in canvas doesn't flash and remains in place, but not on phones where it appears and disappears as soon as created (flash effect)
if(screen > 600){ // screen is screen width
setInterval(function(){
send_pseudo_beta(Math.floor((Math.random() * 10)+1)*8) // random beta
},1000);
}
Heyo, before asking a question I have to admit that javascript hasnt been to kind to me and would ask to not throw too many rocks at me for this question.
So I am writing a canvas based player with live video but the page where canvas is being executed consumes and insane amount of CPU power.
This is the routine that draws on the given canvas(player is the canvas that was passed)
var requestFrames = function(player) {
console.log('Requesting frames for videoId ' + requestStream.videoId);
//var imageElement = player;
var ctx = player.getContext("2d");
var displayImage = function(frame) {
if (frame && frame.imageURL) {
var img = new Image();
img.src = frame.imageURL;
img.onload = function(){
ctx.drawImage(img, 0,0)
};
}
sendFrameRequest(requestStream.videoId, displayImage);
};
sendFrameRequest(requestStream.videoId, displayImage);
};
Please send help, thanks
EDIT : sendFrameRequest is a function that requests latest image from the server. I am working with a server that sends out live images from cameras(Its a video management system)
I am trying to make an upload script which resizes multiple images client side before the saveimage.php handles them. The reason for this is because they will be uploaded at an outside location where there might be very slow internet.
I have been able to find pieces of code around here which helped me put it together. I am only a beginner so it's probably very messy!
What it currently does is it checks whenever files are being input in the 'file_input' field. Then it looks at the amount of files and loop through them until every file has been placed in the canvas and upload while it's in there.
However, the problem is: when I don't add an alert for each file, the loop goes too fast and only processes 1 or 2 out of 10 images for example. Because the upload script will go faster than the canvas can be updated with a new image.
This is my current page, simplified to the core:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
</head>
<body>
<form>
<input id="file_input" type='file' multiple />
</form>
<div id="preview">
<canvas id="canvas"></canvas>
</div>
<script>
var input = document.getElementById('file_input');
input.addEventListener('change', handleFiles);
function handleFiles(e) {
var files = input.files;
alert(files.length +" files are being uploaded!"); // display amount of files for testing purpose
for (var i = 0; i < files.length; ++i) {
alert("Upload file number: "+i); // display file # for testing purpose, when this is removed the script will go too fast
var MAX_HEIGHT = 1000;
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image;
img.onload = function(){
if(img.height > MAX_HEIGHT) {
img.width *= MAX_HEIGHT / img.height;
img.height = MAX_HEIGHT;
}
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
preview.style.width = img.width + "px";
preview.style.height = img.height + "px";
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, img.width, img.height);
// the canvas should contain an image now and this will send it through saveimage.php
var myDrawing = document.getElementById("canvas");
var drawingString = myDrawing.toDataURL("image/jpeg", 0.5);
var postData = "canvasData="+drawingString;
var ajax = new XMLHttpRequest();
ajax.open("POST",'saveimage.php',true);
ajax.setRequestHeader('Content-Type', 'canvas/upload');
ajax.onreadystatechange=function()
{
if (ajax.readyState == 4);
}
ajax.send(postData);
};
img.src = URL.createObjectURL(e.target.files[i]);
}
}
</script>
</body>
</html>
And this is my saveimage.php which works well, but just for the complete overview:
<?php
if (isset($GLOBALS["HTTP_RAW_POST_DATA"]))
{
$rawImage=$GLOBALS['HTTP_RAW_POST_DATA'];
$removeHeaders=substr($rawImage, strpos($rawImage, ",")+1);
$decode=base64_decode($removeHeaders);
// check if the file already exists and keep adding +1 intil it's unique
$i = 1;
while(file_exists('uploads/image'.$i.'.jpg')) {
$i++;
}
$fopen = fopen( 'uploads/image'.$i.'.jpg', 'wb' );
fwrite( $fopen, $decode);
fclose( $fopen );
}
?>
A bit of extra information:
I have added canvas, because as far as I can understand that's the
only way to resize images client side. In the final script it will be
set to hidden.
If the canvas could be skipped and the data sent to saveimage.php
immediately then that would solve the problem too I think.
As I said I am not very experienced with javascript, so if there is a
much simpler way to achieve this goal. Please let me know!
Thanks in advance!
JavaScript loads image asynchronously. That means once it is assigned a new image's .src it will begin loading the image but will simultaneously continue processing the javascript after the .src while the image takes time to load.
You're using the same image variable (var img) inside your for loop. Therefore, each time through the loop you are overwriting the previous img before the previous image has been fully loaded.
Here's an image loader that fully loads all image and then calls the start() function where you can do you processing:
// image loader
// put the paths to your images in imageURLs[]
var imageURLs=[];
imageURLs.push("myImage1.png");
imageURLs.push("myImage2.png");
// etc, etc.
// the loaded images will be placed in imgs[]
var imgs=[];
var imagesOK=0;
loadAllImages(start);
function loadAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.crossOrigin="anonymous";
img.src = imageURLs[i];
}
}
function start(){
// the imgs[] array now holds fully loaded images
// the imgs[] are in the same order as imageURLs[]
}
In the onload callback use the this keyword(instead of img) to for the current image, img may/will be overwritten in the for loop before the callback fires.
img.height --> this.height etc
Also is there a reason you're using a custom content type instead of application/x-www-form-urlencoded ?
Url encoded form data is much easier to work with.
I have struggled with the same problem for some time now, but I finally
cobbled together another solution, using bits of code from others, who have addressed this problem,
and it works. It may not be perfect, and I would welcome any improvement suggestions.
The concept is that you have two Javascript
Functions, which are identicle called Resize0 and Resize1, and they call each other
until all photos have been resized then the Function which resizes the
last image Submits the Form. The two Functions pass parameters between
each other so that one of them will know when to Submit the Form.
Two of those parameters are, Number_Of_Images and current Image_Number.
You can also download a working Example Web page from
http://www.wantitconsulting.co.nz/ExampleResizeUpload.zip .
You will need to create a folder in the base directory of your test web site
for the Images to go into. The foldername is TestResizeUpload. The Server
side uses php.
I'm having an issue while using canvas in a background page to create data URLs for desktop notifications' images.
I want to use the "image" notifications which require a 3:2 ratio to display properly. The images I want to use (from hulu.com) are a different ratio, so I decided to use the canvas element to create the corresponding data URL off of these images so that the ratio is correct. It kind of works in theory, but…
…I'm having issues if I'm creating more than one canvas/notification in the background page. One image is created properly, but the rest comes out empty.
Confusingly, opening the same background page in a new tab (i.e. exact same code) makes everything works just fine: all the notifications are created with the images loaded from hulu.com. Also, just changing the dimensions from 360x240 to 300x200 makes it work. Finally, though they're similar computers with the same Chrome version (34.0.1847.116), it works without modification at work while it doesn't on my own laptop.
I made a test extension available at the bottom of this post. Basically, it only has a generated background page. The code for that page is this:
var images = ["http://ib2.huluim.com/video/60376901?size=290x160&img=1",
"http://ib2.huluim.com/video/60366793?size=290x160&img=1",
"http://ib4.huluim.com/video/60372951?size=290x160&img=1",
"http://ib1.huluim.com/video/60365336?size=290x160&img=1",
"http://ib3.huluim.com/video/60376290?size=290x160&img=1",
"http://ib4.huluim.com/video/60377231?size=290x160&img=1",
"http://ib4.huluim.com/video/60312203?size=290x160&img=1",
"http://ib1.huluim.com/video/60376972?size=290x160&img=1",
"http://ib4.huluim.com/video/60376971?size=290x160&img=1",
"http://ib1.huluim.com/video/60376616?size=290x160&img=1"];
for (var i = 0; i < 10; i++) {
getDataURL(i);
}
/*
* Gets the data URL for an image URL
*/
function getDataURL(i) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = 360;
canvas.height = 240;
var ctx = canvas.getContext('2d');
ctx.drawImage(this, 0, 0);
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
var dataURL = canvas.toDataURL('image/png');
chrome.notifications.create('', {
type: 'image',
iconUrl: 'logo_128x128.png',
title: String(i),
message: 'message',
imageUrl: dataURL
}, function(id) {});
}
//img.src = chrome.extension.getURL('logo_128x128.png');;
img.src = images[i];
}
The commented out line for img.src = ... is a test where it loads a local file instead of a remote one. In that case, all the images are created.
The red rectangle added to the canvas is to show that it's not just the remote image that is an issue: the whole resulting canvas is empty, without any red rectangle.
If you download and add the test extension below, you should get 10 notifications but only one with an image.
Then, to open the background page in a new tab, you can inspect the background page, type this in the console:
chrome.extension.getURL('_generated_background_page.html')
and right-click the URL, and click "Open in a new Tab" (or window). Once open you should get 10 notifications that look fine.
Any idea of what is going on? I haven't been able to find any kind of limitations for background pages relevant to that. Any help would be appreciated, because this has been driving me crazy!
Files available here: https://www.dropbox.com/s/ejbh6wq0qixb7a8/canvastest.zip
edit: based on #GameAlchemist's comment, I also tried the following: same getDataURL method, but the loop wrapped inside an onload for the logo:
function loop() {
for (var i = 0; i < 10; i++) {
getDataURL(i);
}
}
var logo = new Image();
logo.onload = function () {
loop();
}
logo.src = chrome.extension.getURL('logo_128x128.png');
Remember that the create() method is asynchronous and you should use a callback with. The callback can invoke next image fetching.
I would suggest doing this in two steps:
Load all the images first
Process the image queue
The reason is that you can utilize the asynchronous image loading better this way instead of chaining the callbacks which would force you to load one and one image.
For example:
Image loader
var urls = ["http://ib2.huluim.com/video/60376901?size=290x160&img=1",
"http://ib2.huluim.com/video/60366793?size=290x160&img=1",
"http://ib4.huluim.com/video/60372951?size=290x160&img=1",
"http://ib1.huluim.com/video/60365336?size=290x160&img=1",
"http://ib3.huluim.com/video/60376290?size=290x160&img=1",
"http://ib4.huluim.com/video/60377231?size=290x160&img=1",
"http://ib4.huluim.com/video/60312203?size=290x160&img=1",
"http://ib1.huluim.com/video/60376972?size=290x160&img=1",
"http://ib4.huluim.com/video/60376971?size=290x160&img=1",
"http://ib1.huluim.com/video/60376616?size=290x160&img=1"];
var images = [], // store image objects
count = urls.length; // for loader
for (var i = 0; i < urls.length; i++) {
var img = new Image; // create image
img.onload = loader; // share loader handler
img.src = urls[i]; // start loading
images.push(img); // push image object in array
}
function loader() {
count--;
if (count === 0) process(); // all loaded, start processing
}
//TODO need error handling here as well
Fiddle with concept code for loader
Processing
Now the processing can be isolated from the loading:
function process() {
// share a single canvas (use clearRect() later if needed)
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
current = 0;
canvas.width = 360;
canvas.height = 240;
createImage(); // invoke processing for first image
function createImage() {
ctx.drawImage(images[current], 0, 0); // draw current image
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
chrome.notifications.create('', {
type : 'image',
iconUrl : 'logo_128x128.png',
title : String(i),
message : 'message',
imageUrl: canvas.toDataURL() // png is default
},
function(id) { // use callback
current++; // next in queue
if (current < images.length) {
createImage(); // call again if more images
}
else {
done(); // we're done -> continue to done()
}
});
}
}
Disclaimer: I don't have a test environment to test Chrome extensions so typos/errors may be present.
Hope this helps!
I've read about various kinds of ways getting image dimensions once an image has fully loaded, but would it be possible to get the dimensions of any image once it just started to load?
I haven't found much about this by searching (which makes me believe it's not possible), but the fact that a browser (in my case Firefox) shows the dimensions of any image I open up in a new tab right in the title after it just started loading the image gives me hope that there actually is a way and I just missed the right keywords to find it.
You are right that one can get image dimensions before it's fully loaded.
Here's a solution (demo):
var img = document.createElement('img');
img.src = 'some-image.jpg';
var poll = setInterval(function () {
if (img.naturalWidth) {
clearInterval(poll);
console.log(img.naturalWidth, img.naturalHeight);
}
}, 10);
img.onload = function () { console.log('Fully loaded'); }
The following code returns width/height as soon as it's available. For testing change abc123 in image source to any random string to prevent caching.
There is a JSFiddle Demo as well.
<div id="info"></div>
<img id="image" src="https://upload.wikimedia.org/wikipedia/commons/d/da/Island_Archway,_Great_Ocean_Rd,_Victoria,_Australia_-_Nov_08.jpg?abc123">
<script>
getImageSize($('#image'), function(width, height) {
$('#info').text(width + ',' + height);
});
function getImageSize(img, callback) {
var $img = $(img);
var wait = setInterval(function() {
var w = $img[0].naturalWidth,
h = $img[0].naturalHeight;
if (w && h) {
clearInterval(wait);
callback.apply(this, [w, h]);
}
}, 30);
}
</script>
One way is to use the HEAD request, which asks for HTTP Header of the response only. I know in HEAD responses, the size of the body is included. But I don't know if there anything available for size of images.