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

前端 未结 12 897
花落未央
花落未央 2020-11-22 11:59

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 se

相关标签:
12条回答
  • 2020-11-22 12:27

    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>
    
    0 讨论(0)
  • 2020-11-22 12:29

    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;
    };
    
    0 讨论(0)
  • 2020-11-22 12:29

    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>
    
    0 讨论(0)
  • 2020-11-22 12:34

    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

    0 讨论(0)
  • 2020-11-22 12:35

    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;
    });
    
    0 讨论(0)
  • 2020-11-22 12:39

    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
            
        });
    }
    
    0 讨论(0)
提交回复
热议问题