Zugriff auf JPEG EXIF-Rotationsdaten in JavaScript auf der Clientseite

125

Ich möchte Fotos basierend auf ihrer ursprünglichen Drehung drehen, wie sie von der Kamera in JPEG EXIF-Bilddaten festgelegt wurde. Der Trick ist, dass dies alles im Browser mit JavaScript und geschehen sollte <canvas>.

Wie kann JavaScript auf JPEG zugreifen, ein lokales Datei-API-Objekt, lokale <img>oder entfernte <img>EXIF-Daten, um die Rotationsinformationen zu lesen?

Serverseitige Antworten sind nicht in Ordnung. Ich suche eine clientseitige Lösung.

Mikko Ohtamaa
quelle

Antworten:

261

Wenn Sie nur das Orientierungs-Tag und nichts anderes möchten und keine weitere große Javascript-Bibliothek readAsArrayBufferhinzufügen möchten, habe ich einen kleinen Code geschrieben, der das Orientierungs-Tag so schnell wie möglich extrahiert (es verwendet DataView und ist in IE10 + verfügbar, aber Sie können schreiben Ihren eigenen Datenleser für ältere Browser):

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

Werte:

-2: not jpeg
-1: not defined

Geben Sie hier die Bildbeschreibung ein

Für Benutzer von Typescript können Sie den folgenden Code verwenden:

