JS Client-Side Exif Orientation: Rotate and Mirror JPEG Images

JavascriptRotationHtml5 CanvasExif

Javascript Problem Overview


Digital camera photos are often saved as JPEG with an EXIF "orientation" tag. To display correctly, images need to be rotated/mirrored depending on which orientation is set, but browsers ignore this information rendering the image. Even in large commercial web apps, support for EXIF orientation can be spotty 1. The same source also provides a nice summary of the 8 different orientations a JPEG can have:

Summary of EXIF Orientations

Sample images are available at 4.

The question is how to rotate/mirror the image on the client side so that it displays correctly and can be further processed if necessary?

There are JS libraries available to parse EXIF data, including the orientation attribute 2. Flickr noted possible performance problem when parsing large images, requiring use of webworkers 3.

Console tools can correctly re-orient the images 5. A PHP script solving the problem is available at 6

Javascript Solutions


Solution 1 - Javascript

The github project JavaScript-Load-Image provides a complete solution to the EXIF orientation problem, correctly rotating/mirroring images for all 8 exif orientations. See the online demo of javascript exif orientation

The image is drawn onto an HTML5 canvas. Its correct rendering is implemented in js/load-image-orientation.js through canvas operations.

Hope this saves somebody else some time, and teaches the search engines about this open source gem :)

Solution 2 - Javascript

Mederr's context transform works perfectly. If you need to extract orientation only use this function - you don't need any EXIF-reading libs. Below is a function for re-setting orientation in base64 image. Here's a fiddle for it. I've also prepared a fiddle with orientation extraction demo.

function resetOrientation(srcBase64, srcOrientation, callback) {
  var img = new Image();	

  img.onload = function() {
    var width = img.width,
        height = img.height,
        canvas = document.createElement('canvas'),
	    ctx = canvas.getContext("2d");
		
    // set proper canvas dimensions before transform & export
    if (4 < srcOrientation && srcOrientation < 9) {
      canvas.width = height;
      canvas.height = width;
    } else {
   	  canvas.width = width;
      canvas.height = height;
    }
	
    // transform context before drawing image
    switch (srcOrientation) {
      case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
      case 3: ctx.transform(-1, 0, 0, -1, width, height); break;
      case 4: ctx.transform(1, 0, 0, -1, 0, height); break;
      case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
      case 6: ctx.transform(0, 1, -1, 0, height, 0); break;
      case 7: ctx.transform(0, -1, -1, 0, height, width); break;
      case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
      default: break;
    }

    // draw image
    ctx.drawImage(img, 0, 0);

    // export base64
    callback(canvas.toDataURL());
  };

  img.src = srcBase64;
};

Solution 3 - Javascript

If

width = img.width;
height = img.height;
var ctx = canvas.getContext('2d');

Then you can use these transformations to turn the image to orientation 1

From orientation:

  1. ctx.transform(1, 0, 0, 1, 0, 0);
  2. ctx.transform(-1, 0, 0, 1, width, 0);
  3. ctx.transform(-1, 0, 0, -1, width, height);
  4. ctx.transform(1, 0, 0, -1, 0, height);
  5. ctx.transform(0, 1, 1, 0, 0, 0);
  6. ctx.transform(0, 1, -1, 0, height, 0);
  7. ctx.transform(0, -1, -1, 0, height, width);
  8. ctx.transform(0, -1, 1, 0, 0, width);

Before drawing the image on ctx

Solution 4 - Javascript

ok in addition to @user3096626 answer i think it will be more helpful if someone provided code example, the following example will show you how to fix image orientation comes from url (remote images):


Solution 1: using javascript (recommended)

  1. because load-image library doesn't extract exif tags from url images only (file/blob), we will use both exif-js and load-image javascript libraries, so first add these libraries to your page as the follow:

    <script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.1.0/exif.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image-scale.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-load-image/2.12.2/load-image-orientation.min.js"></script>
    

Note the version 2.2 of exif-js seems has issues so we used 2.1

  1. then basically what we will do is

a - load the image using window.loadImage()

