Orientamento Exif lato client JS: ruota e specchia immagini JPEG


154

Le foto delle fotocamere digitali vengono spesso salvate come JPEG con un tag "orientamento" EXIF. Per una corretta visualizzazione, le immagini devono essere ruotate / specchiate a seconda dell'orientamento impostato, ma i browser ignorano queste informazioni che rendono l'immagine. Anche nelle grandi app Web commerciali, il supporto per l'orientamento EXIF ​​può essere imprevedibile 1 . La stessa fonte fornisce anche un bel riassunto degli 8 diversi orientamenti che un JPEG può avere:

Riepilogo degli orientamenti EXIF

Le immagini di esempio sono disponibili al numero 4 .

La domanda è: come ruotare / rispecchiare l'immagine sul lato client in modo che venga visualizzata correttamente e possa essere ulteriormente elaborata se necessario?

Sono disponibili librerie JS per analizzare i dati EXIF, incluso l'attributo orientamento 2 . Flickr ha notato possibili problemi di prestazioni durante l'analisi di immagini di grandi dimensioni, che richiedono l'uso di webworker 3 .

Gli strumenti della console possono riorientare correttamente le immagini 5 . Uno script PHP che risolve il problema è disponibile al numero 6

Risposte:


145

Il progetto github JavaScript-Load-Image fornisce una soluzione completa al problema dell'orientamento EXIF, ruotando / rispecchiando correttamente le immagini per tutti gli 8 orientamenti exif. Guarda la demo online di orientamento exif javascript

L'immagine è disegnata su un canvas HTML5. Il suo rendering corretto è implementato in js / load-image-orientamento.js attraverso operazioni di tela.

Spero che questo salvi qualcun altro qualche tempo, e insegni ai motori di ricerca su questa gemma open source :)


1
Ho usato questa libreria ma recentemente si è rotto su iOS 8.3, che è il punto in cui ne ho più bisogno per funzionare :(
Gordon Sun

2
Faccio davvero fatica a vedere come questo sia utile. Ci sto provando cercando di impararlo in modo da poter analizzare una pagina e ruotare le immagini quando devono essere ruotate, oppure rilevare l'orientamento e ruotare il file (in qualche modo) prima di caricarlo effettivamente. La demo non fa nessuno dei due. Prende semplicemente un file da un input di file e lo visualizza nel modo giusto, quando è utile nel mondo reale? Quando analizzo la mia pagina e invio gli URL dai tag immagine nella libreria loadImage non ci sono dati exif, quindi non posso farlo. Per il caricamento restituisce un oggetto canvas quindi non posso inviarlo al server o altro.
igneosaur,

3
@igneosaur: è possibile inviare dati codificati in base64 al server con canvas.toDataURL()e decodificare e salvarlo sul lato server.
br4nnigan,

1
piuttosto che usare il progetto load-image, puoi usare parte della sua base di codice per creare il tuo - basta guardare in github.com/blueimp/JavaScript-Load-Image/blob/master/js/… .
Andy Lorenz,

1
C'è un modo per rendere la tela che questa libreria produce reattiva come un normale <img>tag?
Erik Berkun-Drevnig,

103

La trasformazione del contesto di Mederr funziona perfettamente. Se è necessario estrarre l'orientamento, utilizzare solo questa funzione : non sono necessarie librerie di lettura EXIF. Di seguito è una funzione per reimpostare l'orientamento nell'immagine base64. Ecco un violino per questo . Ho anche preparato un violino con demo di estrazione dell'orientamento .

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

7
Il caso predefinito nell'orientamento switchnon è necessario, poiché quella trasformazione non fa nulla. Inoltre, considera l'utilizzo srcOrientation > 4 && srcOrientation < 9invece di [5,6,7,8].indexOf(srcOrientation) > -1, perché è più veloce e meno dispendioso in termini di risorse (sia RAM che CPU). Non è necessario disporre di un array lì. Questo è importante quando si raggruppano molte immagini, dove ogni bit conta. Altrimenti, risposta abbastanza buona. Upvoted!
Ryan Casas,

4
@RyanCasas Non ero a conoscenza di quanto possa indexOfessere pesante rispetto a quello che hai proposto. Ho eseguito un semplice ciclo con iterazioni 10M ed è stato del 1400% più veloce. Bello: D Grazie mille!
WunderBart,

1
Ho usato questa risposta in un WebView Android e ho scoperto che ci sono alcuni dispositivi Android che non supportano WebGL in un WebView (vedi bugs.chromium.org/p/chromium/issues/detail?id=555116 ) la rotazione può richiedere molto tempo su tali dispositivi a seconda delle dimensioni dell'immagine.
ndreisg,

1
Se utilizzato con il riferimento getOrientation, sono curioso di sapere se questo è efficiente in termini di prestazioni. getOrientationchiama fileReader.readAsArrayBuffer, quindi chiamiamo URL.createObjectURLe passiamo il risultato in resetOrientation, che carica questo URL come immagine. Questo significa che il file immagine verrà "caricato" / letto dal browser non una, ma due volte, o fraintendere?
Oliver Joseph Ash

1
Inoltre, cosa fare con i browser in cui l'orientamento sarà già rispettato? Credo che questo sia il caso di iOS Safari e dei browser che supportano CSS image-orientation: from-imagequando viene utilizzato.
Oliver Joseph Ash,

40

Se

width = img.width;
height = img.height;
var ctx = canvas.getContext('2d');

Quindi è possibile utilizzare queste trasformazioni per trasformare l'immagine in orientamento 1

Dall'orientamento:

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

Prima di disegnare l'immagine su CTX


58
cosa sta succedendo anche qui?
Muhammad Umer,

1
Con questo è sufficiente conoscere l'orientamento (ad es. Tramite EXIF) e quindi ruotare secondo necessità. La metà di quello che sto cercando.
Brenden,

2
queste trasformazioni non hanno funzionato per me - invece ho usato il codice del progetto load-image su github.com/blueimp/JavaScript-Load-Image/blob/master/js/… per ottenere quello che credo sia un codice ben collaudato che utilizza entrambe le operazioni di traslazione, rotazione sul contesto dell'area di disegno più uno scambio larghezza / altezza. Mi ha dato risultati al 100% dopo molte ore di sperimentazione con altri tentativi di trasformazione ecc.
Andy Lorenz,

1
Perché è importante quando disegni l'immagine su ctx? Non puoi ruotare la tela dopo aver disegnato un'immagine su di essa?
AlxVallejo,

La rotazione per l'orientamento 8 funziona in questo modo per me: ctx.transform (0, -1, 1, 0, 0, altezza);
Giorgio Barchiesi,

24

ok oltre alla risposta @ user3096626 penso che sarà più utile se qualcuno fornisse un esempio di codice, il seguente esempio ti mostrerà come riparare l'orientamento dell'immagine proviene dall'URL (immagini remote):


Soluzione 1: utilizzo di JavaScript (consigliato)

  1. poiché la libreria load-image non estrae i tag exif solo dalle immagini url (file / blob), utilizzeremo sia le librerie exif-js che quelle load-image javascript, quindi prima aggiungi queste librerie alla tua pagina come segue:

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

    Nota che la versione 2.2 di exif-js sembra avere dei problemi, quindi abbiamo usato 2.1

  2. quindi fondamentalmente quello che faremo è

    a - carica l'immagine usando window.loadImage()

    b - leggi i tag exif usando window.EXIF.getData()

    c - converti l'immagine in tela e correggi l'orientamento dell'immagine usando window.loadImage.scale()

    d - posizionare la tela nel documento

Ecco qui :)

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

    });
  }
});

