14

How can I resize an image (using an HTML5 canvas element) and keep the EXIF information from the original image? I can extract EXIF info from from original image but I don't know how to copy it to the resized image.

This is how I retrieve the resized image data to send to the server-side code:

canvas.toDataURL("image/jpeg", 0.7);

For EXIF retrieval, I'm using the exif.js library.

Alex
  • 14,104
  • 11
  • 54
  • 77
Martin Perry
  • 9,232
  • 8
  • 46
  • 114

4 Answers4

21

Working solution: ExifRestorer.js

Usage with HTML5 image resize:

function dataURItoBlob(dataURI) 
{
    var binary = atob(dataURI.split(',')[1]);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}

And main code, taken as part of HTML5 resizer from this page: https://github.com/josefrichter/resize/blob/master/public/preprocess.js (but slightly modified)

var reader = new FileReader();

//reader.readAsArrayBuffer(file); //load data ... old version
reader.readAsDataURL(file);       //load data ... new version
reader.onload = function (event) {
// blob stuff
//var blob = new Blob([event.target.result]); // create blob... old version
var blob = dataURItoBlob(event.target.result); // create blob...new version
window.URL = window.URL || window.webkitURL;
var blobURL = window.URL.createObjectURL(blob); // and get it's URL

// helper Image object
var image = new Image();
image.src = blobURL;

image.onload = function() {

   // have to wait till it's loaded
   var resized = ResizeImage(image); // send it to canvas

   resized = ExifRestorer.restore(event.target.result, resized);  //<= EXIF  

   var newinput = document.createElement("input");
   newinput.type = 'hidden';
   newinput.name = 'html5_images[]';
   newinput.value = resized; // put result from canvas into new hidden input
   form.appendChild(newinput);
 };
};
Martin Perry
  • 9,232
  • 8
  • 46
  • 114
  • 2
    Would you create a GitHub repo for your ExifRestorer.js file and add a license so others can use it? Or would you explicitly state the terms under which others may use the code in your answer here? – Kenny Evitt Oct 16 '14 at 16:11
  • 3
    @KennyEvitt You can use code as you like. I dint create it, I just joined several parts of different codes together. – Martin Perry Oct 16 '14 at 17:13
  • 1
    @MartinPerry - It works perfectly with my code - I do some canvas image manipulation - then I am able to restore data. However I would like to overwrite image exif orientation value. Once I rotate image with canvas - exif orientation remains incorrect. Is there any solution for that? – krystiangw Jul 01 '15 at 12:59
  • Thanks for ExifRestorer.js. Lovely script! <3 – larsemil Dec 13 '17 at 12:18
  • 1
    To make ExifRestorer.js work with file types different than jpeg it's necessary to replace the occurrencies of `'data:image/jpeg;base64,'` in the library with `/data:image\/\w+;base64,/` – whirmill Jun 29 '18 at 11:38
2

You can use copyExif.js.

This module is more efficient than Martin's solution and uses only Blob and ArrayBuffer without Base64 encoder/decoder.

Besides, there is no need to use exif.js if you only want to keep EXIF. Just copy the entire APP1 marker from the original JPEG to the destination canvas blob and it would just work. It is also how copyExif.js does.

Usage

demo: https://codepen.io/tonytonyjan/project/editor/XEkOkv

<input type="file" id="file" accept="image/jpeg" />
import copyExif from "./copyExif.js";

document.getElementById("file").onchange = async ({ target: { files } }) => {
  const file = files[0],
    canvas = document.createElement("canvas"),
    ctx = canvas.getContext("2d");

  ctx.drawImage(await blobToImage(file), 0, 0, canvas.width, canvas.height);
  canvas.toBlob(
    async blob =>
      document.body.appendChild(await blobToImage(await copyExif(file, blob))),
    "image/jpeg"
  );
};

const blobToImage = blob => {
  return new Promise(resolve => {
    const reader = new FileReader(),
      image = new Image();
    image.onload = () => resolve(image);
    reader.onload = ({ target: { result: dataURL } }) => (image.src = dataURL);
    reader.readAsDataURL(blob);
  });
};
Weihang Jian
  • 7,826
  • 4
  • 44
  • 55
  • 1
    I have made some changes in the above code as I required. Now that the image is rotating I want to set "Orientation to 0", can you help me? and I cant use "ctx.transform" because I want "Blob object" at the end. – mangeshbhuskute Apr 28 '21 at 06:05
  • Hi @mangeshbhuskute I made another version of `copyExif.js` to copy all EXIF fields but only writes to "orientation" field to "1". Can you try and see if it sovles your problem? https://gist.github.com/tonytonyjan/ffb7cd0e82cb293b843ece7e79364233#file-copyexifwithoutorientation-js – Weihang Jian Jun 02 '22 at 09:27
1

It looks my code is used in 'ExifRestorer.js'...

I've try resizing image by canvas. And I felt that resized image is bad quality. If you felt so, too, try my code. My code resizes JPEG by bilinear interpolation. Of course it doesn't lose exif.

https://github.com/hMatoba/JavaScript-MinifyJpegAsync

function post(data) {
    var req = new XMLHttpRequest();
    req.open("POST", "/jpeg", false);
    req.setRequestHeader('Content-Type', 'image/jpeg');
    req.send(data.buffer);
}

function handleFileSelect(evt) {
    var files = evt.target.files;

    for (var i = 0, f; f = files[i]; i++){
        var reader = new FileReader();
        reader.onloadend = function(e){
            MinifyJpegAsync.minify(e.target.result, 1280, post);
        };
    reader.readAsDataURL(f);
    }
}

document.getElementById('files').addEventListener('change', handleFileSelect, false);
hMatoba
  • 519
  • 4
  • 7
0

Canvas generates images with 20 bytes header (before jpeg data segments start). You can slice head with exif segments from original file and replace first 20 bytes in resized one.

Vitaly
  • 3,340
  • 26
  • 21