Resize a Base-64 image in JavaScript without using canvas - javascript

I need a way to resize pictures in JavaScript without using an HTML element.
My mobile HTML app capture photos and then converts them into base64 strings. Finally I want to resize them before they are sent to the API.
I'm looking for a different and more suitable way to resize than using a canvas element, is there a way?

A way to avoid the main HTML to be affected is to create an off-screen canvas that is kept out of the DOM-tree.
This will provide a bitmap buffer and native compiled code to encode the image data. It is straight forward to do:
function imageToDataUri(img, width, height) {
// create an off-screen canvas
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
// set its dimension to target size
canvas.width = width;
canvas.height = height;
// draw source image into the off-screen canvas:
ctx.drawImage(img, 0, 0, width, height);
// encode image to data-uri with base64 version of compressed image
return canvas.toDataURL();
}
If you want to produce a different format than PNG (default) just specify the type like this:
return canvas.toDataURL('image/jpeg', quality); // quality = [0.0, 1.0]
Worth to note that CORS restrictions applies to toDataURL().
If your app is giving only base64 encoded images (I assume they are data-uri's with base64 data?) then you need to "load" the image first:
var img = new Image;
img.onload = resizeImage;
img.src = originalDataUriHere;
function resizeImage() {
var newDataUri = imageToDataUri(this, targetWidth, targetHeight);
// continue from here...
}
If the source is pure base-64 string simply add a header to it to make it a data-uri:
function base64ToDataUri(base64) {
return 'data:image/png;base64,' + base64;
}
Just replace the image/png part with the type the base64 string represents (ie. make it an optional argument).

Ken's answer is the right answer, but his code doesn't work. I made some adjustments on it and it now works perfectly. To resize a Data URI :
// Takes a data URI and returns the Data URI corresponding to the resized image at the wanted size.
function resizedataURL(datas, wantedWidth, wantedHeight)
{
// We create an image to receive the Data URI
var img = document.createElement('img');
// When the event "onload" is triggered we can resize the image.
img.onload = function()
{
// We create a canvas and get its context.
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// We set the dimensions at the wanted size.
canvas.width = wantedWidth;
canvas.height = wantedHeight;
// We resize the image with the canvas method drawImage();
ctx.drawImage(this, 0, 0, wantedWidth, wantedHeight);
var dataURI = canvas.toDataURL();
/////////////////////////////////////////
// Use and treat your Data URI here !! //
/////////////////////////////////////////
};
// We put the Data URI in the image's src attribute
img.src = datas;
}
// Use it like that : resizedataURL('yourDataURIHere', 50, 50);

Pierrick Martellière is far the best answer, I just wanted to point that you should implement that with a async function. Once that, you would be able to do something like:
var newDataUri = await resizedataURL(datas,600,600);
This will wait for the result of the function before going to the next step. It's a cleaner way to write code. Here is the function from Pierrick with the little edit:
// Takes a data URI and returns the Data URI corresponding to the resized image at the wanted size.
function resizedataURL(datas, wantedWidth, wantedHeight){
return new Promise(async function(resolve,reject){
// We create an image to receive the Data URI
var img = document.createElement('img');
// When the event "onload" is triggered we can resize the image.
img.onload = function()
{
// We create a canvas and get its context.
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
// We set the dimensions at the wanted size.
canvas.width = wantedWidth;
canvas.height = wantedHeight;
// We resize the image with the canvas method drawImage();
ctx.drawImage(this, 0, 0, wantedWidth, wantedHeight);
var dataURI = canvas.toDataURL();
// This is the return of the Promise
resolve(dataURI);
};
// We put the Data URI in the image's src attribute
img.src = datas;
})
}// Use it like : var newDataURI = await resizedataURL('yourDataURIHere', 50, 50);
For more details you can check MDN Docs : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Promise

Yes, you can. These solutions good for resizing not just converting image to base64.
You can convert js file to image bitmap by jpg-js.And you can resize only by this lib, but in a case of resizing from very large image to very small, quality will be very bad.Best way for high-res images is to convert file to bitmap by jpg-js and then resize this bitmap by Pica lib.
You can get image data from a file by jpg-js (or draw an image on canvas)and then resize canvasImageData by resizing lib pica. (good for High-resolution images, without canvas size restriction)
You can use offscreen canvas, without attaching the canvas to a body, and resize an image. This solution will be faster but will be the worse solution for high-resolution images, for example 6000x6000 pixels. In that case, result canvas can be with bad quality or just empty, or browser can fall with memory limit exception. (good for normal and small images)
Jpg-js and Pica will not use dom elements at all. These libs are working only with image data, without dom elements (canvas and image).
About the canvas, size restriction see this post

I think this method is best way for this solution.
function base64Resize(sourceBase64, scale , callBack) {
const _scale = scale;
var img = document.createElement('img');
img.setAttribute("src", sourceBase64);
img.onload = () => {
var canvas = document.createElement('canvas');
canvas.width = img.width * _scale;
canvas.height = img.height * _scale;
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var maxW = img.width * _scale;
var maxH = img.height * _scale;
var iw = img.width;
var ih = img.height;
var scl = Math.min((maxW / iw), (maxH / ih));
var iwScaled = iw * scl;
var ihScaled = ih * scl;
canvas.width = iwScaled;
canvas.height = ihScaled;
ctx.drawImage(img, 0, 0, iwScaled, ihScaled);
const newBase64 = canvas.toDataURL("image/jpeg", scl);
callBack(newBase64);
}
}
Important point is that you should use img.onload event.

My last suggestion (as Chris) was deemed to not answer the question as it used canvas. Because canvas is too obvious.
It's not possible to do without loading the image into memory somewhere so it can be manipulated. Why not just use a library like jimp?
import Jimp from "jimp";
async function resizeBase64Image(base64: string, targetWidth: number, targetHeight: number): Promise<string> {
// Decode the base64 image data and save it to a buffer
const imageBuffer = Buffer.from(base64, "base64");
// Use Jimp to load the image from the buffer and resize it
const image = await Jimp.read(imageBuffer);
image.resize(targetWidth, targetHeight);
// Convert the image back to a base64 data URI
const resizedImageBase64 = await image.getBase64Async(Jimp.MIME_PNG);
return resizedImageBase64;
}

You may want a resize function that returns the resized data uri:
const resizeBase64Image = (base64: string, width: number, height: number): Promise<string> => {
// Create a canvas element
const canvas = document.createElement('canvas') as HTMLCanvasElement;
// Create an image element from the base64 string
const image = new Image();
image.src = base64;
// Return a Promise that resolves when the image has loaded
return new Promise((resolve, reject) => {
image.onload = () => {
// Calculate the aspect ratio of the image
const aspectRatio = image.width / image.height;
// Calculate the best fit dimensions for the canvas
if (width / height > aspectRatio) {
canvas.width = height * aspectRatio;
canvas.height = height;
} else {
canvas.width = width;
canvas.height = width / aspectRatio;
}
// Draw the image to the canvas
canvas.getContext('2d')!.drawImage(image, 0, 0, canvas.width, canvas.height);
// Resolve the Promise with the resized image as a base64 string
resolve(canvas.toDataURL());
};
image.onerror = reject;
});
};
And simply await it like this:
const resizedImage: string = await resizeBase64Image(base64, 100, 100);

function resizeImage(base64Str) {
var img = new Image();
img.src = base64Str;
var canvas = document.createElement('canvas');
var MAX_WIDTH = 400;
var MAX_HEIGHT = 350;
var width = img.width;
var height = img.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
} else {
if (height > MAX_HEIGHT) {
width *= MAX_HEIGHT / height;
height = MAX_HEIGHT;
}
}
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
return canvas.toDataURL();
}
https://gist.github.com/ORESoftware/ba5d03f3e1826dc15d5ad2bcec37f7bf