ovviamente puoi anche ottenere l'immagine come base64 dall'oggetto canvas e posizionarla nell'attributo img src, quindi puoi usare jQuery;)

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

ecco il codice completo su: github: https://github.com/digital-flowers/loadimage-exif-example


Soluzione 2: utilizzo di html (browser hack)

c'è un hack molto veloce e facile, la maggior parte dei browser visualizza l'immagine con il giusto orientamento se l'immagine viene aperta all'interno di una nuova scheda direttamente senza alcun html (LOL non so perché), quindi in pratica puoi visualizzare l'immagine usando iframe inserendo direttamente l'attributo iframe src come url dell'immagine:

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

Soluzione 3: utilizzo di CSS (solo Firefox e Safari su iOS)

c'è l'attributo css3 per correggere l'orientamento dell'immagine, ma il problema funziona solo su Firefox e Safari / iOS, ma vale la pena menzionarlo perché presto sarà disponibile per tutti i browser (informazioni di supporto del browser da caniuse )

img {
   image-orientation: from-image;
}

La soluzione 1 non ha funzionato per me. Sta tornando window.loadImage non è definito
Perry

Questo accadrebbe se non includessi le biblioteche che menziono nel passaggio 1
Fareed Alnamrouti l'

Ho incluso le biblioteche. Mi piace la semplicità del tuo esempio ma continuo a ricevere quel messaggio di errore.
Perry,

1
sembra che exif-js sia stato rotto, per favore controlla la mia modifica ho anche aggiunto il codice completo su github: github.com/digital-flowers/loadimage-exif-example
Fareed Alnamrouti

1
ok dai un'occhiata al progetto github c'è un nuovo file "upload.html", sei il benvenuto :)
Fareed Alnamrouti

10

