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

数码相机的照片经常被保存为带有EXIF“方向”标签的JPEG格式。 为了正确显示,图像需要根据设置的方向进行旋转/镜像,但是浏览器会忽略渲染图像的这些信息。 即使在大型商业Web应用程序中,对EXIF方向的支持也可能是多余的1 。 同样的来源也提供了JPEG可以具有的8个不同方向的很好的总结:

EXIF方向概述

示例图像可在4 。

问题是如何旋转/镜像客户端的图像,使其正确显示,并可以进一步处理,如果有必要?

有JS库可用来parsingEXIF数据,包括方向属性2 。 Flickr注意到parsing大图片时可能出现的性能问题,需要使用webworkers 3 。

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

github项目JavaScript-Load-Image为EXIF方向问题提供了一个完整的解决scheme,正确旋转/镜像所有8个exif方向的图像。 请参阅javascript exif方向的在线演示

图像被绘制到HTML5canvas上。 其正确的渲染是通过canvas操作在js / load-image-orientation.js中实现的。

希望这节省了别人的时间,并教导search引擎关于这个开源gem 🙂

Mederr的上下文转换非常完美。 如果你只需要使用这个函数来提取方向 – 你不需要任何EXIF阅读库。 以下是在base64图像中重新设置方向的function。 这是一个小提琴 。 我还准备了一个提取方向提取演示的小提琴 。

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

如果

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

然后,您可以使用这些转换将图像转向方向1

从方向:

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

在ctx上绘制图像之前

确定除了@ user3096626答案我认为这将是更有帮助的,如果有人提供代码示例,下面的示例将告诉你如何解决从url(远程图像)来的图像方向:


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

  1. 因为加载图像库不会从url图像(文件/ blob)中提取exif标签,所以我们将同时使用exif-js加载图像 javascript库,所以首先将这些库添加到您的页面,如下所示:

     <script src="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> 

    注意 exif-js的2.2版似乎有问题,所以我们用了2.1

  2. 那么基本上我们会做的是

    – 使用window.loadImage()加载图像

    b – 使用window.EXIF.getData()读取exif标签

    c – 将图像转换为canvas并使用window.loadImage.scale()修复图像方向

    d – 将canvas放入文档中

干得好 :)

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

当然你也可以从canvas对象中以base64的forms获取图像,并将其放在img src属性中,所以使用jQuery你可以做到;)

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

这里是完整的代码:github: https : //github.com/digital-flowers/loadimage-exif-example


解决scheme2:使用html(浏览器黑客)

有一个非常快速和容易的黑客攻击,大多数浏览器显示图像在正确的方向,如果图像打开一个新的标签直接没有任何HTML(大声笑,我不知道为什么),所以基本上可以显示您的图像使用iframe通过直接将iframe src属性作为图像url:

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

解决scheme3:使用CSS(仅在iOS上使用firefox&safari)

有css3属性来修复图像的方向,但它只是在firefox和safari / ios上工作的问题,它仍然值得一提,因为很快它将可用于所有浏览器(来自caniuse的浏览器支持信息)

 img { image-orientation: from-image; } 

我正在使用混合解决scheme(PHP + CSS)。

容器需要用于:

  • 需要旋转div.imgCont2容器;
  • div.imgCont1容器需要div.imgCont1width:150% ;
  • 当图像是div.imgCont时,滚动条需要div.imgCont容器。

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

WunderBart的回答对我来说是最好的。 请注意,如果您的图像经常是正确的,可以加快速度,只需通过先testing方向并绕过代码的其余部分(如果不需要旋转)即可。

把所有来自wunderbart的信息放在一起,像这样;

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

除了@呃namrouti的答案,

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

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

对于那些有input控件的文件,不知道它的方向是什么,有点懒,不想在下面包含大型库,是由@WunderBart提供的代码与他链接的答案融合( https://stackoverflow.com/a/32490603 )find方向。

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