How to compress image through javascript in browser?

TL; DR;

Is there a way to compress an image (mainly jpeg, png and gif) directly on the browser side before downloading it? I'm sure JavaScript can do this, but I can't find a way to achieve it.


Here is the full scenario that I would like to implement:

  • a user goes to my site and selects an image using the input type="file" element,
  • this image is extracted using javascript, we do some checking, such as the correct file format, maximum file size, etc.,
  • If everything is in order, the page displays a preview of the image,
  • the user can perform some basic operations, such as rotating the image 90 Β° / -90 Β°, crop it to a predetermined ratio, etc., or the user can load another image and return to step 1,
  • when the user is satisfied, the edited image is then compressed and "saved" locally (not saved in the file, but in the browser memory / page),
  • the user fills out the form with data such as name, age, etc.
  • the user clicks the "Finish" button, then the form containing the data + compressed image is sent to the server (without AJAX),

The complete process to the last step should be performed on the client side and should be compatible with the latest browsers Chrome and Firefox, Safari 5+ and IE 8 + . If possible, only JavaScript should be used (but I'm sure this is not possible).

I did not code anything right now, but I was already thinking about it. Reading files locally is possible through the file API , previewing and editing images can be done using the Canvas element, but I can’t find a way to do part of the image compression .

According to html5please.com and caniuse.com, support for these browsers is rather complicated (thanks to IE), but this can be done using polyfill, such as FlashCanvas and FileReader .

Actually, the goal is to reduce the file size, so I see image compression as a solution. But I know that the downloaded images will be displayed on my site every time in one place, and I know the size of this display area (for example, 200x400). That way, I could resize the image to fit those sizes, thereby reducing the file size. I have no idea what the compression ratio will be for this technique.

What do you think? Do you have any tips to tell me? Do you know what browser-side image compression method is in JavaScript? Thank you for your responses.

+68
javascript cross-browser image compression
03 Feb '13 at 13:02
source share
5 answers

In short:

  • Read files using HTML5 FileReader API with .readAsArrayBuffer
  • Create an e Blob with the file data and get its url with window.URL.createObjectURL (blob)
  • Create a new image element and set its src to blob url file
  • Send image to canvas. The canvas size is set to the desired output size.
  • Get reduced data from the canvas through canvas.toDataURL ("image / jpeg", 0.7) (set your own format and output quality)
  • Attach new hidden inputs to the original form and transfer the dataURI images basically like plain text
  • On the backend read dataURI, decode it with Base64 and save it

Source: code .

+122
Feb 03 '13 at 13:24
source share
Answer to

@PsychoWoods is good. I would like to offer my own solution. This Javascript function takes the image data URL and width, scales it to a new width, and returns the 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 wherever you have a data URL and you need a data URL for a thumbnail.

+13
Sep 27 '16 at
source share

I see two things that are not in the other answers:

  • canvas.toBlob (if available) is more efficient than canvas.toDataURL as well as async.
  • file -> image -> canvas -> file conversion loses EXIF ​​data; in particular, image rotation data typically 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); } 

Usage example:

 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(...); }); }; 
+11
Jun 30 '17 at 14:46
source share

As far as I know, you cannot compress images using canvas, you can resize it instead. Using canvas.toDataURL will not let you choose the compression ratio to use. You can take a look at canimage, which does exactly what you want: https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

Actually, often just changing the size of the image is enough to reduce its size, but if you want to go further, you will need to use the newly introduced .readAsArrayBuffer method file to get a buffer containing the image data.

Then simply use the DataView to read the content according to the image format specification ( http://en.wikipedia.org/wiki/JPEG or http://en.wikipedia.org/wiki/Portable_Network_Graphics ).

Compressing image data will be difficult to handle, but it is even worse. On the other hand, you can try to remove PNG headers or exif JPEG data to reduce the image. It should be easier.

You will need to create another DataWiew in another buffer and fill it with the contents of the filtered image. Then you just need to encode your image content in DataURI using window.btoa.

Let me know if you implement something similar, it will be interesting to pass the code.

+3
May 26 '13 at 15:48
source share

You can use the best JIC (Javascript Image Compression) compression technology to compress JPG images. This will definitely help you -> https://github.com/brunobar79/JIC

0
Jan 31 '17 at 7:20
source share



All Articles