#Canvasに滑らかな線を描きたい
大学生の時にFlashで作っていた書道風お絵かきアプリをJSで書き直してみました。
https://fujiformat.com/astro_pen/
※今回はスマホ版の話になります。
書道風ですので筆で描いたように線の太さが変化するように作っています。(体験したい方は[設定]からモードを「筆」にしてみてください。)一般的にこうしたスマホでcanvasにお絵かきをするアプリは線(lineTo)を使って軌跡を描くと思いますが、それだと指を早く動かす「撥ね」などの時にいびつな線が出来てしまうので、今回はちょっと重いですが、滑らかな線を描くプログラムを書いてみました。
描いてみた字です。※一部重ね書きしました。
#一般的なコードとその弱点
まずlineToで描くプログラムの仕組みです。
簡単に仕組みを説明しますと指を動かすと一定時間ごとにその座標がとられ、それを直線を使って繋ぐことで軌跡が描かれます。(例えばこちらの方の記事のような:https://qiita.com/mo49/items/eff5156ec9e9999e305b)
通常のお絵かきアプリならこれでいいのですが、今回のように太さを変化させる場合、変化する太さの量を軌跡の数で割って各軌跡の太さを変えていくことになります。方法としてはペンを動かし座標を取得したときに画面を押している強さ(e.touches[0].force)を取得し、それを軌跡の太さに変換します。しかし、現状のスマホの性能ですと指を早く動かしたときに座標間の距離が大きくなってしまい、ぼこぼこの線になってしまいます。
イメージ
#コードの改善
そこで本アプリでは直線の代わりに円を使い、一定時間ごとに指の座標を取得。現在の座標と「現在の座標と直前の座標の間」へ円を補間して描くプログラムへ変更しました。これでしたら取得された座標間が開いていても補間する円の数だけ太さの分割量が多くなり、滑らかな太さの変化がある線が描けます。
追記:一部変数の情報を書き忘れていましたので修正しました。
/*設定*/
//補間する円の数(今回は50)
divide_num=50;
//円弧の始点と終点(0度から360度まで描く→円になる)
pen_head_start_angle=0 * Math.PI / 180;
pen_head_end_angle=360 * Math.PI / 180;
//canvas要素とそのcontextを取得。今回はid=media
const cvs = document.getElementById("media");
const ctx = cvs.getContext("2d");
/*ペンを動かしたとき(touchmoveとか)*/
//画面を押している強さを取得。
pen_force = e.touches[0].force;
d_force=pen_force-old_force;
//canvasの表示位置のズレを取得
offsetx = $("#media").offset().left;
offsety = $("#media").offset().top;
//指のタッチ位置を取得
new_x=e.changedTouches[0].pageX;
new_y=e.changedTouches[0].pageY;
//前回の指位置との差を計測
dx=new_x-old_x;
dy=new_y-old_y;
//補間線を描く
ctx.beginPath();
for(i=0; i<divide_num; i++){
ctx.arc(old_x+dx/divide_num*i-offsetx,
old_y+dy/divide_num*i-offsety,
(old_force+d_force/divide_num*i)*20,
pen_head_start_angle,
pen_head_end_angle, true);
}
ctx.fill();
//古い座標、太さに現在の座標、太さを格納
old_x=new_x;
old_y=new_y;
old_force=pen_force;
/*ペンを動かしはじめたとき(touchstartとか)*/
old_x=e.changedTouches[0].pageX;
old_y=e.changedTouches[0].pageY;
old_force=0;
爪とかつかって押す強さを変えながら描くとこんな感じで描けます。
(iPhoneだともっと繊細に描けるようです)
#forceが取れない機種の対応
iPadなど一部機種ではforceが取得できませんでしたので、固定値にするモードも作成しました。
(上記プログラムでそのモードの時、pen_forceに0.1を設定してあります。)
公開しているアプリはそちらがデフォルトになっています。
#おまけ
今回は首題が太さが滑らかに変化する線が描けるプログラムなので作りませんでしたが、応用すれば透明度や虹色に変化する線も描けます。