İstemci tarafında JavaScript'te JPEG EXIF ​​rotasyon verilerine erişim


125

Fotoğrafları, JPEG EXIF ​​görüntü verilerinde kamera tarafından ayarlandığı şekliyle orijinal dönüşlerine göre döndürmek istiyorum. İşin püf noktası, tüm bunların tarayıcıda JavaScript ve <canvas>.

JavaScript , döndürme bilgilerini okumak için yerel bir dosya API nesnesi olan JPEG, yerel <img>veya uzak <img>, EXIF ​​verilerine nasıl erişebilir ?

Sunucu tarafı yanıtları tamam değil; İstemci tarafı bir çözüm arıyorum .

Yanıtlar:


261

Sadece yönlendirme etiketini istiyor ve başka bir şey istemiyorsanız ve başka bir büyük javascript kitaplığı eklemekten hoşlanmıyorsanız, yön etiketini olabildiğince hızlı çıkaran küçük bir kod yazdım (DataView kullanıyor ve readAsArrayBufferIE10 + 'da bulunanlar, ancak yazabilirsiniz) eski tarayıcılar için kendi veri okuyucunuz):

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

değerler:

-2: not jpeg
-1: not defined

görüntü açıklamasını buraya girin

Typescript kullananlar için aşağıdaki kodu kullanabilirsiniz:

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

2,4,5,7 için doğru görüntüyü elde etmek için döndürmeniz ve çevirmeniz gerekir, değil mi?
Muhammed Umer

Resmimin yönü 3. Yönü nasıl 1 olarak ayarlayabilirim?
Lucy

3
@Mick PNG veya GIF, görüntü yönünü depolamak için herhangi bir standart biçime sahip değil stackoverflow.com/questions/9542359/…
Ali,

2
Benim için çalışıyor, ancak son satırı sadece reader.readAsArrayBuffer (dosya) olarak değiştirmem gerekiyordu; base64 resmim için arabelleği kullanmayı planladığım için dilim olmadan, aksi takdirde görüntünün sadece ilk dilimini göreceksiniz. BTW, sadece oryantasyon bilgisine ihtiyacınız varsa bu gerekli değildir. Teşekkürler
Philip Murphy

2
@DaraJava Dilim kısmını kaldırdım çünkü bazen etiket limitten sonra geliyor, ancak etiket bulunmazsa işlemi yavaşlatacak. Her neyse, yönlendirme etiketinin aksine, Flash etiketi IFD0 dizininde değil ve kodum sadece bu kısmı arıyor. Flash etiketi almak için SubIFD dizinini aramanız gerekir. EXIF ile ilgili iyi bir öğretici burada bulabilirsiniz: media.mit.edu/pia/Research/deepview/exif.html
Ali

22

Sen kullanabilirsiniz exif-js : HTML5 Dosya API ile birlikte kütüphaneyi 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
});

Teşekkürler. Sorudaki JS kitaplığı biraz modası geçmiş görünüyor, ancak muhtemelen işe yarayacaktır.
Mikko Ohtamaa

Ayrıca az önce yazdığım bir dosya yükleme widget'ının tanıtımına bakın. Görüntü dosyasının meta verilerindeki EXIF ​​yönlendirme bayrağını okumak için yukarıda bahsedilen EXIF.js kitaplığını kullanır. Bilgiye dayanarak, döndürmeyi bir tuval öğesi kullanarak uygular ... sandbox.juurlink.org/html5imageuploader
Rob Juurlink

Binaryajax.js'yi projeme dahil etmeye çalışmak bile erişim reddedildi hatasına neden oluyor.
Obi Wan

EXIF nesnesi nereden geliyor? BinaryFile betiği içermiyor gibi görünüyor ve anlayabildiğim kadarıyla
jquery'nin

6
Kütüphane web sitesi kapanmış görünüyor ve bulduğum diğer yalnızca ExifReader kitaplıkları tarayıcı desteğinde sınırlıydı. İyi bir alternatif var mı?
Praxis Ashelin

19

Firefox 26 image-orientation: from-imageşunları destekler : resimler, EXIF ​​verilerine bağlı olarak dikey veya yatay olarak görüntülenir. (Bkz. Sethfowler.org/blog/2013/09/13/new-in-firefox-26-css-image-orientation .)

Bunu Chrome'da uygulamak için bir hata da var .

Bu özelliğin yalnızca Firefox tarafından desteklendiğine ve muhtemelen kullanımdan kaldırılacağına dikkat edin .


