Javascript JS客户端Exif方向:旋转和镜像JPEG图像
数码相机照片通常保存为带有EXIF“方向”标记的JPEG格式。要正确显示,需要根据设置的方向旋转/镜像图像,但浏览器会忽略渲染图像的此信息。即使在大型商业web应用程序中,对EXIF定向的支持也可能参差不齐。同一来源还提供了JPEG可以具有的8种不同方向的摘要: 样本图像可从以下网址获得: 问题是如何在客户端旋转/镜像图像,使其正确显示,并在必要时进行进一步处理? 有JS库可用于解析EXIF数据,包括方向属性。Flickr注意到解析大型图像时可能存在性能问题,需要使用webworkers 控制台工具可以正确地重新定向图像。解决此问题的PHP脚本可在上找到。github项目为EXIF方向问题提供了完整的解决方案,为所有8个EXIF方向正确旋转/镜像图像。请参阅的在线演示 图像被绘制到HTML5画布上。它的正确呈现是通过画布操作在中实现的 希望这能为其他人节省一些时间,并让搜索引擎了解这个开源的宝石:) 然后可以使用这些变换将图像旋转到方向1 从 方向:Javascript JS客户端Exif方向:旋转和镜像JPEG图像,javascript,rotation,html5-canvas,exif,Javascript,Rotation,Html5 Canvas,Exif,数码相机照片通常保存为带有EXIF“方向”标记的JPEG格式。要正确显示,需要根据设置的方向旋转/镜像图像,但浏览器会忽略渲染图像的此信息。即使在大型商业web应用程序中,对EXIF定向的支持也可能参差不齐。同一来源还提供了JPEG可以具有的8种不同方向的摘要: 样本图像可从以下网址获得: 问题是如何在客户端旋转/镜像图像,使其正确显示,并在必要时进行进一步处理? 有JS库可用于解析EXIF数据,包括方向属性。Flickr注意到解析大型图像时可能存在性能问题,需要使用webworkers 控制
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(推荐)
<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
});
}