130

I'd like to rotate photos based on their original rotation, as set by the camera in JPEG EXIF image data. The trick is that all this should happen in the browser, using JavaScript and <canvas>.

How could JavaScript access JPEG, a local file API object, local <img> or remote <img>, EXIF data to read the rotation information?

Server-side answers are not OK; I am looking for a client-side solution.

Ali
  • 18,627
  • 13
  • 75
  • 90
Mikko Ohtamaa
  • 69,174
  • 40
  • 208
  • 346

8 Answers8

274

If you only want the orientation tag and nothing else and don't like to include another huge javascript library I wrote a little code that extracts the orientation tag as fast as possible (It uses DataView and readAsArrayBuffer which are available in IE10+, but you can write your own data reader for older browsers):

function getOrientation(file, callback) {
    var reader = new FileReader();
    reader.onload = function(e) {

        var view = new DataView(e.target.result);
        if (view.getUint16(0, false) != 0xFFD8)
        {
            return callback(-2);
        }
        var length = view.byteLength, offset = 2;
        while (offset < length) 
        {
            if (view.getUint16(offset+2, false) <= 8) return callback(-1);
            var marker = view.getUint16(offset, false);
            offset += 2;
            if (marker == 0xFFE1) 
            {
                if (view.getUint32(offset += 2, false) != 0x45786966) 
                {
                    return callback(-1);
                }

                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)
                    {
                        return callback(view.getUint16(offset + (i * 12) + 8, little));
                    }
                }
            }
            else if ((marker & 0xFF00) != 0xFF00)
            {
                break;
            }
            else
            { 
                offset += view.getUint16(offset, false);
            }
        }
        return callback(-1);
    };
    reader.readAsArrayBuffer(file);
}

// usage:
var input = document.getElementById('input');
input.onchange = function(e) {
    getOrientation(input.files[0], function(orientation) {
        alert('orientation: ' + orientation);
    });
}
<input id='input' type='file' />

values:

-2: not jpeg
-1: not defined

enter image description here

For those using Typescript, you can use the following code:

export const getOrientation = (file: File, callback: Function) => {
  var reader = new FileReader();

  reader.onload = (event: ProgressEvent) => {

    if (! event.target) {
      return;
    }

    const file = event.target as FileReader;
    const view = new DataView(file.result as ArrayBuffer);

    if (view.getUint16(0, false) != 0xFFD8) {
        return callback(-2);
    }

    const length = view.byteLength
    let offset = 2;

    while (offset < length)
    {
        if (view.getUint16(offset+2, false) <= 8) return callback(-1);
        let marker = view.getUint16(offset, false);
        offset += 2;

        if (marker == 0xFFE1) {
          if (view.getUint32(offset += 2, false) != 0x45786966) {
            return callback(-1);
          }

          let little = view.getUint16(offset += 6, false) == 0x4949;
          offset += view.getUint32(offset + 4, little);
          let tags = view.getUint16(offset, little);
          offset += 2;
          for (let i = 0; i < tags; i++) {
            if (view.getUint16(offset + (i * 12), little) == 0x0112) {
              return callback(view.getUint16(offset + (i * 12) + 8, little));
            }
          }
        } else if ((marker & 0xFF00) != 0xFF00) {
            break;
        }
        else {
            offset += view.getUint16(offset, false);
        }
    }
    return callback(-1);
  };

  reader.readAsArrayBuffer(file);
}
Jamie
  • 329
  • 2
  • 16
