Accesso ai dati di rotazione JPEG EXIF ​​in JavaScript sul lato client


125

Vorrei ruotare le foto in base alla loro rotazione originale, come impostata dalla fotocamera nei dati immagine JPEG EXIF. Il trucco è che tutto questo dovrebbe avvenire nel browser, utilizzando JavaScript e <canvas>.

In che modo JavaScript può accedere a JPEG, un oggetto API file locale, locale <img>o remoto <img>, dati EXIF ​​per leggere le informazioni di rotazione?

Le risposte lato server non sono OK; Sto cercando una soluzione lato client .

Risposte:


261

Se vuoi solo il tag di orientamento e nient'altro e non ti piace includere un'altra enorme libreria javascript ho scritto un piccolo codice che estrae il tag di orientamento il più velocemente possibile (usa DataView e readAsArrayBufferche sono disponibili in IE10 +, ma puoi scrivere il tuo lettore di dati per i browser meno recenti):

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

valori:

-2: not jpeg
-1: not defined

inserisci qui la descrizione dell'immagine

Per coloro che utilizzano Typescript, è possibile utilizzare il seguente codice:

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

per 2,4,5,7 per ottenere l'immagine corretta devi ruotare e capovolgere, giusto?
Muhammad Umer

L'orientamento della mia immagine è 3 .. Come faccio a impostare l'orientamento su 1 ??
Lucy

3
@Mick PNG o GIF non hanno alcun formato standard per memorizzare l'orientamento dell'immagine stackoverflow.com/questions/9542359/…
Ali

2
Funzionava per me, ma avevo bisogno di cambiare l'ultima riga in reader.readAsArrayBuffer (file); senza lo slice poiché intendo utilizzare il buffer per la mia immagine base64, altrimenti vedrai solo il primo slice dell'immagine. BTW, questo non è richiesto se hai solo bisogno delle informazioni di orientamento. Grazie
Philip Murphy

2
@DaraJava Ho rimosso la parte slice perché a volte il tag è arrivato dopo il limite, ma rallenterà l'operazione se il tag non viene mai trovato. Ad ogni modo, a differenza del tag di orientamento, il tag Flash non è nella directory IFD0 e il mio codice cerca solo questa parte. per ottenere il tag Flash è necessario cercare nella directory SubIFD. Puoi trovare un buon tutorial su EXIF ​​qui: media.mit.edu/pia/Research/deepview/exif.html
Ali

22

Puoi utilizzare la libreria exif-js in combinazione con l'API file 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
});

Grazie. La libreria JS nella domanda sembra un po 'obsoleta, ma probabilmente funzionerebbe.
Mikko Ohtamaa

Vedi anche la mia demo di un widget di caricamento file che ho appena scritto. Utilizza la libreria EXIF.js sopra menzionata per leggere il flag di orientamento EXIF ​​nei metatdati del file immagine. Sulla base delle informazioni, applica la rotazione utilizzando un elemento canvas ... sandbox.juurlink.org/html5imageuploader
Rob Juurlink

Il tentativo di includere anche binaryajax.js nel mio progetto causa un errore di accesso negato.
Obi Wan

Da dove proviene l'oggetto EXIF? Lo script BinaryFile non sembra contenerlo e, per quanto ne so, non fa parte di jquery o di qualsiasi altro script che uso regolarmente ...
jrista

6
Il sito web della biblioteca sembra inattivo e le uniche altre librerie ExifReader che ho trovato erano limitate nel supporto del browser. C'è qualche buona alternativa?
Praxis Ashelin

19

Firefox 26 supporta image-orientation: from-image: le immagini vengono visualizzate in verticale o in orizzontale, a seconda dei dati EXIF. (Vedi sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation .)

C'è anche un bug per implementarlo in Chrome .

Attenzione che questa proprietà è supportata solo da Firefox e probabilmente sarà deprecata .


5
Grazie per il collegamento alla segnalazione di bug. L'ho recitato in modo che il team di Chrome sappia che più persone lo vogliono.
DemiImp

Secondo questo commento bugs.chromium.org/p/chromium/issues/detail?id=158753#c104 da un membro del progetto Chromium: "La modifica è in Chrome 81. Che verrà implementata al pubblico come versione stabile in 8 -10 settimana "
jeff forest il

1
Implementato su Chrome a partire da 81 🎉 Ci vorrà del tempo prima che le persone aggiornino il proprio browser - tieni d'occhio caniuse
Robin Métral


4

Se lo vuoi cross-browser, la soluzione migliore è farlo sul server. Potresti avere un'API che accetta l'URL di un file e ti restituisce i dati EXIF; PHP ha un modulo per questo .

Questo potrebbe essere fatto usando Ajax in modo che fosse senza problemi per l'utente. Se non ti interessa la compatibilità tra browser e puoi fare affidamento sulla funzionalità del file HTML5 , guarda nella libreria JsJPEGmeta che ti consentirà di ottenere quei dati in JavaScript nativo.


21
@MikkoOhtamaa: Devi capire che Stack Overflow risponde alle domande di tutti , solo della persona originale che lo chiede. La prossima persona che ha lo stesso obiettivo di te potrebbe essere uno sviluppatore PHP: perché dovresti negargli le informazioni incluse in Xeon06? E 'stato inopportuno di modifica che fuori, proprio perché si non si desidera una soluzione PHP.
Jon Skeet

5
La domanda dice "in Javascript" quindi la parte era irrilevante. Ci sono molte altre domande e risposte simili per PHP già sul sito ed è inutile fare rumore riguardo a questa domanda.
Mikko Ohtamaa

2
Se le persone chiedono una soluzione Javascript non vogliono vedere la soluzione PHP come primo post.
Mikko Ohtamaa

1
@MikkoOhtamaa sembrerebbe che la maggior parte non sia d'accordo con te meta.stackexchange.com/questions/157338/… Sembra che tu abbia un senso di appartenenza errato sulle risposte alle tue domande.
Alex Turpin

1
Ho modificato la risposta per avere la risposta corretta all'inizio. Ci scusiamo per la peluria.
Mikko Ohtamaa

3

Guarda un modulo che ho scritto (puoi usarlo nel browser) che converte l'orientamento exif in trasformazione CSS: https://github.com/Sobesednik/exif2css

C'è anche questo programma nodo per generare dispositivi JPEG con tutti gli orientamenti: https://github.com/Sobesednik/generate-exif-fixtures


1
Bel modulo! Tuttavia, in primo luogo come ottiene le informazioni EXIF ​​da JPEG?
Mikko Ohtamaa

@MikkoOhtamaa grazie e nah non lo fa, devi farlo con exif-js o exiftool lato server
zavr

Questo è utile. Ma mi sembra che funzioni correttamente solo per i ritratti, non per quelli di paesaggio.
Sridhar Sarnobat

3

Carico il codice di espansione per mostrare la foto dalla fotocamera Android su html come di consueto su alcuni tag img con rotazione a destra, specialmente per tag img la cui larghezza è maggiore dell'altezza. So che questo codice è brutto ma non è necessario installare altri pacchetti. (Ho usato il codice sopra per ottenere il valore di rotazione exif, grazie.)

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

e quindi utilizzare come

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

});

2

Migliorando / aggiungendo più funzionalità alla risposta di Ali di prima, ho creato un metodo util in Typescript che si adattava alle mie esigenze per questo problema. Questa versione restituisce la rotazione in gradi che potrebbe essere necessaria anche per il tuo progetto.

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

Uso:

import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
  getOrientation(pics[0], rotationInDegrees => {
    this.setState({ image: pics[0], rotate: rotationInDegrees });
  });
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.