更新履歴:
v0.03 ArrayBufferのtransfer設定を追加.(パフォーマンスが改善される?)
v0.04 msToBlobの使い方を間違えていた部分を修正
下記のコードをjsファイルにコピペしてWEBページから読み込んでください.
canvas要素をサポートする環境ではHTMLCanvasElementが拡張され,画像形式に"image/bmp"を指定可能となります.
拡張されるメソッドはtoDataURLとtoBlobです.
・toBlobメソッドではWebWorkerが動作する環境ではWorkerを使ってBlobを生成します.
・WEBGLには対応していません.
動作環境
FireFox,Chrome(Opera),IE10+.・IE9ではbase64.jsを読み込ませることで動作するようになります.
・toBlobメソッドはBlobをサポートしている環境でのみ動作します.
・toBlobメソッドが存在しない場合は,toDataURLから無理矢理Blobを生成してtoBlobを実装します.
・とは言え,それほど動作検証はしていませんのでご注意ください.
/*
bmpcanvas.js 0.04
Extends HTMLCanvasElement to output BMP image (OS/2 type).
Copyright (c) 2014 DEFGHI1977
https://twitter.com/DEFGHI1977
http://defghi1977-onblog.blogspot.jp/
FireFox,Chrome,Opera,IE11+(IE9+)
* If you want to work on IE9 you should import base64.js.
see https://code.google.com/p/stringencoders/source/browse/trunk/javascript/base64.js?r=210
* On IE9 and old Opera toBlob does not work.
MIT License
*/
"use strict";
if(!window.HTMLCanvasElement){return;}
(function(cproto){
//for IE9
var Uint8Array = window.Uint8Array;
if(!Uint8Array){
//NOTE:my Uint8Array does'nt have buffer property.
Uint8Array = function(length){this.length = length};
Uint8Array.prototype = [];
Uint8Array.prototype.set = function(arr, offset){
for(var i = 0, len = arr.length; i<len; i++){
this[i + offset] = arr[i] & 255;
}
};
}
//define btoa by base64.js
var btoa = window.btoa || window.base64.encode;
//original methods.
var toDataURL = cproto.toDataURL;
var toBlob = cproto.toBlob || cproto.webkitToBlob;
//string contstants.
var bmpRegex = /image\/(?:bmp|x-ms-bmp|x-windows-bmp)/;
var bmpType = "image/bmp";
var base64 = "base64,";
var bmpScheme = "data:" + bmpType + ";" + base64;
var message = "message";
var nullstr = "";
//define toBlob for chrome, ie e.t.c.
toBlob = toBlob || function(f, type, params){
var url = this.toDataURL(type, params);
var b64 = url.split(base64)[1];
var barr = new Uint8Array(atob(b64).split(nullstr).map(function(c){
return c.charCodeAt(0);
}));
f(new Blob([barr], {type: type}));
};
//override toDataURL
cproto.toDataURL = function(type){
if(!bmpRegex.test(type)){
return toDataURL.apply(this, arguments);
}
//see http://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string
var bmp = toBMP(getAll(this), this.width, this.height);
var chars = new Array(bmp.length);
for(var i = 0, len = bmp.length; i<len; i++){
chars[i] = (String.fromCharCode(bmp[i]));
}
return bmpScheme + btoa(chars.join(nullstr));
};
//override toBlob
cproto.toBlob = function(callback, type){
if(!bmpRegex.test(type)){
return toBlob.apply(this, arguments);
}
try{
var worker = getWorker();
worker.addEventListener(message, function(e){
//callback
var data = e.data.buffer ? new Uint8Array(e.data.buffer): e.data.data;
callback(new Blob([data], {type: bmpType}));
URL.revokeObjectURL(worker.URL);
}, false);
worker.onerror = function(e){console.log(e.message);}
var data = getAll(this);
worker.postMessage({
data: data,
buffer: data.buffer,
w: this.width,
h: this.height
}, [data.buffer]);//transferable
}catch(e){
//when worker doesn't work by security error.
var bmp = toBMP(getAll(this), this.width, this.height);
callback(new Blob([bmp], {type: bmpType}));
}
};
//create WebWorker
var getWorker = (function(){
var listener = function(e){
try{
var data = e.data.buffer ? new Uint8Array(e.data.buffer) : e.data.data;
var bmp = toBMP(data, e.data.w, e.data.h);
postMessage({data: bmp, buffer: bmp.buffer}, [bmp.buffer]);//transferable
}finally{
self.close();
}
}
var workerSource = "'use strict';" + toBMP.toString()
+ "self.addEventListener('message', " + listener.toString() + ", false);";
var jsType = "text/javascript";
return function(){
var workerSourceBlob
= new Blob([workerSource], {type: jsType});
var workerURL = URL.createObjectURL(workerSourceBlob);
var worker = new Worker(workerURL);
worker.URL = workerURL;
return worker;
};
})();
//get whole image data.
function getAll(canvas){
return canvas.getContext("2d")
.getImageData(0, 0, canvas.width, canvas.height).data;
}
//convert Uint8ClampledArray of canvas to Uint8Array of bmp image.
function toBMP(data, w, h){
//see http://ja.wikipedia.org/wiki/Windows_bitmap
var bgrw = w * 3;//horizontal byte length
var filler = bgrw % 4 == 0 ? 0 : 4 - bgrw % 4
var size = (bgrw + filler) * h + 26;
var bmp = new Uint8Array(size);
//BITMAPFILEHEADER 14bytes
bmp.set([
0x42, 0x4d, //fileType
size, size>>>8, size>>>16, size>>>24, //fileSize
0, 0, 0, 0,//reserved
26, 0, 0, 0 //offset
], 0);
//BITMAPCOREHEADER 12bytes
bmp.set([
12, 0, 0, 0, //header size
w, w>>>8, //width
h, h>>>8, //height
1, 0, //plane
24, 0 //bits par pixcel
], 14);
//IMAGE DATA
var i = 26;//offset
for(var y = 0; y < h; y++){
for(var x = 0; x < w; x++){
var s = (x + (h - y - 1) * w) * 4;//bottom up
bmp[i++] = data[s + 2];//B
bmp[i++] = data[s + 1];//G
bmp[i++] = data[s];//R
}
//0 filling
for(var j = 0; j < filler; j++){
bmp[i++] = 0;
}
}
return bmp;
}
})(HTMLCanvasElement.prototype);
見どころ
メイン処理とWorker内部とで同じ関数を呼び出したい場合,Workerが参照するコードをFunction.toStringで取得して文字列として切り貼りし,Blobを使ってURL化するとキレイに記述できます.ですが,この方法だとオリジンの問題からセキュリティエラーを発生する事もあります.
0 件のコメント:
コメントを投稿