Related

Once I have a base64 url how can I change the filter for brightness and contrast?

If I have an image in base64 ex.
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABDAAwfqNRk/rjcYX+PxrV/wtWqwJSTlboMgAAAABJRU5ErkJggg==
What's the fastest way to change a filter (ex. brightness, contrast) programmatically and without creating any img or canvas tag in the view? Then once changed, convert it back to a base64 url?
You can do this by putting the image onto a HTML canvas with filters applied, then turning that canvas back into a base64 URL.
Since you never add the canvas to the document, it will never be seen.
This is how you would do it asynchronously:
This takes about 1-10ms
function applyFiltersToImage(imageURL, callBack) {
// load the image url into an image object
const image = new Image();
image.src = imageURL;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext('2d');
image.onload = () => {
canvas.width = image.width;
canvas.height = image.height;
// apply css filters here
ctx.filter = 'brightness(0.5) contrast(2)';
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
// turn canvas back into a base64 image URL
callBack(canvas.toDataURL("image/png"));
}
}
You could do it synchronously if you already have the Image instance loaded.
This is how you will do it synchronously:
function applyFiltersToImageSync(imageObject) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext('2d');
canvas.width = imageObject.width;
canvas.height = imageObject.height;
// apply css filters here
ctx.filter = 'brightness(0.5) contrast(2)';
ctx.drawImage(imageObject, 0, 0, canvas.width, canvas.height);
//turn canvas back into a base64 image
return canvas.toDataURL("image/png");
}

Can't get base64 resized image from javascript canvas