Ali
  • 18,627
  • 13
  • 75
  • 90
  • for 2,4,5,7 to get correct image you need to rotate and flip, right? – Muhammad Umer Mar 27 '16 at 14:39
  • The orientation of my image is 3..How do i set the orientation to 1?? – Lucy Apr 17 '16 at 16:12
  • I tried using a few jpg's and most of them returned -1, regardless of their orientation. Does it mean it can't acces the EXIF data? – SimeriaIonut May 03 '16 at 09:12
  • @SimeriaIonut I believe it means, I checked different jpeg from different phones and it worked, you can use a hex reader to check how EXIF data is stored, or send me one of the so I can refine the code! – Ali May 03 '16 at 09:15
  • @Ali Sure, here is one of them: http://i.imgur.com/gOnS43q.jpg EDIT: Better yet, I took one with my phone (iPhone 6) just a minute ago in portrait mode, it returned -1. http://i.imgur.com/MCxV975.jpg – SimeriaIonut May 03 '16 at 09:22
  • @SimeriaIonut I checked the image in photoshop, actually it has no EXIF data, I believe services like imgur remove EXIF data from images. – Ali May 03 '16 at 11:02
  • What about png / gif? – Mick Oct 26 '16 at 13:10
  • 3
    @Mick PNG or GIF don't have any standard format to store image orientation http://stackoverflow.com/questions/9542359/does-png-contain-exif-data-like-jpg – Ali Oct 26 '16 at 13:28
  • @Ali Very nice. Is there a reason why you specifically chose `64 * 1024` for the second parameter to `slice`? – Hoagy Carmichael Oct 26 '16 at 21:19
  • @HoagyCarmichael The exif data wont be after the first 64KB of file data, it can even be less or you can pass the whole file. – Ali Oct 27 '16 at 02:42
  • 2
    Working for me, but I needed to change the last line to just reader.readAsArrayBuffer(file); without the slice as I intend to use the buffer for my base64 image, otherwise, you'll just see the first slice of the image. BTW, this is not required if you just need the orientation information. Thanks – Philip Murphy Dec 05 '16 at 17:31
  • @PhilipMurphy How exactly did you extend this to get other parts of EXIF data? I don't see any slice in the source, I want to use it to get flash data – Dara Java Jun 20 '17 at 11:46
  • 2
    @DaraJava I removed the slice part because sometimes the tag came in after the limit, but it will slow the operation if the tag is never found. Anyway, unlike orientation tag, Flash tag is not in the IFD0 directory and my code only search this part. to get Flash tag you must search SubIFD directory. You can find a good tutorial on EXIF here: https://www.media.mit.edu/pia/Research/deepview/exif.html – Ali Jun 20 '17 at 15:28
  • Thank you very much! That should be enough for me. – Dara Java Jun 20 '17 at 17:24
  • @Ali, Seems I need you help. Look at this : https://stackoverflow.com/questions/45650465/how-do-i-fix-orientation-upload-image-before-saving-in-folder-javascript – Success Man Aug 12 '17 at 13:04
  • @Ali: is it critical to read the whole file, as you do in `reader.readAsArrayBuffer(file);`? Or can we settle for reading lesser bytes? I ask because I'd want the solution to be independent of size of file selected by user. Afterall, we solely need the orientation tag, nothing more. – Hassan Baig Feb 06 '18 at 17:21
  • @HassanBaig, no, the beginning is enough, I used slice before, but due larger headers I removed it. – Ali Feb 06 '18 at 18:31
  • In some cases the line `ar tags = view.getUint16(offset, little);` throws **RangeError: argument 1 accesses an index that is out of range**. For instance, try this image: https://user-images.githubusercontent.com/18588945/28531470-85e0855a-70c9-11e7-9bba-107a664e6635.jpeg For answer completeness, what work-around would you suggest? – Hassan Baig Mar 09 '18 at 21:25
  • @HassanBaig I gave that image a try and received -1 (no Orientation data / not defined). When I checked it out with `exiftool` there is exif data there but not much, and it's missing the Orientation entirely – Chuck Bergeron Jul 27 '18 at 21:16
  • How would you do this with an `` tag? – martisj Dec 07 '18 at 19:15
  • @Ali This used to include a `slice` for performance reasons but this was removed due to "large headers". What is meant by large headers? I'm keen to include `slice` if it's more performant, but I would like to understand the implications of doing so. – Oliver Joseph Ash Jan 15 '19 at 08:26
  • To answer my own question: Exif should appear in the first 64 KB, however this is not guaranteed—it may be possible for the Exif to extend beyond (https://stackoverflow.com/questions/3248946/what-is-the-maximum-size-of-jpeg-metadata/17415204#17415204). Therefore we have to read the whole file for safety. Alternatively, instead of reading the file as an `ArrayBuffer`, we could use streams (where supported) so we lazily read the file until the Exif has been found: https://mobile.twitter.com/jaffathecake/status/1085443592678752256. If anyone has success with this approach, please share! – Oliver Joseph Ash Jan 28 '19 at 17:25
  • Thank you for you answer and for the included typescript version! – Tom Feb 17 '20 at 06:52
  • your answer saved my time man. Not 100% but it solved my solution – Mirza Obaid Mar 12 '20 at 04:14
23

You can use the exif-js library in combination with the HTML5 File API: http://jsfiddle.net/xQnMd/1/.

$("input").change(function() {
    var file = this.files[0];  // file
        fr   = new FileReader; // to read file contents

    fr.onloadend = function() {
        // get EXIF data
        var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));

        // alert a value
        alert(exif.Make);
    };

    fr.readAsBinaryString(file); // read the file
});
Tetramputechture
  • 2,791
  • 2
  • 25
  • 45
