JSクライアント側のExif方向:JPEG画像の回転とミラーリング


154

デジタルカメラの写真は、多くの場合、EXIFの「向き」タグが付いたJPEGとして保存されます。正しく表示するには、設定されている方向に応じて画像を回転またはミラーリングする必要がありますが、ブラウザはこの情報を無視して画像をレンダリングします。大規模な商用Webアプリでも、EXIFオリエンテーションのサポートは不安定な場合があります1。同じソースは 、JPEGが持つことができる8つの異なる方向の優れた要約も提供します。

EXIFオリエンテーションの概要

サンプル画像は4にあります。

問題は、クライアント側で画像を回転/ミラーリングして、正しく表示し、必要に応じてさらに処理できるようにする方法です。

EXIFデータを解析するために利用できるJSライブラリがあり、方向属性2を含みます。webworkersの使用が必要な、大きな画像を解析するときのFlickrは、可能なパフォーマンス上の問題を指摘し3

コンソールツールは、画像の向きを正しく変更できます5。問題を解決するPHPスクリプトは6で利用可能です

回答:


145

githubプロジェクトのJavaScript-Load-Imageは、EXIFの向きの問題に対する完全なソリューションを提供し、8つのすべてのexif向きの画像を正しく回転/ミラーリングします。JavaScript exifオリエンテーションのオンラインデモをご覧ください

画像はHTML5キャンバスに描画されます。その正しいレンダリングは、キャンバス操作を介してjs / load-image-orientation.jsに実装されます。

これが誰か他の人の時間を節約し、このオープンソースgemについて検索エンジンに教えることを願っています:)


1
私はこのライブラリを使用してきましたが、最近iOS 8.3で壊れました。これが最も機能するために必要な場所です:(
Gordon Sun

2
これがどのように役立つのか、私は本当に苦労しています。実際にアップロードする前に、ページを解析して画像を回転する必要があるときに画像を回転したり、方向を検出してファイルを回転したりするために(どういうわけか)、それを学習しようと遊んでいます。デモはどちらも行いません。それは単にファイル入力からファイルを取り、それを正しい方法で表示します、これが現実の世界でいつ役立つのですか?ページを解析し、イメージタグからloadImageライブラリにURLをフィードすると、exifデータがないため、それを行うことができません。アップロードの場合はキャンバスオブジェクトを返すので、サーバーなどには送信できません。
イグノサウルス2016年

3
@igneosaur:base64でエンコードされたデータをサーバーに送信しcanvas.toDataURL()、サーバー側でデコードして保存できます。
br4nnigan 2016年

1
load-imageプロジェクトを使用するのではなく、そのコードベースの一部を使用して独自のプロジェクトをベイクできます-github.com/blueimp/JavaScript-Load-Image/blob/master/js/…を参照してください
Andy Lorenz 2016年

1
このライブラリが生成するキャンバスを通常の<img>タグのようにレスポンシブにする方法はありますか?
Erik Berkun-Drevnig 2016

103

Mederrのコンテキスト変換は完全に機能します。方向を抽出する必要がある場合は、この関数のみを使用しください。EXIF読み取りライブラリは必要ありません。以下は、base64画像の向きを再設定するための関数です。 ここではそれのためのフィドルですオリエンテーション抽出デモのフィドルも用意しました。

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
switchその変換は何もしないので、向きのデフォルトのケースは必要ありません。また、のsrcOrientation > 4 && srcOrientation < 9代わりにを使用することを検討してください。これは[5,6,7,8].indexOf(srcOrientation) > -1、RAMとCPUの両方で、より高速でリソース消費が少ないためです。そこに配列を持つ必要はありません。これは、すべてのビットが重要な多数の画像をバッチ処理する場合に重要です。そうでなければ、かなり良い答え。賛成です!
Ryan Casas

4
@RyanCasas私がindexOfあなたが提案したものと比較してどれほど重いかを知りませんでした。10M反復の単純なループを実行したところ、1400%高速でした。ニース:Dたくさんありがとう!
WunderBart 2017

1
私はAndroid WebViewでこの回答を使用しましたが、WebView内でWebGLをサポートしていないAndroidデバイスがいくつかあることが判明しました(bugs.chromium.org/p/chromium/issues/detail?id=555116を参照)。このようなデバイスでは、画像のサイズによっては回転に非常に時間がかかる場合があります。
ndreisg 2018年

1
参照されていると一緒に使用すると、getOrientationこれがパフォーマンスの点で効率的かどうかが気になります。getOrientationを呼び出しfileReader.readAsArrayBuffer、次に呼び出しURL.createObjectURLて結果をresetOrientationに渡します。これにより、このURLが画像として読み込まれます。これは、画像ファイルがブラウザによって1回ではなく2回「ロード」/読み込まれることを意味しますか、それとも誤解しますか?
Oliver Joseph Ash

1
また、向きが既に尊重されるブラウザーについてはどうしますか?これはiOS Safariの場合と、CSSをimage-orientation: from-image使用するブラウザーをサポートしている場合に当てはまると思います。
Oliver Joseph Ash

40

もし

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

次に、これらの変換を使用して、画像を方向に向けることができます1

オリエンテーションから:

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

ctxで画像を描く前に


58
ここで何が起こっているのですか?
Muhammad Umer 2016年

1
これにより、(EXIFなどを介して)方向を把握し、必要に応じて回転するだけです。私が探しているものの半分。
ブレンデン

2
これらの変換は私にとっては機能しませんでした-代わりに、github.com / blueimp / JavaScript -Load-Image / blob / master / js /…にあるload-imageプロジェクトのコードを使用して、十分に実証されたコードであると確信しているものを取得しましたこれは、キャンバスコンテキストでの移動と回転の操作に加えて、幅と高さの入れ替えを使用します。変換などの他の試行を何時間も行った後、100%の結果が得られました
Andy Lorenz

1
ctxで画像を描画するときになぜ重要なのですか?キャンバスに画像を描いた後、キャンバスを回転させることはできませんか?
AlxVallejo 2017

方向8の回転は、私にとって次のように機能します。ctx.transform(0、-1、1、0、0、height);
Giorgio Barchiesi

24

@ user3096626の回答に加えて、誰かがコード例を提供した方がより役立つと思います。次の例では、画像の向きをURL(リモート画像)から修正する方法を示します。


解決策1:JavaScriptを使用する(推奨)

  1. ので、負荷イメージライブラリーは、URLの画像のみ(ファイル/ブロブ)からEXIFタグを抽出しない、我々は両方を使用しますEXIF-JS負荷-画像その最初次のように自分のページにこれらのライブラリを追加し、JavaScriptライブラリを:

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

    なお、 EXIF-jsからの2.2は、我々は2.1を使用して問題があると思われるバージョン

  2. それから基本的に私たちがすることは

    a-を使用して画像をロードする window.loadImage()

    b-使用してexifタグを読み取る window.EXIF.getData()

    c-画像をキャンバスに変換し、使用して画像の向きを修正します window.loadImage.scale()

    d-キャンバスをドキュメントに配置します

どうぞ :)

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

    });
  }
});

