Compress and upload multiple images [duplicate] - javascript

TL;DR;
Is there a way to compress an image (mostly jpeg, png and gif) directly browser-side, before uploading it ? I'm pretty sure JavaScript can do this, but I can't find a way to achieve it.
Here's the full scenario I would like to implement:
the user goes to my website, and choose an image via an input type="file" element,
this image is retrieved via JavaScript, we do some verification such as correct file format, maximum file size etc,
if every thing is OK, a preview of the image is displayed on the page,
the user can do some basic operations such as rotate the image by 90°/-90°, crop it following a pre-defined ratio, etc, or the user can upload another image and return to step 1,
when the user is satisfied, the edited image is then compressed and "saved" locally (not saved to a file, but in the browser memory/page),-
the user fill a form with data like name, age etc,
the user click on the "Finish" button, then the form containing datas + compressed image is sent to the server (without AJAX),
The full process up to the last step should be done client side, and should be compatible on latest Chrome and Firefox, Safari 5+ and IE 8+. If possible, only JavaScript should be used (but I'm pretty sure this is not possible).
I've not code anything right now, but I've thought about it already. File reading locally is possible via File API, image previewing and editing could be done using Canvas element, but I can't find a way to do the image compression part.
According to html5please.com and caniuse.com, supporting those browser is quite hard (thanks to IE), but could be done using polyfill such as FlashCanvas and FileReader.
Actually, the goal is to reduce file size, so I see image compression as a solution. But, I know that uploaded images are going to be displayed on my website, every time at the same place, and I know the dimension of this display area (eg. 200x400). So, I could resize the image to fit those dimensions, thus reducing file size. I have no idea what would be the compression ratio for this technique.
What do you think ? Do you have any advice to tell me ? Do you know any way to compress an image browser-side in JavaScript ? Thanks for your replies.

In short:
Read the files using the HTML5 FileReader API with .readAsArrayBuffer
Create a Blob with the file data and get its url with window.URL.createObjectURL(blob)
Create new Image element and set it's src to the file blob url
Send the image to the canvas. The canvas size is set to desired output size
Get the scaled-down data back from canvas via canvas.toDataURL("image/jpeg",0.7) (set your own output format and quality)
Attach new hidden inputs to the original form and transfer the dataURI images basically as normal text
On backend, read the dataURI, decode from Base64, and save it
Source: code.

I see two things missing from the other answers:
canvas.toBlob (when available) is more performant than canvas.toDataURL, and also async.
the file -> image -> canvas -> file conversion loses EXIF data; in particular, data about image rotation commonly set by modern phones/tablets.
The following script deals with both points:
// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
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'}));
}
});
}
window.URL = window.URL || window.webkitURL;
// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
// Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
if (file.slice) {
file = file.slice(0, 131072);
} else if (file.webkitSlice) {
file = file.webkitSlice(0, 131072);
}
var reader = new FileReader();
reader.onload = function(e) {
var view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xFFD8) {
callback(-2);
return;
}
var length = view.byteLength, offset = 2;
while (offset < length) {
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1) {
if (view.getUint32(offset += 2, false) != 0x45786966) {
callback(-1);
return;
}
var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + (i * 12), little) == 0x0112) {
callback(view.getUint16(offset + (i * 12) + 8, little));
return;
}
}
else if ((marker & 0xFF00) != 0xFF00) break;
else offset += view.getUint16(offset, false);
}
callback(-1);
};
reader.readAsArrayBuffer(file);
}
// Derived from https://stackoverflow.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
var canvas = document.createElement('canvas');
if (orientation > 4) {
canvas.width = rawHeight;
canvas.height = rawWidth;
} else {
canvas.width = rawWidth;
canvas.height = rawHeight;
}
if (orientation > 1) {
console.log("EXIF orientation = " + orientation + ", rotating picture");
}
var ctx = canvas.getContext('2d');
switch (orientation) {
case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
}
ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
return canvas;
}
function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
if (file.size <= acceptFileSize) {
callback(file);
return;
}
var img = new Image();
img.onerror = function() {
URL.revokeObjectURL(this.src);
callback(file);
};
img.onload = function() {
URL.revokeObjectURL(this.src);
getExifOrientation(file, function(orientation) {
var w = img.width, h = img.height;
var scale = (orientation > 4 ?
Math.min(maxHeight / w, maxWidth / h, 1) :
Math.min(maxWidth / w, maxHeight / h, 1));
h = Math.round(h * scale);
w = Math.round(w * scale);
var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
canvas.toBlob(function(blob) {
console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
callback(blob);
}, 'image/jpeg', quality);
});
};
img.src = URL.createObjectURL(file);
}
Example usage:
inputfile.onchange = function() {
// If file size > 500kB, resize such that width <= 1000, quality = 0.9
reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
let body = new FormData();
body.set('file', blob, blob.name || "file.jpg");
fetch('/upload-image', {method: 'POST', body}).then(...);
});
};

#PsychoWoods' answer is good. I would like to offer my own solution. This Javascript function takes an image data URL and a width, scales it to the new width, and returns a new data URL.
// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
"use strict";
var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
// Provide default values
imageType = imageType || "image/jpeg";
imageArguments = imageArguments || 0.7;
// Create a temporary image so that we can compute the height of the downscaled image.
image = new Image();
image.src = dataUrl;
oldWidth = image.width;
oldHeight = image.height;
newHeight = Math.floor(oldHeight / oldWidth * newWidth)
// Create a temporary canvas to draw the downscaled image on.
canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the downscaled image on the canvas and return the new data URL.
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
newDataUrl = canvas.toDataURL(imageType, imageArguments);
return newDataUrl;
}
This code can be used anywhere you have a data URL and want a data URL for a downscaled image.

You can take a look at image-conversion,Try it here --> demo page

Try this Customizable Pure JS Sample - Compress over 90% :
<div id="root">
<p>Upload an image and see the result</p>
<input id="img-input" type="file" accept="image/*" style="display:block" />
</div>
<script>
const MAX_WIDTH = 320;
const MAX_HEIGHT = 180;
const MIME_TYPE = "image/jpeg";
const QUALITY = 0.7;
const input = document.getElementById("img-input");
input.onchange = function (ev) {
const file = ev.target.files[0]; // get the file
const blobURL = URL.createObjectURL(file);
const img = new Image();
img.src = blobURL;
img.onerror = function () {
URL.revokeObjectURL(this.src);
// Handle the failure properly
console.log("Cannot load image");
};
img.onload = function () {
URL.revokeObjectURL(this.src);
const [newWidth, newHeight] = calculateSize(img, MAX_WIDTH, MAX_HEIGHT);
const canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, newWidth, newHeight);
canvas.toBlob(
(blob) => {
// Handle the compressed image. es. upload or save in local state
displayInfo('Original file', file);
displayInfo('Compressed file', blob);
},
MIME_TYPE,
QUALITY
);
document.getElementById("root").append(canvas);
};
};
function calculateSize(img, maxWidth, maxHeight) {
let width = img.width;
let height = img.height;
// calculate the width and height, constraining the proportions
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else {
if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
}
return [width, height];
}
// Utility functions for demo purpose
function displayInfo(label, file) {
const p = document.createElement('p');
p.innerText = `${label} - ${readableBytes(file.size)}`;
document.getElementById('root').append(p);
}
function readableBytes(bytes) {
const i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
}
</script>

