主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

JavaScript: Exifを保ったまま画像を縮小 Resize a Jpeg Image by JS Keeping EXIF[obsolete]

                
tags: JavaScript
後日記事: Resize a Jpeg Image by JS Keeping EXIF: part 2


Demo
リサイズ後の画像は、FireFix, Chrome, IE10, OperaでXMLHttpRequestオブジェクトを使ってポストできることを確認してある。
Operaでならブラウザ上の画像を右クリックでExif情報が確認できる。Chrome、FireFoxならローカルに保存して別の手段で。
IE10ではリサイズできるが、ローカル保存は今のところ不可。IE9は非対応。


 なつかしい友だちとの集まりで、みんなの撮った写真を集めて並べてみたくなったので、写真のアップロードサーバを立てた。そのときにExifから撮影日時を抜き出して、すべての写真を撮影日時順で並べた。写真の流れを追うことで、その日が映像のように再生できた。

 さいきんのデジカメは画素数がやたら多く、写真にこだわらない人でもすごいサイズで画像を保存している。でもたいていの人は写真をスマフォで閲覧という具合だ。やたらでかい画像である必要はない。サーバの負担になるから縮小してしまえ。
 画像はどこで縮小するのが最善か。クライアント側でやってもらえれば、サーバへの負担が軽減できる。CPUの使用時間で料金が決まるクラウドサービスを使っているしそれがいい。
 なにを使う? 重要なことはクライアントにとって簡単なこと。基本的には、クライアントになにかをインストールさせる必要がないほうがいい。.NETにしろJAVAにしろ。Flashは普及していたが、いろんなところで非対応になってきている。というわけで、JavaScript。以前に書いた画像縮小コードを流用した。
HTML5とJavaScriptを使って画像(JPEGやPNG)をリサイズ

 ここで問題が起きた。以前に書いた縮小コードは、新しい真っ白な画像に元画像を張り付けて縮小している。ベースが空白画像なので、Exifが失われる。というわけで、Exifをキープしたまま縮小画像を生成するコードを書いた(正しくは、元画像からExifを抜き出して新しい画像に移植している)。この記事執筆時点で、GoogleChrome、Opera、FireFoxで動作を確認した。FileReaderを使っているのでIE9では無理。

 似たようなコードがないかググったところ、イメージとExifが分離されていて、イメージ自体がbase64エンコードされている状態でPOSTしているものがあった。base64エンコードは生データよりサイズが肥大化する。データ量減らすためにイメージの解像度下げておいて、データ量が多いエンコードをかけたままにしておくってどうなのさ。

 Webのフォトストレージサービスをいくつかのぞいてみた。見た限りでは、Exif情報を写真の脚注に表示しているが、リサイズ後の写真すべてからExifが抜けていた。


スクリプト
MinifyJpeg_ob.js

使い方例
加工前の元画像を一度Imageオブジェクトに読み込んで、そのオンロードイベント内で処理をするようにする。そうしなければ正しい画像サイズが得られずにリサイズができなくなる。
メソッドminifyにbase64エンコードされたデータと、縮小画像の長辺の長さをわたす。Uint8Array型で縮小された画像が返されるので、XMLHttpRequestオブジェクトのsendメソッドでポストできる。メソッドencode64は、base64エンコードされて画像として扱える文字列を返す。
    var img = new Image();
img.onload = function(){
minified = MinifyJpeg.minify(image, length);
enc = "data:image/jpeg;base64," + MinifyJpeg.encode64(minified);
html = '<img src="' + enc + '">';
}
img.src = image

MinifyJpeg.minify() - return Uint8Array
image - image base64encoded, it can be obtained "FileReader().readAsDataURL(f)"
length - the long side length of the rectangle
MinifyJpeg.encode64() - convert array to base64encoded string

サーバ Python webapp2
def post(self):
photo = self.request.body

