Javascript - Draw specific part of image on canvas - javascript

I'm trying to send images from desk to webapp, on the desk part I have a button that takes a screen-shot then compares it with the one taken before, it calculates the difference between them then generate a new Bitmap based on this difference, then it sends this new Bitmap to the webapp which draws it on a canvas, this all happens in real-time using socket.io.
My problem is, let's assume I use Google Chrome on my screen and took a screen-shot, then I opened cmd.exe & took another screenshot, assuming that cmd.exe size is 300 * 100, and starts at half of the screen, the new generated bitmap will contains the cmd.exe screen-shots but rest of the screen (the uncover parts of Google Chrome) will be black, which is perfect for my case since i want to reduce bandwidth usage, now what I want to do, is take the difference bitmap (blob) on the JavaScript side and draw the difference on the previous screen-shot, which will make it look like I transferred the whole screen-shot, If i simply parse the blobs on the canvas, It will make a black screen with the cmd.exe on the center of it, here is my current code :
socket.on("image up", (bin) => {
var ctx = canvas[0].getContext('2d');
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
}
var urlCreator = window.URL || window.webkitURL;
var binaryData = [];
binaryData.push(bin);
img.src = urlCreator.createObjectURL(new Blob(binaryData));
});
Any suggestions ? Thanks in advance

Related

Why is html canvas botching this source image?

I'm toying around with making a super simple HTML Canvas crop tool. The first thing I tested was to see if the output image would be perceptually identical to the input image.
Using this image as a source, canvas fails to maintain the smooth gradients as you can see in the image comparison I posted here (still visible despite the imgur compression). You can also replicate it in any online photo editor such as https://pixlr.com.
Is there some way to fix this?
Code snippet I am using:
const loadImageToCanvas = (file) => { // file is from input.files
const img = new Image();
img.onload = () => {
const { width, height } = img;
canvas.width = width;
canvas.height = height;
ctx.clearRect(0, 0, width, height);
ctx.drawImage(img, 0, 0);
}
img.src = URL.createObjectURL(file);
};
Two words: gamma correction. Your PNG file has a gAMA chunk of 1.0000. Web browsers are (correctly) using this information to adjust the displayed pixels for an output device having the standard sRGB gamma of 2.2. This behaviour is the same for both <canvas> and <img> elements.[1]
I don't know what viewer or conversion tool you are using to produce your imgur image, but it is either stripping or ignoring the gamma chunk.
If your image is in fact encoded with a gamma of 2.2 (and thus the gamma chunk is erroneous), you can remove the chunk with:
pngcrush -rem gAMA 1024.png 1024.nogamma.png
[1] The spec mandates this consistency. Are you really seeing different behaviour between your (correct, although using createObjectURL is unnecessary and a bad idea) code and an <img> tag?

Create a progress bar for a loading image in javascript

I am creating a webpage that generates in line svg images and then allows the user to download them in various formats. (png,jpg,jpeg,svg) I have an exporting function to convert the images from inline svg to canvas as then canvas to dataURL for download. When I try exporting with Chrome, it takes time to shrink larger images down (7,000x10,000px) to the canvas because of Chrome's data cap. (FF doesn't have any issue and can shrink massive images in a fraction of the time that chrome can)
I need to create a loading progress bar for when the image is taking a while to populate and download from the canvas. I tried the solutions in this answer to no avail because I am using a objectURL created from a svg blob and not a image file on the server.
Is there a way to view the progress of an image load when setting the image src using an objectURL.
canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d',{alpha:false});
// creates a new blank image
var img = new Image();
// encode the svg to a string
var data = (new XMLSerializer()).serializeToString(svg);
// creates a blob from the encoded svg
var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
// creates an object url for the download
var url = DOMURL.createObjectURL(svgBlob);
// when the image is done being created and its loaded
img.onload = function(){ /* drawImage to canvas and save as dataURL*/ }
// load the image src using the objectURL
img.src = url;
Is there a way to read the progress of the image loading when the url is a objectURL not a image file?
Short answer, there's no way I know of to do what you're asking. As you say, you're not downloading from a server, so there's no 'onprogress' event to hook into. You need something to provide regular alerts from inside the createObjectURL function, and it does not provide an event to hook into that it will fire 20 times during processing to give you a status.
What you could do instead is estimate how long it will take in Chrome, and provide an estimated status bar that way. If you know how big your canvas is, you can estimate how long it will take Chrome to generate your image based on its fixed data cap. If a 7Kx10K image takes x seconds, it should generally take that same time to generate every time, due to the fixed data cap, correct? Do a little math to figure out the seconds based on overall pixels. If it take 100 seconds to process (or whatever it takes), then that's 700,000 px / sec. Again, because of the fixed data cap, this value should remain the same until Chrome changes its data cap in a new version.
You could then provide a simulated progress bar that advances at that rate for how many px total you have. If you have 7M px, then it should take 10 seconds to advance the bar from 0 to 100% (based on my sample rate of 700Kpx/sec).
This is all based on Chrome having a fixed data cap; you can calculate a math rate against that based on the number of px you have to process.