I find that there's simpler solution compared to the accepted answer.
Read the files using the HTML5 FileReader API with .readAsArrayBuffer
Create a Blob with the file data and get its url with window.URL.createObjectURL(blob)
Create new Image element and set it's src to the file blob url
Send the image to the canvas. The canvas size is set to desired output size
Get the scaled-down data back from canvas via canvas.toDataURL("image/jpeg",0.7) (set your own output format and quality)
Attach new hidden inputs to the original form and transfer the dataURI images basically as normal text
On backend, read the dataURI, decode from Base64, and save it
As per your question:
Is there a way to compress an image (mostly jpeg, png and gif)
directly browser-side, before uploading it
My solution:
Create a blob with the file directly with URL.createObjectURL(inputFileElement.files[0]).
Same as accepted answer.
Same as accepted answer. Worth mentioning that, canvas size is necessary and use img.width and img.height to set canvas.width and canvas.height. Not img.clientWidth.
Get the scale-down image by canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5). Setting 'image/jpg' has no effect. image/png is also supported. Make a new File object inside the callbackfunction body with let compressedImageBlob = new File([blob]).
Add new hidden inputs or send via javascript . Server doesn't have to decode anything.
Check https://javascript.info/binary for all information. I came up the solution after reading this chapter.
Code:
<!DOCTYPE html>
<html>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="fileToUpload" id="fileToUpload" multiple>
<input type="submit" value="Upload Image" name="submit">
</form>
</body>
</html>
This code looks far less scary than the other answers..
Update:
One has to put everything inside img.onload. Otherwise canvas will not be able to get the image's width and height correctly as the time canvas is assigned.
function upload(){
var f = fileToUpload.files[0];
var fileName = f.name.split('.')[0];
var img = new Image();
img.src = URL.createObjectURL(f);
img.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
canvas.toBlob(function(blob){
console.info(blob.size);
var f2 = new File([blob], fileName + ".jpeg");
var xhr = new XMLHttpRequest();
var form = new FormData();
form.append("fileToUpload", f2);
xhr.open("POST", "upload.php");
xhr.send(form);
}, 'image/jpeg', 0.5);
}
}
3.4MB .png file compression test with image/jpeg argument set.
|0.9| 777KB |
|0.8| 383KB |
|0.7| 301KB |
|0.6| 251KB |
|0.5| 219kB |

I had an issue with the downscaleImage() function posted above by #daniel-allen-langdon in that the image.width and image.height properties are not available immediately because the image load is asynchronous.
Please see updated TypeScript example below that takes this into account, uses async functions, and resizes the image based on the longest dimension rather than just the width
function getImage(dataUrl: string): Promise<HTMLImageElement>
{
return new Promise((resolve, reject) => {
const image = new Image();
image.src = dataUrl;
image.onload = () => {
resolve(image);
};
image.onerror = (el: any, err: ErrorEvent) => {
reject(err.error);
};
});
}
export async function downscaleImage(
dataUrl: string,
imageType: string, // e.g. 'image/jpeg'
resolution: number, // max width/height in pixels
quality: number // e.g. 0.9 = 90% quality
): Promise<string> {
// Create a temporary image so that we can compute the height of the image.
const image = await getImage(dataUrl);
const oldWidth = image.naturalWidth;
const oldHeight = image.naturalHeight;
console.log('dims', oldWidth, oldHeight);
const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
console.log('longest dim', longestDimension, currentRes);
if (currentRes > resolution) {
console.log('need to resize...');
// Calculate new dimensions
const newSize = longestDimension == 'width'
? Math.floor(oldHeight / oldWidth * resolution)
: Math.floor(oldWidth / oldHeight * resolution);
const newWidth = longestDimension == 'width' ? resolution : newSize;
const newHeight = longestDimension == 'height' ? resolution : newSize;
console.log('new width / height', newWidth, newHeight);
// Create a temporary canvas to draw the downscaled image on.
const canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the downscaled image on the canvas and return the new data URL.
const ctx = canvas.getContext('2d')!;
ctx.drawImage(image, 0, 0, newWidth, newHeight);
const newDataUrl = canvas.toDataURL(imageType, quality);
return newDataUrl;
}
else {
return dataUrl;
}
}

For Moderm browser use createImageBitmap() instead of img.onload
async function compressImage(blobImg, percent) {
let bitmap = await createImageBitmap(blobImg);
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0);
let dataUrl = canvas.toDataURL("image/jpeg", percent/100);
return dataUrl;
}
inputImg.addEventListener('change', async(e) => {
let img = e.target.files[0];
console.log('File Name: ', img.name)
console.log('Original Size: ', img.size.toLocaleString())
let imgCompressed = await compressImage(img, 75) // set to 75%
let compSize = atob(imgCompressed.split(",")[1]).length;
console.log('Compressed Size: ', compSize.toLocaleString())
//console.log(imgCompressed)
})
<input type="file" id="inputImg">

Edit: As per the Mr Me comment on this answer, it looks like compression is now available for JPG/WebP formats ( see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ).
As far as I know, you cannot compress images using canvas, instead, you can resize it. Using canvas.toDataURL will not let you choose the compression ratio to use. You can take a look at canimage that does exactly what you want : https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
In fact, it's often sufficient to just resize the image to decrease it's size but if you want to go further, you'll have to use newly introduced method file.readAsArrayBuffer to get a buffer containing the image data.
Then, just use a DataView to read it's content according to the image format specification (http://en.wikipedia.org/wiki/JPEG or http://en.wikipedia.org/wiki/Portable_Network_Graphics).
It'll be hard to deal with image data compression, but it is worse a try. On the other hand, you can try to delete the PNG headers or the JPEG exif data to make your image smaller, it should be easier to do so.
You'll have to create another DataWiew on another buffer and fill it with the filtered image content. Then, you'll just have to encode you're image content to DataURI using window.btoa.
Let me know if you implement something similar, will be interesting to go through the code.

Compressor.js
https://github.com/fengyuanchen/compressorjs
import axios from 'axios';
import Compressor from 'compressorjs';
document.getElementById('file').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) {
return;
}
new Compressor(file, {
quality: 0.6,
// The compression process is asynchronous,
// which means you have to access the `result` in the `success` hook function.
success(result) {
const formData = new FormData();
// The third parameter is required for server
formData.append('file', result, result.name);
// Send the compressed image file to server with XMLHttpRequest.
axios.post('/path/to/upload', formData).then(() => {
console.log('Upload success');
});
},
error(err) {
console.log(err.message);
},
});
});