pimvdb
  • 141,012
  • 68
  • 291
  • 345
  • Thanks. The JS lib in the question looks little bit outdated, but would probably work. – Mikko Ohtamaa Oct 21 '11 at 16:09
  • See also my demo of a file upload widget I just wrote. It uses the EXIF.js library mentioned above to read the EXIF orientation flag in the image file’s metatdata. Based on the information, it applies the rotation using a canvas element... http://sandbox.juurlink.org/html5imageuploader – Rob Juurlink Feb 13 '13 at 20:17
  • Attempting to even include binaryajax.js in my project causes an access denied error. – Obi Wan Oct 18 '13 at 20:53
  • Where does the EXIF object come from? The BinaryFile script does not seem to contain it, and as far as I can tell, it isn't part of jquery or any other script I regularly use... – jrista Nov 04 '13 at 23:57
  • @jrista There's 2 js files you need to add. Check the jsfiddle link in the answer: http://www.nihilogic.dk/labs/exif/exif.js + http://www.nihilogic.dk/labs/binaryajax/binaryajax.js – DemiImp Apr 18 '14 at 16:13
  • Thanks! That's what I needed...I eventually found the exif.js file. – jrista Apr 18 '14 at 16:54
  • 6
    The library website seems down, and the only other ExifReader libraries I have found were limited in browser support. Is there any good alternative? – Praxis Ashelin Dec 24 '14 at 13:25
  • Binaryajax.js can be found here, https://github.com/jseidelin/binaryajax/blob/master/binaryajax.js – Peppelorum May 05 '15 at 08:29
19

Firefox 26 supports image-orientation: from-image: images are displayed portrait or landscape, depending on EXIF data. (See sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation.)

There is also a bug to implement this in Chrome.

Beware that this property is only supported by Firefox and is likely to be deprecated.

robocat
  • 4,857
  • 40
  • 62