b - read exif tags using window.EXIF.getData()

c - convert the image to canvas and fix the image orientation using window.loadImage.scale()

d - place the canvas into the document

here you go :)

window.loadImage("/your-image.jpg", function (img) {
  if (img.type === "error") {
    console.log("couldn't load image:", img);
  } else {
    window.EXIF.getData(img, function () {
        var orientation = EXIF.getTag(this, "Orientation");
        var canvas = window.loadImage.scale(img, {orientation: orientation || 0, canvas: true});
        document.getElementById("container").appendChild(canvas); 
        // or using jquery $("#container").append(canvas);
        
    });
  }
});

of course also you can get the image as base64 from the canvas object and place it in the img src attribute, so using jQuery you can do ;)

$("#my-image").attr("src",canvas.toDataURL());

here is the full code on: github: https://github.com/digital-flowers/loadimage-exif-example


Solution 2: using html (browser hack)

there is a very quick and easy hack, most browsers display the image in the right orientation if the image is opened inside a new tab directly without any html (LOL i don't know why), so basically you can display your image using iframe by putting the iframe src attribute as the image url directly:

<iframe src="/my-image.jpg"></iframe>

Solution 3: using css (only firefox & safari on ios)

there is css3 attribute to fix image orientation but the problem it is only working on firefox and safari/ios it is still worth mention because soon it will be available for all browsers (Browser support info from caniuse)

img {
   image-orientation: from-image;
}

Solution 5 - Javascript

For those who have a file from an input control, don't know what its orientation is, are a bit lazy and don't want to include a large library below is the code provided by @WunderBart melded with the answer he links to (https://stackoverflow.com/a/32490603) that finds the orientation.

function getDataUrl(file, callback2) {
        var callback = function (srcOrientation) {
            var reader2 = new FileReader();
            reader2.onload = function (e) {
                var srcBase64 = e.target.result;
                var img = new Image();

                img.onload = function () {
                    var width = img.width,
                        height = img.height,
                        canvas = document.createElement('canvas'),
                        ctx = canvas.getContext("2d");

                    // set proper canvas dimensions before transform & export
                    if (4 < srcOrientation && srcOrientation < 9) {
                        canvas.width = height;
                        canvas.height = width;
                    } else {
                        canvas.width = width;
                        canvas.height = height;
                    }

                    // transform context before drawing image
                    switch (srcOrientation) {
                        case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
                        case 3: ctx.transform(-1, 0, 0, -1, width, height); break;
                        case 4: ctx.transform(1, 0, 0, -1, 0, height); break;
                        case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
                        case 6: ctx.transform(0, 1, -1, 0, height, 0); break;
                        case 7: ctx.transform(0, -1, -1, 0, height, width); break;
                        case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
                        default: break;
                    }

                    // draw image
                    ctx.drawImage(img, 0, 0);

                    // export base64
                    callback2(canvas.toDataURL());
                };

                img.src = srcBase64;
            }

            reader2.readAsDataURL(file);
        }

        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);
    }

which can easily be called like such

getDataUrl(input.files[0], function (imgBase64) {
      vm.user.BioPhoto = imgBase64;
});

Solution 6 - Javascript

WunderBart's answer was the best for me. Note that you can speed it up a lot if your images are often the right way around, simply by testing the orientation first and bypassing the rest of the code if no rotation is required.

Putting all of the info from wunderbart together, something like this;

var handleTakePhoto = function () {
    let fileInput: HTMLInputElement = <HTMLInputElement>document.getElementById('photoInput');
    fileInput.addEventListener('change', (e: any) => handleInputUpdated(fileInput, e.target.files));
    fileInput.click();
}

var handleInputUpdated = function (fileInput: HTMLInputElement, fileList) {
    let file = null;

    if (fileList.length > 0 && fileList[0].type.match(/^image\//)) {
        isLoading(true);
        file = fileList[0];
        getOrientation(file, function (orientation) {
            if (orientation == 1) {
                imageBinary(URL.createObjectURL(file));
                isLoading(false);
            }
            else 
            {
                resetOrientation(URL.createObjectURL(file), orientation, function (resetBase64Image) {
                    imageBinary(resetBase64Image);
                    isLoading(false);
                });
            }
        });
    }

    fileInput.removeEventListener('change');
}


// from http://stackoverflow.com/a/32490603
export function getOrientation(file, callback) {
    var reader = new FileReader();

    reader.onload = function (event: any) {
        var view = new DataView(event.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.slice(0, 64 * 1024));
};

export function resetOrientation(srcBase64, srcOrientation, callback) {
    var img = new Image();

    img.onload = function () {
        var width = img.width,
            height = img.height,
            canvas = document.createElement('canvas'),
            ctx = canvas.getContext("2d");

        // set proper canvas dimensions before transform & export
        if (4 < srcOrientation && srcOrientation < 9) {
            canvas.width = height;
            canvas.height = width;
        } else {
            canvas.width = width;
            canvas.height = height;
        }

        // transform context before drawing image
        switch (srcOrientation) {
            case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
            case 3: ctx.transform(-1, 0, 0, -1, width, height); break;
            case 4: ctx.transform(1, 0, 0, -1, 0, height); break;
            case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
            case 6: ctx.transform(0, 1, -1, 0, height, 0); break;
            case 7: ctx.transform(0, -1, -1, 0, height, width); break;
            case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
            default: break;
        }

        // draw image
        ctx.drawImage(img, 0, 0);

        // export base64
        callback(canvas.toDataURL());
    };

    img.src = srcBase64;
}

Solution 7 - Javascript

One liner anyone?

I haven't seen anyone mention the https://www.npmjs.com/package/browser-image-compression">`browser-image-compression` library. It's got a helper function perfect for this.

Usage: const orientation = await imageCompression.getExifOrientation(file)

Such a useful tool in many other ways too.

Solution 8 - Javascript

I created a class wrapped in an ES6 module that solves exactly this.

It's 103 lines, no dependencies, and fairly nicely structured and documented, meant to be easy to modify/reuse.

Handles all 8 possible orientations, and is Promise-based.

Here you go, hope this still helps someone: https://gist.github.com/vdavid/3f9b66b60f52204317a4cc0e77097913

Solution 9 - Javascript

Wunderbart's post worked for me combined with statler's improvements. Adding a few more comments and syntax cleanup, and also passing back the orientation value and I have the following code feel free to use. Just call readImageFile() function below and you get back the transformed image and the original orientation.

const JpegOrientation = [
    "NOT_JPEG",
    "NORMAL",
    "FLIP-HORIZ",
    "ROT180",
    "FLIP-HORIZ-ROT180",
    "FLIP-HORIZ-ROT270",
    "ROT270",
    "FLIP-HORIZ-ROT90",
    "ROT90"
];


//Provided a image file, determines the orientation of the file based on the EXIF information.
//Calls the "callback" function with an index into the JpegOrientation array. 
//If the image is not a JPEG, returns 0. If  the orientation value cannot be read (corrupted file?) return -1.
function getOrientation(file, callback) {
    
    const reader = new FileReader();
    reader.onload = (e) => {

        const view = new DataView(e.target.result);
        
        if (view.getUint16(0, false) !== 0xFFD8) {
            return callback(0);  //NOT A JPEG FILE
        }
        
        const length = view.byteLength;
        let offset = 2;
        while (offset < length) {
            
            if (view.getUint16(offset+2, false) <= 8)   //unknown?
                return callback(-1);
            
            const marker = view.getUint16(offset, false);
            offset += 2;
            if (marker === 0xFFE1) {
                
                if (view.getUint32(offset += 2, false) !== 0x45786966) 
                    return callback(-1); //unknown?
                

                const little = view.getUint16(offset += 6, false) === 0x4949;
                offset += view.getUint32(offset + 4, little);
                const 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));   //found orientation code
                    }
                }
            }
            else if ((marker & 0xFF00) !== 0xFF00) {
                break;
            }
            else { 
                offset += view.getUint16(offset, false);
            }
        }
        
        return callback(-1); //unknown?
    };
    reader.readAsArrayBuffer(file);
}