I used the following package:
https://www.npmjs.com/package/browser-image-compression
npm install browser-image-compression
or
yarn add browser-image-compression
Then just following the docs:
import imageCompression from 'browser-image-compression';
const options = {
maxSizeMB: 0.5, // pretty much self-explanatory
maxWidthOrHeight: 500, // apparently px
}
imageCompression(file, options)
.then(function(compressedFile) {
console.log(
"compressedFile instanceof Blob",
compressedFile instanceof Blob
); // true
console.log(
`compressedFile size ${compressedFile.size /
1024 /
1024} MB`
); // smaller than maxSizeMB
return uploader(compressedFile); // code to actual upload, in my case uploader() is a function to upload to Firebase storage.
})
Just in case if you were curios about the uploader(), here's the code of it:
import { initializeApp } from "firebase/app";
const firebaseConfig = {
// your config
};
initializeApp(firebaseConfig);
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
const storage = getStorage();
const sRef = ref(storage);
const uploader = async (file) => {
/* uploads to root */
// const imageRef = ref(sRef, file.name);
// console.log(imageRef);
// await uploadBytes(imageRef, file).then((snapshot) => {
// console.log("Uploaded a blob or file!", snapshot);
// });
/* upload to folder 'techs/' */
const folderRef = ref(sRef, "techs/" + file.name);
await uploadBytes(folderRef, file);
// get URL
const url = await getDownloadURL(ref(storage, folderRef));
console.log("url: ", url);
return url;
};

You can compress an image using the HTML <canvas> element:
function compressImage(imgToCompress, resizingFactor, quality) {
// resizing the image
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const originalWidth = imgToCompress.width;
const originalHeight = imgToCompress.height;
const canvasWidth = originalWidth * resizingFactor;
const canvasHeight = originalHeight * resizingFactor;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
context.drawImage(
imgToCompress,
0,
0,
originalWidth * resizingFactor,
originalHeight * resizingFactor
);
// reducing the quality of the image
canvas.toBlob(
(blob) => {
if (blob) {
// showing the compressed image
resizedImage.src = URL.createObjectURL(resizedImageBlob);
}
},
"image/jpeg",
quality
);
}
See this blog post for an in depth explanation: https://img.ly/blog/how-to-compress-an-image-before-uploading-it-in-javascript/

i improved the function a head to be this :
var minifyImg = function(dataUrl,newWidth,imageType="image/jpeg",resolve,imageArguments=0.7){
var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
(new Promise(function(resolve){
image = new Image(); image.src = dataUrl;
log(image);
resolve('Done : ');
})).then((d)=>{
oldWidth = image.width; oldHeight = image.height;
log([oldWidth,oldHeight]);
newHeight = Math.floor(oldHeight / oldWidth * newWidth);
log(d+' '+newHeight);
canvas = document.createElement("canvas");
canvas.width = newWidth; canvas.height = newHeight;
log(canvas);
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
//log(ctx);
newDataUrl = canvas.toDataURL(imageType, imageArguments);
resolve(newDataUrl);
});
};
the use of it :
minifyImg(<--DATAURL_HERE-->,<--new width-->,<--type like image/jpeg-->,(data)=>{
console.log(data); // the new DATAURL
});
enjoy ;)

For JPG Image compression you can use the best compression technique called JIC
(Javascript Image Compression)This will definitely help you -->https://github.com/brunobar79/J-I-C

Related

Compress and upload an image but the image is only green

I using Reactjs to build my app,I use https://github.com/alextanhongpin/compress.js library to compress before upload images, but sometimes the image uploaded to the server returns the image only in green color. And this happens only very rarely with some guests (I thought it was the client's device being too old so it was, but I tried devices on browserstack but no such case.
)
I also don't know if the problem is caused by image compression or is it really the customer's device
here is the code
import Compress from "compress.js";
export async function resizeImageFn(file, maxHeight = 600, maxWidth = 800) {
const compress = new Compress();
const resizedImage = await compress.compress([file], {
size: 1, // the max size in MB, defaults to 2MB
quality: 1, // the quality of the image, max is 1,
maxWidth: maxWidth, // the max width of the output image, defaults to 1920px
maxHeight: maxHeight, // the max height of the output image, defaults to 1920px
resize: true, // defaults to true, set false if you do not want to resize the image width and height
});
const img = resizedImage[0];
const base64str = img.data;
const file2 = Compress.convertBase64ToFile(base64str, img.ext);
// const resizedFiile = dataURLtoFile(base64str, file?.name);
return file2;
}
and then I switched to image compression using canvas like this :
const resizeImgFnByCanvas = async (
file_png,
minHeight = 1200,
minWidth = 2000,
maxSize = 1.2,
ext = "jpg"
) => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.readAsDataURL(file_png);
reader.onload = function (e) {
let image = document.createElement("img");
image.src = e.target.result;
image.onload = async function () {
let canvas = document.createElement("canvas"),
width = image.width,
height = image.height;
let resizedFile = null;
while (true) {
height = height * 0.95;
width = width * 0.95;
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
let dataUrl = canvas.toDataURL(`image/${ext}`);
let resizedImage = dataURLToBlob(dataUrl);
resizedFile = new File(
[resizedImage],
file_png?.name || `resizedPNG.${ext}`,
{
type: `image/${ext}`,
}
);
if (
resizedFile.size <= maxSize * 1024 * 1024 &&
height < minHeight &&
width < minWidth
) {
break;
}
}
resolve(resizedFile);
};
};
reader.onerror = (e) => {
reject(new Error("Cannot resize image"));
};
});
};
export default resizeImgFnByCanvas;
Both methods work normally, but sometimes one of my customers has a case of green image uploading look like.
Note : I using Nestjs for BE to upload with multer
I want my photos to always be uploaded in the best way, can someone tell me the cause of it or give me a library, another solution, thanks a lot.

Upload compressed image file from client-side using JavaScript

