Orientação Exif do lado do cliente JS: girar e espelhar imagens JPEG


154

As fotos das câmeras digitais geralmente são salvas como JPEG com uma etiqueta de "orientação" EXIF. Para exibir corretamente, as imagens precisam ser giradas / espelhadas, dependendo da orientação definida, mas os navegadores ignoram essas informações que renderizam a imagem. Mesmo em grandes aplicativos comerciais da Web, o suporte à orientação EXIF ​​pode ser irregular 1 . A mesma fonte também fornece um bom resumo das 8 orientações diferentes que um JPEG pode ter:

Resumo das orientações do EXIF

Imagens de amostra estão disponíveis em 4 .

A questão é como girar / espelhar a imagem no lado do cliente, para que ela seja exibida corretamente e possa ser processada posteriormente, se necessário?

Existem bibliotecas JS disponíveis para analisar dados EXIF, incluindo o atributo de orientação 2 . O Flickr observou um possível problema de desempenho ao analisar imagens grandes, exigindo o uso de webworkers 3 .

As ferramentas do console podem reorientar corretamente as imagens 5 . Um script PHP para resolver o problema está disponível em 6

Respostas:


145

O projeto github JavaScript-Load-Image fornece uma solução completa para o problema de orientação EXIF, girando / espelhando corretamente as imagens para todas as 8 orientações exif. Veja a demonstração online da orientação exif javascript

A imagem é desenhada em uma tela HTML5. Sua renderização correta é implementada em js / load-image-oriented.js através de operações de tela.

Espero que isso economize mais tempo para alguém e ensine os mecanismos de pesquisa sobre essa jóia de código aberto :)


1
Tenho vindo a utilizar essa biblioteca, mas recentemente ele quebrou no iOS 8.3, que é onde eu preciso dele para trabalho mais :(
Gordon Sun

2
Estou realmente lutando para ver como isso é útil. Estou brincando com ele tentando aprendê-lo para poder analisar uma página e girar imagens quando elas precisam ser giradas, ou detectar a orientação e girar o arquivo (de alguma forma) antes de realmente carregá-lo. A demo também não. Ele simplesmente pega um arquivo de uma entrada de arquivo e o exibe da maneira certa, quando isso é útil no mundo real? Quando analiso minha página e alimento os URLs das tags de imagem para a biblioteca loadImage, não há dados exif; portanto, não é possível fazer isso. Para o upload, ele retorna um objeto de tela, para que eu não possa enviá-lo ao servidor ou algo assim.
Igneosaur # 20/16

3
@igneosaur: você pode enviar dados codificados em base64 para o servidor canvas.toDataURL()e decodificar e salvá-lo no servidor.
Br4nnigan # 30/16

1
em vez de usar o projeto load-image, você pode usar parte de sua base de código para criar o seu próprio - basta olhar em github.com/blueimp/JavaScript-Load-Image/blob/master/js/… .
Andy Lorenz

1
Existe alguma maneira de tornar a tela que essa biblioteca produz responsiva como uma <img>tag comum ?
Erik Berkun-Drevnig 14/09/16

103

A transformação de contexto de Mederr funciona perfeitamente. Se você precisar extrair a orientação, use apenas esta função - não precisa de nenhuma biblioteca de leitura EXIF. Abaixo está uma função para redefinir a orientação na imagem base64. Aqui está um violino para isso . Também preparei um violino com demonstração de extração de orientação .

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
O caso padrão na orientação switchnão é necessário, pois essa transformação não faz nada. Além disso, considere usar em srcOrientation > 4 && srcOrientation < 9vez de [5,6,7,8].indexOf(srcOrientation) > -1, porque é mais rápido e consome menos recursos (RAM e CPU). Não há necessidade de ter uma matriz lá. Isso é importante ao agrupar muitas imagens, onde cada bit conta. Caso contrário, resposta muito boa. Voto a favor!
Ryan Casas

4
@RyanCasas Eu não sabia o quão pesado pode indexOfser comparado ao que você propôs. Eu executei um loop simples com 10M de iterações e foi 1400% mais rápido. Nice: D Muito obrigado!
WunderBart

1
Usei esta resposta em um Android WebView e, por fim, existem alguns dispositivos Android que não suportam WebGL em um WebView (consulte bugs.chromium.org/p/chromium/issues/detail?id=555116 ) a rotação pode demorar muito nesses dispositivos, dependendo do tamanho da imagem.
Ndreisg 27/03

1
Quando usado com o referenciado getOrientation, estou curioso para saber se isso é eficiente em termos de desempenho. getOrientationchama fileReader.readAsArrayBuffere, em seguida, chamamos URL.createObjectURLe passamos o resultado para resetOrientation, que carrega esse URL como uma imagem. Isso significa que o arquivo de imagem será "carregado" / lido pelo navegador não uma vez, mas duas vezes, ou entendo errado?
Oliver Joseph Ash

1
Além disso, o que fazer com os navegadores em que a orientação já será respeitada? Acredito que esse seja o caso do iOS Safari e em navegadores que suportam CSS image-orientation: from-imagequando usados.
Oliver Joseph Ash

40

E se

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

Então você pode usar essas transformações para transformar a imagem em orientação 1

Da orientação:

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

Antes de desenhar a imagem em ctx


58
o que está acontecendo aqui?
Muhammad Umer 27/03

1
Com isso, você só precisa conhecer a orientação (por exemplo, via EXIF) e depois girar conforme necessário. Metade do que estou procurando.
Brenden

2
essas transformações não funcionaram para mim - em vez disso, usei o código do projeto load-image em github.com/blueimp/JavaScript-Load-Image/blob/master/js/… para obter o que acredito ser um código comprovado que usa operações de conversão e rotação no contexto da tela, além de uma troca de largura / altura. Deu-me 100% de resultados após muitas horas de experimentação com outras tentativas de transformar etc
Andy Lorenz

1
Por que importa quando você desenha a imagem no ctx? Você não pode girar a tela depois de desenhar uma imagem nela?
AlxVallejo 5/17/17

A rotação para a orientação 8 funciona assim: ctx.transform (0, -1, 1, 0, 0, height);
Giorgio Barchiesi

24

ok, além da resposta @ user3096626, acho que será mais útil se alguém forneceu um exemplo de código, o exemplo a seguir mostrará como corrigir a orientação da imagem proveniente do URL (imagens remotas):


Solução 1: usando javascript (recomendado)

  1. como a biblioteca load-image não extrai tags exif apenas de imagens de URL (arquivo / blob), usaremos as bibliotecas javascript exif-js e load-image , portanto, primeiro adicione essas bibliotecas à sua página da seguinte maneira:

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

    Observe que a versão 2.2 do exif-js parece ter problemas, então usamos o 2.1

  2. então basicamente o que faremos é

    a - carregue a imagem usando window.loadImage()

    b - leia tags exif usando window.EXIF.getData()

    c - converta a imagem em tela e corrija a orientação da imagem usando window.loadImage.scale()

    d - coloque a tela no documento

aqui está :)

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

    });
  }
});

