I have this code that resize the image and I want it to upload after resizing photo and submit the form using Ajax. I tried doing it but, it doesn't seems to resize my photo. I'm new to Javascript, please guide me. Below is my code:-
<form id="imgLocal" name="imgLocal" accept-charset="utf-8" enctype="multipart/form-data">
<input type="file" name="receipt" id="receipt" accept="images/*" onchange="handleFiles();" />
</form>
// Form validation above
submitHandler:function(form) {
var formData = new FormData(form);
$.ajax({
type: 'POST',
url: 'a/imgUpload',
dataType: 'json',
data: formData,
cache: false,
contentType: false,
processData: false
}).success(function(data){
show('loading', false);
alert('success');
}else {
alert('error');
}
function handleFiles()
{
var filesToUpload = document.getElementById('receipt').files;
var file = filesToUpload[0];
// Create an image
var img = document.createElement("img");
// Create a file reader
var reader = new FileReader();
// Set the image once loaded into file reader
reader.onload = function(e)
{
img.src = e.target.result;
var canvas = document.createElement("canvas");
//var canvas = $("<canvas>", {"id":"testing"})[0];
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 400;
var MAX_HEIGHT = 300;
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);
var dataurl = canvas.toDataURL("image/png");
$('#receipt').attr('value', dataurl);
}
reader.readAsDataURL(file);
}
}
You cannot set a file input with new data, the user has to choose a file for security reasons.
In order to upload a generated image you can upload the data url and decode the base64 string back to binary format and save it server side. Or you can get the image from the canvas as a blob and then attach it to your FormData object.
instead of calling toDataURL you can call toBlob to get a blob object.
canvas.toBlob(function(imgBlob){
formData.append("receipt",imgBlob,"filenameForImage.png");
},'image/png');
Note that the toBlob method is not yet fully supported, but the MDN reference does give a polyfill that can be used:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, toBlob, {
value: function (callback, type, quality) {
var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len);
for (var i=0; i<len; i++ ) {
arr[i] = binStr.charCodeAt(i);
}
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}
});
}
You can try this
html
<form id="imgLocal" name="imgLocal" accept-charset="utf-8" enctype="multipart/form-data">
<input type="file" name="receipt" id="receipt" accept="images/*" onchange="handleFiles();" />
</form>
<script>
function handleFiles()
{
var image= document.getElementById('receipt');
var file = image.files[0];
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {
img.src = e.target.result;
img.onload = function () {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var MAX_WIDTH = 400;
var MAX_HEIGHT = 300;
var width = this.width;
var height = this.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);
var dataurl = canvas.toDataURL("image/png");
upload(dataurl);
};
image.onerror= function() {
alert('Invalid file type: '+ file.type);
};
}
reader.readAsDataURL(file);
}
}
function upload(file) {
var form = new FormData();
form.append('image', file);
$.ajax({
url: 'server.php',
data: form,
processData: false,
contentType: false,
type: 'POST',
success: function(data){
alert(data);
}
});
}
</script>
Server side :
<?php
$filename = "image_".uniqid().".png";
$dest = __DIR__.$filename;
$imgData = str_replace(' ','+',$_POST['image']);
$imgData = substr($imgData,strpos($imgData,",")+1);
$imgData = base64_decode($imgData);
$file = fopen($dest, 'w');
fwrite($file, $imgData);
fclose($file);
?>
Check my blog
Related
I tried to Html5 input capture from camera(Samsung Tab s7) and then resize(my main goal this point is reducing image size) and than upload post method via my controller. But when I upload, my file recognized .bin and save like this. Icouldnt save jpeg. How can I solve it?
HTML :
<input type="file" id="captureImageInput" style="display:none;" accept="image/*" capture>
JS :
const fileInput = document.getElementById('captureImageInput');
fileInput.addEventListener('change', e => {
if (e.target.files.length == 0) {
// No file captured, ignore
return;
}
else{
var imageFile = e.target.files[0];
//--resize image START
var canvas = document.createElement("canvas");
var img1 = document.createElement("img");
img1.setAttribute('src', imageFile);
var ctx = canvas.getContext("2d");
ctx.drawImage(img1, 0, 0);
var MAX_WIDTH = 800;
var MAX_HEIGHT = 600;
var width = img1.width;
var height = img1.height;
if (width > height) {
if (width > MAX_WIDTH) {
height *= MAX_WIDTH / width;
width = MAX_WIDTH;
}
}
else if (width == 0 && height == 0)
{
width = MAX_WIDTH;
height = MAX_HEIGHT;
}
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(img1, 0, 0, width, height);
var dataurl = canvas.toDataURL("image/jpeg");
var blobBin = atob(dataurl.split(',')[1]);
var array = [];
for (var i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
var resizedFileBlob = new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
//--resize image END
var fullFileName = serialNo + "-" + stockNo;
var fileImage = new File([resizedFileBlob], fullFileName);
var myFormData = new FormData();
myFormData.append('FormFile', fileImage);
$.ajax({
//POST OPERATION TO CONTROLLER
});
}
});
Controller side:
[HttpPost]
public JsonResult SavePhysicalPath(FileModel myFormData)
{
//update physical path operatıons..
}
FileModel class:
public class FileModel
{
public string FileFolderPath { get; set; }
public string Files { get; set; }
public IFormFile[] FormFile { get; set; }
}
According to your codes, I think the issue is related you have create a new file by using this line var fileImage = new File([resizedFileBlob], fullFileName);.
I suggest you could remove this line and directly use the resizedFileBlob as the parameter and pass to backend.
I have create a test demo as below and you could refer to it.
Js:
<script type="text/javascript">
$(document).ready(function () {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload = function () {
// set size proportional to image
canvas.height = canvas.width * (img.height / img.width);
// step 1 - resize to 50%
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
// step 3, resize to final size
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
}
img.src = "https://i.imgur.com/SHo6Fub.jpg";
var cav = document.getElementById("canvas");
const file = dataURLtoBlob(cav.toDataURL());
var fileImage = new File([file], "test");
const fd = new FormData;
fd.append('image', fileImage);
$.ajax({
type: 'POST',
url: '/api/Foo',
data: fd,
processData: false,
contentType: false
});
});
function dataURLtoBlob(dataURL) {
let array, binary, i, len;
binary = atob(dataURL.split(',')[1]);
array = [];
i = 0;
len = binary.length;
while (i < len) {
array.push(binary.charCodeAt(i));
i++;
}
return new Blob([new Uint8Array(array)], {
type: 'image/png'
});
};
</script>
View:
<img src="https://i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>
Result:
I am trying to resize bulky image to reduce size image. For this i have implemented following code.
<html>
<head>
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
</head>
<body>
<input type='file' id="fileUpload" />
<canvas id="canvas" width="450" height="300"></canvas>
</body>
<script type="text/javascript">
function el(id) {
return document.getElementById(id);
}
var canvas = el("canvas");
var context = canvas.getContext("2d");
function readImage() {
if ( this.files && this.files[0] ) {
var FR= new FileReader();
FR.onload = function(e) {
var img = new Image();
img.addEventListener("load", function() {
context.drawImage(img, 0, 0);
var MAX_WIDTH = 450;
var MAX_HEIGHT = 300;
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.mozImageSmoothingEnabled = true;
ctx.webkitImageSmoothingEnabled = true;
ctx.msImageSmoothingEnabled = true;
ctx.imageSmoothingEnabled = true;
ctx.drawImage(img, 0, 0, width, height);
});
img.src = e.target.result;
};
FR.readAsDataURL( this.files[0] );
} else {
alert("error");
return false;
}
var canvas = el("canvas");
var dataURL = canvas.toDataURL();
$.ajax({
type: "POST",
url: "http://localhost/newtry/lipak.php",
data: {
imgBase64: dataURL
}
}).done(function(o) {
console.log('saved');
});
}
el("fileUpload").addEventListener("change",readImage);
</script>
</html>
and in another php file i have a code to retrieve this code as follows
file_put_contents('photo.png', base64_decode(substr($_POST['imgBase64'], strpos($_POST['imgBase64'], ",")+1)));
but when i am uploading my first image its not getting saved. but after uploading another image first image got saved .. similarly second, third and so on.
So how can i retrieve an image on first attempt?
Vinay please call ajax in load function
function readImage() {
if ( this.files && this.files[0] ) {
var FR= new FileReader();
FR.onload = function(e) {
var img = new Image();
img.addEventListener("load", function() {
context.drawImage(img, 0, 0);
var MAX_WIDTH = 450;
var MAX_HEIGHT = 300;
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.mozImageSmoothingEnabled = true;
ctx.webkitImageSmoothingEnabled = true;
ctx.msImageSmoothingEnabled = true;
ctx.imageSmoothingEnabled = true;
ctx.drawImage(img, 0, 0, width, height);
$.ajax({
type: "POST",
url: "http://localhost/newtry/lipak.php",
data: {
imgBase64: dataURL
}
}).done(function(o) {
console.log('saved');
});
});
img.src = e.target.result;
};
FR.readAsDataURL( this.files[0] );
} else {
alert("error");
return false;
}
var canvas = el("canvas");
var dataURL = canvas.toDataURL();
}
el("fileUpload").addEventListener("change",readImage);
</script>
I have found a few different posts and even questions on stackoverflow answering this question. I am basically implementing this same thing as this post.
So here is my issue. When I upload the photo, I also need to submit the rest of the form. Here is my html:
<form id="uploadImageForm" enctype="multipart/form-data">
<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<input id="name" value="#{name}" />
... a few more inputs ...
</form>
Previously, I did not need to resize the image, so my javascript looked like this:
window.uploadPhotos = function(url){
var data = new FormData($("form[id*='uploadImageForm']")[0]);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
... handle error...
}
}
});
};
This all worked great... now that I need to resize the images... how can I replace the image in the form so that the resized one is posted and not the uploaded image?
window.uploadPhotos = function(url){
var resizedImage;
// Read in file
var file = event.target.files[0];
// Ensure it's an image
if(file.type.match(/image.*/)) {
console.log('An image has been loaded');
// Load the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resize the image
var canvas = document.createElement('canvas'),
max_size = 1200,
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
resizedImage = canvas.toDataURL('image/jpeg');
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
// TODO: Need some logic here to switch out which photo is being posted...
var data = new FormData($("form[id*='uploadImageForm']")[0]);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
... handle error...
}
}
});
};
I've thought about moving the file input out of the form and having a hidden input in the form that I set the value of to the value of the resized image... But I'm wondering if I can just replace the image that is already in the form.
Here is what I ended up doing and it worked great.
First I moved the file input outside of the form so that it is not submitted:
<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
<input id="name" value="#{name}" />
... a few more inputs ...
</form>
Then I changed the uploadPhotos function to handle only the resizing:
window.uploadPhotos = function(url){
// Read in file
var file = event.target.files[0];
// Ensure it's an image
if(file.type.match(/image.*/)) {
console.log('An image has been loaded');
// Load the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resize the image
var canvas = document.createElement('canvas'),
max_size = 544,// TODO : pull max size from a site config
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL('image/jpeg');
var resizedImage = dataURLToBlob(dataUrl);
$.event.trigger({
type: "imageResized",
blob: resizedImage,
url: dataUrl
});
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
};
As you can see I'm using canvas.toDataURL('image/jpeg'); to change the resized image into a dataUrl adn then I call the function dataURLToBlob(dataUrl); to turn the dataUrl into a blob that I can then append to the form. When the blob is created, I trigger a custom event. Here is the function to create the blob:
/* Utility function to convert a canvas to a BLOB */
var dataURLToBlob = function(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = parts[1];
return new Blob([raw], {type: contentType});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {type: contentType});
}
/* End Utility function to convert a canvas to a BLOB */
Finally, here is my event handler that takes the blob from the custom event, appends the form and then submits it.
/* Handle image resized events */
$(document).on("imageResized", function (event) {
var data = new FormData($("form[id*='uploadImageForm']")[0]);
if (event.blob && event.url) {
data.append('image_data', event.blob);
$.ajax({
url: event.url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
//handle errors...
}
});
}
});
if any interested I've made a typescript version:
interface IResizeImageOptions {
maxSize: number;
file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
const file = settings.file;
const maxSize = settings.maxSize;
const reader = new FileReader();
const image = new Image();
const canvas = document.createElement('canvas');
const dataURItoBlob = (dataURI: string) => {
const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
atob(dataURI.split(',')[1]) :
unescape(dataURI.split(',')[1]);
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
const max = bytes.length;
const ia = new Uint8Array(max);
for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
return new Blob([ia], {type:mime});
};
const resize = () => {
let width = image.width;
let height = image.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
let dataUrl = canvas.toDataURL('image/jpeg');
return dataURItoBlob(dataUrl);
};
return new Promise((ok, no) => {
if (!file.type.match(/image.*/)) {
no(new Error("Not an image"));
return;
}
reader.onload = (readerEvent: any) => {
image.onload = () => ok(resize());
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
})
};
and here's the javascript result:
var resizeImage = function (settings) {
var file = settings.file;
var maxSize = settings.maxSize;
var reader = new FileReader();
var image = new Image();
var canvas = document.createElement('canvas');
var dataURItoBlob = function (dataURI) {
var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
atob(dataURI.split(',')[1]) :
unescape(dataURI.split(',')[1]);
var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
var max = bytes.length;
var ia = new Uint8Array(max);
for (var i = 0; i < max; i++)
ia[i] = bytes.charCodeAt(i);
return new Blob([ia], { type: mime });
};
var resize = function () {
var width = image.width;
var height = image.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL('image/jpeg');
return dataURItoBlob(dataUrl);
};
return new Promise(function (ok, no) {
if (!file.type.match(/image.*/)) {
no(new Error("Not an image"));
return;
}
reader.onload = function (readerEvent) {
image.onload = function () { return ok(resize()); };
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
});
};
usage is like:
resizeImage({
file: $image.files[0],
maxSize: 500
}).then(function (resizedImage) {
console.log("upload resized image")
}).catch(function (err) {
console.error(err);
});
or (async/await):
const config = {
file: $image.files[0],
maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")
If some of you, like me, encounter orientation problems I have combined the solutions here with a exif orientation fix
https://gist.github.com/SagiMedina/f00a57de4e211456225d3114fd10b0d0
I have made my own version without actually using the file reader. Instead I use createObjectUrl which is supported by majority of modern browsers.
/**
* Function scaling an image from a file input to specified dimensions
* If the specified dimensions are not proportional to image dimensions the output image will be cropped at center
*
* #param file {File} Input file of a form
* #param dimensions {{width: number, height: number}} Dimenstions of the output image
* #returns {Promise<Blob | null>} Promise resolving to a scale image or a null if provided an invalid file type
*/
export async function scaleImageBeforeUpload(file: File, dimensions: {width: number, height: number}): Promise<Blob | null> {
// ensure the file is an image
if (!file.type.match(/image.*/)) return null;
const image = new Image();
image.src = URL.createObjectURL(file);
await new Promise<Event>((res) => image.onload = res);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d", {alpha: true});
canvas.width = dimensions.width;
canvas.height = dimensions.height;
if (image.height <= image.width) {
const scaleProportions = canvas.height / image.height;
const scaledWidth = scaleProportions * image.width;
context.drawImage(image, (canvas.width - scaledWidth)/2, 0, scaledWidth, canvas.height);
}
else {
const scaleProportions = canvas.width / image.width;
const scaledHeight = scaleProportions * image.height;
context.drawImage(image, 0, (canvas.height - scaledHeight)/2, canvas.width, scaledHeight);
}
return new Promise((res) => canvas.toBlob(res));
}
In 2022 we have some new APIs available to us. This is the solution I came up with. We don't have to mess with FileReader API or image onload callback.
The following code accepts a file object or a Blob object and outputs a blob of a cropped, centered, resized image, and also converts it to webp.
export default async (file, size) => {
size ??= 256
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = size
canvas.height = size
const bitmap = await createImageBitmap(file)
const { width, height } = bitmap
const ratio = Math.max(size / width, size / height)
const x = (size - (width * ratio)) / 2
const y = (size - (height * ratio)) / 2
ctx.drawImage(bitmap, 0, 0, width, height, x, y, width * ratio, height * ratio)
return new Promise(resolve => {
canvas.toBlob(blob => {
resolve(blob)
}, 'image/webp', 1)
})
}
I have the following form:
<form style="margin:5px;" id="submit-image" method="post" action="/site/recognize" enctype="multipart/form-data">
<input name="photo" id="upload-image" type="file" accept="image/*;capture=camera" >
<input name="photo-resized" id="upload-image-resized" type="hidden">
</form>
Then I have the following javascript:
<script>
var myInput = document.getElementById('upload-image');
function sendPic() {
//var file = myInput.files[0];
// functionality to rezise image BEFORE upload needs to go here
// from an input element
var filesToUpload = document.getElementById("upload-image").files;
var file = filesToUpload[0];
console.log(file);
var canvas = document.createElement('canvas');
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {img.src = e.target.result}
reader.readAsDataURL(file);
console.log(img); // This still outputs a proper image
var MAX_WIDTH = 1000;
var MAX_HEIGHT = 1000;
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");
var dataurl;
img.onload = function () {
canvas.getContext("2d").drawImage(img, 0, 0, width, height);
dataurl = canvas.toDataURL();
console.log(dataurl); // Somehow this STILL outputs an empty dataurl, need to find out why
};
document.getElementById('upload-image-resized').value= dataurl;
document.getElementById('submit-image').submit();
var loader = document.getElementsByClassName('ui-loader');
loader[0].style.display = "block";
loader[0].style.opacity = 0.7;
}
myInput.addEventListener('change', sendPic, false);
</script>
As you can see there I have several console.log() statements to help me understand where things are going wrong. I've put a comment there saying "// Somehow this outputs an empty dataurl, need to find out why". That's the point where I'm lost.
I don't understand why the dataurl variable is empty. It just contains "data: , " or something like that.
Any help appreciated.
I have found a few different posts and even questions on stackoverflow answering this question. I am basically implementing this same thing as this post.
So here is my issue. When I upload the photo, I also need to submit the rest of the form. Here is my html:
<form id="uploadImageForm" enctype="multipart/form-data">
<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<input id="name" value="#{name}" />
... a few more inputs ...
</form>
Previously, I did not need to resize the image, so my javascript looked like this:
window.uploadPhotos = function(url){
var data = new FormData($("form[id*='uploadImageForm']")[0]);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
... handle error...
}
}
});
};
This all worked great... now that I need to resize the images... how can I replace the image in the form so that the resized one is posted and not the uploaded image?
window.uploadPhotos = function(url){
var resizedImage;
// Read in file
var file = event.target.files[0];
// Ensure it's an image
if(file.type.match(/image.*/)) {
console.log('An image has been loaded');
// Load the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resize the image
var canvas = document.createElement('canvas'),
max_size = 1200,
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
resizedImage = canvas.toDataURL('image/jpeg');
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
// TODO: Need some logic here to switch out which photo is being posted...
var data = new FormData($("form[id*='uploadImageForm']")[0]);
$.ajax({
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
... handle error...
}
}
});
};
I've thought about moving the file input out of the form and having a hidden input in the form that I set the value of to the value of the resized image... But I'm wondering if I can just replace the image that is already in the form.
Here is what I ended up doing and it worked great.
First I moved the file input outside of the form so that it is not submitted:
<input name="imagefile[]" type="file" id="takePictureField" accept="image/*" onchange="uploadPhotos(\'#{imageUploadUrl}\')" />
<form id="uploadImageForm" enctype="multipart/form-data">
<input id="name" value="#{name}" />
... a few more inputs ...
</form>
Then I changed the uploadPhotos function to handle only the resizing:
window.uploadPhotos = function(url){
// Read in file
var file = event.target.files[0];
// Ensure it's an image
if(file.type.match(/image.*/)) {
console.log('An image has been loaded');
// Load the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resize the image
var canvas = document.createElement('canvas'),
max_size = 544,// TODO : pull max size from a site config
width = image.width,
height = image.height;
if (width > height) {
if (width > max_size) {
height *= max_size / width;
width = max_size;
}
} else {
if (height > max_size) {
width *= max_size / height;
height = max_size;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL('image/jpeg');
var resizedImage = dataURLToBlob(dataUrl);
$.event.trigger({
type: "imageResized",
blob: resizedImage,
url: dataUrl
});
}
image.src = readerEvent.target.result;
}
reader.readAsDataURL(file);
}
};
As you can see I'm using canvas.toDataURL('image/jpeg'); to change the resized image into a dataUrl adn then I call the function dataURLToBlob(dataUrl); to turn the dataUrl into a blob that I can then append to the form. When the blob is created, I trigger a custom event. Here is the function to create the blob:
/* Utility function to convert a canvas to a BLOB */
var dataURLToBlob = function(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = parts[1];
return new Blob([raw], {type: contentType});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {type: contentType});
}
/* End Utility function to convert a canvas to a BLOB */
Finally, here is my event handler that takes the blob from the custom event, appends the form and then submits it.
/* Handle image resized events */
$(document).on("imageResized", function (event) {
var data = new FormData($("form[id*='uploadImageForm']")[0]);
if (event.blob && event.url) {
data.append('image_data', event.blob);
$.ajax({
url: event.url,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(data){
//handle errors...
}
});
}
});
if any interested I've made a typescript version:
interface IResizeImageOptions {
maxSize: number;
file: File;
}
const resizeImage = (settings: IResizeImageOptions) => {
const file = settings.file;
const maxSize = settings.maxSize;
const reader = new FileReader();
const image = new Image();
const canvas = document.createElement('canvas');
const dataURItoBlob = (dataURI: string) => {
const bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
atob(dataURI.split(',')[1]) :
unescape(dataURI.split(',')[1]);
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
const max = bytes.length;
const ia = new Uint8Array(max);
for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i);
return new Blob([ia], {type:mime});
};
const resize = () => {
let width = image.width;
let height = image.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
let dataUrl = canvas.toDataURL('image/jpeg');
return dataURItoBlob(dataUrl);
};
return new Promise((ok, no) => {
if (!file.type.match(/image.*/)) {
no(new Error("Not an image"));
return;
}
reader.onload = (readerEvent: any) => {
image.onload = () => ok(resize());
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
})
};
and here's the javascript result:
var resizeImage = function (settings) {
var file = settings.file;
var maxSize = settings.maxSize;
var reader = new FileReader();
var image = new Image();
var canvas = document.createElement('canvas');
var dataURItoBlob = function (dataURI) {
var bytes = dataURI.split(',')[0].indexOf('base64') >= 0 ?
atob(dataURI.split(',')[1]) :
unescape(dataURI.split(',')[1]);
var mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
var max = bytes.length;
var ia = new Uint8Array(max);
for (var i = 0; i < max; i++)
ia[i] = bytes.charCodeAt(i);
return new Blob([ia], { type: mime });
};
var resize = function () {
var width = image.width;
var height = image.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
var dataUrl = canvas.toDataURL('image/jpeg');
return dataURItoBlob(dataUrl);
};
return new Promise(function (ok, no) {
if (!file.type.match(/image.*/)) {
no(new Error("Not an image"));
return;
}
reader.onload = function (readerEvent) {
image.onload = function () { return ok(resize()); };
image.src = readerEvent.target.result;
};
reader.readAsDataURL(file);
});
};
usage is like:
resizeImage({
file: $image.files[0],
maxSize: 500
}).then(function (resizedImage) {
console.log("upload resized image")
}).catch(function (err) {
console.error(err);
});
or (async/await):
const config = {
file: $image.files[0],
maxSize: 500
};
const resizedImage = await resizeImage(config)
console.log("upload resized image")
If some of you, like me, encounter orientation problems I have combined the solutions here with a exif orientation fix
https://gist.github.com/SagiMedina/f00a57de4e211456225d3114fd10b0d0
I have made my own version without actually using the file reader. Instead I use createObjectUrl which is supported by majority of modern browsers.
/**
* Function scaling an image from a file input to specified dimensions
* If the specified dimensions are not proportional to image dimensions the output image will be cropped at center
*
* #param file {File} Input file of a form
* #param dimensions {{width: number, height: number}} Dimenstions of the output image
* #returns {Promise<Blob | null>} Promise resolving to a scale image or a null if provided an invalid file type
*/
export async function scaleImageBeforeUpload(file: File, dimensions: {width: number, height: number}): Promise<Blob | null> {
// ensure the file is an image
if (!file.type.match(/image.*/)) return null;
const image = new Image();
image.src = URL.createObjectURL(file);
await new Promise<Event>((res) => image.onload = res);
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d", {alpha: true});
canvas.width = dimensions.width;
canvas.height = dimensions.height;
if (image.height <= image.width) {
const scaleProportions = canvas.height / image.height;
const scaledWidth = scaleProportions * image.width;
context.drawImage(image, (canvas.width - scaledWidth)/2, 0, scaledWidth, canvas.height);
}
else {
const scaleProportions = canvas.width / image.width;
const scaledHeight = scaleProportions * image.height;
context.drawImage(image, 0, (canvas.height - scaledHeight)/2, canvas.width, scaledHeight);
}
return new Promise((res) => canvas.toBlob(res));
}
In 2022 we have some new APIs available to us. This is the solution I came up with. We don't have to mess with FileReader API or image onload callback.
The following code accepts a file object or a Blob object and outputs a blob of a cropped, centered, resized image, and also converts it to webp.
export default async (file, size) => {
size ??= 256
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = size
canvas.height = size
const bitmap = await createImageBitmap(file)
const { width, height } = bitmap
const ratio = Math.max(size / width, size / height)
const x = (size - (width * ratio)) / 2
const y = (size - (height * ratio)) / 2
ctx.drawImage(bitmap, 0, 0, width, height, x, y, width * ratio, height * ratio)
return new Promise(resolve => {
canvas.toBlob(blob => {
resolve(blob)
}, 'image/webp', 1)
})
}