I am trying to compress images on client side using JavaScript on some low bandwidth devices and I'm currently stuck in a limbo using the HTML5 File API. I'm new to this, please bear with me if I'm missing something important.
I have some input tags which should ideally open the mobile camera, capture single image, compress and send files to the backend. Although this can be done with a single input field with multiple uploads enabled but I need the multiple image fields to segregate images based on some categories.
Here's the input boxes:
<input type="file" name="file1" id="file1" capture="camera" accept="image/*">
<input type="file" name="file2" id="file2" capture="camera" accept="image/*">...
Here's the image compression logic:
// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = function(id, maxSize){
var file = document.getElementById(id).files[0];
// Ensuring it's an image
if(file.type.match(/image.*/)) {
// Loading the image
var reader = new FileReader();
reader.onload = function (readerEvent) {
var image = new Image();
image.onload = function (imageEvent) {
// Resizing the image and keeping its aspect ratio
var canvas = document.createElement("canvas"),
max_size = maxSize,
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);
}
};
// 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});
}
// Handling image resized events
$(document).on("imageResized", function (event) {
if (event.blob && event.url) {
document.getElementById('file1').files[0] = event.url; // --> Tried this, did not work
document.getElementById('file1').files[0].value = (URL || webkitURL).createObjectURL(event.blob); // --> Tried doing this looking at some other answers but did not work
console.log(document.getElementById('file1').files[0]); // Original file is loading fine
console.log(event.url); // Image compression is working correctly and producing the base64 data
}
});
$(window).on("load", function() {
// Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
$("#file1").on("click touchstart" , function(){
$(this).val("");
});
// Action triggers when user has selected any file
$("#file1").change(function(e) {
resizePhotos("file1", 1024)
});
});
In PHP script, I'd usually try to catch files from the POST request like:
$file1 = $_FILES["file1"]["tmp_name"];
$file2 = $_FILES["file2"]["tmp_name"];
...
But this doesn't work because it looks for the original user selected file at a tmp directory (e.g. the actual temporary file in my case is C:\xampp\tmp\php25CB.tmp )
One thing I've tried is put the input fields outside of the form tags, enabled the click behaviour using a button and created new input field with the modified data within the form like:
var newinput = document.createElement("input");
newinput.type = 'file';
newinput.name = 'file1';
newinput.files[0] = event.url;
document.getElementById('parentdiv').appendChild(newinput);
Needless to say, this had no effect and the PHP script could not identify any file.
Please guide me and suggest any changes required in the JavaScript/PHP script so I can accept the modified file and not the original user uploaded file from the input field.
You can only change a file input value with another list here is how: https://stackoverflow.com/a/52079109/1008999 (also in the example)
Using the FileReader is a waste of time, CPU, Encoding & decoding and RAM...use URL.createObjectURL instead
Don't use canvas.toDataURL... use canvas.toBlob instead
Canvas have bad compression, read earlier comment and see the jsfiddle proff...If you insist on using canvas to try and squeeze the size down then
First try to see if the image is in a reasonable size first
Compare if the pre existing image file.size is smaller than what the canvas.toBlob provides and choose if you want the old or the new one instead.
If resizing the image isn't enough have a look at this solution that change the quality until a desired file size & image aspect have been meet.
Without any testing, this is how i would have refactor your code too:
/**
* #params {File[]} files Array of files to add to the FileList
* #return {FileList}
*/
function fileListItems (files) {
var b = new ClipboardEvent('').clipboardData || new DataTransfer()
for (var i = 0, len = files.length; i<len; i++) b.items.add(files[i])
return b.files
}
// Takes upload element id ("file1") and a maxSize to resize, ideally on a change event
window.resizePhotos = async function resizePhotos (input, maxSize) {
const file = input.files
if (!file || !file.type.match(/image.*/)) return
const image = new Image()
const canvas = document.createElement('canvas')
const max_size = maxSize
image.src = URL.createObjectURL(file)
await image.decode()
let width = image.width
let height = image.height
// Resizing the image and keeping its aspect ratio
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)
const resizedImage = await new Promise(rs => canvas.toBlob(rs, 'image/jpeg', 1))
// PS: You might have to disable the event listener as this might case a change event
// and triggering this function over and over in a loop otherwise
input.files = fileListItems([
new File([resizedImage], file.name, { type: resizedImage.type })
])
}
jQuery($ => {
// Resets the value to when navigating away from the page and choosing to upload the same file (extra feature)
$('#file1').on('click touchstart' , function(){
$(this).val('')
})
// Action triggers when user has selected any file
$('#file1').change(function(e) {
resizePhotos(this, 1024)
})
})

Resizing Image on client side and save to server [duplicate]