Per coloro che hanno un file da un controllo di input, non sanno quale sia il loro orientamento, sono un po 'pigri e non vogliono includere una grande libreria di seguito è il codice fornito da @WunderBart combinato con la risposta a cui si collega ( https://stackoverflow.com/a/32490603 ) che trova l'orientamento.

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

che può essere facilmente chiamato così

getDataUrl(input.files[0], function (imgBase64) {
      vm.user.BioPhoto = imgBase64;
});

2
Grazie dopo oltre 2 ore di ricerca su Google trovo la soluzione giusta.
Mr.Trieu,

2
perché qualcuno dovrebbe essere pigro e preferire una soluzione molto più leggera?
Rugiada

4
Portato su Typescript e ottimizzato da circa 4 secondi a ~ 20 millisecondi. La codifica Base64 era il collo di bottiglia: usare al image/jpegposto del valore predefinito image/pnge ridimensionare l'immagine di output alla massima larghezza (nel mio caso 400px) ha fatto una differenza enorme. (Questo funziona bene per le anteprime, ma se devi visualizzare l'immagine completa, suggerirei di evitare base64 e di iniettare semplicemente l'elemento canvas direttamente nella pagina ...)
mindplay.dk,

8

La risposta di WunderBart è stata la migliore per me. Nota che puoi accelerare molto se le tue immagini sono spesso nel modo giusto, semplicemente testando prima l'orientamento e bypassando il resto del codice se non è richiesta alcuna rotazione.

Mettere insieme tutte le informazioni da wunderbart, qualcosa del genere;

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

1
Ottimo approccio Dovresti anche considerare la gestione -2e il -1ritorno da in getOrientationmodo da non provare a ruotare non jpg o immagini senza dati di rotazione.
Steven Lambert,

Ho apportato ulteriori ottimizzazioni, vedi il mio commento sopra : ho appena aggiunto anche la tua file.slice()ottimizzazione, ma il vero vantaggio deriva dall'utilizzo di immagini più piccole e dal image/jpegformato di output per creare URL di dati più piccoli. (Non c'è alcun ritardo evidente anche per le immagini da 10 megapixel.)
mindplay.dk

Attento file.slice(), poiché impedirete il supporto delle fonti di immagini base64.
Segna il

Grazie mark - ti interessa fornire una modifica per un'alternativa?
statler

7

Una fodera qualcuno?

Non ho visto nessuno menzionare la browser-image-compressionbiblioteca . Ha una funzione di supporto perfetta per questo.

Uso: const orientation = await imageCompression.getExifOrientation(file)

Uno strumento così utile anche in molti altri modi.


Questo ottiene l'orientamento. Ma come posso produrre un nuovo oggetto File .jpg sul lato client usando queste informazioni?
masterxilo,

Non puoi creare Fileoggetti per quanto ne so. La prossima cosa migliore sarebbe inviare l'immagine e l'orientamento al server e fare lì la manipolazione dei file. Oppure potresti generare una stringa base64 usando canvase il toDataURLmetodo
Gilbert-V,

1
No, puoi creare un oggetto File da un BLOB. File File (parti dell'array, nome file della stringa, proprietà BlobPropertyBag); developer.mozilla.org/de/docs/Web/API/File
masterxilo

2
Adesivo d'oro! @masterxilo
gilbert-v

2
Questo ha funzionato alla grande per me! Ho avuto difficoltà con gli orientamenti negli ultimi 2 giorni! Tutte le dichiarazioni degli switch di trasformazione che sono state pubblicate non gestiscono le immagini dei ritratti dal mio telefono per qualche motivo, ci sarebbero delle barre nere. Probabilmente perché stavo cercando di comprimere e ruotare allo stesso tempo.
cjd82187,


1

Sto usando una soluzione mista (php + css).

I contenitori sono necessari per:

  • div.imgCont2 il contenitore doveva ruotare;
  • div.imgCont1contenitore necessario per zoomout - width:150%;
  • div.imgCont contenitore necessario per le barre di scorrimento, quando l'immagine è ZoomOut.

.

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

Grazie, questa sembra la soluzione migliore, per le mie esigenze comunque. Non sono necessarie librerie esterne e non ci sono lunghi blocchi di codice non necessario. Basta determinare l'orientamento dall'exif con php, inviarlo alla vista e usarlo per attivare le classi CSS che ruotano il numero appropriato di gradi. Bello e pulito! Modifica: questo ruoterà le immagini nei browser che leggono effettivamente i dati exif e ruotano di conseguenza. AKA risolverà il problema sul desktop ma ne crea uno nuovo su iOS Safari.
cwal,

0

Oltre alla risposta di @fareed namrouti,

Questo dovrebbe essere usato se l'immagine deve essere sfogliata da un elemento di input del file

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

0

Ho scritto un piccolo script php che ruota l'immagine. Assicurati di archiviare l'immagine in favore di ricalcolare ogni richiesta.

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

?>

Saluti

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.