//Takes a jpeg image file as base64 and transforms it back to original, providing the
//transformed image in callback.  If the image is not a jpeg or is already in normal orientation,
//just calls the callback directly with the source.
//Set type to the desired output type if transformed, default is image/jpeg for speed.
function resetOrientation(srcBase64, srcOrientation, callback, type = "image/jpeg") {
    
    if (srcOrientation <= 1) {  //no transform needed
        callback(srcBase64);
        return;
    }
    
    const img = new Image();    

    img.onload = () => {
        const width = img.width;
        const height = img.height;
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext("2d");

        // set proper canvas dimensions before transform & export
        if (4 < srcOrientation && srcOrientation < 9) {
            canvas.width = height;
            canvas.height = width;
        } else {
            canvas.width = width;
            canvas.height = height;
        }

        // transform context before drawing image
        switch (srcOrientation) {
              
              //case 1: normal, no transform needed
              
              case 2:  
                  ctx.transform(-1, 0, 0, 1, width, 0); 
                  break;
              case 3:
                  ctx.transform(-1, 0, 0, -1, width, height); 
                  break;
              case 4: 
                  ctx.transform(1, 0, 0, -1, 0, height); 
                  break;
              case 5: 
                  ctx.transform(0, 1, 1, 0, 0, 0); 
                  break;
              case 6: 
                  ctx.transform(0, 1, -1, 0, height, 0); 
                  break;
              case 7: 
                  ctx.transform(0, -1, -1, 0, height, width); 
                  break;
              case 8: 
                  ctx.transform(0, -1, 1, 0, 0, width); 
                  break;
              default: 
                  break;
        }

        // draw image
        ctx.drawImage(img, 0, 0);

        //export base64
        callback(canvas.toDataURL(type), srcOrientation);
    };

    img.src = srcBase64;
};


