WebGLを使ったマンガビューワを作っている
https://gyazo.com/c214bb108f225a5ea462e7c3131ad79d
御池ビルの前の木々
こんにちは
daiizです
Nota Inc.でスクボ.iconScrapbox というwikiを作っています https://gyakky.herokuapp.com/capture/twitter/1030059159431999488#.png
趣味開発では、画像に関するネタで何か作るのが好き
漫画に限らず、短編小説とかにも使えるはず
複数の画像を1枚にまとめる
1年くらいScrapboxで眠っていたアイデアをようやく使えた
当時は応用先として思いつかなかったが、最近マンガと相性良さに気づいた
白黒表示で問題なくて、1話ぶんならページ数もそれほど多くないはず
2値化された複数の白黒画像を1枚のpng画像にする
入出力画像
入力: $ W \times H
2値化済み白黒画像 複数枚
出力: $ W \times H
RGBカラー画像 1枚 (hangaと呼んでいる)
まとめる前後で座標は対応関係にある
元の白黒画像の点$ (x_1, y_1)はそのまま$ (x_1, y_1)に対応する
例
点$ (0, 0)での様子
黒:0, 白:1 として、白黒画像の最初の8ページの色を並べて、8bitの2進数として扱う
これが出力画像の点$ (0, 0)のRGBのRになる
9ページ目以降を使って、G, Bも同様に求まる
https://gyazo.com/1ec34d67441f2f82d0a50ce9ad8fe89c
hangaには最大24枚まで格納できる
R, G, B はそれぞれ8bitの2進数で表現できるため、合計24bit
こういう画像ができあがる
カラフル画像
https://gyazo.com/b42a9b0508400a135771f845bfecee21
この画像だけを管理すればよい
自炊に便利
Gyazoにuploadしやすい。一番嬉しいこと。 まとめた画像を1枚ずつ見たい
hanga画像$ W \times Hピクセルぶんに対して逆操作をすればいい
ブラウザで動くビューワを作りたい
結構大きめの行列を相手にすることになり大変そう
WebGLで画像ビューワを作る
WebGLはじめて触った
主に fragment shader を書いた
今回は各ピクセルでの色を決定するだけでよい
WebGLRenderingContext
const gl = canvas.getContext('webgl', {alpha: true})
普段'2d'としていたところを'webgl'にするだけで良い手軽さ
各点ごとに計算できる
hanga上の各点$ (x, y)に対して以下を行う
ページ番号に応じて、R, G, Bのどれかを選び、値を取得する
glslでは配列の添字に変数を使えない
code:frag.glsl
float getColor (vec4 rgba, int pageNum) {
if (pageNum <= 7) return rgba0; if (pageNum <= 15) return rgba1; }
0 ~ 1 の範囲で返ってくるので255倍して使う
AND演算すると、任意のページの、このピクセルでの色 (黒 or 透明) が確定する
この点での、1ページ目の色を求める例
RGBのR値 & 01000000 を計算するだけで良い
0 $ \longrightarrow 黒相当で着色
0以外の値 $ \longrightarrow 透明にする
白ではなく、透明にしておくのが後のポイント
WebGLでは &演算子がないらしいので、自前でand関数を書く必要がある fragment shader
application js と shader で共有する値
code:frag.glsl
uniform sampler2D u_image;
uniform int u_page; // ページ番号
uniform float u_ink; // 黒相当の色の値
アプリ側でページ送りbuttonが押される度に新たなu_pageが渡されて再計算される
テクスチャに画像をupload
code:app.js
// create texture
const texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)
// upload image to texture
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
予め取得したlocationに値を渡す
code:app.js
const locPage = gl.getUniformLocation(program, 'u_page')
const locInk = gl.getUniformLocation(program, 'u_ink')
gl.uniform1f(locInk, ink)
gl.uniform1i(locPage, state.page)
fragment shader が返す値
そのピクセルを何色で着色するか (gl_FragColor)
code:frag.glsl
// 1ページ目の色を取り出す例
void main () {
float page = 64.; // 01000000
vec4 pixel_color = texture2D(u_image, v_texCoord).rgba;
float color = getColor(pixel_color, page); // 0 ~ 1
float alpha = and(int(color * 255.), int(page)) == 0 ? 1. : 0.; // 黒なら、alphaを1
gl_FragColor = alpha == 1. ? vec4(vec3(u_ink), 1.) : vec4(0.);
}
gl_FragColorのとりうる値
黒相当: vec4(vec3(u_ink), 1.)
透明: vec4(0.)
この色がcanvas要素の各点に着色される
canvasの内容
1個のcanvasに全ページぶんの画像情報が入っていて、適宜切り替えて表示している状態
この画像をダウンロードしておき、ファイル選択から読み込むと、全ページを一気にロードできる
少々粗いが一瞬で24ページぶん読める
https://gyazo.com/c30e1ff2f06a50822b808c850ad6d523
ページ送りのたびに通信されない
最初に一回だけ1MB程度の画像をロードするだけ
https://gyazo.com/e82861f9a7d9985b069818d11c8a6be3
ページジャンプのような非連続な表示も素早い
栞機能とか作れる
https://gyazo.com/6ba20471b9fed1311c58dca3e58224b4
これだと白か黒しか表現できない?
単純なモノクロ画像を作るときは、256段階の中央として、128を採用した
オリジナル画像の各ピクセル色$ Cに対して、$ C \le \text{閾値}ならば黒、それ以外は白とする
https://gyazo.com/c5c67d1b0d10ef62710a89d975dd2e7b/thumb/200.png$ \longrightarrow https://gyazo.com/92b5f4a0c1d9dcbb51ad3d6840fdc5c0/thumb/200.png
閾値を変えてhangaを作ることで、グレーとして扱う画像を作成できる
閾値を大きくすると、黒相当で着色する領域が広がる
漫画なら3段階くらいグレーでもそこそこ綺麗
code:app.js
// グレーレイヤーの有効化/無効化
setupGrayLayerSwitch(1, {ink: 0.500, gray: 192}) // Gray1
setupGrayLayerSwitch(2, {ink: 0.750, gray: 224}) // Gray2
setupGrayLayerSwitch(3, {ink: 0.875, gray: 240}) // Gray3
grayは閾値
inkは0に近いほど黒く、1に近いほど白い
グレー層を複数のcanvasに描画する
3段階のグレー扱いの画像をそれぞれの<canvas>に描画する
それぞれのcanvasは黒 (またはグレー) 以外のピクセルは透過にしてあるので重ねて見せられる
code:html
<div>
<canvas class='c128'></canvas>
<!-- 以下、グレー層 -->
<canvas class='c192'></canvas>
<canvas class='c224'></canvas>
<canvas class='c240'></canvas>
</div>
すべてのcanvasは同時にページ送りされる
粗い画像を数枚重ねればそれなりにいい感じになる
多段階のグレー層を使うことで表現力が上がる
次々スクリーントーンを貼っていく感じ
薄い文字が読みやすくなる
https://gyazo.com/1a52840e7ecf48169ee68158dda3f76d
通信速度が安定しているときだけ、グレーを配信すればいい
最初のシンプルな白黒バージョンだけでもとりあえず読める
グレー画像層は独立して配信できる
https://gyazo.com/7bfed67895aa28988d9491a8ccfe4ab4
意外と嬉しいこと多かった
軽量データなのでダウンロードが速い
予想以上にページ送り (canvas書き換え) が軽快
通信環境に応じて描画クオリティを変えられる
おわり
ソースコード
もう少し詰めたら公開できそう
デモで登場したマンガ
購入して試してうまくいった漫画
個別にお見せできますので、よければお声掛けください