Can't get base64 resized image from javascript canvas - javascript

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);

Related

canvas context getImageData() function won't execute, probably because it's executed before the image is loaded

what I'm trying to do is draw an image to an html canvas using only javascript, and by using the getImageData() function, I'm trying to get the image's bits and then manipulate them in multiple ways.
however, because i'm loading the image using javascript, my suspicion is that the image can't load in time before the call to getImageData(), so the program just stops working when that call is made, as you can see the _log(3) statement isn't being called.
this happens even though I've called getImageData() function inside the images onload() event.
i've already seen a few suggestions in some other stackoverflow threads regarding this issue, but none of them seem to work for me.
any help in the right direction would be really appreciated.
here's the jsfiddle: https://jsfiddle.net/houqdfx9/2/
this is the html:
<p id="log"></p>
this is the javascript:
function _log(string){
document.getElementById("log").innerHTML += string;
}
function _createCanvas(){
var canvas = document.createElement("canvas");
canvas.setAttribute("id", "canvas");
document.body.appendChild(canvas);
return canvas;
}
function _drawAndProcessImage(img, ctx){
ctx.drawImage(img, 0, 0);
_log(img.width + " " + img.height + " ");
var img_data = ctx.getImageData(0,0,img.width,img.height);
_log(3);
/* DO THINGS WITH THE IMG_DATA HERE */
}
function setCanvasAndLoadImage(url, canvas, callback) {
var img = new Image;
var ctx = canvas.getContext("2d");
img.onload = function(){callback(img, ctx);};
img.src = url;
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
};
var canvas = _createCanvas();
setCanvasAndLoadImage("https://pixabay.com/static/uploads/photo/2014/02/01/17/30/apple-256267_960_720.jpg", canvas, _drawAndProcessImage);
Please try this. This works. But, there is a cross domain violation because of the different domains. You would need to proxy this through a server maybe. See this for a solution.
function _log(string){
document.getElementById("log").innerHTML += string;
}
function _createCanvas(){
var canvas = document.createElement("canvas");
canvas.setAttribute("id", "canvas");
document.body.appendChild(canvas);
return canvas;
}
function _drawAndProcessImage(img, ctx){
ctx.drawImage(img, 0, 0);
_log(img.width + " " + img.height + " ");
var img_data = ctx.getImageData(0,0,img.width,img.height);
_log(3);
/* DO THINGS WITH THE IMG_DATA HERE */
}
function setCanvasAndLoadImage(url, canvas, callback) {
var img = new Image;
var ctx = canvas.getContext("2d");
img.onload = function() {
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
callback(img, ctx);
};
img.src = url;
};
var canvas = _createCanvas();
setCanvasAndLoadImage("https://pixabay.com/static/uploads/photo/2014/02/01/17/30/apple-256267_960_720.jpg", canvas, _drawAndProcessImage);
<p id="log"></p>
I think the problem is stems from the lines
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
You are asking for the image dimensions but the image has not been loaded. You have to move that to the image's onload event.

Image not getting copied properly in javascript

I need images to be copied and sent to server for image rotations etc.
To copy an image I'm using this code( from Get image data in JavaScript?):
function getBase64Image(img) {
// Create an empty canvas element
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var dataURL = canvas.toDataURL("image/png");
var replaced = dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
console.log("dataurl=",dataURL);
console.log("replaced=",replaced);
return replaced;
}
It is called like this:
$("#xyz").load(send_image).each(function () {
if (this.complete)
$(this).load();
});
function send_image(){
getBase64Image(document.getElementById('xyz'));
}
However in getBase64Image function once in 40-50 times dataURL is being returned as empty.
What could be the reason?

Saving image to localstorage - canvas

I'm using html5 storage to store my image.
It works great in chrome. In FireFox it works 8/10 times.
function getBase64Image(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
console.log("line 1 : " + img.src);
var dataURL = canvas.toDataURL('image/jpeg');
console.log("line 2 : " + dataURL.replace(/^data:image\/(jpeg);base64,/, ""));
return dataURL.replace(/^data:image\/(jpeg);base64,/, "");
}
Sometimes it returns only "data,".
I see that the canvas size is 0x0:
canvas.width = img.width;
canvas.height = img.height;
Why is that happening?
If the height or width of the canvas is 0, the string "data:," is returned.
This is most likely the cause for some images to fail to receive a proper dataUrl.
Check your img elements and/or scripts to obtain and set the width and height of the canvas.
The auto width and height are only available after the image is loaded.
The function is trying to get the height/width before the image is loaded so I get 0x0. To solve that add img.onload before calling the save function.
function readURL(input, image, width, height) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
//Save the image
var img = new Image();
img.src = e.target.result;
img.onload = function (e){
if (image.id == 'photo1') {
save(img, 'image1');
}
else {
save(img, 'image2');
}
}
}
reader.readAsDataURL(input.files[0]);
}
}

Resize a Base-64 image in JavaScript without using canvas

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

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