//Read an image file, providing the returned data to callback. If the image is jpeg
//and is transformed according to EXIF info, transform it first.
//The callback function receives the image data and the orientation value (index into JpegOrientation)
export function readImageFile(file, callback) {

    getOrientation(file, (orientation) => {

        console.log("Read file \"" + file.name + "\" with orientation: " + JpegOrientation[orientation]);

        const reader = new FileReader();
        reader.onload = () => {  //when reading complete

            const img = reader.result;
            resetOrientation(img, orientation, callback);
        };
        reader.readAsDataURL(file);  //start read
        
    });
}

Solution 10 - Javascript

I am using mixed solution (php+css).

Containers are needed for:

  • div.imgCont2 container needed to rotate;
  • div.imgCont1 container needed to zoomOut - width:150%;
  • div.imgCont container needed for scrollbars, when image is zoomOut.

.

<?php
    $image_url = 'your image url.jpg';
    $exif = @exif_read_data($image_url,0,true);
    $orientation = @$exif['IFD0']['Orientation'];
?>

<style>
.imgCont{
    width:100%;
    overflow:auto;
}
.imgCont2[data-orientation="8"]{
    transform:rotate(270deg);
    margin:15% 0;
}
.imgCont2[data-orientation="6"]{
    transform:rotate(90deg);
    margin:15% 0;
}
.imgCont2[data-orientation="3"]{
    transform:rotate(180deg);
}
img{
    width:100%;
}
</style>

<div class="imgCont">
  <div class="imgCont1">
    <div class="imgCont2" data-orientation="<?php echo($orientation) ?>">
      <img src="<?php echo($image_url) ?>">
    </div>
  </div>
</div>

Solution 11 - Javascript

In addition to @fareed namrouti's answer,

This should be used if the image has to be browsed from a file input element

<input type="file" name="file" id="file-input"><br/>
image after transform: <br/>
<div id="container"></div>