もちろん、canvasオブジェクトからbase64として画像を取得してimg src属性に配置することもできるので、jQueryを使用して実行できます;)

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

ここに完全なコードがあります:github:https : //github.com/digital-flowers/loadimage-exif-example


解決策2:HTMLを使用する(ブラウザーのハック)

非常に迅速かつ簡単なハックがあり、ほとんどのブラウザーは、画像が新しいタブ内でHTMLなしで直接開かれた場合に正しい方向で画像を表示します(LOL理由はわかりません)。したがって、基本的にはiframeを使用して画像を表示できますiframe src属性を画像のURLとして直接配置する:

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

解決策3:CSSを使用する(iOSではFirefoxとSafariのみ)

画像の向きを修正するためのcss3属性がありますが、Firefoxとsafari / iosでのみ機能する問題は、すぐにすべてのブラウザーで利用できるようになるため、言及する価値があります(caniuseのブラウザーサポート情報)

img {
   image-orientation: from-image;
}

解決策1がうまくいきませんでした。window.loadImageが未定義
Perry

これは、手順1で言及したライブラリを含めなかった場合に発生しました
Fareed Alnamrouti 2017

ライブラリを含めました。私はあなたの例の単純さを気に入っていますが、そのエラーメッセージを受け取り続けます。
ペリー

1
exif-jsが壊れているようです。編集内容を確認してください。githubに
Fareed Alnamrouti

1
ok githubプロジェクトに新しいファイル "upload.html"があるのをチェックアウトしてください、どういたしまして:)
Fareed Alnamrouti 2017

10

