Image upload, resizing and encoding to base64 crashes Chrome on mobile - javascript

My users can select an image using the file-upload HTML input element -
1. From there I downscale
2. I convert to base64
For some reason Chrome mobile & Android browser completely crash - and
display an 'Out of Memory error'.
If the browser runs on a more 'modern/capable' device all goes perfectly fine.
What could be causing the error here - can it be fixed?
Here is the function that downscales(whilst keeping aspect ratios) and returns a Base64 string of the image.
function resizeAndConvertB64(img, maxWidth, maxHeight) {
var imgWidth = img.width,
imgHeight = img.height;
var ratio = 1, ratio1 = 1, ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;
// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
}
else {
ratio = ratio2;
}
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);
// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
var rounds = 1;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;
copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);
// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
} // end for
// return Base64 string of the downscaled image
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);
var dataURL = canvas.toDataURL();
return dataURL;
}

I think it is an error of Chrome mobile.
Chrome mobile is not stable and my handy crashes often when I use it.
The out of memory message says that your device hasn't got enough memory for this script.
Check if it works with Dolphin Browser.
I think it will work but I don't know it.

Related

Reduce the dimension of base64 image maintaining aspect ratio in javascript

I have a base64 string for an image, which i have to display as an icon. If the dimension of an image are bigger i have to display icon maintaining aspect ratio.
I have written a logic to identify if the image is landscape or portrait based on which height and width will be settled for canvas. But seems height and width of icons are not proper as i have hard coded it.
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var height, width;
if (this.height>this.width) {
height = 50;
width = 30;
} else if (this.height<this.width) {
height = 30;
width = 50;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(image,
0, 0, this.width, this.height,
0, 0, canvas.width, canvas.height
);
var scaledImage = new Image();
scaledImage.src = canvas.toDataURL();
Is there any way we can calculate it dynamically or any other way to preserve aspect ratio for icons.
Working Example can be found on https://codepen.io/imjaydeep/pen/ewBZRK
It will be fine if some space will be left on x-y axis.
You just need to calculate the scale or ratio and multiply both dimensions by that. Here's an example function, and here's your edited codepen.
Creates trimmed, scaled image:
function scaleDataURL(dataURL, maxWidth, maxHeight){
return new Promise(done=>{
var img = new Image;
img.onload = ()=>{
var scale, newWidth, newHeight, canvas, ctx;
if(img.width < maxWidth){
scale = maxWidth / img.width;
}else{
scale = maxHeight / img.height;
}
newWidth = img.width * scale;
newHeight = img.height * scale;
canvas = document.createElement('canvas');
canvas.height = newHeight;
canvas.width = newWidth;
ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, newWidth, newHeight);
done(canvas.toDataURL());
};
img.src = dataURL;
});
}
Or, if you want the image to always be the provided dimensions with empty surrounding area, you can use this: (codepen)
Creates scaled image of exact provided dimensions:
function scaleDataURL(dataURL, maxWidth, maxHeight){
return new Promise(done=>{
var img = new Image;
img.onload = ()=>{
var scale, newWidth, newHeight, canvas, ctx, dx, dy;
if(img.width < maxWidth){
scale = maxWidth / img.width;
}else{
scale = maxHeight / img.height;
}
newWidth = img.width * scale;
newHeight = img.height * scale;
canvas = document.createElement('canvas');
canvas.height = maxWidth;
canvas.width = maxHeight;
ctx = canvas.getContext('2d');
dx = (maxWidth - newWidth) / 2;
dy = (maxHeight - newHeight) / 2;
console.log(dx, dy);
ctx.drawImage(img, 0, 0, img.width, img.height, dx, dy, newWidth, newHeight);
done(canvas.toDataURL());
};
img.src = dataURL;
});
}
For someone who search for a solution when the image is bigger that you need.
function scaleDataURL(dataURL: string, maxWidth: number, maxHeight: number) {
return new Promise((done) => {
var img = new Image()
img.onload = () => {
var scale, newWidth, newHeight, canvas, ctx
if (img.width > maxWidth) {
scale = maxWidth / img.width
} else if (img.height > maxHeight) {
scale = maxHeight / img.height
} else {
scale = 1
}
newWidth = img.width * scale
newHeight = img.height * scale
canvas = document.createElement('canvas')
canvas.height = newHeight
canvas.width = newWidth
ctx = canvas.getContext('2d')
console.log('img', 'scale', scale, 0, 0, img.width, img.height, 0, 0, newWidth, newHeight)
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, newWidth, newHeight)
done(canvas.toDataURL())
}
img.src = dataURL
})
}
Usage:
scaleDataURL(base64data, 600, 200)
.then((newBase64data) => {
console.log(newBase64data)
})
.catch((err) => console.log(err))

