Javascript JS客户端Exif方向:旋转和镜像JPEG图像

Javascript JS客户端Exif方向:旋转和镜像JPEG图像,javascript,rotation,html5-canvas,exif,Javascript,Rotation,Html5 Canvas,Exif,数码相机照片通常保存为带有EXIF“方向”标记的JPEG格式。要正确显示,需要根据设置的方向旋转/镜像图像,但浏览器会忽略渲染图像的此信息。即使在大型商业web应用程序中,对EXIF定向的支持也可能参差不齐。同一来源还提供了JPEG可以具有的8种不同方向的摘要: 样本图像可从以下网址获得: 问题是如何在客户端旋转/镜像图像,使其正确显示,并在必要时进行进一步处理? 有JS库可用于解析EXIF数据,包括方向属性。Flickr注意到解析大型图像时可能存在性能问题,需要使用webworkers 控制

数码相机照片通常保存为带有EXIF“方向”标记的JPEG格式。要正确显示,需要根据设置的方向旋转/镜像图像,但浏览器会忽略渲染图像的此信息。即使在大型商业web应用程序中,对EXIF定向的支持也可能参差不齐。同一来源还提供了JPEG可以具有的8种不同方向的摘要:

样本图像可从以下网址获得:

问题是如何在客户端旋转/镜像图像,使其正确显示,并在必要时进行进一步处理?

有JS库可用于解析EXIF数据,包括方向属性。Flickr注意到解析大型图像时可能存在性能问题,需要使用webworkers

控制台工具可以正确地重新定向图像。解决此问题的PHP脚本可在

上找到。github项目为EXIF方向问题提供了完整的解决方案,为所有8个EXIF方向正确旋转/镜像图像。请参阅的在线演示

图像被绘制到HTML5画布上。它的正确呈现是通过画布操作在中实现的

希望这能为其他人节省一些时间,并让搜索引擎了解这个开源的宝石:)

然后可以使用这些变换将图像旋转到方向1

从 方向:

  • ctx.transform(1,0,0,1,0,0)
  • ctx.transform(-1,0,0,1,width,0)
  • ctx.transform(-1,0,0,-1,宽度,高度)
  • ctx.transform(1,0,0,-1,0,高度)
  • ctx.transform(0,1,1,0,0,0)
  • ctx.transform(0,1,-1,0,高度,0)
  • ctx.transform(0,-1,-1,0,高度,宽度)
  • ctx.transform(0,-1,1,0,0,宽度)

  • 在ctx上绘制图像之前

    确定除了@user3096626答案之外,我认为如果有人提供代码示例会更有帮助,下面的示例将向您展示如何修复来自url的图像方向(远程图像):


    解决方案1:使用javascript(推荐)

  • 由于load image库不会仅从url图像(文件/blob)中提取exif标记,因此我们将同时使用javascript库,因此首先将这些库添加到页面中,如下所示:

    <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>
    
    当然,您也可以从canvas对象中获取图像作为base64,并将其放置在img src属性中,因此使用jQuery可以做到;)

    以下是:github上的完整代码:


    解决方案2:使用html(浏览器黑客)

    有一个非常简单快捷的破解方法,如果图像直接在新选项卡中打开而不使用任何html(LOL,我不知道为什么),大多数浏览器都会以正确的方向显示图像,因此基本上您可以使用iframe直接将iframe src属性作为图像url来显示图像:

    <iframe src="/my-image.jpg"></iframe>
    
    工作完美。如果您只需要提取方向,请使用-您不需要任何EXIF读取库。下面是在base64图像中重新设置方向的函数。 . 我还准备了一份

    函数resetOrientation(srcBase64、srcOrientation、回调){
    var img=新图像();
    img.onload=函数(){
    变量宽度=img.width,
    高度=最小高度,
    canvas=document.createElement('canvas'),
    ctx=canvas.getContext(“2d”);
    //在转换和导出之前设置适当的画布尺寸
    if(4
    除了@Fared namrouti的回答

    如果必须从文件输入元素浏览图像,则应使用此选项

    <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>
    

    变换后的图像:
    document.getElementById('file-input')。onchange=function(e){ var image=e.target.files[0]; window.loadImage(图像、函数(img){ 如果(img.type==“错误”){ log(“无法加载图像:”,img); }否则{ window.EXIF.getData(图像,函数(){ log(“加载图像完成!”); var-orientation=window.EXIF.getTag(这个“方向”); var canvas=window.loadImage.scale(img, {orientation:orientation | | 0,canvas:true,maxWidth:200}); document.getElementById(“容器”).appendChild(画布); //或者使用jquery$(“#容器”).append(画布); }); } }); };
    我使用的是混合解决方案(php+css)

    以下各项需要容器:

    • div.imgCont2
      需要旋转的容器
    • div.imgCont1
      缩放所需的容器-
      宽度:150%
    • div.imgCont
      图像缩放时滚动条所需的容器

    
    .imgCont{
    宽度:100%;
    溢出:自动;
    }
    .imgCont2[data orientation=“8”]{
    变换:旋转(270度);
    保证金:1
    
    $("#my-image").attr("src",canvas.toDataURL());
    
    <iframe src="/my-image.jpg"></iframe>
    
    img {
       image-orientation: from-image;
    }
    
    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;
    };
    
    <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>
    
    <?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>
    
    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);
        }
    
    getDataUrl(input.files[0], function (imgBase64) {
          vm.user.BioPhoto = imgBase64;
    });
    
    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;
    }
    
    <?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);
    
    ?>
    
    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
            
        });
    }