入力コントロールからのファイルを持っている人は、その向きがわからず、少し怠惰であり、下に大きなライブラリを含めたくない場合は、@ WunderBartが提供するコードに、彼がリンクしている回答を追加します(https://stackoverflow.com/a/32490603)向きを見つけます

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

そのように簡単に呼び出すことができます

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

2
グーグルによる2時間以上の検索の後に感謝します私は正しい解決策を見つけます。
Mr.Trieu

2
なぜ誰かがずっと軽い解決策を好むのが面倒なのでしょうか?
dewd '

4
Typescriptに移植され、約4秒から約20ミリ秒に最適化されました。Base64エンコーディングがボトルネックでした- image/jpegデフォルトの代わりに使用しimage/png、出力画像を最大幅(私の場合は400px)にサイズ変更すると、大きな違いが生じました。(これはサムネイルに適していますが、画像全体を表示する必要がある場合は、base64を使用せず、キャンバス要素をページに直接挿入することをお勧めします...)
mindplay.dk 2018

8

WunderBartの答えは私にとって最高でした。画像の向きが正しい場合は、最初に方向をテストし、回転が不要な場合はコードの残りの部分をバイパスするだけで、速度を大幅に向上できることに注意してください。

wunderbartのすべての情報をまとめると、次のようになります。

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
素晴らしいアプローチ。また、回転データなしでjpg以外の画像や画像を回転させないように、の処理-2-1からの戻りを検討する必要がありgetOrientationます。
Steven Lambert

さらに最適化を行いました。上記の私のコメントを参照してください- file.slice()最適化も追加しましたが、本当の効果は、より小さな画像とimage/jpeg出力形式を使用してより小さなデータURLを作成することです。(10メガピクセルの画像でも目立った遅延はありません。)
mindplay.dk 2018

file.slice()base64画像ソースのサポートを回避するため、注意してください。
マーク

おかげでマーク-代替案の編集を提供するように気をつけますか?
statler

7

ワンライナーは誰ですか?

browser-image-compressionライブラリについて言及している人を見たことがありません。これに最適なヘルパー関数があります。

使用法: const orientation = await imageCompression.getExifOrientation(file)

他の多くの点でもこのような便利なツールです。


これは方向を取得します。しかし、この情報を使用してクライアント側で新しい.jpgファイルオブジェクトを作成するにはどうすればよいですか?
masterxilo

File私の知る限り、オブジェクトを作成することはできません。次善の策は、サーバーに画像と向きを送信し、そこでファイル操作を行うことです。またはcanvastoDataURLメソッドを使用してbase64文字列を生成することもできます。
gilbert-v

1
いいえ、BlobからFileオブジェクトを作成することはできます。ファイルファイル(配列部分、文字列ファイル名、BlobPropertyBagプロパティ); developer.mozilla.org/de/docs/Web/API/File
masterxilo

2
ゴールドステッカー!@masterxilo
gilbert-v

2
これは私にとってはうまくいきました!過去2日間、オリエンテーションに苦労していました。投稿されたすべての変換スイッチステートメントは、何らかの理由で私の電話からの縦向きの画像を処理しません。黒いバーが表示されます。おそらく、同時に圧縮と回転を試みていたからでしょう。
cjd82187

2

これを正確に解決するES6モジュールでラップされたクラスを作成しました。

それは103行で、依存関係はなく、かなりうまく構造化および文書化されているため、変更/再利用が簡単です。

8つの可能な方向すべてを処理し、Promiseベースです。

これで、これがまだ誰かを助けることを願っています:https : //gist.github.com/vdavid/3f9b66b60f52204317a4cc0e77097913


1

混合ソリューション(php + css)を使用しています。

コンテナは次の場合に必要です。

  • div.imgCont2 回転するために必要なコンテナ。
  • div.imgCont1zoomOut- width:150%;に必要なコンテナ
  • div.imgCont 画像が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>

おかげで、これは私のとにかく最善の解決策のようです。外部ライブラリや不要なコードの長いブロックは必要ありません。phpでexifから方向を決定し、それをビューに送信し、それを使用して、適切な度数を回転させるCSSクラスをトリガーします。素敵できれい!編集:これは、実際にexifデータを読み取り、それに応じて回転するブラウザーの画像をオーバー回転します。別名、デスクトップで問題を修正しますが、iOSサファリで新しい問題を作成します。
cwal

0

@fareed namroutiの回答に加えて、

これは、画像をファイル入力要素から参照する必要がある場合に使用する必要があります

<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

画像を回転させる小さなphpスクリプトを作成しました。リクエストごとに再計算するために、必ず画像を保存してください。

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

?>

乾杯

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.