5
Hata raporu bağlantısı için teşekkürler. Chrome ekibinin daha fazla insanın bunu istediğini anlaması için yıldız ekledim.
DemiImp

Bu yoruma göre bugs.chromium.org/p/chromium/issues/detail?id=158753#c104 , bir Chromium proje üyesi tarafından: "Değişiklik Chrome 81'de. Bu, Kararlı sürüm olarak halka sunulacak. -10 hafta "
jeff forest

1
81 ile başlayarak Chrome'da uygulandı 🎉 İnsanların tarayıcılarını güncellemeleri biraz zaman alacak - caniuse'a
Robin Métral


4

Çapraz tarayıcı istiyorsanız, en iyi bahsiniz bunu sunucuda yapmaktır. Bir dosya URL'si alan ve size EXIF ​​verilerini döndüren bir API'niz olabilir; PHP'nin bunun için bir modülü var .

Bu, Ajax kullanılarak yapılabilir, böylece kullanıcı için sorunsuz olacaktır. Tarayıcılar arası uyumluluk umurunuzda değilse ve HTML5 dosya işlevselliğine güveniyorsanız, bu verileri yerel JavaScript'te almanızı sağlayacak JsJPEGmeta kitaplığına bakın .


21
@MikkoOhtamaa: Stack Overflow'un herkes için soruları yanıtladığını anlamalısın , sadece soran asıl kişi. Sizinle aynı amaca sahip bir sonraki kişi bir PHP geliştiricisi olabilir - Xeon06'nın içerdiği bilgileri neden reddetmek isteyesiniz? Bu sırf düzenleyebilir dikkat uygunsuz oldu sen bir PHP çözüm istemiyoruz.
Jon Skeet

5
Soru "Javascript'te" diyor, bu yüzden parça alakasızdı. Sitede zaten PHP için daha birçok benzer soru ve cevap var ve bu soruya ilişkin gereksiz gürültü.
Mikko Ohtamaa

2
İnsanlar Javascript çözümü isterlerse ilk gönderi olarak PHP çözümünü görmek istemiyorlar.
Mikko Ohtamaa

1
@MikkoOhtamaa, çoğu size katılmıyor gibi görünüyor meta.stackexchange.com/questions/157338/… Sorularınızın cevaplarında haksız bir sahiplenme duygusuna sahip gibi görünüyorsunuz.
Alex Turpin

1
Başlangıçta doğru cevaba sahip olmak için cevabı düzenledim. Tüyler için üzgünüm.
Mikko Ohtamaa

3

Exif yönünü CSS dönüşümüne dönüştüren yazdığım bir modüle (tarayıcıda kullanabilirsiniz) göz atın: https://github.com/Sobesednik/exif2css

Tüm yönlere sahip JPEG fikstürleri oluşturmak için şu düğüm programı da var: https://github.com/Sobesednik/generate-exif-fixtures


1
Güzel modül! Bununla birlikte, ilk etapta JPEG'den EXIF ​​bilgilerini nasıl alır?
Mikko Ohtamaa

@MikkoOhtamaa teşekkürler ve hayır değil, bunu exif-js veya exiftool sunucu tarafı ile yapmak zorundasın
zavr

Bu kullanışlı. Ama bana öyle geliyor ki sadece portre fotoğrafları için doğru çalışıyor, manzara fotoğrafları için değil.
Sridhar Sarnobat

3

Özellikle genişliği yükseklikten daha geniş olan img etiketi için doğru rotasyona sahip bazı img etiketlerinde normal olarak html'de android kamera ile fotoğrafı göstermek için genişletme kodunu yüklüyorum. Bu kodun çirkin olduğunu biliyorum ama başka bir paket kurmanıza gerek yok. (Exif rotasyon değerini elde etmek için yukarıdaki kodu kullandım, Teşekkürler.)

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

ve sonra şu şekilde kullanın

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

Ali'nin daha önceki yanıtına daha fazla işlevsellik ekleyerek / geliştirerek, Typescript'te bu sorun için ihtiyaçlarıma uygun bir kullanım yöntemi oluşturdum. Bu sürüm, projeniz için de ihtiyaç duyabileceğiniz derece cinsinden dönüş döndürür.

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

Kullanımı:

import { getOrientation } from './ImageUtils';
...
onDrop = (pics: any) => {
  getOrientation(pics[0], rotationInDegrees => {
    this.setState({ image: pics[0], rotate: rotationInDegrees });
  });
};
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.