クライアント HTML5
結果を表示するためにJQueryを読み込んだが、縮小スクリプト自体は他のスクリプトに依存がない
<!DOCTYPE html>
<html>
<head>
<style>
</style>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="/js/MinifyJpeg.js"></script>
</head>
<body>
<input type="file" id="files" class="form" name="files[]" multiple />
<script>
PROCESSED = [];

function handleFileSelect(evt) {
var files = evt.target.files; // FileList object

// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
// Only process image files.
if (!f.type.match('image.*')) {
continue;
}

var reader = new FileReader();

// Closure to capture the file information.
reader.onloadend = (function(theFile) {
return function(e) {
if (PROCESSED.indexOf(theFile.name) < 0)
{
PROCESSED.push(theFile.name);
if (e.target.result.match("data:image/jpeg;base64,"))
{
var img = new Image();
img.onload = function()
{
var minified = MinifyJpeg.minify(e.target.result, 70);
postData(minified.image.buffer);
var enc = "data:image/jpeg;base64," + MinifyJpeg.encode64(minified);
$('body').append('<img src="' + enc + '">');
}
img.src = e.target.result;
}
}
};
})(f);

// Read in the image file as a data URL.
reader.readAsDataURL(f);

}
}

function postData(data)
{
var oReq = new XMLHttpRequest();
oReq.open("POST", "/resize/", true);
oReq.setRequestHeader('Content-Type', 'image\/jpeg');
oReq.onload = function (oEvent)
{
// Uploaded.
};

oReq.send(data);
}

document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>
</body>
</html>


ChromeやOperaならば、HTMLでformにmultipartが指定できる。これはFileReaderと組み合わせて、一気に複数のファイルを選択して処理できる。下記のリンクを参考に。
http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-reading-files

フォーム変更のイベントをキャッチして、e.target.filesで複数ファイルを受け取っている。このe.target.filesには、ChromeとOperaで違いがある。Chromeだと、『ファイル選択』ボタンを押してファイルを選択するたびに、そのとき選んだファイルがe.target.filesに入っている。Operaだと、『ファイル選択』ボタンを押してファイルを選択するたびに、ファイルがe.target.filesに保持されていく。スタックとでもいったものか。間違ったファイルを選択したときは、ページを更新する必要があるということだ。というわけで、該当箇所は以下のように書き換えて、ChromeでもOperaでも問題ないようにした。

processed = []; //global
reader.onloadend = (function(theFile) {
return function(e) {
if (processed.indexOf(theFile.name) < 0)
{
processed.push(theFile.name);
someFunction(); //process
}
};
})(f);



参考資料

JPEG
http://labs.gree.jp/blog/category/jpeg/

JavaScriptでのイメージファイルの中身のようなバイナリ値のpostのしかたに関して
ChromeではArrayBufferは使えない。Uint8ArrayなどのようなArrayBufferViewを使う。
http://updates.html5rocks.com/2012/07/Arrived-xhr-send-ArrayBufferViews
https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Sending_and_Receiving_Binary_Data



※JavaScriptの特性として感じたこと(PythonやC#との差異)
今回、ローカルから画像ファイルを読み込んで、サーバへポストするものを書いた。その読み込み動作で一つネックがあったので、コードを画像のロードイベントで駆動する形に書き換えた。
                  var img = new Image();
img.onload = function()
{
var w = img.width;
hoge(w);
}
img.src = e.target.result;

上記のものを下記のように書き換えると、Chromeではimg.widthの値が得られない。imgの読み込みが完了していない状態でスレッドが進行するためだ。
                  var img = new Image();
img.src = e.target.result;
var w = img.width;
hoge(w);

PythonやC#などでは、基本的に書いた手続きの順に処理が実行されていく。このため、途中で負荷の重い処理を入れれば、そこで処理が停滞する。一方で、JavaScriptではImageオブジェクトにもFileReaderオブジェクトにも、さまざまなものにonloadなどのような、オブジェクトの状態をキャッチするメンバが含まれている。これを使って、オブジェクトの状態(読み込み処理の開始や終了)を意識してコードを書いていくべきなんだろう。
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。