JS客户端Exif方向:旋转和镜像JPEG图像
数码相机的照片经常被保存为带有EXIF“方向”标签的JPEG格式。 为了正确显示,图像需要根据设置的方向进行旋转/镜像,但是浏览器会忽略渲染图像的这些信息。 即使在大型商业Web应用程序中,对EXIF方向的支持也可能是多余的1 。 同样的来源也提供了JPEG可以具有的8个不同方向的很好的总结:
示例图像可在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
从方向:
-
ctx.transform(1, 0, 0, 1, 0, 0);
-
ctx.transform(-1, 0, 0, 1, width, 0);
-
ctx.transform(-1, 0, 0, -1, width, height );
-
ctx.transform(1, 0, 0, -1, 0, height );
-
ctx.transform(0, 1, 1, 0, 0, 0);
-
ctx.transform(0, 1, -1, 0, height , 0);
-
ctx.transform(0, -1, -1, 0, height , width);
-
ctx.transform(0, -1, 1, 0, 0, width);
在ctx上绘制图像之前
确定除了@ user3096626答案我认为这将是更有帮助的,如果有人提供代码示例,下面的示例将告诉你如何解决从url(远程图像)来的图像方向:
解决方法1:使用javascript(推荐)
-
因为加载图像库不会从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
-
那么基本上我们会做的是
– 使用
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.imgCont1
–width: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; });