JS 客户端 Exif 定位: 旋转和镜像 JPEG 图像

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

Summary of EXIF Orientations

样本图像可在 4获得。

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

有 JS 库可用于解析 EXIF 数据,包括方向属性 2。Flickr 指出在解析大图像时可能存在性能问题,需要使用 webworker 3

控制台工具可以正确地重定向图像 5。解决这个问题的 PHP 脚本可以在 6上找到

192628 次浏览

Github 项目 JavaScript-Load-Image提供了 EXIF 方向问题的完整解决方案,可以正确地旋转/镜像所有8个 EXIF 方向的图像。参见 面向 exif 的 javascript的在线演示

将图像绘制到 HTML5画布上,通过画布操作在 加载-图像-方向中实现正确的渲染。

希望这样可以节省其他人一些时间,并教给搜索引擎关于这个开源的宝石:)

如果

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 图像(file/blob)中提取 exif 标记,我们将同时使用 Ref = “ https://github.com/exif-js/exif-js”rel = “ norefrer”> exif-js Http://github.com/bluimp/JavaScript-Load-Image”rel = “ noReferrer”> load-image 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>
    

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

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

    使用 window.loadImage()加载图像

    使用 window.EXIF.getData()读取 exif 标记

    C-将图像转换为画布并使用 window.loadImage.scale()修正图像方向

    将画布放入文档中

给你:)

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


});
}
});

当然,您也可以从画布对象获得 base64图像,并将其放在 img src 属性中,因此可以使用 jQuery;)

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

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


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

有一个非常快速和简单的黑客,大多数浏览器显示图像在正确的方向,如果图像是在一个新的标签打开直接没有任何 html (LOL 我不知道为什么) ,所以基本上你可以显示你的图像使用 iframe 通过把 iframe src 属性作为图像网址直接:

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

解决方案3: 使用 css (仅在 ios 上使用 firefox & safari)

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

img {
image-orientation: from-image;
}

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

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

除了@Fareed 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>

我使用混合溶液(php + css)。

需要容器:

  • div.imgCont2容器需要旋转;
  • 放大所需的 div.imgCont1容器 -width:150%;
  • 当图像放大时,滚动条需要 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 提供的代码,它与他链接到的找到方向的答案(https://stackoverflow.com/a/32490603)融合在一起。

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

神童巴特的回答对我来说是最好的。请注意,如果您的图像通常是正确的,那么您可以大大加快速度,只需首先测试方向,如果不需要旋转,则跳过代码的其余部分即可。

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

我已经写了一个小的 php 脚本来旋转图像。一定要存储图像,以便每个请求都重新计算它。

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


?>

干杯

有人要班轮吗?

我没有看到任何人提到 browser-image-compression。它有一个助手函数非常适合这个。

用法: const orientation = await imageCompression.getExifOrientation(file)

在许多其他方面也是如此有用的工具。

我创建了一个包装在 ES6模块中的类,正好解决了这个问题。

它有103行,没有依赖项,结构和文档都很好,很容易修改/重用。

处理所有8种可能的方向,并且是基于承诺的。

给你,希望这个还能帮到别人: https://gist.github.com/vdavid/3f9b66b60f52204317a4cc0e77097913

Wunderbart 的 邮寄和 Statler 的 改进结合起来为我工作。添加一些注释和语法清理,同时还传回方向值,下面的代码可以随意使用。只要调用下面的 readImageFile()函数,就可以得到转换后的图像和原始方向。

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
        

});
}

在我的例子中,我需要的是 exif 自动旋转库。

我有来自后端的 base64格式的图像,我必须旋转它到正确的方向之前使用它。改变旋转后返回 base64对我来说也是个好处。

下面是该库的 npm 链接: https://www.npmjs.com/package/exif-auto-rotate

这个答案是为 角度的人,这里有一个软件包,可以帮助你找到方向。

在下面的示例中,我使用 changeEvent 作为输入标记,但是如果您有 dataUrl,则可以使用它

如果没有 dataUrl,可以轻松地转换 File-> dataUrl 或 blob-> dataUrl。我有附加的文件-> 转换器函数,帮助我转换我选择的图像文件到 dataUrl

Https://www.npmjs.com/package/ngx-image-compress 这是包裹 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);
});
}


}