I'm trying to resize an image on a mobile device. (Using ionic 2 framework, based on angular 2).
My maximum file size is 5MB and some devices capture images bigger than that. So I'm currently trying to resize the image using canvas.toDataURL()
but this is extremely slow. (App doesn't respond for 15-20 seconds).
My current resize function is the following:
private resize(outputFormat, sourceImgObj, quality) {
let mimeType = "image/jpeg";
let cvs = document.createElement('canvas');
cvs.width = sourceImgObj.naturalWidth;
cvs.height = sourceImgObj.naturalHeight;
let ctx = cvs.getContext("2d").drawImage(sourceImgObj, 0, 0);
let newImageData = cvs.toDataURL(mimeType, quality/100);
return newImageData;
}
Which (I believe) at the time was based on, if not the same as, j-i-c.
This function does work. In the browser it's decent but still slow (Chrome). But when running this function on a device while for example selecting an image of 8 MB, the app will basically crash.
Is there any way to make this compression/resizing of the image faster?
Additional info
I'm getting the file itself by using cordova-plugin-camera which is a direct link to the file on the user's device. So this is not a base64 image (but I do am able to obtain one easily if necessary).
Your resize function really is badly named. What it does is to change the quality of the jpeg lossy algorithm. It doesn't really resize your images, and I guess your 8MB image is quite large and if it comes from the device camera, the compression might already be quite decent.
So when you draw it on the canvas, you are actually producing a new raw image (without any compression), with the same amount of pixels than in your original file.
This means that your canvas itself will be way larger than the original 8MB in memory. When calling toDataURL, it will have to extract this data, process it etc, eating even more memory.
And it's not even sure that at the end you'll get a lighter file...
If you're ok to really resize (i.e change the dimensions of) your images, then it will be easier for your device to handle it :
function resize(sourceImgObj) {
const mimeType = "image/jpeg"; // would probably be better to keep the one of the file...
let quality = .92; // default;
const cvs = document.createElement('canvas');
const MAX_SIZE = 500; // in px for both height and width
const w = sourceImgObj.naturalWidth;
const h = sourceImgObj.naturalHeight;
let ratio = MAX_SIZE / Math.max(w, h);
if(ratio > 1){ // if it's smaller than our defined MAX_SIZE
ratio = 1;
quality = .5; // lower the jpeg quality
}
cvs.width = ratio * w;
cvs.height = ratio * h;
let ctx = cvs.getContext("2d").drawImage(sourceImgObj, 0, 0, cvs.width, cvs.height);
// note : if it's not a problem to convert it to async then toBlob would be a better choice
let newImageData = cvs.toDataURL(mimeType, quality);
return newImageData;
}
inp.onchange = e => {
const MAX_SIZE = 100000; // 100KB
let img = new Image();
img.src = URL.createObjectURL(inp.files[0]);
img.onload = e => {
// if it's not too big, return the image's current src
let dataURL = (inp.files[0].size > MAX_SIZE) ?
resize(img) : img.src;
let out = new Image();
out.src = dataURL;
document.body.appendChild(out);
};
}
<input type="file" id="inp">
edit (ivaro18)
I just wanted to add in my modified version of this answer. I don't like reducing the quality parameter to 50%, so I stayed with the ratio and modified it to suit my needs. This resizes an 8.1MB image within 600ms to 900kB.
resize(sourceImgObj, callback) {
const mimeType = "image/jpeg";
let quality = .92; // default;
const cvs = document.createElement('canvas');
const MAX_SIZE = 1024; // in px for both height and width
const w = sourceImgObj.naturalWidth;
const h = sourceImgObj.naturalHeight;
let ratio = MAX_SIZE / Math.max(w, h);
if(ratio > 1) {
let percentage = (w >= h) ? ((w - MAX_SIZE) / w * 100) : ((h - MAX_SIZE) / h * 100);
cvs.width = (percentage + 100)/100 * w;
cvs.height = (percentage + 100)/100 * h;
} else {
cvs.width = w * ratio;
cvs.height = h * ratio;
}
let ctx = cvs.getContext("2d").drawImage(sourceImgObj, 0, 0, cvs.width, cvs.height);
let newImageData = cvs.toDataURL(mimeType, quality);
callback(newImageData);
}