TL;DR;
Is there a way to compress an image (mostly jpeg, png and gif) directly browser-side, before uploading it ? I'm pretty sure JavaScript can do this, but I can't find a way to achieve it.
Here's the full scenario I would like to implement:
the user goes to my website, and choose an image via an input type="file" element,
this image is retrieved via JavaScript, we do some verification such as correct file format, maximum file size etc,
if every thing is OK, a preview of the image is displayed on the page,
the user can do some basic operations such as rotate the image by 90°/-90°, crop it following a pre-defined ratio, etc, or the user can upload another image and return to step 1,
when the user is satisfied, the edited image is then compressed and "saved" locally (not saved to a file, but in the browser memory/page),-
the user fill a form with data like name, age etc,
the user click on the "Finish" button, then the form containing datas + compressed image is sent to the server (without AJAX),
The full process up to the last step should be done client side, and should be compatible on latest Chrome and Firefox, Safari 5+ and IE 8+. If possible, only JavaScript should be used (but I'm pretty sure this is not possible).
I've not code anything right now, but I've thought about it already. File reading locally is possible via File API, image previewing and editing could be done using Canvas element, but I can't find a way to do the image compression part.
According to html5please.com and caniuse.com, supporting those browser is quite hard (thanks to IE), but could be done using polyfill such as FlashCanvas and FileReader.
Actually, the goal is to reduce file size, so I see image compression as a solution. But, I know that uploaded images are going to be displayed on my website, every time at the same place, and I know the dimension of this display area (eg. 200x400). So, I could resize the image to fit those dimensions, thus reducing file size. I have no idea what would be the compression ratio for this technique.
What do you think ? Do you have any advice to tell me ? Do you know any way to compress an image browser-side in JavaScript ? Thanks for your replies.
In short:
Read the files using the HTML5 FileReader API with .readAsArrayBuffer
Create a Blob with the file data and get its url with window.URL.createObjectURL(blob)
Create new Image element and set it's src to the file blob url
Send the image to the canvas. The canvas size is set to desired output size
Get the scaled-down data back from canvas via canvas.toDataURL("image/jpeg",0.7) (set your own output format and quality)
Attach new hidden inputs to the original form and transfer the dataURI images basically as normal text
On backend, read the dataURI, decode from Base64, and save it
Source: code.
I see two things missing from the other answers:
canvas.toBlob (when available) is more performant than canvas.toDataURL, and also async.
the file -> image -> canvas -> file conversion loses EXIF data; in particular, data about image rotation commonly set by modern phones/tablets.
The following script deals with both points:
// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari:
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'}));
}
});
}
window.URL = window.URL || window.webkitURL;
// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0
// -2 = not jpeg, -1 = no data, 1..8 = orientations
function getExifOrientation(file, callback) {
// Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/:
if (file.slice) {
file = file.slice(0, 131072);
} else if (file.webkitSlice) {
file = file.webkitSlice(0, 131072);
}
var reader = new FileReader();
reader.onload = function(e) {
var view = new DataView(e.target.result);
if (view.getUint16(0, false) != 0xFFD8) {
callback(-2);
return;
}
var length = view.byteLength, offset = 2;
while (offset < length) {
var marker = view.getUint16(offset, false);
offset += 2;
if (marker == 0xFFE1) {
if (view.getUint32(offset += 2, false) != 0x45786966) {
callback(-1);
return;
}
var little = view.getUint16(offset += 6, false) == 0x4949;
offset += view.getUint32(offset + 4, little);
var tags = view.getUint16(offset, little);
offset += 2;
for (var i = 0; i < tags; i++)
if (view.getUint16(offset + (i * 12), little) == 0x0112) {
callback(view.getUint16(offset + (i * 12) + 8, little));
return;
}
}
else if ((marker & 0xFF00) != 0xFF00) break;
else offset += view.getUint16(offset, false);
}
callback(-1);
};
reader.readAsArrayBuffer(file);
}
// Derived from https://stackoverflow.com/a/40867559, cc by-sa
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) {
var canvas = document.createElement('canvas');
if (orientation > 4) {
canvas.width = rawHeight;
canvas.height = rawWidth;
} else {
canvas.width = rawWidth;
canvas.height = rawHeight;
}
if (orientation > 1) {
console.log("EXIF orientation = " + orientation + ", rotating picture");
}
var ctx = canvas.getContext('2d');
switch (orientation) {
case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break;
case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break;
case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break;
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break;
case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break;
case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break;
}
ctx.drawImage(img, 0, 0, rawWidth, rawHeight);
return canvas;
}
function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) {
if (file.size <= acceptFileSize) {
callback(file);
return;
}
var img = new Image();
img.onerror = function() {
URL.revokeObjectURL(this.src);
callback(file);
};
img.onload = function() {
URL.revokeObjectURL(this.src);
getExifOrientation(file, function(orientation) {
var w = img.width, h = img.height;
var scale = (orientation > 4 ?
Math.min(maxHeight / w, maxWidth / h, 1) :
Math.min(maxWidth / w, maxHeight / h, 1));
h = Math.round(h * scale);
w = Math.round(w * scale);
var canvas = imgToCanvasWithOrientation(img, w, h, orientation);
canvas.toBlob(function(blob) {
console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB");
callback(blob);
}, 'image/jpeg', quality);
});
};
img.src = URL.createObjectURL(file);
}
Example usage:
inputfile.onchange = function() {
// If file size > 500kB, resize such that width <= 1000, quality = 0.9
reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => {
let body = new FormData();
body.set('file', blob, blob.name || "file.jpg");
fetch('/upload-image', {method: 'POST', body}).then(...);
});
};
#PsychoWoods' answer is good. I would like to offer my own solution. This Javascript function takes an image data URL and a width, scales it to the new width, and returns a new data URL.
// Take an image URL, downscale it to the given width, and return a new image URL.
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) {
"use strict";
var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
// Provide default values
imageType = imageType || "image/jpeg";
imageArguments = imageArguments || 0.7;
// Create a temporary image so that we can compute the height of the downscaled image.
image = new Image();
image.src = dataUrl;
oldWidth = image.width;
oldHeight = image.height;
newHeight = Math.floor(oldHeight / oldWidth * newWidth)
// Create a temporary canvas to draw the downscaled image on.
canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the downscaled image on the canvas and return the new data URL.
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
newDataUrl = canvas.toDataURL(imageType, imageArguments);
return newDataUrl;
}
This code can be used anywhere you have a data URL and want a data URL for a downscaled image.
You can take a look at image-conversion,Try it here --> demo page
Try this Customizable Pure JS Sample - Compress over 90% :
<div id="root">
<p>Upload an image and see the result</p>
<input id="img-input" type="file" accept="image/*" style="display:block" />
</div>
<script>
const MAX_WIDTH = 320;
const MAX_HEIGHT = 180;
const MIME_TYPE = "image/jpeg";
const QUALITY = 0.7;
const input = document.getElementById("img-input");
input.onchange = function (ev) {
const file = ev.target.files[0]; // get the file
const blobURL = URL.createObjectURL(file);
const img = new Image();
img.src = blobURL;
img.onerror = function () {
URL.revokeObjectURL(this.src);
// Handle the failure properly
console.log("Cannot load image");
};
img.onload = function () {
URL.revokeObjectURL(this.src);
const [newWidth, newHeight] = calculateSize(img, MAX_WIDTH, MAX_HEIGHT);
const canvas = document.createElement("canvas");
canvas.width = newWidth;
canvas.height = newHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, newWidth, newHeight);
canvas.toBlob(
(blob) => {
// Handle the compressed image. es. upload or save in local state
displayInfo('Original file', file);
displayInfo('Compressed file', blob);
},
MIME_TYPE,
QUALITY
);
document.getElementById("root").append(canvas);
};
};
function calculateSize(img, maxWidth, maxHeight) {
let width = img.width;
let height = img.height;
// calculate the width and height, constraining the proportions
if (width > height) {
if (width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
} else {
if (height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
}
return [width, height];
}
// Utility functions for demo purpose
function displayInfo(label, file) {
const p = document.createElement('p');
p.innerText = `${label} - ${readableBytes(file.size)}`;
document.getElementById('root').append(p);
}
function readableBytes(bytes) {
const i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
}
</script>
I find that there's simpler solution compared to the accepted answer.
Read the files using the HTML5 FileReader API with .readAsArrayBuffer
Create a Blob with the file data and get its url with window.URL.createObjectURL(blob)
Create new Image element and set it's src to the file blob url
Send the image to the canvas. The canvas size is set to desired output size
Get the scaled-down data back from canvas via canvas.toDataURL("image/jpeg",0.7) (set your own output format and quality)
Attach new hidden inputs to the original form and transfer the dataURI images basically as normal text
On backend, read the dataURI, decode from Base64, and save it
As per your question:
Is there a way to compress an image (mostly jpeg, png and gif)
directly browser-side, before uploading it
My solution:
Create a blob with the file directly with URL.createObjectURL(inputFileElement.files[0]).
Same as accepted answer.
Same as accepted answer. Worth mentioning that, canvas size is necessary and use img.width and img.height to set canvas.width and canvas.height. Not img.clientWidth.
Get the scale-down image by canvas.toBlob(callbackfunction(blob){}, 'image/jpeg', 0.5). Setting 'image/jpg' has no effect. image/png is also supported. Make a new File object inside the callbackfunction body with let compressedImageBlob = new File([blob]).
Add new hidden inputs or send via javascript . Server doesn't have to decode anything.
Check https://javascript.info/binary for all information. I came up the solution after reading this chapter.
Code:
<!DOCTYPE html>
<html>
<body>
<form action="upload.php" method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="fileToUpload" id="fileToUpload" multiple>
<input type="submit" value="Upload Image" name="submit">
</form>
</body>
</html>
This code looks far less scary than the other answers..
Update:
One has to put everything inside img.onload. Otherwise canvas will not be able to get the image's width and height correctly as the time canvas is assigned.
function upload(){
var f = fileToUpload.files[0];
var fileName = f.name.split('.')[0];
var img = new Image();
img.src = URL.createObjectURL(f);
img.onload = function(){
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
canvas.toBlob(function(blob){
console.info(blob.size);
var f2 = new File([blob], fileName + ".jpeg");
var xhr = new XMLHttpRequest();
var form = new FormData();
form.append("fileToUpload", f2);
xhr.open("POST", "upload.php");
xhr.send(form);
}, 'image/jpeg', 0.5);
}
}
3.4MB .png file compression test with image/jpeg argument set.
|0.9| 777KB |
|0.8| 383KB |
|0.7| 301KB |
|0.6| 251KB |
|0.5| 219kB |
I had an issue with the downscaleImage() function posted above by #daniel-allen-langdon in that the image.width and image.height properties are not available immediately because the image load is asynchronous.
Please see updated TypeScript example below that takes this into account, uses async functions, and resizes the image based on the longest dimension rather than just the width
function getImage(dataUrl: string): Promise<HTMLImageElement>
{
return new Promise((resolve, reject) => {
const image = new Image();
image.src = dataUrl;
image.onload = () => {
resolve(image);
};
image.onerror = (el: any, err: ErrorEvent) => {
reject(err.error);
};
});
}
export async function downscaleImage(
dataUrl: string,
imageType: string, // e.g. 'image/jpeg'
resolution: number, // max width/height in pixels
quality: number // e.g. 0.9 = 90% quality
): Promise<string> {
// Create a temporary image so that we can compute the height of the image.
const image = await getImage(dataUrl);
const oldWidth = image.naturalWidth;
const oldHeight = image.naturalHeight;
console.log('dims', oldWidth, oldHeight);
const longestDimension = oldWidth > oldHeight ? 'width' : 'height';
const currentRes = longestDimension == 'width' ? oldWidth : oldHeight;
console.log('longest dim', longestDimension, currentRes);
if (currentRes > resolution) {
console.log('need to resize...');
// Calculate new dimensions
const newSize = longestDimension == 'width'
? Math.floor(oldHeight / oldWidth * resolution)
: Math.floor(oldWidth / oldHeight * resolution);
const newWidth = longestDimension == 'width' ? resolution : newSize;
const newHeight = longestDimension == 'height' ? resolution : newSize;
console.log('new width / height', newWidth, newHeight);
// Create a temporary canvas to draw the downscaled image on.
const canvas = document.createElement('canvas');
canvas.width = newWidth;
canvas.height = newHeight;
// Draw the downscaled image on the canvas and return the new data URL.
const ctx = canvas.getContext('2d')!;
ctx.drawImage(image, 0, 0, newWidth, newHeight);
const newDataUrl = canvas.toDataURL(imageType, quality);
return newDataUrl;
}
else {
return dataUrl;
}
}
For Moderm browser use createImageBitmap() instead of img.onload
async function compressImage(blobImg, percent) {
let bitmap = await createImageBitmap(blobImg);
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0);
let dataUrl = canvas.toDataURL("image/jpeg", percent/100);
return dataUrl;
}
inputImg.addEventListener('change', async(e) => {
let img = e.target.files[0];
console.log('File Name: ', img.name)
console.log('Original Size: ', img.size.toLocaleString())
let imgCompressed = await compressImage(img, 75) // set to 75%
let compSize = atob(imgCompressed.split(",")[1]).length;
console.log('Compressed Size: ', compSize.toLocaleString())
//console.log(imgCompressed)
})
<input type="file" id="inputImg">
Edit: As per the Mr Me comment on this answer, it looks like compression is now available for JPG/WebP formats ( see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL ).
As far as I know, you cannot compress images using canvas, instead, you can resize it. Using canvas.toDataURL will not let you choose the compression ratio to use. You can take a look at canimage that does exactly what you want : https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js
In fact, it's often sufficient to just resize the image to decrease it's size but if you want to go further, you'll have to use newly introduced method file.readAsArrayBuffer to get a buffer containing the image data.
Then, just use a DataView to read it's content according to the image format specification (http://en.wikipedia.org/wiki/JPEG or http://en.wikipedia.org/wiki/Portable_Network_Graphics).
It'll be hard to deal with image data compression, but it is worse a try. On the other hand, you can try to delete the PNG headers or the JPEG exif data to make your image smaller, it should be easier to do so.
You'll have to create another DataWiew on another buffer and fill it with the filtered image content. Then, you'll just have to encode you're image content to DataURI using window.btoa.
Let me know if you implement something similar, will be interesting to go through the code.
Compressor.js
https://github.com/fengyuanchen/compressorjs
import axios from 'axios';
import Compressor from 'compressorjs';
document.getElementById('file').addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) {
return;
}
new Compressor(file, {
quality: 0.6,
// The compression process is asynchronous,
// which means you have to access the `result` in the `success` hook function.
success(result) {
const formData = new FormData();
// The third parameter is required for server
formData.append('file', result, result.name);
// Send the compressed image file to server with XMLHttpRequest.
axios.post('/path/to/upload', formData).then(() => {
console.log('Upload success');
});
},
error(err) {
console.log(err.message);
},
});
});
I used the following package:
https://www.npmjs.com/package/browser-image-compression
npm install browser-image-compression
or
yarn add browser-image-compression
Then just following the docs:
import imageCompression from 'browser-image-compression';
const options = {
maxSizeMB: 0.5, // pretty much self-explanatory
maxWidthOrHeight: 500, // apparently px
}
imageCompression(file, options)
.then(function(compressedFile) {
console.log(
"compressedFile instanceof Blob",
compressedFile instanceof Blob
); // true
console.log(
`compressedFile size ${compressedFile.size /
1024 /
1024} MB`
); // smaller than maxSizeMB
return uploader(compressedFile); // code to actual upload, in my case uploader() is a function to upload to Firebase storage.
})
Just in case if you were curios about the uploader(), here's the code of it:
import { initializeApp } from "firebase/app";
const firebaseConfig = {
// your config
};
initializeApp(firebaseConfig);
import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
const storage = getStorage();
const sRef = ref(storage);
const uploader = async (file) => {
/* uploads to root */
// const imageRef = ref(sRef, file.name);
// console.log(imageRef);
// await uploadBytes(imageRef, file).then((snapshot) => {
// console.log("Uploaded a blob or file!", snapshot);
// });
/* upload to folder 'techs/' */
const folderRef = ref(sRef, "techs/" + file.name);
await uploadBytes(folderRef, file);
// get URL
const url = await getDownloadURL(ref(storage, folderRef));
console.log("url: ", url);
return url;
};
You can compress an image using the HTML <canvas> element:
function compressImage(imgToCompress, resizingFactor, quality) {
// resizing the image
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const originalWidth = imgToCompress.width;
const originalHeight = imgToCompress.height;
const canvasWidth = originalWidth * resizingFactor;
const canvasHeight = originalHeight * resizingFactor;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
context.drawImage(
imgToCompress,
0,
0,
originalWidth * resizingFactor,
originalHeight * resizingFactor
);
// reducing the quality of the image
canvas.toBlob(
(blob) => {
if (blob) {
// showing the compressed image
resizedImage.src = URL.createObjectURL(resizedImageBlob);
}
},
"image/jpeg",
quality
);
}
See this blog post for an in depth explanation: https://img.ly/blog/how-to-compress-an-image-before-uploading-it-in-javascript/
i improved the function a head to be this :
var minifyImg = function(dataUrl,newWidth,imageType="image/jpeg",resolve,imageArguments=0.7){
var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl;
(new Promise(function(resolve){
image = new Image(); image.src = dataUrl;
log(image);
resolve('Done : ');
})).then((d)=>{
oldWidth = image.width; oldHeight = image.height;
log([oldWidth,oldHeight]);
newHeight = Math.floor(oldHeight / oldWidth * newWidth);
log(d+' '+newHeight);
canvas = document.createElement("canvas");
canvas.width = newWidth; canvas.height = newHeight;
log(canvas);
ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, newWidth, newHeight);
//log(ctx);
newDataUrl = canvas.toDataURL(imageType, imageArguments);
resolve(newDataUrl);
});
};
the use of it :
minifyImg(<--DATAURL_HERE-->,<--new width-->,<--type like image/jpeg-->,(data)=>{
console.log(data); // the new DATAURL
});
enjoy ;)
For JPG Image compression you can use the best compression technique called JIC
(Javascript Image Compression)This will definitely help you -->https://github.com/brunobar79/J-I-C