é claro que você também pode obter a imagem como base64 a partir do objeto canvas e colocá-la no atributo img src; portanto, usando o jQuery, você pode fazer;)

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

aqui está o código completo em: github: https://github.com/digital-flowers/loadimage-exif-example


Solução 2: usando html (hack do navegador)

existe um hack muito rápido e fácil, a maioria dos navegadores exibe a imagem na orientação correta se a imagem for aberta dentro de uma nova guia diretamente sem nenhum html (LOL, não sei por quê), então basicamente você pode exibir sua imagem usando iframe colocando o atributo iframe src como o URL da imagem diretamente:

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

Solução 3: usando css (apenas firefox e safari no ios)

existe um atributo css3 para corrigir a orientação da imagem, mas o problema está funcionando apenas no firefox e no safari / ios, ainda vale a pena mencionar, pois em breve estará disponível para todos os navegadores (informações de suporte do navegador do caniuse )

img {
   image-orientation: from-image;
}

A solução 1 não funcionou para mim. Ele está retornando window.loadImage é indefinido
Perry

Isso aconteceu se você não incluiu as bibliotecas mencionadas na etapa 1
Fareed Alnamrouti

Eu incluí as bibliotecas. Eu gosto da simplicidade do seu exemplo, mas continuo recebendo essa mensagem de erro.
7897 Perry

1
parece EXIF-js estava quebrado, por favor, verifique a minha edição eu também adicionou código completo no github: github.com/digital-flowers/loadimage-exif-example
Fareed Alnamrouti

1
ok check-out do projeto github há um novo arquivo "Upload.html", você está convidado :)
Fareed Alnamrouti

10