Sam Dutton
  • 13,238
  • 6
  • 50
  • 61
  • 5
    Thanks for the link to the bug report. I starred it so that the Chrome team knows more people want this. – DemiImp Apr 18 '14 at 16:16
  • According to this comment https://bugs.chromium.org/p/chromium/issues/detail?id=158753#c104 by a Chromium project member: "The change is in Chrome 81. That will roll out to the public as the Stable version in 8-10 week's time" – jeff forest Jan 15 '20 at 08:59
  • 1
    Implemented on Chrome starting with 81 It will take a while before people update their browser though - keep an eye on [caniuse](https://caniuse.com/#feat=css-image-orientation) – Robin Métral Apr 09 '20 at 12:10
11

https://github.com/blueimp/JavaScript-Load-Image is a modern javascript library that can not only extract the exif orientation flag - it can also correctly mirror/rotate JPEG images on the client side.

I just solved the same problem with this library: JS Client-Side Exif Orientation: Rotate and Mirror JPEG Images

Community
  • 1
  • 1
flexponsive
  • 5,516
  • 6
  • 23
  • 36
4

If you want it cross-browser, your best bet is to do it on the server. You could have an API that takes a file URL and returns you the EXIF data; PHP has a module for that.

This could be done using Ajax so it would be seamless to the user. If you don't care about cross-browser compatibility, and can rely on HTML5 file functionality, look into the library JsJPEGmeta that will allow you to get that data in native JavaScript.

Community
  • 1
  • 1
Alex Turpin
  • 43,483
  • 22
  • 107
  • 143
  • The last part of [this article](http://benno.id.au/blog/2009/12/30/html5-fileapi-jpegmeta) explains how the library works. – Alex Turpin Sep 28 '11 at 14:30
  • Thank's. The JS script looks sweet. I am not using PHP (in fact I hate it) and I was looking for pure client side Javascript solution. – Mikko Ohtamaa Oct 21 '11 at 16:08
  • Here is a reference implementation https://github.com/miohtama/Krusovice/blob/master/src/tools/resizer.js – Mikko Ohtamaa Nov 28 '12 at 09:10
  • 21
    @MikkoOhtamaa: You need to understand that Stack Overflow answers questions for *everybody*, just just the original person asking it. The next person who has the same objective as you may be a PHP developer - why would you want to deny them the information that Xeon06 included? It was inappropriate to edit that out, just because *you* don't want a PHP solution. – Jon Skeet Dec 01 '12 at 23:32
  • 6
    The question says "in Javascript" so the part was irrelevant. There are many other similar questions and answers for PHP already on the site and it is unnecessary noise regarding this question. – Mikko Ohtamaa Dec 03 '12 at 14:02
  • 2
    If people ask for Javascript solution they don't want to see PHP solution as the first post. – Mikko Ohtamaa Dec 03 '12 at 14:03
  • I'll add PHP part here for the comments for the sake of those poor PHP programming bastards: http://php.net/manual/en/book.exif.php – Mikko Ohtamaa Dec 03 '12 at 14:04
  • 1
    @MikkoOhtamaa it would seem like most disagree with you http://meta.stackexchange.com/questions/157338/answer-edited-to-be-less-complete-because-of-language-bias You seem to have some wrongful sense of ownership on the answers to your questions. – Alex Turpin Dec 03 '12 at 15:33
  • 1
    I edited the answer to have the correct answer at the beginning. Sorry for the fuzz. – Mikko Ohtamaa Dec 04 '12 at 04:30
  • I tried using the jsJPEGmeta.js library and it does not seem to work for me. When trying to view the orientation of a camera image from an iPhone it gives the result as undefined when I know the orientation flag is set. – Obi Wan Oct 21 '13 at 14:41
  • @ObiWan I wouldn't be sure what the problem is, I would try contacting the authors of the library or making a new question here on Stack Overflow. – Alex Turpin Oct 21 '13 at 22:51
  • @Alex Turpin, Seems I need you help. Look at this : https://stackoverflow.com/questions/45650465/how-do-i-fix-orientation-upload-image-before-saving-in-folder-javascript – Success Man Aug 12 '17 at 13:07
3

Check out a module I've written (you can use it in browser) which converts exif orientation to CSS transform: https://github.com/Sobesednik/exif2css

There is also this node program to generate JPEG fixtures with all orientations: https://github.com/Sobesednik/generate-exif-fixtures

zavr
  • 1,695
  • 1
  • 14
  • 25
3

I upload expansion code to show photo by android camera on html as normal on some img tag with right rotaion, especially for img tag whose width is wider than height. I know this code is ugly but you don't need to install any other packages. (I used above code to obtain exif rotation value, Thank you.)

function getOrientation(file, callback) {
  var reader = new FileReader();
  reader.onload = function(e) {

    var view = new DataView(e.target.result);
    if (view.getUint16(0, false) != 0xFFD8) return callback(-2);
    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) return callback(-1);
        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)
            return callback(view.getUint16(offset + (i * 12) + 8, little));
      }
      else if ((marker & 0xFF00) != 0xFF00) break;
      else offset += view.getUint16(offset, false);
    }
    return callback(-1);
  };
  reader.readAsArrayBuffer(file);
}

var isChanged = false;
function rotate(elem, orientation) {
    if (isIPhone()) return;

    var degree = 0;
    switch (orientation) {
        case 1:
            degree = 0;
            break;
        case 2:
            degree = 0;
            break;
        case 3:
            degree = 180;
            break;
        case 4:
            degree = 180;
            break;
        case 5:
            degree = 90;
            break;
        case 6:
            degree = 90;
            break;
        case 7:
            degree = 270;
            break;
        case 8:
            degree = 270;
            break;
    }
    $(elem).css('transform', 'rotate('+ degree +'deg)')
    if(degree == 90 || degree == 270) {
        if (!isChanged) {
            changeWidthAndHeight(elem)
            isChanged = true
        }
    } else if ($(elem).css('height') > $(elem).css('width')) {
        if (!isChanged) {
            changeWidthAndHeightWithOutMargin(elem)
            isChanged = true
        } else if(degree == 180 || degree == 0) {
            changeWidthAndHeightWithOutMargin(elem)
            if (!isChanged)
                isChanged = true
            else
                isChanged = false
        }
    }
}