Is there a way to use a web worker to resize an image client side?

The way I'm resizing images now is by sticking it into a canvas element and then scaling the context of the canvas. The problem is, when I'm resizing many images the UI basically freezes. Is there anyways I can move this resizing step to a web worker? Problem I'm having is that you can't use document.createElement('canvas') or Image(), two functions crucial to this implementation.
It is possible. However, because canvas isn't available in a worker, you would have to use your own/3rd party code to manipulate the image data in the worker.
For example, you could use
https://github.com/nodeca/pica, which quite handily does its processing in a web worker if web workers are supported.
A rough example of using this to resize image from an img element to a canvas element...
<button onclick="resize()">Resize</button>
<img id="original" src="my-image.jpg">
<canvas id="resized">
With Javascript
function resize() {
// Find the original image
var originalImg = document.getElementById("original");
// Create an empty canvas element of the same dimensions as the original
var originalCanvas = document.createElement("canvas");
originalCanvas.width = originalImg.width;
originalCanvas.height = originalImg.height;
// Copy the image contents to the canvas
var ctx = originalCanvas.getContext("2d");
ctx.drawImage(originalImg, 0, 0);
// Set the target dimensions
var resizedCanvas = document.getElementById("resized");
resizedCanvas.width = originalCanvas.width / 2;
resizedCanvas.height = originalCanvas.height / 2;
// Resize (using web workers if supported)
pica.resizeCanvas(originalCanvas, resizedCanvas, {}, function(err) {
// Do something on finish/error
});
}
Which can be seen at https://plnkr.co/edit/yPRjxqQkHryqeZKw4YIH?p=preview
Unfortunately, you cannot use integrated browser functions for that. Instead, you need to obtain pixel data:
var data = ctx.getImageData(0,0,canvas.width, canvas.height);
You need to send those to worker. You can use transfer mode for the array:
worker.postMessage( {
name: "image_data",
data: data.data,
width: data.width,
height: data.height
},
[data.data] // this tells browser to transfer data to web worker
);
I modified function from some other answer so that it can scale image using the image data array. It's quite limited, as the scale is only allowed to be integer - that means you can't scale down: https://jsfiddle.net/n3drn8v9/5/
I recommend googling some libraries for this, rather than reinventing the wheel.

Why does canvas.toDataURL() not produce the same base64 as in Ruby for an image?

I'm trying to produce the same base64 data for an image file in both JavaScript and in Ruby. Unfortunately both are outputting two very different values.
In Ruby I do this:
Base64.encode64(File.binread('test.png'));
And then in JavaScript:
var image = new Image();
image.src = 'http://localhost:8000/test.png';
$(image).load(function() {
var canvas, context, base64ImageData;
canvas = document.createElement('canvas');
context = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
context.drawImage(this, 0, 0);
imageData = canvas.toDataURL('image/png').replace(/data:image\/[a-z]+;base64,/, '');
console.log(imageData);
});
Any idea why these outputs are different?
When you load the image in Ruby the binary file without any modifications will be encoded directly to base-64.
When you load an image in the browser it will apply some processing to the image before you will be able to use it with canvas:
ICC profile will be applied (if the image file contains that)
Gamma correction (where supported)
By the time you draw the image to canvas, the bitmap values has already been changed and won't necessarily be identical to the bitmap that was encoded before loading it as image (if you have an alpha channel in the file this may affect the color values when drawn to canvas - canvas is a little peculiar at this..).
As the color values are changed the resulting string from canvas will naturally also be different, before you even get to the stage of re-encoding the bitmap (as PNG is loss-less the encoding/compressing should be fairly identical, but factors may exist depending on the browser implementation that will influence that as well. to test, save out a black unprocessed canvas as PNG and compare with a similar image from your application - all values should be 0 incl. alpha and at the same size of course).
The only way to avoid this is to deal with the binary data directly. This is of course a bit overkill (in general at least) and a relative slow process in a browser.
A possible solution that works in some cases, is to remove any ICC profile from the image file. To save an image from Photoshop without ICC choose "Save for web.." in the file menu.
The browser is re-encoding the image as you save the canvas.
It does not generate an identical encoding to the file you rendered.
So I actually ended up solving this...
Fortunately I am using imgcache.js to cache images in the local filesystem using the FileSystem API. My solution is to use this API (and imgcache.js makes it easy) to get the base64 data from the actual cached copy of the file. The code looks like this:
var imageUrl = 'http://localhost:8000/test.png';
ImgCache.init(function() {
ImgCache.cacheFile(imageUrl, function() {
ImgCache.getCachedFile(imageUrl, function(url, fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
console.log($.md5(this.result.replace(/data:image\/[a-z]+;base64,/, '')));
};
reader.readAsDataURL(file);
});
});
});
});
Also, and very importantly, I had to remove line breaks from the base64 in Ruby:
Base64.encode64(File.binread('test.png')).gsub("\n", '');