<script>
    document.getElementById('file-input').onchange = function (e) {
        var image = e.target.files[0];
        window.loadImage(image, function (img) {
            if (img.type === "error") {
                console.log("couldn't load image:", img);
            } else {
                window.EXIF.getData(image, function () {
                    console.log("load image done!");
                    var orientation = window.EXIF.getTag(this, "Orientation");
                    var canvas = window.loadImage.scale(img,
                        {orientation: orientation || 0, canvas: true, maxWidth: 200});
                    document.getElementById("container").appendChild(canvas);
                    // or using jquery $("#container").append(canvas);
                });
            }
        });
    };
</script>

Solution 12 - Javascript

I've written a little php script which rotates the image. Be sure to store the image in favour of just recalculate it each request.

<?php

header("Content-type: image/jpeg");
$img = 'IMG URL';

$exif = @exif_read_data($img,0,true);
$orientation = @$exif['IFD0']['Orientation'];
if($orientation == 7 || $orientation == 8) {
    $degrees = 90;
} elseif($orientation == 5 || $orientation == 6) {
    $degrees = 270;
} elseif($orientation == 3 || $orientation == 4) {
    $degrees = 180;
} else {
    $degrees = 0;
}
$rotate = imagerotate(imagecreatefromjpeg($img), $degrees, 0);
imagejpeg($rotate);
imagedestroy($rotate);

?>

Cheers

Solution 13 - Javascript

In my case, exif-auto-rotate library is what I needed.

I had base64 format image coming from backend and I had to rotate it to its correct orientation before using it. It returns you base64 after changing the rotation, which is a plus for me too.

Here is the npm link for that library: https://www.npmjs.com/package/exif-auto-rotate

Solution 14 - Javascript

This answer is for Angular people, here is a package that helps you find the orientation.

In the below example I have used changeEvent for a input tag but you can use this if you have a dataUrl

If you don't have the dataUrl , you can convert the File -> dataUrl or blob -> dataUrl easily. I Have attached File -> convertor function that helps me convert my selected image File to dataUrl

https://www.npmjs.com/package/ngx-image-compress This is the package npm i ngx-image-compress

import { DOC_ORIENTATION, NgxImageCompressService } from 'ngx-image-compress';

@Component({
  selector: 'app-test',
  templateUrl: './test.component.html',
  styleUrls: ['./test.component.scss'],
})

export class Test {
     constructor(private imageCompress: NgxImageCompressService) {}

      async onFileSelected(event: any) {
         const file: File = event.target.files[0]
         const dataUrl : string = await this.fileToDataURL(file)
         const orientation : DOC_ORIENTATION = await this.imageCompress.getOrientation(file)

      }
      
     fileToDataURL(blob: Blob): Promise<string> {
        return new Promise<string>((resolve, reject) => {
           const reader = new FileReader();
           reader.onload = _e => resolve(reader.result as string);
           reader.onerror = _e => reject(reader.error);
           reader.onabort = _e => reject(new Error("Read aborted"));
           reader.readAsDataURL(blob);
    });
  }

}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionflexponsiveView Question on Stackoverflow
Solution 1 - JavascriptflexponsiveView Answer on Stackoverflow
Solution 2 - JavascriptWunderBartView Answer on Stackoverflow
Solution 3 - JavascriptMederrView Answer on Stackoverflow
Solution 4 - JavascriptFareed AlnamroutiView Answer on Stackoverflow
Solution 5 - Javascriptrunxc1 Bret FerrierView Answer on Stackoverflow
Solution 6 - JavascriptstatlerView Answer on Stackoverflow
Solution 7 - Javascriptgilbert-vView Answer on Stackoverflow
Solution 8 - JavascriptDavid VeszelovszkiView Answer on Stackoverflow
Solution 9 - JavascriptLocutusView Answer on Stackoverflow
Solution 10 - JavascriptSergey SView Answer on Stackoverflow
Solution 11 - JavascriptPerryView Answer on Stackoverflow
Solution 12 - JavascriptThomView Answer on Stackoverflow
Solution 13 - JavascriptBaran TatarView Answer on Stackoverflow
Solution 14 - JavascriptShrihariView Answer on Stackoverflow