function changeWidthAndHeight(elem){
    var e = $(elem)
    var width = e.css('width')
    var height = e.css('height')
    e.css('width', height)
    e.css('height', width)
    e.css('margin-top', ((getPxInt(height) - getPxInt(width))/2).toString() + 'px')
    e.css('margin-left', ((getPxInt(width) - getPxInt(height))/2).toString() + 'px')
}

function changeWidthAndHeightWithOutMargin(elem){
    var e = $(elem)
    var width = e.css('width')
    var height = e.css('height')
    e.css('width', height)
    e.css('height', width)
    e.css('margin-top', '0')
    e.css('margin-left', '0')
}

function getPxInt(pxValue) {
    return parseInt(pxValue.trim("px"))
}

function isIPhone(){
    return (
        (navigator.platform.indexOf("iPhone") != -1) ||
        (navigator.platform.indexOf("iPod") != -1)
    );
}

and then use such as

$("#banner-img").change(function () {
    var reader = new FileReader();
    getOrientation(this.files[0], function(orientation) {
        rotate($('#banner-img-preview'), orientation, 1)
    });

    reader.onload = function (e) {
        $('#banner-img-preview').attr('src', e.target.result)
        $('#banner-img-preview').css('display', 'inherit')

    };

    // read the image file as a data URL.
    reader.readAsDataURL(this.files[0]);

});
2

Improving / Adding more functionality to Ali's answer from earlier, I created a util method in Typescript that suited my needs for this issue. This version returns rotation in degrees that you might also need for your project.

ImageUtils.ts

/**
 * Based on StackOverflow answer: https://stackoverflow.com/a/32490603
 *
 * @param imageFile The image file to inspect
 * @param onRotationFound callback when the rotation is discovered. Will return 0 if if it fails, otherwise 0, 90, 180, or 270
 */
export function getOrientation(imageFile: File, onRotationFound: (rotationInDegrees: number) => void) {
  const reader = new FileReader();
  reader.onload = (event: ProgressEvent) => {
    if (!event.target) {
      return;
    }

    const innerFile = event.target as FileReader;
    const view = new DataView(innerFile.result as ArrayBuffer);

    if (view.getUint16(0, false) !== 0xffd8) {
      return onRotationFound(convertRotationToDegrees(-2));
    }

    const length = view.byteLength;
    let offset = 2;

    while (offset < length) {
      if (view.getUint16(offset + 2, false) <= 8) {
        return onRotationFound(convertRotationToDegrees(-1));
      }
      const marker = view.getUint16(offset, false);
      offset += 2;

      if (marker === 0xffe1) {
        if (view.getUint32((offset += 2), false) !== 0x45786966) {
          return onRotationFound(convertRotationToDegrees(-1));
        }

        const little = view.getUint16((offset += 6), false) === 0x4949;
        offset += view.getUint32(offset + 4, little);
        const tags = view.getUint16(offset, little);
        offset += 2;
        for (let i = 0; i < tags; i++) {
          if (view.getUint16(offset + i * 12, little) === 0x0112) {
            return onRotationFound(convertRotationToDegrees(view.getUint16(offset + i * 12 + 8, little)));
          }
        }
        // tslint:disable-next-line:no-bitwise
      } else if ((marker & 0xff00) !== 0xff00) {
        break;
      } else {
        offset += view.getUint16(offset, false);
      }
    }
    return onRotationFound(convertRotationToDegrees(-1));
  };
  reader.readAsArrayBuffer(imageFile);
}

/**
 * Based off snippet here: https://github.com/mosch/react-avatar-editor/issues/123#issuecomment-354896008
 * @param rotation converts the int into a degrees rotation.
 */
function convertRotationToDegrees(rotation: number): number {
  let rotationInDegrees = 0;
  switch (rotation) {
    case 8:
      rotationInDegrees = 270;
      break;
    case 6:
      rotationInDegrees = 90;
      break;
    case 3:
      rotationInDegrees = 180;
      break;
    default:
      rotationInDegrees = 0;
  }
  return rotationInDegrees;
}

Usage:

import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
  getOrientation(pics[0], rotationInDegrees => {
    this.setState({ image: pics[0], rotate: rotationInDegrees });
  });
};
grayswan
  • 3
  • 2
Kevin Grant
  • 2,131
  • 21
  • 16