Put text on an image and save as image

I have one problem with HTML5 Canvas.
I have one image. On this image I want to put text and display/save this as an image.
I have this code:
window.onload = function(){
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var imageObj = new Image();
imageObj.onload = function(){
context.drawImage(imageObj, 10, 10);
context.font = "20px Calibri";
context.fillText("My TEXT!", 50, 200);
};
imageObj.src = "mail-image.jpg";
};
This works fine. There is my image and the text on it.
But it is still a canvas and no image.
Can anybody help me?
For security reasons, there's no convenient way of saving a canvas drawing to a user's local drive.
As a workaround, go "old school": Convert the canvas to an image and display it in a new window.
window.onload = function(){
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var imageObj = new Image();
imageObj.onload = function(){
context.drawImage(imageObj, 10, 10);
context.font = "20px Calibri";
context.fillText("My TEXT!", 50, 200);
// open the image in a new browser tab
// the user can right-click and save that image
var win=window.open();
win.document.write("<img src='"+canvas.toDataURL()+"'/>");
};
imageObj.src = "mail-image.jpg";
};
Sand boxing
Browsers are sand-boxed when it deals with saving content to user's hard disk. This is for security (you don't want a bad hacker (or spy) to overwrite system files or plant a virus or a backdoor etc.). So direct access is prevented and local storage is isolated.
You always need to "bridge" the content by an user interaction that approves the operation and therefor the browser will request you to choose a location for the file by popping up a dialog to make the user aware of that the browser tries to deliver content to be saved (see demo below).
Invoking save dialogs
Here are a couple of other possibilities to enable download.
If a link for example under the image is ok then you can do:
/// create an anchor/link (or use an existing)
var lnk = document.createElement('a');
/// set your image as data-uri link
lnk.href = canvas.toDataURL();
/// and the key, when user click image will be downloaded
lnk.download = 'filename.png';
/// add lnk to DOM, here after the canvas
canvas.parentElement.appendChild(lnk);
The download attribute is a new HTML5 feature. Instead of "navigating" to this location the browser will show a save dialog instead and let the user save its content to disk.
You can also automate the whole clicking feature by generating an event for it.
For example:
function download(canvas, filename) {
if (typeof filename !== 'string' || filename.trim().length === 0)
filename = 'Untitled';
var lnk = document.createElement('a'),
e;
lnk.download = filename;
lnk.href = canvas.toDataURL();
if (document.createEvent) {
e = document.createEvent("MouseEvents");
e.initMouseEvent('click', true, true, window,
0, 0, 0, 0, 0, false, false,
false, false, 0, null);
/// send event
lnk.dispatchEvent(e);
} else if (lnk.fireEvent) {
lnk.fireEvent("onclick");
}
}
Saving to server
You can always go by the step of saving the file to a server. However, you will also have to go through the save dialog step when retrieving the file from server (the dialog).
If you want to store the file only to be shown in the browser this is perfect.
There are various ways to do this (there are many solutions on SO for this).
Local storage
And a different option is to store the file in the browser's local storage. You have Web Storage, however this is very limited (typically between 2.5 - 5 mb) and considering that each char stored takes two bytes the actual storage is just half of that (it can only store strings such as the data-uri and data-uris is about 33% larger than the original file). But if you save small icons, sprites etc. this might do.
In addition you can use Indexed DB (and the now deprectaed Web SQL) which can store larger data and you can also request user's permission to store x mb of dat.
The same goes with File API (which is currently only implemented in Chrome). This acts more like a file system and is intended to store huge files.
These might seem more complex if you are not familiar with them, but I mention them as possible options as these also saves you bandwidth communicating with a server and you move the "burden" to the client instead of the server.

Categories

Resources