Related
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
I am using jsPDF to create a PDF by looping through some HTML code to find the values it needs. I am able to get the values just fine, but when I try to insert the header_img it does not work. I have found many solutions on Google but the one I am working with now is the only one that does not throw an error.
This is the code being used to get take the url that is provided via the loop, convert it to a DataURL, and then insert the image into the PDF. It does not give me any errors, but all that is in the PDF is the black border and no image. Any ideas?
function getDataUri(url, cb) {
var image = new Image();
image.setAttribute('crossOrigin', 'anonymous'); //getting images from external domain
image.onload = function () {
console.log(url);
var canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
//next three lines for white background in case png has a transparent background
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff'; /// set white fill style
ctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.getContext('2d').drawImage(this, 0, 0);
cb(canvas.toDataURL('image/jpeg'));
};
image.src = url;
}
var pdf = new jsPDF('p', 'in', 'letter');
var left_margin = .5;
var top_margin =.5;
var height = 2.313;
var width = 3.25;
var header_img_height = 1;
//Draw border
pdf.setLineWidth(1/92);
pdf.setDrawColor(0, 0, 0);
pdf.rect(left_margin, top_margin, width, height);
//example image
var header_img = 'http://www.quotehd.com/imagequotes/authors24/jeff-olsen-quote-two-clicks-and-its-broke.jpg';
let logo = null;
//callback to get DataURL
getDataUri(header_img, function(dataUri) {
logo = dataUri;
console.log("logo=" + logo);
//Add image to PDF
pdf.addImage(logo, 'JPEG', left_margin, top_margin, header_img_height, width);
});
pdf.output('save', 'test.pdf');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.4.1/jspdf.debug.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I ended up solving the issue by writing a PHP script that would convert the image to Base64.
$img_src = urldecode($_GET['url']);
$img_str = base64_encode(file_get_contents($img_src));
echo 'data:' . getimagesize($img_src)['mime'] . ';base64,' . $img_str;
Then using jQuery, I passed the url to the PHP script and got the Base64 data in return:
function getBase64(url) {
var out = '';
$.ajax({
url: 'get-dataurl.php?url=' + encodeURI(url),
async: false,
dataTye: 'text',
success: function(data) {
out = data;
}
});
return out;
}
Certainly was not ideal, but it works.
You can encode the image to base64, you would use this tool or any similar and replace it on your code.
I have:
if(window.FileReader) {
reader = new FileReader();
var sendingcontext = sendingcanvas.getContext('2d');
reader.onload = (function(theFile) {
var image = new Image();
image.src = theFile.target.result;
image.onload = function() {
sendingcanvas.width=this.width;
sendingcanvas.height= this.height;
console.log("height: " + sendingcanvas.height + " width: " + sendingcanvas.width)
sendingcontext.drawImage(image, 0,0);
};
});
reader.onloadend = function(e){
showUploadedItem(e.target.result, file.fileName);
}
reader.readAsDataURL(file);
alert(sendingcanvas.toDataURL());
}
I Suppose I don't have to explain what the code does, But what I want is to get the file api image height and width, set it to sendingcanvas and send it to server using ajax by using it's toDataURL(). I know This is weird but If I remove the alert(sendingcanvas.toDataURL()) from my script My server receives an empty image but If I have it in my script In my server the image is exactly how the client have it.
Thanks for everything.
Just want to point out that the image you are uploading is being converted to a png (if that is what you want)The uploaded file is no longer the same as the user selected
Btw, sending a blob is better then uploading a base64 string that is ~3x larger you can use this polyfill to get support for canvas.toBlob(...) in older browsers
This is basicly what you are doing:
function showUploadedItem(image){
document.body.appendChild(image)
}
// Simulate a file (image)
var black_1x1 = new Uint8Array([71,73,70,56,57,97,1,0,1,0,128,0,0,5,4,4,0,0,0,44,0,0,0,0,1,0,1,0,0,2,2,68,1,0,59])
var file = new Blob([black_1x1], {type:"image/gif"})
console.log(file)
var img = new Image
img.onload = function() {
console.log("height: " + this.height + " width: " + this.width)
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
canvas.width = this.width
canvas.height = this.height
showUploadedItem(this)
ctx.drawImage(this, 0, 0)
canvas.toBlob(function(blob){
console.log(blob)
// Send the canvas pic to server (using fetch, xhr or jQuery
// With or without FormData
// fetch('/upload', {method: 'post', body: blob})
})
}
// No need to use fileReader + base64
img.src = URL.createObjectURL(file)
/* Just to make the pixel more vissible */
body img {
width: 10px
}
<!-- toBlob polyfill -->
<script src="https://cdn.rawgit.com/eligrey/canvas-toBlob.js/master/canvas-toBlob.js"></script>
You can try let the image in a <img> tag and get the real width and size of the image in case what you having a DOM:
var height = $( "#main-reference" ).get( 0 ).naturalWidth;
var width = $( "#main-reference" ).get( 0 ).naturalHeight;
this property gives you the real height and width of the img although you have broken the img with css
I want to resize (as in reduce the filesize if it exceeds limit) an Image using canvas. I see that the resultant image size drawn on canvas, keeping the width and height constant, results in different file size depending upon what has been drawn on canvas.
I am willing to reduce the resolution further provided I get the desired file size.
How do I make sure whatever the resolution I get file(image) size within the limit?
Refer to this fiddle
var el = document.getElementById('f');
el.addEventListener('change', onChange);
function onChange(e) {
var file = this.files[0];
console.log("CHANGING");
var fr = new FileReader();
fr.onload = function(e) {
drawOnCanvas(e.target.result);
}
fr.readAsDataURL(file);
}
function drawOnCanvas(imgSrc) {
var canv = document.createElement('canvas'),
img = new Image();
console.log("DRAWING");
canv.width = canv.height = 500;
ctx = canv.getContext('2d');
img.onload = function() {
console.log("IMAGE LOADED");
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canv.width, canv.height);
try {
document.body.removeChild(document.querySelector('canvas'));
} catch (e) {}
document.body.appendChild(canv);
blob = canvasToFile(canv.toDataURL());
alert("FILE SIZE " + blob.size);
}
img.src = imgSrc;
}
function canvasToFile(dataUrl) {
var encData = dataUrl.split(',')[1],
binString = atob(encData),
binData = new Uint8Array(binString.length),
i, blob;
for (i = 0; i < binString.length; i++) {
binData[i] = binString.charCodeAt(i);
}
blob = new Blob([binData], {
type: 'image/jpeg'
});
return blob;
}
<p>Try uploading different image file and see the difference in size for same output resolution:</p>
<input type="file" id="f">
What you are experiencing is that the default type argument for canvas.toDataURL(type, encoderOptions) is image/png, and also that it's actually a base64 encode of the image data.
For the former, most browsers do support image/jpeg as image type. This may reduce the data size, thanks to jpg compression, but this will above all enable the encoderOptions parameter of the method, which could be translated to quality parameter and requires a 0 to 1 float.
Note that you can't be sure that the data will be lighter if converted into jpg though : if the image is mainly composed of transparent pixels, then png will be lighter. Also, remember that jpeg compression will reduce the quality of your image, see the last snippet to see what 0 results in :)
For the later, ( you can find an explanation of the why here ), the solution is to transform back the dataURL to a blob.
Here is a snippet to demonstrate it :
var input = document.querySelector('input');
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
function draw(){
var img = new Image();
img.src = URL.createObjectURL(this.files[0]);
img.onload = function(){
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
ctx.drawImage(this, 0,0);
var noData = canvas.toDataURL();
png_data.innerHTML = 'dataURL length: '+noData.length;
var noBlob = dataURItoBlob(noData);
png_blob.innerHTML = 'blob size: '+ noBlob.size;
var jpgData10 = canvas.toDataURL('image/jpeg', 1),
jpgData5 = canvas.toDataURL('image/jpeg', 0.5),
jpgData0 = canvas.toDataURL('image/jpeg', 0);
jpg_data.innerHTML = 'quality = 1 : '+ jpgData10.length+'<br>';
jpg_data.innerHTML += 'quality = 0.5 : '+ jpgData5.length+'<br>';
jpg_data.innerHTML += 'quality = 0 :'+ jpgData0.length+'<br>';
var jpgBlob10 = dataURItoBlob(jpgData10);
jpg_blob.innerHTML = 'blob quality = 1 : '+jpgBlob10.size+'<br>';
var jpgBlob5 = dataURItoBlob(jpgData5);
jpg_blob.innerHTML += 'blob quality = .5 : '+jpgBlob5.size+'<br>';
var jpgBlob0 = dataURItoBlob(jpgData0);
jpg_blob.innerHTML += 'blob quality = 0 : '+jpgBlob0.size;
}
this.previousElementSibling.innerHTML = 'orginal size : '+this.files[0].size;
}
input.onchange = draw;
// Taken from https://stackoverflow.com/a/5100158/3702797
function dataURItoBlob(dataURI) {
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
<div></div>
<input type="file"/>
<br>PNG :
<div id="png_data"></div>
<div id="png_blob"></div>
<br>Jpeg :
<div id="jpg_data"></div>
<div id="jpg_blob"></div>
canvas.toDataURL('image/jpeg', 0) demo ?
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
document.querySelector('input').onchange = function(){
var img = new Image();
img.src = URL.createObjectURL(this.files[0]);
img.onload = function(){
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
ctx.drawImage(this, 0,0);
document.images[0].src = canvas.toDataURL('image/jpeg', 0);
}
}
<input type="file"/>
<img/>
Also, if you need to make your file lighter than a specific size, I wrote something on another question, which limited the data size to 3Mb (just change maxSize to your needs and remove the localStorage part).
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