Resize image with javascript canvas (smoothly)

I'm trying to resize some images with canvas but I'm clueless on how to smoothen them.
On photoshop, browsers etc.. there are a few algorithms they use (e.g. bicubic, bilinear) but I don't know if these are built into canvas or not.
Here's my fiddle: http://jsfiddle.net/EWupT/
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width=300
canvas.height=234
ctx.drawImage(img, 0, 0, 300, 234);
document.body.appendChild(canvas);
The first one is a normal resized image tag, and the second one is canvas. Notice how the canvas one is not as smooth. How can I achieve 'smoothness'?
You can use down-stepping to achieve better results. Most browsers seem to use linear interpolation rather than bi-cubic when resizing images.
(Update There has been added a quality property to the specs, imageSmoothingQuality which is currently available in Chrome only.)
Unless one chooses no smoothing or nearest neighbor the browser will always interpolate the image after down-scaling it as this function as a low-pass filter to avoid aliasing.
Bi-linear uses 2x2 pixels to do the interpolation while bi-cubic uses 4x4 so by doing it in steps you can get close to bi-cubic result while using bi-linear interpolation as seen in the resulting images.
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 = "//i.imgur.com/SHo6Fub.jpg";
<img src="//i.imgur.com/SHo6Fub.jpg" width="300" height="234">
<canvas id="canvas" width=300></canvas>
Depending on how drastic your resize is you can might skip step 2 if the difference is less.
In the demo you can see the new result is now much similar to the image element.
Since Trung Le Nguyen Nhat's fiddle isn't correct at all
(it just uses the original image in the last step)
I wrote my own general fiddle with performance comparison:
FIDDLE
Basically it's:
img.onload = function() {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d"),
oc = document.createElement('canvas'),
octx = oc.getContext('2d');
canvas.width = width; // destination canvas size
canvas.height = canvas.width * img.height / img.width;
var cur = {
width: Math.floor(img.width * 0.5),
height: Math.floor(img.height * 0.5)
}
oc.width = cur.width;
oc.height = cur.height;
octx.drawImage(img, 0, 0, cur.width, cur.height);
while (cur.width * 0.5 > width) {
cur = {
width: Math.floor(cur.width * 0.5),
height: Math.floor(cur.height * 0.5)
};
octx.drawImage(oc, 0, 0, cur.width * 2, cur.height * 2, 0, 0, cur.width, cur.height);
}
ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height);
}
I created a reusable Angular service to handle high quality resizing of images / canvases for anyone who's interested: https://gist.github.com/transitive-bullshit/37bac5e741eaec60e983
The service includes two solutions because they both have their own pros / cons. The lanczos convolution approach is higher quality at the cost of being slower, whereas the step-wise downscaling approach produces reasonably antialiased results and is significantly faster.
Example usage:
angular.module('demo').controller('ExampleCtrl', function (imageService) {
// EXAMPLE USAGE
// NOTE: it's bad practice to access the DOM inside a controller,
// but this is just to show the example usage.
// resize by lanczos-sinc filter
imageService.resize($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
// resize by stepping down image size in increments of 2x
imageService.resizeStep($('#myimg')[0], 256, 256)
.then(function (resizedImage) {
// do something with resized image
})
})
While some of those code-snippets are short and working, they aren't trivial to follow and understand.
As i am not a fan of "copy-paste" from stack-overflow, i would like developers to understand the code they are push into they software, hope you'll find the below useful.
DEMO: Resizing images with JS and HTML Canvas Demo fiddler.
You may find 3 different methods to do this resize, that will help you understand how the code is working and why.
https://jsfiddle.net/1b68eLdr/93089/
Full code of both demo, and TypeScript method that you may want to use in your code, can be found in the GitHub project.
https://github.com/eyalc4/ts-image-resizer
This is the final code:
export class ImageTools {
base64ResizedImage: string = null;
constructor() {
}
ResizeImage(base64image: string, width: number = 1080, height: number = 1080) {
let img = new Image();
img.src = base64image;
img.onload = () => {
// Check if the image require resize at all
if(img.height <= height && img.width <= width) {
this.base64ResizedImage = base64image;
// TODO: Call method to do something with the resize image
}
else {
// Make sure the width and height preserve the original aspect ratio and adjust if needed
if(img.height > img.width) {
width = Math.floor(height * (img.width / img.height));
}
else {
height = Math.floor(width * (img.height / img.width));
}
let resizingCanvas: HTMLCanvasElement = document.createElement('canvas');
let resizingCanvasContext = resizingCanvas.getContext("2d");
// Start with original image size
resizingCanvas.width = img.width;
resizingCanvas.height = img.height;
// Draw the original image on the (temp) resizing canvas
resizingCanvasContext.drawImage(img, 0, 0, resizingCanvas.width, resizingCanvas.height);
let curImageDimensions = {
width: Math.floor(img.width),
height: Math.floor(img.height)
};
let halfImageDimensions = {
width: null,
height: null
};
// Quickly reduce the dize by 50% each time in few iterations until the size is less then
// 2x time the target size - the motivation for it, is to reduce the aliasing that would have been
// created with direct reduction of very big image to small image
while (curImageDimensions.width * 0.5 > width) {
// Reduce the resizing canvas by half and refresh the image
halfImageDimensions.width = Math.floor(curImageDimensions.width * 0.5);
halfImageDimensions.height = Math.floor(curImageDimensions.height * 0.5);
resizingCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
0, 0, halfImageDimensions.width, halfImageDimensions.height);
curImageDimensions.width = halfImageDimensions.width;
curImageDimensions.height = halfImageDimensions.height;
}
// Now do final resize for the resizingCanvas to meet the dimension requirments
// directly to the output canvas, that will output the final image
let outputCanvas: HTMLCanvasElement = document.createElement('canvas');
let outputCanvasContext = outputCanvas.getContext("2d");
outputCanvas.width = width;
outputCanvas.height = height;
outputCanvasContext.drawImage(resizingCanvas, 0, 0, curImageDimensions.width, curImageDimensions.height,
0, 0, width, height);
// output the canvas pixels as an image. params: format, quality
this.base64ResizedImage = outputCanvas.toDataURL('image/jpeg', 0.85);
// TODO: Call method to do something with the resize image
}
};
}}
I don't understand why nobody is suggesting createImageBitmap.
createImageBitmap(
document.getElementById('image'),
{ resizeWidth: 300, resizeHeight: 234, resizeQuality: 'high' }
)
.then(imageBitmap =>
document.getElementById('canvas').getContext('2d').drawImage(imageBitmap, 0, 0)
);
works beautifully (assuming you set ids for image and canvas).
I created a library that allows you to downstep any percentage while keeping all the color data.
https://github.com/danschumann/limby-resize/blob/master/lib/canvas_resize.js
That file you can include in the browser. The results will look like photoshop or image magick, preserving all the color data, averaging pixels, rather than taking nearby ones and dropping others. It doesn't use a formula to guess the averages, it takes the exact average.
Based on K3N answer, I rewrite code generally for anyone wants
var oc = document.createElement('canvas'), octx = oc.getContext('2d');
oc.width = img.width;
oc.height = img.height;
octx.drawImage(img, 0, 0);
while (oc.width * 0.5 > width) {
oc.width *= 0.5;
oc.height *= 0.5;
octx.drawImage(oc, 0, 0, oc.width, oc.height);
}
oc.width = width;
oc.height = oc.width * img.height / img.width;
octx.drawImage(img, 0, 0, oc.width, oc.height);
UPDATE JSFIDDLE DEMO
Here is my ONLINE DEMO
I solved this by using scale for canvas and the image quality in my case becomes really good.
So first I scale the content inside of canvas:
ctx.scale(2, 2)
And then scale out the canvas tag with css:
#myCanvas { transform: scale(0.5); }
export const resizeImage = (imageFile, size = 80) => {
let resolver = ()=>{};
let reader = new FileReader();
reader.onload = function (e) {
let img = document.createElement("img");
img.onload = function (event) {
// Dynamically create a canvas element
let canvas = document.createElement("canvas");
canvas.width=size;
canvas.height=size;
// let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
// Actual resizing
ctx.drawImage(img, 0, 0, size, size);
// Show resized image in preview element
let dataurl = canvas.toDataURL(imageFile.type);
resolver(dataurl);
}
img.src = e.target.result;
}
reader.readAsDataURL(imageFile);
return new Promise((resolve, reject) => {
resolver = resolve;
})
};
I wrote small js-utility to crop and resize image on front-end. Here is link on GitHub project. Also you can get blob from final image to send it.
import imageSqResizer from './image-square-resizer.js'
let resizer = new imageSqResizer(
'image-input',
300,
(dataUrl) =>
document.getElementById('image-output').src = dataUrl;
);
//Get blob
let formData = new FormData();
formData.append('files[0]', resizer.blob);
//get dataUrl
document.getElementById('image-output').src = resizer.dataUrl;
Here is my code, which I hope may be useful for someone out there in the SO community:
You can include your target image dimension as a param in your script call. That will be the result value of your image width or height, whichever is bigger. The smaller dimension is resized keeping your image aspect ratio unchanged. You can also hard-code your default target size in the script.
You can easily change the script to suit your specific needs, such as the image type you want (default is "image/png") for an output and decide in how many steps percentwise you want to resize your image for a finer result (see const percentStep in code).
const ResizeImage = ( _ => {
const MAX_LENGTH = 260; // default target size of largest dimension, either witdth or height
const percentStep = .3; // resizing steps until reaching target size in percents (30% default)
const canvas = document.createElement("canvas");
const canvasContext = canvas.getContext("2d");
const image = new Image();
const doResize = (callback, maxLength) => {
// abort with error if image has a dimension equal to zero
if(image.width == 0 || image.height == 0) {
return {blob: null, error: "either image width or height was zero "};
}
// use caller dimension or default length if none provided
const length = maxLength == null ? MAX_LENGTH : maxLength;
canvas.width = image.width;
canvas.height = image.height;
canvasContext.drawImage(image, 0, 0, image.width, image.height);
// if image size already within target size, just copy and return blob
if(image.width <= length && image.height <= length) {
canvas.toBlob( blob => {
callback({ blob: blob, error: null });
}, "image/png", 1);
return;
}
var startDim = Math.max(image.width, image.height);
var startSmallerDim = Math.min(image.width, image.height);
// gap to decrease in size until we reach the target size,
// be it by decreasing the image width or height,
// whichever is largest
const gap = startDim - length;
// step length of each resizing iteration
const step = parseInt(percentStep*gap);
// no. of iterations
var nSteps = 0;
if(step == 0) {
step = 1;
} else {
nSteps = parseInt(gap/step);
}
// length of last additional resizing step, if needed
const lastStep = gap % step;
// aspect ratio = value by which we'll multiply the smaller dimension
// in order to keep the aspect ratio unchanged in each iteration
const ratio = startSmallerDim/startDim;
var newDim; // calculated new length for the bigger dimension of the image, be it image width or height
var smallerDim; // length along the smaller dimension of the image, width or height
for(var i = 0; i < nSteps; i++) {
// decrease longest dimension one step in pixels
newDim = startDim - step;
// decrease shortest dimension proportionally, so as to keep aspect ratio
smallerDim = parseInt(ratio*newDim);
// assign calculated vars to their corresponding canvas dimension, width or height
if(image.width > image.height) {
[canvas.width, canvas.height] = [newDim, smallerDim];
} else {
[canvas.width, canvas.height] = [smallerDim, newDim];
}
// draw image one step smaller
canvasContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);
// cycle var startDim for new loop
startDim = newDim;
}
// do last missing resizing step to finally reach target image size
if(lastStep > 0) {
if(image.width > image.height) {
[canvas.width, canvas.height] = [startDim - lastStep, parseInt(ratio*(startDim - lastStep))];
} else {
[canvas.width, canvas.height] = [parseInt(ratio*(startDim -lastStep)), startDim - lastStep];
}
canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
}
// send blob to caller
canvas.toBlob( blob => {
callback({blob: blob, error: null});
}, "image/png", 1);
};
const resize = async (imgSrc, callback, maxLength) => {
image.src = imgSrc;
image.onload = _ => {
doResize(callback, maxLength);
};
};
return { resize: resize }
})();
Usage:
ResizeImage.resize("./path/to/image/or/blob/bytes/to/resize", imageObject => {
if(imageObject.error != null) {
// handle errors here
console.log(imageObject.error);
return;
}
// do whatever you want with the blob, like assinging it to
// an img element, or uploading it to a database
// ...
document.querySelector("#my-image").src = imageObject.blob;
// ...
}, 300);

