gl-reactを使ってReactで画像処理してみます。
このプラグインはReact、React Native(Bare)、React Native(Expo)、Headlessで利用できるようです。
今回はExpo Managedの環境で使っていきますがメインの内容はGLSL(OpenGL Shading Language)なので大きな違いはないと思います。
パッケージ内でexpo-glを使っているようなのでついでに少し読んでおく。
expo 35.0.0 gl-react v3
最初のサンプル
クックブックが用意されているのでそれに従って使ってみます。
まずは警告が出なくなるまで色々追加。なぜかカメラもいるみたいです。
1 2 |
yarn add gl-react gl-react-expo expo install expo-gl expo-camera |
最初のサンプルを多少書き換えて実行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import React, { Component } from "react"; import { View, Text } from "react-native"; import { Shaders, Node, GLSL } from "gl-react"; import { Surface } from "gl-react-expo"; const shaders = Shaders.create({ helloGL: { frag: GLSL` precision highp float; varying vec2 uv; void main() { gl_FragColor = vec4(uv.x, uv.y, 0.5, 1.0); } ` } }); export default class Example extends Component { render() { return ( <View style={{ marginTop: 24 }}> <Text>TEST GL</Text> <Surface style={{ width: 300, height: 300 }}> <Node shader={shaders.helloGL} /> </Surface> </View> ); } } |
width, heightはサンプル通り書いてもダメなのでスタイル形式で設定。
gl-react-expo <Surface>: no such width/height prop. instead you must use the style prop like for a <View>.
ちなみに100%以外の%指定してもなんか変な感じになります。
重要なのはこれを使うとGLSLが書けるようになることでしょうか。
GLSLについて
OpenGLのシェーダーを書く言語らしいけど使ったことはないはず。
ただ妙に既視感があってGoでグラフィックやるときとかUnityのシェーダーとかで似たようなものを書いたような。
Webでコード実行できるサービスがありました。
http://jp.wgld.org/js4kintro/editor/
VS Codeのプラグインでもいくつかあったけどちょこっと試したいだけならブラウザで出来るのは楽でいいですね。
画像処理・他サンプル
画像処理もしてみたいので切抜サンプルを見つつ改変してみます。
画像の参考先をローカル(lena.png
)にして切抜をひし形から円に変更。
円はステップ関数の中で(uv.x-0.5)2+(uv.y-0.5)2<0.25
を判定すればいいのかと思ったけどpow
関数が見つからないエラー(のちに整数指定のせいだと分かった)。
distance
で距離が測れたのでそっちで中心との距離を比較した。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import React, { Component } from "react"; import { View } from "react-native"; import { Shaders, Node, GLSL } from "gl-react"; import { Surface } from "gl-react-expo"; import lena from "./lena.png"; const shaders = Shaders.create({ DiamondCrop: { frag: GLSL` precision highp float; varying vec2 uv; uniform sampler2D t; void main() { gl_FragColor = mix( texture2D(t, uv), vec4(0.0), step(0.5, distance(uv, vec2(0.5))) ); } ` } }); export default class Example extends Component { render() { return ( <View style={{ marginTop: 24, flex: 1 }}> <View style={{ flex: 1 }}> <Surface style={{ width: "100%", height: "100%" }}> <Node shader={shaders.DiamondCrop} uniforms={{ t: lena }} /> </Surface> </View> </View> ); } } |
簡単に切り抜くことが出来ました。
他サンプルを見ると文字入れ、色の変更、ウェブカメラ、3D、シミュレーションなどかなり種類があります。
サンプルになかったんですが、画像処理といえばフィルタをかけたくなるのでラプラシアンフィルタを書いてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
const shaders = Shaders.create({ Lap: { frag: GLSL` precision highp float; varying vec2 uv; uniform sampler2D t; uniform float filter[9]; void main() { float inv = 1.0 / 512.0; vec2 coo = vec2(gl_FragCoord.s, gl_FragCoord.t); vec3 col = vec3(0.0); col += texture2D(t, (coo + vec2(-1.0, -1.0)) * inv).rgb * filter[0]; col += texture2D(t, (coo + vec2( 0.0, -1.0)) * inv).rgb * filter[1]; col += texture2D(t, (coo + vec2( 1.0, -1.0)) * inv).rgb * filter[2]; col += texture2D(t, (coo + vec2(-1.0, 0.0)) * inv).rgb * filter[3]; col += texture2D(t, (coo + vec2( 0.0, 0.0)) * inv).rgb * filter[4]; col += texture2D(t, (coo + vec2( 1.0, 0.0)) * inv).rgb * filter[5]; col += texture2D(t, (coo + vec2(-1.0, 1.0)) * inv).rgb * filter[6]; col += texture2D(t, (coo + vec2( 0.0, 1.0)) * inv).rgb * filter[7]; col += texture2D(t, (coo + vec2( 1.0, 1.0)) * inv).rgb * filter[8]; if(gl_FragCoord.t>512.0){ gl_FragColor = vec4(0.0,0.0,0.0, 1.0); }else{ gl_FragColor = vec4(col, 1.0); } } ` } }); export default class Example extends Component { render() { let lap = [1.0, 1.0, 1.0, 1.0, -8.0, 1.0, 1.0, 1.0, 1.0]; return ( <View style={{ marginTop: 24, flex: 1 }}> <View style={{ flex: 1 }}> <Surface style={{ width: "100%", height: "100%" }}> <Node shader={shaders.Lap} uniforms={{ t: lena, filter: lap }} /> </Surface> </View> </View> ); } } |
uvでなく座標に直して上下左右の画素値をとってそのまま計算します。
するとViewで縦長にした分は引き延ばされずおかしくなるので上限(512.0)を超えたら黒くしておきました。
使い始めたばかりだけどこれまで作ったアプリの大半はこれで出来そうな気がする。
面白い使い方を考えてみつつもっと中身を勉強してみよう。