canvas.toDataURL() results black image by trying to resize base64 string

I am currently trying to resize base64 images, since the image files are too big to be processed later on with php. I've found a way to achieve this by resizing the image using canvas. Unfortunately the image I get is just a black field which is 300px wide and 150px high. Maybe it has something to do with img.onload and canvas.toDataURL() order, or I am just using the wrong event (img.onload). Any idea where the mistake can be?
function exportImg(val){
var imageData = $('#image-cropper').cropit('export', {originalSize: true});
imageData = imageData.replace(/^data:image\/[a-z]+;base64,/, "");
var imageDataRes = resize(imageData);
$.post('php/upload.php', { imageDataRes: imageDataRes });
}
function resize(base64){
// Max size for thumbnail
var maxWidth = 900;
var maxHeight = 900;
// Create and initialize two canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
// Create original image
var img = new Image();
img.src = base64;
img.onload = function(){
// Determine new ratio based on max size
var ratio = 1;
if(img.width > maxWidth) {
ratio = maxWidth / img.width;
}
else if(img.height > maxHeight) {
ratio = maxHeight / img.height;
}
// Draw original image in second canvas
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
// Copy and resize second canvas to first canvas
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
}
alert(canvas.toDataURL());
return canvas.toDataURL();
}
EDIT:
What is async in this case and how to solve it? Sorry, but unfortunately I don't see how this could help me further. The $.post works perfectly, I get the images. I just don't get the idea of img.onload and toDataURL() and how I should parse them from one function to another. At first, I got a blank result, with no string at all (just data,), but by adding this img.onload I got finally some base64 string...but it was just black screen.
You will need to wait onload event after that use global variable to save data and call upload function on the end .
Try this :
function exportImg(val){
var imageData = $('#image-cropper').cropit('export', {originalSize: true});
imageData = imageData.replace(/^data:image\/[a-z]+;base64,/, "");
resize(imageData);
}
var SendWhenisReady = function(imageDataRes){
$.post('php/upload.php', { imageDataRes: imageDataRes });
};
function resize(base64){
// Max size for thumbnail
var maxWidth = 900;
var maxHeight = 900;
// Create and initialize two canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
// Create original image
var img = new Image();
img.src = base64;
img.onload = function(){
// Determine new ratio based on max size
var ratio = 1;
if(img.width > maxWidth) {
ratio = maxWidth / img.width;
}
else if(img.height > maxHeight) {
ratio = maxHeight / img.height;
}
// Draw original image in second canvas
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
// Copy and resize second canvas to first canvas
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
alert(canvas.toDataURL());
window['imageDataRes'] = canvas.toDataURL();
SendWhenisReady(window['imageDataRes'])
}
}

Canvas resize with image in it