can html canvas draw uploaded images?

I am trying to create an uploading image resizing function using javascript. However, using filereader to convert upload files into base64, gives no image on canvas. Rather, if I use an external image, and convert that into base64, the function works like a charm. How would that be? What is the problem?
Here is my code, it's pretty long.
//When changes are made to our input field
$ ('#input-file').change (function (evt) {
//The selected file is recovered
var file = evt.target.files [0];
//And processFiles function that will resize and send the file to the server is started
processFiles (file);
});
processFiles = function (file) {
var reader = new FileReader ();
//When the file has been completely read, the function will be executed ResizeImage
reader.onloadend = function (evt) {
//Reader.result represents our base64 encoded file
ResizeImage (reader.result, file);
};
//Allows you to play the file
reader.readAsDataURL (file);
};
ResizeImage = function (data, file) {
var fileType = file.type,
maxWidth = 960
maxHeight = 960;
//The file is loaded in a <img>
var image = new Image ();
image.src = data;
//Once the image is loaded, the following operations are performed
image.onload = function () {
//The ImageSize function calculates the final file size keeping the proportions
var size = ImageSize (image.width, image.height, maxWidth, maxHeight)
ImageWidth = size.width,
imageHeight = size.height,
//We create a canvas element
//canvas = document.createElement ('canvas');
canvas=document.getElementById('hahaha')
canvas.width = ImageWidth;
canvas.height = imageHeight;
var ctx = canvas.getContext ("2d");
//DrawImage will allow resizing the image
//This is our image here
var img=document.getElementById('haha')
ctx.drawImage (img, 0, 0, ImageWidth, imageHeight);
//Allows you to export the contents of the canvas element (our resized image) base64
data = canvas.toDataURL(fileType);
alert(data)
//All the elements used for resizing is suppressed
delete image;
delete canvas;
//SubmitFile (data);
}
};
//Function to resize an image keeping the proportions
ImageSize = function (width, height, maxWidth, maxHeight) {
var newWidth = width,
newHeight = height;
if (width> height) {
if (width> maxWidth) {
newHeight = maxWidth / width;
newWidth = maxWidth;
}
else {}
if (height> maxHeight) {
newWidth = maxHeight / height;
newHeight = maxHeight;
}
}
return {width: newWidth, height: newHeight};
};
You may refer this fiddle, http://jsfiddle.net/aliasm2k/tAum2/ for some ideas. It may help you. This is a fiddle I implemented just for fun to generate a thumbnail of a user selected image. It uses FileReader API to select the file and then loads the thumbnail on a canvas.
Personally I'd preffer classic JS than using jQuery and lot of event handlers are used
oFileIn.addEventListener()
oFileReader.addEventListener()
oImage.addEventListener()

Categories