export const getOrientation = (file: File, callback: Function) => {
  var reader = new FileReader();

  reader.onload = (event: ProgressEvent) => {

    if (! event.target) {
      return;
    }

    const file = event.target as FileReader;
    const view = new DataView(file.result as ArrayBuffer);

    if (view.getUint16(0, false) != 0xFFD8) {
        return callback(-2);
    }

    const length = view.byteLength
    let offset = 2;

    while (offset < length)
    {
        if (view.getUint16(offset+2, false) <= 8) return callback(-1);
        let marker = view.getUint16(offset, false);
        offset += 2;

        if (marker == 0xFFE1) {
          if (view.getUint32(offset += 2, false) != 0x45786966) {
            return callback(-1);
          }

          let little = view.getUint16(offset += 6, false) == 0x4949;
          offset += view.getUint32(offset + 4, little);
          let tags = view.getUint16(offset, little);
          offset += 2;
          for (let 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);
}
Ali
quelle
Damit 2,4,5,7 das richtige Bild liefert, müssen Sie drehen und drehen, oder?
Muhammad Umer
Die Ausrichtung meines Bildes ist 3. Wie stelle ich die Ausrichtung auf 1 ein?
Lucy
3
@ Mick PNG oder GIF haben kein Standardformat zum Speichern der Bildorientierung stackoverflow.com/questions/9542359/…
Ali
2
Ich habe für mich gearbeitet, aber ich musste die letzte Zeile in "reader.readAsArrayBuffer (file)" ändern. Ohne das Slice, da ich beabsichtige, den Puffer für mein Base64-Image zu verwenden. Andernfalls wird nur das erste Slice des Images angezeigt. Übrigens ist dies nicht erforderlich, wenn Sie nur die Orientierungsinformationen benötigen. Vielen Dank
Philip Murphy
2
@DaraJava Ich habe den Slice-Teil entfernt, weil das Tag manchmal nach dem Limit einging, aber es verlangsamt den Vorgang, wenn das Tag nie gefunden wird. Im Gegensatz zum Orientierungs-Tag befindet sich das Flash-Tag nicht im IFD0-Verzeichnis, und mein Code durchsucht nur diesen Teil. Um ein Flash-Tag zu erhalten, müssen Sie das SubIFD-Verzeichnis durchsuchen. Ein gutes Tutorial zu EXIF ​​finden Sie hier: media.mit.edu/pia/Research/deepview/exif.html
Ali
22

Sie können die exif-js- Bibliothek in Kombination mit der HTML5-Datei-API verwenden: 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
});
pimvdb
quelle
Vielen Dank. Die JS-Bibliothek in der Frage sieht etwas veraltet aus, würde aber wahrscheinlich funktionieren.
Mikko Ohtamaa
Siehe auch meine Demo eines Datei-Upload-Widgets, das ich gerade geschrieben habe. Es verwendet die oben erwähnte Bibliothek EXIF.js, um das EXIF-Orientierungsflag in den Metataten der Bilddatei zu lesen. Basierend auf den Informationen wendet es die Drehung unter Verwendung eines Canvas-Elements an ... sandbox.juurlink.org/html5imageuploader
Rob Juurlink
Der Versuch, binaryajax.js überhaupt in mein Projekt aufzunehmen, führt zu einem Fehler, dem der Zugriff verweigert wurde.
Obi Wan
Woher kommt das EXIF-Objekt? Das BinaryFile-Skript scheint es nicht zu enthalten, und soweit ich das beurteilen kann, ist es nicht Teil von jquery oder einem anderen Skript, das ich regelmäßig verwende ...
jrista
6
Die Website der Bibliothek scheint nicht verfügbar zu sein, und die einzigen anderen ExifReader-Bibliotheken, die ich gefunden habe, waren in der Browserunterstützung eingeschränkt. Gibt es eine gute Alternative?
Praxis Ashelin
19

Firefox 26 unterstützt image-orientation: from-image: Bilder werden je nach EXIF-Daten im Hoch- oder Querformat angezeigt. (Siehe sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation .)

Es gibt auch einen Fehler bei der Implementierung in Chrome .

Beachten Sie, dass diese Eigenschaft nur von Firefox unterstützt wird und wahrscheinlich veraltet ist .

Sam Dutton
quelle
5
Danke für den Link zum Fehlerbericht. Ich habe es mit einem Stern markiert, damit das Chrome-Team weiß, dass mehr Leute dies wollen.
DemiImp
Laut diesem Kommentar bugs.chromium.org/p/chromium/issues/detail?id=158753#c104 von einem Chromium-Projektmitglied: "Die Änderung ist in Chrome 81. Das wird in 8 als stabile Version für die Öffentlichkeit verfügbar sein -10 Wochen Zeit "
Jeff Forest
1
Implementiert auf Chrome ab 81 🎉 Es wird eine Weile dauern, bis die Leute ihren Browser aktualisieren - behalten Sie caniuse im
Robin Métral
4

Wenn Sie es browserübergreifend möchten, ist es am besten, es auf dem Server zu tun. Sie könnten eine API haben, die eine Datei-URL verwendet und Ihnen die EXIF-Daten zurückgibt. PHP hat dafür ein Modul .

Dies könnte mit Ajax erfolgen, so dass es für den Benutzer nahtlos ist. Wenn Sie sich nicht für die Cross-Browser-Kompatibilität interessieren und sich auf die Funktionalität von HTML5- Dateien verlassen können, schauen Sie in die Bibliothek JsJPEGmeta , mit der Sie diese Daten in nativem JavaScript abrufen können .

Alex Turpin
quelle
21
@MikkoOhtamaa: Sie müssen verstehen, dass Stack Overflow Fragen für alle beantwortet , nur für die ursprüngliche Person, die sie gestellt hat. Die nächste Person, die das gleiche Ziel hat wie Sie, ist möglicherweise ein PHP-Entwickler. Warum sollten Sie ihnen die in Xeon06 enthaltenen Informationen verweigern? Es war unangemessen, das zu ändern, nur weil Sie keine PHP-Lösung wollen.
Jon Skeet
5
Die Frage lautet "in Javascript", daher war der Teil irrelevant. Es gibt viele andere ähnliche Fragen und Antworten für PHP bereits auf der Website und es ist unnötiges Rauschen in Bezug auf diese Frage.
Mikko Ohtamaa
2
Wenn Leute nach einer Javascript-Lösung fragen, möchten sie die PHP-Lösung nicht als ersten Beitrag sehen.
Mikko Ohtamaa
1
@MikkoOhtamaa Es scheint, als wären die meisten anderer Meinung als Sie. Meta.stackexchange.com/questions/157338/… Sie scheinen ein falsches Gefühl der Eigenverantwortung für die Antworten auf Ihre Fragen zu haben.
Alex Turpin
1
Ich habe die Antwort so bearbeitet, dass sie am Anfang die richtige Antwort hat. Entschuldigung für den Flaum.
Mikko Ohtamaa
3

Schauen Sie sich ein Modul an, das ich geschrieben habe (Sie können es im Browser verwenden) und das die Exif-Ausrichtung in eine CSS-Transformation konvertiert: https://github.com/Sobesednik/exif2css

Es gibt auch dieses Knotenprogramm zum Generieren von JPEG-Fixtures mit allen Ausrichtungen: https://github.com/Sobesednik/generate-exif-fixtures

zavr
quelle
1
Schönes Modul! Wie werden EXIF-Informationen überhaupt aus JPEG herausgeholt?
Mikko Ohtamaa
@ MikkoOhtamaa danke und nein, das tut es nicht, du musst es mit exif-js oder exiftool serverseitig machen
zavr
Das ist nützlich. Aber es scheint mir, dass es nur für Porträtfotos richtig funktioniert, nicht für Landschaftsfotos.
Sridhar Sarnobat
3

Ich lade den Erweiterungscode hoch, um das Foto mit der Android-Kamera auf HTML wie gewohnt auf einem IMG-Tag mit der richtigen Rotation anzuzeigen, insbesondere für IMG-Tags, deren Breite breiter als die Höhe ist. Ich weiß, dass dieser Code hässlich ist, aber Sie müssen keine anderen Pakete installieren. (Ich habe den obigen Code verwendet, um den Exif-Rotationswert zu erhalten. Danke.)

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

und dann verwenden wie

$("#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]);

});
Wonhyuk Cho
quelle
2

Ich habe Alis Antwort von früher verbessert / um weitere Funktionen erweitert und in Typescript eine util-Methode erstellt, die meinen Anforderungen für dieses Problem entspricht. Diese Version gibt die Drehung in Grad zurück, die Sie möglicherweise auch für Ihr Projekt benötigen.

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

Verwendung:

import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
  getOrientation(pics[0], rotationInDegrees => {
    this.setState({ image: pics[0], rotate: rotationInDegrees });
  });
};
Kevin Grant
quelle