Para quem tem um arquivo de um controle de entrada, não sabe qual é a orientação, é um pouco preguiçoso e não deseja incluir uma grande biblioteca abaixo, é o código fornecido pelo @WunderBart combinado com a resposta à qual ele vincula ( https://stackoverflow.com/a/32490603 ) que localiza a orientação.

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

que pode ser facilmente chamado como tal

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

2
Obrigado, após mais de duas horas de pesquisa pelo google, encontrei a solução certa.
Mr.Trieu

2
por que alguém teria preguiça de preferir uma solução muito mais leve?
dewd

4
Portado para o Typecript e otimizado de aproximadamente 4 segundos a ~ 20 milissegundos. A codificação Base64 foi o gargalo - usar em image/jpegvez do padrão image/pnge redimensionar a imagem de saída para uma largura máxima (400px no meu caso) fez uma enorme diferença. (isso funciona bem para as miniaturas, mas se você tem que mostrar a imagem completa, eu sugiro evitando base64 e simplesmente injetar o elemento canvas diretamente na página ...)
mindplay.dk

8

A resposta do WunderBart foi a melhor para mim. Observe que você pode acelerá-lo muito se suas imagens geralmente são o caminho certo, simplesmente testando primeiro a orientação e ignorando o restante do código, se nenhuma rotação for necessária.

Reunindo todas as informações do wunderbart, algo assim;

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
Ótima abordagem. Você também deve considerar manipular -2e -1retornar de getOrientationpara não tentar girar imagens que não sejam jpg ou sem dados de rotação.
Steven Lambert

Fiz mais otimizações, veja meu comentário acima - acabei de adicionar sua file.slice()otimização também, mas o verdadeiro impulso vem do uso de imagens e image/jpegformato de saída menores para criar URLs de dados menores. (não há atraso perceptível mesmo para imagens de 10 megapixels).
mindplay.dk

Cuidado com file.slice(), pois você impedirá o suporte a fontes de imagem base64.
Mark

Obrigado, marca - cuidado em fornecer uma edição para uma alternativa?
statler

7

Um forro alguém?

Eu não vi ninguém mencionar a browser-image-compressionbiblioteca . Tem uma função auxiliar perfeita para isso.

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

Uma ferramenta tão útil de muitas outras maneiras também.


Isso recebe a orientação. Mas como produzo um novo objeto de arquivo .jpg no lado do cliente usando essas informações?
Masterxilo # 6/19

Você não pode criar Fileobjetos, tanto quanto eu sei. A melhor coisa a seguir seria enviar a imagem e a orientação até o servidor e fazer a manipulação de arquivos lá. Ou você pode gerar uma string base64 usando canvase o toDataURLmétodo
precisa

1
Não, você pode criar um objeto File a partir de um Blob. File File (partes da matriz, nome do arquivo String, propriedades BlobPropertyBag); developer.mozilla.org/de/docs/Web/API/File
masterxilo 15/11/19

2
Etiqueta de ouro! @masterxilo
gilbert-v

2
Isso funcionou muito bem para mim! Eu estava lutando com orientações nos últimos 2 dias! Todas as declarações de chave de transformação postadas não lidam com imagens de retrato do meu telefone por algum motivo, haverá barras pretas. Provavelmente porque eu estava tentando comprimir e girar ao mesmo tempo.
cjd82187


1

Eu estou usando solução mista (php + css).

Os contêineres são necessários para:

  • div.imgCont2 recipiente necessário para girar;
  • div.imgCont1contêiner necessário para diminuir o zoom - width:150%;
  • div.imgCont contêiner necessário para barras de rolagem, quando a imagem é 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>

Obrigado, esta parece ser a melhor solução, para minhas necessidades de qualquer maneira. Não há necessidade de bibliotecas externas nem longos blocos de código desnecessário. Apenas determine a orientação do exif com php, envie-o para a visualização e use-o para disparar classes CSS que giram o número apropriado de graus. Legal e Limpo! Editar: isso fará com que as imagens sejam giradas em navegadores que realmente lêem os dados exif e rodam de acordo. Também é possível corrigir o problema no desktop, mas cria um novo no ios safari.
C11 #

0

Além da resposta de @fareed namrouti,

Isso deve ser usado se a imagem precisar ser pesquisada a partir de um elemento de entrada de arquivo

<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

Eu escrevi um pequeno script php que gira a imagem. Certifique-se de armazenar a imagem em favor de apenas recalcular cada solicitação.

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

?>

Felicidades

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.