I have two main questions. I'm trying to resize a base64 image according to the answer in
JavaScript reduce the size and quality of image with based64 encoded code
This seemed simple enough, except for the fact that I need to get only the base64 string from that function, without the data:image... header.
I tried to modify the function like this:
function resizeBase64Img(base64, width, height) {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var context = canvas.getContext("2d");
var deferred = $.Deferred();
$("<img/>").attr("src", "data:image/gif;base64," + base64).load(function () {
context.scale(width / this.width, height / this.height);
context.drawImage(this, 0, 0);
deferred.resolve($("<img/>").attr("src", canvas.toDataURL()));
});
var result = canvas.toDataURL();
return result.substr(result.indexOf(",") + 1)
}
which returns a base64 string. This string is corrupted, and it never displays correctly. Why is this happening and how can I fix it?
The other question is, is there a way to resize the image based on percentage and not on pixel size?
Thanks
I ended up rewriting the function based on another canvas function I found. This function takes the src of a base64 img, modifies the size and assigns the result in base64 to another element:
function resizeBase64Img(url, width, height) {
var img = new Image();
img.setAttribute('crossOrigin', 'anonymous');
img.onload = function () {
var canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.scale(width/this.width, height/this.height);
ctx.drawImage(this, 0, 0);
result=canvas.toDataURL();
$('#ASSIGNEDELEMENT').val(result.substr(result.indexOf(",") + 1));
};
img.src = url;
}
And it should be invoked like this:
resizeBase64Img($('#image').attr('src'),300,300);

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'])
}
}

Blank image after resizing in canvas

I'm trying to resize an image using the canvas method. I am then getting the ArrayBuffer and using that to upload the new image. Here's my resize method.
function resizeImage(img) {
var perferedWidth = 160;
var ratio = perferedWidth / img.width;
var $canvas = $("#canvasTest");
var canvas = $("#canvasTest")[0];
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0,0,canvas.width, canvas.height);
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data.buffer;
return imageData;
}
The problem is once the image gets uploaded, it's just a blank image. I am not doing something correct as the imageData is not giving me a valid buffer. I know it's not valid because when I convert the imageData to a base64 string, it's not a valid base64 encoded image (image doesn't show up when I set its src to it).
I also know that the drawImage method is working, as I have temporarily created a canvas in the DOM and it is correctly drawing the image to that canvas.
What am I doing wrong? Thanks!

Loss of quality while adding drawing image on HTML 5 canvas from binary string

I am trying to draw an image from binary string on to the canvas.
var reader = new FileReader();
//reader.readAsDataURL(file);
reader.readAsBinaryString(file);
reader.onload = function(event){
var d = $(thisObj.CreateIndoorFormDivControlName).dialog();
var canvas =document.getElementById('canvasfloorLayout');
var cxt=canvas.getContext("2d");
var img=new Image();
img.onload = function() {
cxt.drawImage(img, 0, 0,canvas.width,canvas.height);
}
img.src = "data:image/jpeg;base64,"+window.btoa(reader.result);
I am using the above code but the problem is the image size is getting reduced to the canvas size and quality is dropping like anything. I have been tried with
cxt.drawImage(img, 0, 0,img.width,img.height);
But the image gets cropped.
I donot want to use reader.readAsDataURL as I need to post the binary to the server. My requirement is to show the full image and draw lines on it .
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
...if you draw the image at the canvas' native resolution, then when the image gets scaled, it's obviously going to lose quality.
...if you draw the image at the image's native resolution, but the canvas hasn't changed size, then you're going to end up with a partial image, or a partially-filled canvas.
So if you want neither of those, then set the dimensions of the canvas to match the dimensions of the image, and you'll have a canvas-drawn image which matches the resolution of the data-image.
EDIT
Adding an example of a proxy, between the two.
var img = new Image(),
canvas = document.createElement("canvas"),
context = canvas.getContext("2d"),
// ......etc
dimensions = {
max_height : 800,
max_width : 600,
width : 800, // this will change
height : 600, // this will change
largest_property : function () {
return this.height > this.width ? "height" : "width";
},
read_dimensions : function (img) {
this.width = img.width;
this.height = img.height;
return this;
},
scaling_factor : function (original, computed) {
return computed / original;
},
scale_to_fit : function () {
var x_factor = this.scaling_factor(this.width, this.max_width),
y_factor = this.scaling_factor(this.height, this.max_height),
largest_factor = Math.min(x_factor, y_factor);
this.width *= largest_factor;
this.height *= largest_factor;
}
};
dimensions.read_dimensions(img).scale_to_fit();
canvas.width = dimensions.width;
canvas.height = dimensions.height;
context.drawImage(img, 0, 0, dimensions.width, dimensions.height);

Categories