pure JavaScriptでアニメーションGIFを作る
こんばんは、国民的スポーツのアニメGIFについてのニュースをお届けします。
今回はクライアントサイドのJavaScriptだけでアニメーションGIFを作る方法を共有させて頂きます。
ふつうアニメGIFを作ろうと思うとimagemagickなどを使ってサーバーサイドで処理させると思いますが、今回紹介するのはブラウザだけでアニメGIFを作る方法です。
jsgif
antimatter15/jsgif · GitHub
jsgifというJavaScriptでCanvasをアニメGIFに変換してくれる超絶便利ライブラリがあるのでこれを使います。
同名でアニメGIFをCanvasで制御再生できるライブラリがあって、そっちのほうが有名ですがそれとは別です。
どうやらAS3のライブラリをJSに移植したものみたいでソースコードを読もうとしても全然分からないけど、便利すぎるので全面の信頼をおいて使います。
だいたいこんな感じで使える。実際には画像をロードするのを待たないといけない。
var img1 = new Image(), img2 = new Image(); img1.src = 'sozai/1.jpg'; img2.src = 'sozai/2.jpg'; var encoder = new GIFEncoder(); encoder.setRepeat(0); encoder.setDelay(100); encoder.setSize(100,100); context.drawImage(img1, 0, 0, canvas.width, canvas.height); encoder.addFrame(context); context.drawImage(img1, 0, 0, canvas.width, canvas.height); encoder.addFrame(context); encoder.finish();
気をつけること
クロスオリジン制約によるCanvasの汚染
canvasには外部ドメインから読み込んだ画像を描画したcanvasは"汚染"されて、画像として保存するなどデータを引き出すことができなくなるセキュリティ上の制約があります。
CORS Enabled Image | MDN
jsgifはcanvasを使って画像データを引き出しているので、外部ドメインから画像を読み込んでいる場合使うことができません。
外部ドメインの画像を使ってアニメGIFを作りたい場合は、CORSなプロキシを通すなどする必要があります。
file://で画像を参照する場合もクロスオリジン制約に引っかかるので、適当なHTTPサーバーを立てて動かす必要があります。
デモ
jsgifを使ったアニメGIF生成の簡単なデモを作ってみました。
GIF生成の処理は重いので、WebWorkerを使ってバックグラウンドで処理させるようにしています。
デモの様子はここから見れます。Chromeで動くと思います。素材にはパブリックドメインな人物画像を使用しています。
http://uiureo.github.com/jsgif-demo/
こんな感じの画像が生成されます。
client.js
// Array{DOM Image} -> callback(dataURL) function createGIF (args, callback) { var images = args.images || []; var option = { delay: args.delay || 100, repeat: args.repeat || 0, // default: auto loop width: args.width || 400, height: args.height || 400 }; var canvas = $('#canvas')[0]; var context = canvas.getContext('2d'); canvas.width = option.width; canvas.height = option.height; // GIFは透明にできないから白色で塗る context.fillStyle = "rgb(255,255,255)"; context.fillRect(0, 0, canvas.width, canvas.height); var worker = new Worker('encoder.js'); worker.postMessage({ cmd: 'start', data: option }); images.forEach(function (image) { context.drawImage(image, 0, 0, canvas.width, canvas.height); // Workerにフレームのデータを送る worker.postMessage({ cmd: 'frame', data: context.getImageData(0, 0, canvas.width, canvas.height).data }); context.fillRect(0, 0, canvas.width, canvas.height); }); worker.postMessage({ cmd: 'finish' }); worker.onmessage = function (e) { callback('data:image/gif;base64,' + encode64(e.data)); }; } // from https://github.com/antimatter15/jsgif/blob/master/Demos/b64.js function encode64(input) { var output = "", i = 0, l = input.length, key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", chr1, chr2, chr3, enc1, enc2, enc3, enc4; while (i < l) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) enc3 = enc4 = 64; else if (isNaN(chr3)) enc4 = 64; output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); } return output; } $(function () { var srcs = ['sozai/1.jpg', 'sozai/2.jpg']; var images = srcs.map(function (src) { var image = new Image(); image.src = src; return image; }); // 画像が全ロードされるまで待つ window.onload = function() { createGIF({ images: images }, function (dataURL) { $("#image").attr('src', dataURL); }); }; }());
encoder.js
WebWorkerでアニメGIFを生成する部分です。
importScripts('LZWEncoder.js', 'NeuQuant.js', 'GIFEncoder.js'); var encoder = new GIFEncoder(); onmessage = function (e) { if (e.data.cmd === "start") { var data = e.data.data; encoder.setRepeat(data.repeat); encoder.setDelay(data.delay); encoder.setSize(data.width, data.height); encoder.start(); } else if (e.data.cmd === "finish") { encoder.finish(); postMessage(encoder.stream().getData()); } else if (e.data.cmd === "frame"){ encoder.addFrame(e.data.data, true); } };
問題点
データが大きすぎてURLに収まらない
小さい画像ならなんとか収まってURLで参照できるけど、アニメGIF用途ではたいていURLに収まらない。URLをコピーして参照とかできない。ちょっと不便。
サイズがでかすぎたら名前をつけて保存ができなかったりする。つらいですね。
まとめ
pure JavaScriptでアニメGIFを作る方法をご紹介しました。
サーバーサイドを書くことなく、ブラウザだけでアニメGIFを作ることができるのでとても便利ですね。
以上、スポーツニュース速報でした。