I'm trying to get image resized from mm to pixels after clicking submit button but all I can achieve is cropping original image. It looks like canvas is not refreshing image after changing size. How can I do it properly?
Example on jsfiddle
JS Code:
document.addEventListener("DOMContentLoaded", function() {
var width = 0;
var height = 0;
var colors = 0;
var order = 0;
var canvas = document.getElementById('project');
var context = canvas.getContext('2d');
var logo = document.getElementById('logo');
logo.onload = function() {
canvas.width = logo.width;
canvas.height = logo.height;
context.drawImage(logo, 0, 0);
};
document.getElementById("btn_sub").addEventListener("click", function(event) {
event.preventDefault();
width = document.getElementById("form1").elements[0].value;
height = document.getElementById("form1").elements[1].value;
colors = document.getElementById("form1").elements[2].value;
order = document.getElementById("form1").elements[3].value;
logo.width = (width * 118) / 25.4;
logo.height = (height * 118) / 25.4;
canvas.width = (width * 118) / 25.4;
canvas.height = (height * 118) / 25.4;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(logo, 0, 0);
The drawImage function is not taking into consideration the width and height of the image element. To make this work you can use this overload of the drawImage function context.drawImage(img,x,y,width,height).
Like this for example, context.drawImage(logo, 0, 0, logo.width, logo.height).

How to prevent a base64 encoding from hanging the browser?

At my 9-to-5 we regularly Base64 images when we work with image uploads for forms/apps etc for it's 'simplicity' in transmitting over AJAX and some other minor reasons.
However we regularly have issues when using this method with 'old'
Android devices.
The browser sometimes hangs - displays an available memory error
message and just restarts. Other times it just restarts without
popping any alerts.
This happens only on large images and on old Android devices that
have a lot of apps running etc, etc.
One solution is to reduce the size of the image before DataUrl'ing the whole thing - but sometimes this is not enough - we need the image to be of a relative size, e.g we can't use a pinhead size of an image.
How can we prevent the browser from hanging over this?
I have thought of a solution where I would use WebWorkers to asynchronously encode it in the background - but I am thinking what difference would that make? The UI might not freeze but memory is being used anyway.
Are there any other possible solutions?
Note: We are targeting only Android 4.1+ and iOS6+
Anyway,
This is the function we currently use for resizing and encoding to Base64.
function resizeCanvasImage(img, maxWidth, maxHeight) {
var imgWidth = img.width,
imgHeight = img.height;
var ratio = 1, ratio1 = 1, ratio2 = 1;
ratio1 = maxWidth / imgWidth;
ratio2 = maxHeight / imgHeight;
// Use the smallest ratio that the image best fit into the maxWidth x maxHeight box.
if (ratio1 < ratio2) {
ratio = ratio1;
}
else {
ratio = ratio2;
}
var canvas = document.createElement("canvas");
var canvasContext = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
var canvasCopy2 = document.createElement("canvas");
var copyContext2 = canvasCopy2.getContext("2d");
canvasCopy.width = imgWidth;
canvasCopy.height = imgHeight;
copyContext.drawImage(img, 0, 0);
// init
canvasCopy2.width = imgWidth;
canvasCopy2.height = imgHeight;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
var rounds = 2;
var roundRatio = ratio * rounds;
for (var i = 1; i <= rounds; i++) {
// tmp
canvasCopy.width = imgWidth * roundRatio / i;
canvasCopy.height = imgHeight * roundRatio / i;
copyContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvasCopy.width, canvasCopy.height);
// copy back
canvasCopy2.width = imgWidth * roundRatio / i;
canvasCopy2.height = imgHeight * roundRatio / i;
copyContext2.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvasCopy2.width, canvasCopy2.height);
} // end for
// return Base64 string of the downscaled image
canvas.width = imgWidth * roundRatio / rounds;
canvas.height = imgHeight * roundRatio / rounds;
canvasContext.drawImage(canvasCopy2, 0, 0, canvasCopy2.width, canvasCopy2.height, 0, 0, canvas.width, canvas.height);
var dataURL = canvas.toDataURL();
return dataURL;
}

Scaling an image to fit on canvas

I have a form that allows a user to upload an image.
Once the image is loaded, we perform some scaling on it in order to reduce its filesize before we pass it back to the server.
To do this, we place it on the canvas and manipulate it there.
This code will render the scaled image on the canvas, with the canvas of size 320 x 240px:
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
... where canvas.width and canvas.height is the image height and width x a scaling factor based on the size of the original image.
But when I go to use the code:
ctx.drawImage(img, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height
... I only get part of the image on the canvas, in this case the top left corner. I need the whole image 'scaled' to fit on the canvas, despite the actual image size being larger than the 320x240 canvas size.
So for the code above, the width and heights are 1142x856, as that is the final image size. I need to maintain that size to pass beck to the server when the form is submitted, but only want a smaller view of it to appear in the canvas for the user.
What am I missing here? Can anyone point me in the right direction please?
You made the error, for the second call, to set the size of source to the size of the target.
Anyway i bet that you want the same aspect ratio for the scaled image, so you need to compute it :
var hRatio = canvas.width / img.width ;
var vRatio = canvas.height / img.height ;
var ratio = Math.min ( hRatio, vRatio );
ctx.drawImage(img, 0,0, img.width, img.height, 0,0,img.width*ratio, img.height*ratio);
i also suppose you want to center the image, so the code would be :
function drawImageScaled(img, ctx) {
var canvas = ctx.canvas ;
var hRatio = canvas.width / img.width ;
var vRatio = canvas.height / img.height ;
var ratio = Math.min ( hRatio, vRatio );
var centerShift_x = ( canvas.width - img.width*ratio ) / 2;
var centerShift_y = ( canvas.height - img.height*ratio ) / 2;
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.drawImage(img, 0,0, img.width, img.height,
centerShift_x,centerShift_y,img.width*ratio, img.height*ratio);
}
you can see it in a jsbin here :
http://jsbin.com/funewofu/1/edit?js,output
Provide the source image (img) size as the first rectangle:
ctx.drawImage(img, 0, 0, img.width, img.height, // source rectangle
0, 0, canvas.width, canvas.height); // destination rectangle
The second rectangle will be the destination size (what source rectangle will be scaled to).
Update 2016/6: For aspect ratio and positioning (ala CSS' "cover" method), check out:
Simulation background-size: cover in canvas
I guess that you want the image to be scaled to a smaller size, without losing the ratio of the dimensions. I have a solution.
var ratio = image.naturalWidth / image.naturalHeight;
var width = canvas.width;
var height = width / ratio;
ctx.drawImage(image, 0, 0, width, height);
the ratio will be maintained. And the image drawn on the canvas will be of the same ratio. you can use the if loop if the height of the image is long, you can replace the canvas.width to some other width
You can call ctx.scale() before calling ctx.drawImage:
var factor = Math.min ( canvas.width / img.width, canvas.height / img.height );
ctx.scale(factor, factor);
ctx.drawImage(img, 0, 0);
ctx.scale(1 / factor, 1 / factor);
This should preserve the aspect ratio.
HTML:
<div id="root"></div>
JavaScript:
const images = [
'https://cdn.pixabay.com/photo/2022/07/25/15/18/cat-7344042_960_720.jpg',
'https://cdn.pixabay.com/photo/2022/06/27/08/37/monk-7287041_960_720.jpg',
'https://cdn.pixabay.com/photo/2022/07/18/19/57/dog-7330712_960_720.jpg',
'https://cdn.pixabay.com/photo/2022/05/22/18/25/spain-7214284_960_720.jpg',
];
const root = document.getElementById('root');
const image = new Image();
image.crossOrigin = 'anonumys';
image.src = images[3];
const canvas = document.createElement('canvas');
canvas.classList.add('track');
canvas.width = 600;
canvas.height = 400;
const ctx = canvas.getContext('2d');
const meta = {
ratio: image.width / image.height,
width: 0,
height: 0,
offsetX: 0,
offsetY: 0,
};
if (meta.ratio >= 1) {
meta.width = canvas.width > image.width ? image.width : canvas.width;
meta.height = meta.width / meta.ratio;
} else {
meta.height = canvas.height > image.height ? image.height : canvas.height;
meta.width = meta.height * meta.ratio;
}
meta.offsetX = canvas.width > meta.width ? (canvas.width - meta.width) / 2 : 0;
meta.offsetY = canvas.height > meta.height ? (canvas.height - meta.height) / 2 : 0;
image.addEventListener('load', () => {
ctx.drawImage(image, meta.offsetX, meta.offsetY, meta.width, meta.height);
root.append(canvas);
});
console.log(meta);

Categories