Доступ к данным JPEG EXIF для вращения в JavaScript на стороне клиента
Я хотел бы повернуть фотографии на основе их первоначального вращения, заданного камерой в данных JPEG EXIF. Фокус в том, что все это должно произойти в браузере, используя JavaScript и <canvas>
.
Как можно использовать JavaScript для доступа к JPEG, локальному файлу API, локальному <img>
или удаленному <img>
, EXIF-данным для чтения информации о ротации?
Ответы на стороне сервера не в порядке; Я ищу клиентское решение.
Ответы
Ответ 1
Если вам нужен только тег ориентации и больше ничего, и вы не хотите включать в себя еще одну огромную библиотеку JavaScript, я пишу небольшой код, который извлекает только тег ориентации как можно быстрее (он использует DataView и readAsArrayBuffer
которые доступны в IE10+, но Вы можете написать свой собственный читатель данных для старых браузеров):
function getOrientation(file, callback) {
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)
{
if (view.getUint16(offset+2, false) <= 8) return callback(-1);
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);
}
// usage:
var input = document.getElementById('input');
input.onchange = function(e) {
getOrientation(input.files[0], function(orientation) {
alert('orientation: ' + orientation);
});
}
<input id='input' type='file' />
Ответ 2
Вы можете использовать библиотеку exif-js в сочетании с API-интерфейсом файлов HTML5: http://jsfiddle.net/xQnMd/1/.
$("input").change(function() {
var file = this.files[0]; // file
fr = new FileReader; // to read file contents
fr.onloadend = function() {
// get EXIF data
var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));
// alert a value
alert(exif.Make);
};
fr.readAsBinaryString(file); // read the file
});
Ответ 3
Firefox 26 поддерживает image-orientation: from-image
: изображения отображаются в книжной или альбомной image-orientation: from-image
, в зависимости от данных EXIF. (См. Sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation.)
Существует также ошибка для реализации этого в Chrome.
Помните, что это свойство поддерживается только Firefox и, скорее всего, не рекомендуется к использованию.
Ответ 4
https://github.com/blueimp/JavaScript-Load-Image - это современная библиотека javascript, которая может не только извлекать флаг ориентации exif - он также может корректно отображать/поворачивать изображения JPEG на стороне клиента.
Я просто решил ту же проблему с этой библиотекой: Ориентация Exif на стороне клиента JS: поворот и зеркальное отображение изображений в формате JPEG
Ответ 5
Если вам нужен кросс-браузер, лучше всего сделать это на сервере. У вас может быть API, который принимает URL-адрес файла и возвращает данные EXIF; У PHP есть модуль для этого.
Это можно сделать, используя Ajax, чтобы он был бесшовным для пользователя. Если вам не нужна совместимость с кросс-браузером и вы можете полагаться на HTML5, посмотрите в библиотеку JsJPEGmeta, который позволит вам получить эти данные на родном JavaScript.
Ответ 6
Проверьте модуль, который я написал (вы можете использовать его в браузере), который преобразует ориентацию exif в преобразование CSS: https://github.com/Sobesednik/exif2css
Существует также программа node для создания JPEG-светильников со всеми ориентациями: https://github.com/Sobesednik/generate-exif-fixtures
Ответ 7
Я загружаю код расширения , чтобы показать фото с камеры android на html, как обычно, на каком-то теге img с правильным rotaion, особенно для тега img, ширина которого шире, чем высота. Я знаю, что этот код уродлив, но вам не нужно устанавливать какие-либо другие пакеты. (Я использовал код выше, чтобы получить значение поворота exif, спасибо.)
function getOrientation(file, callback) {
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);
}
var isChanged = false;
function rotate(elem, orientation) {
if (isIPhone()) return;
var degree = 0;
switch (orientation) {
case 1:
degree = 0;
break;
case 2:
degree = 0;
break;
case 3:
degree = 180;
break;
case 4:
degree = 180;
break;
case 5:
degree = 90;
break;
case 6:
degree = 90;
break;
case 7:
degree = 270;
break;
case 8:
degree = 270;
break;
}
$(elem).css('transform', 'rotate('+ degree +'deg)')
if(degree == 90 || degree == 270) {
if (!isChanged) {
changeWidthAndHeight(elem)
isChanged = true
}
} else if ($(elem).css('height') > $(elem).css('width')) {
if (!isChanged) {
changeWidthAndHeightWithOutMargin(elem)
isChanged = true
} else if(degree == 180 || degree == 0) {
changeWidthAndHeightWithOutMargin(elem)
if (!isChanged)
isChanged = true
else
isChanged = false
}
}
}
function changeWidthAndHeight(elem){
var e = $(elem)
var width = e.css('width')
var height = e.css('height')
e.css('width', height)
e.css('height', width)
e.css('margin-top', ((getPxInt(height) - getPxInt(width))/2).toString() + 'px')
e.css('margin-left', ((getPxInt(width) - getPxInt(height))/2).toString() + 'px')
}
function changeWidthAndHeightWithOutMargin(elem){
var e = $(elem)
var width = e.css('width')
var height = e.css('height')
e.css('width', height)
e.css('height', width)
e.css('margin-top', '0')
e.css('margin-left', '0')
}
function getPxInt(pxValue) {
return parseInt(pxValue.trim("px"))
}
function isIPhone(){
return (
(navigator.platform.indexOf("iPhone") != -1) ||
(navigator.platform.indexOf("iPod") != -1)
);
}
а затем используйте, например,
$("#banner-img").change(function () {
var reader = new FileReader();
getOrientation(this.files[0], function(orientation) {
rotate($('#banner-img-preview'), orientation, 1)
});
reader.onload = function (e) {
$('#banner-img-preview').attr('src', e.target.result)
$('#banner-img-preview').css('display', 'inherit')
};
// read the image file as a data URL.
reader.readAsDataURL(this.files[0]);
});
Ответ 8
Улучшая/добавляя больше функциональности к ответу Али из ранее, я создал метод util в Typescript, который отвечал моим потребностям в этой проблеме. Эта версия возвращает поворот в градусах, которые также могут понадобиться вашему проекту.
ImageUtils.ts
/**
* Based on StackOverflow answer: https://stackoverflow.com/a/32490603
*
* @param imageFile The image file to inspect
* @param onRotationFound callback when the rotation is discovered. Will return 0 if if it fails, otherwise 0, 90, 180, or 270
*/
export function getOrientation(imageFile: File, onRotationFound: (rotationInDegrees: number) => void) {
const reader = new FileReader();
reader.onload = (event: ProgressEvent) => {
if (!event.target) {
return;
}
const innerFile = event.target as FileReader;
const view = new DataView(innerFile.result as ArrayBuffer);
if (view.getUint16(0, false) !== 0xffd8) {
return onRotationFound(convertRotationToDegrees(-2));
}
const length = view.byteLength;
let offset = 2;
while (offset < length) {
if (view.getUint16(offset + 2, false) <= 8) {
return onRotationFound(convertRotationToDegrees(-1));
}
const marker = view.getUint16(offset, false);
offset += 2;
if (marker === 0xffe1) {
if (view.getUint32((offset += 2), false) !== 0x45786966) {
return onRotationFound(convertRotationToDegrees(-1));
}
const little = view.getUint16((offset += 6), false) === 0x4949;
offset += view.getUint32(offset + 4, little);
const tags = view.getUint16(offset, little);
offset += 2;
for (let i = 0; i < tags; i++) {
if (view.getUint16(offset + i * 12, little) === 0x0112) {
return onRotationFound(convertRotationToDegrees(view.getUint16(offset + i * 12 + 8, little)));
}
}
// tslint:disable-next-line:no-bitwise
} else if ((marker & 0xff00) !== 0xff00) {
break;
} else {
offset += view.getUint16(offset, false);
}
}
return onRotationFound(convertRotationToDegrees(-1));
};
reader.readAsArrayBuffer(imageFile);
}
/**
* Based off snippet here: https://github.com/mosch/react-avatar-editor/issues/123#issuecomment-354896008
* @param rotation converts the int into a degrees rotation.
*/
function convertRotationToDegrees(rotation: number): number {
let rotationInDegrees = 0;
switch (rotation) {
case 8:
rotationInDegrees = 270;
break;
case 6:
rotationInDegrees = 90;
break;
case 3:
rotationInDegrees = 180;
break;
default:
rotationInDegrees = 0;
}
return rotationInDegrees;
}
Использование:
import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
getOrientation(pics[0], rotationInDegrees => {
this.setState({ image: pics[0], rotate: rotationInDegrees });
});
};