1個の矩形や1本のベジェ曲線を描く
まずは単純に図形を描く実装だけしてみましょう。まだマウスでお絵描きするわけではありません。指定した座標に図形を描くだけです。この連載では、頂点と曲線を繋いで一筆書きするので、ここでは少しサイズのある頂点を矩形で描いてみて、次に曲線をベジェ曲線で描いてみます。
1個の矩形を描く
輪郭線だけを描く場合は「stroke○○」というメソッドを使います。strokeというのは、絵画において「筆のタッチ」を意味する単語です。<canvas>タグは自由に絵を描画できる矩形領域で3Dも描けますが、今回は2Dを描きます。「context(コンテキスト)」は「文脈」や「状況」を表す単語で、ここでは描画領域の文脈を表すのでしょう。
// TAURI
const { invoke } = window.__TAURI__.core;
// 変数
let _canvas = document.querySelector('canvas');
let _context = _canvas.getContext('2d');
// 矩形の描画
_context.strokeStyle = 'rgb(0,0,0)';
_context.lineWidth = 10;
_context.strokeRect(200,150,100,100);
サンプルコードの解説
「invoke」関数はTauriコマンド関数とデータをやり取りするもので、まだ使いません。
<canvas>タグを「_canvas」変数に取得し、getContext('2d')で二次元の描画領域のコンテキストを取得して「_context」変数に代入します。
コンテキストの「strokeStyle」プロパティで輪郭線を真っ黒にセットします。
コンテキストの「lineWidth」プロパティで線の太さを10にセットします。
コンテキストの「strokeRect」メソッドで(X,Y)=(200,150)から(幅,高さ)=(100,100)の正方形を描きます。
1本のベジェ曲線を描く
ベジェ曲線のプログラミングを書く前に、ベジェ曲線とはどういうものなのか次の図を見てください。ベジェ曲線は2つの頂点Aと頂点Bを結んだ線分を、コントロールポイントの座標で磁石のように引っ張ったような曲線を描きます。ベジェ曲線も輪郭線である筆なのでstrokeを使います。
// TAURI
const { invoke } = window.__TAURI__.core;
// 変数
let _canvas = document.querySelector('canvas');
let _context = _canvas.getContext('2d');
// ベジェ曲線の描画
_context.strokeStyle = 'rgb(0,0,0)';
_context.lineWidth = 10;
_context.beginPath();
_context.moveTo(100,100);
_context.quadraticCurveTo(300,300,500,100);
_context.stroke();
サンプルコードの解説
コンテキストの「beginPath」メソッドから線のパス(道筋)を描き始めます。
コンテキストの「moveTo」メソッドで(X,Y)=(100,100)に頂点の始点の位置をセットします。
コンテキストの「quadraticCurveTo」メソッドで曲線のコントロールポイント(300,300)と終点(500,100)をセットします。
コンテキストの「stroke」メソッドで現在のパスの輪郭線を描画します。
お絵描きアプリの開発を始める
マウスクリックを繰り返したら頂点だけ描画するアプリを作ります。まだ線は描画しません。まず、<canvas>タグにおけるマウスの正確な座標は考慮せずにクリック座標を取得してお絵描きします。後で正確なマウス座標を「getPos」関数で取得します。
マウスクリックで一筆書き
次のサンプルコードのように追記してプロジェクトをデバッグビルドして実行したら、マウスをクリックしたときに、「_pos」配列にベジェ曲線の情報が入ります。その要素にはコントロールポイント(X1,Y1)、頂点(X1,Y1)、コントロールポイント(X2,Y2)、頂点(X2,Y2),・・・と繰り返されます。ただし、まだコントロールポイントのデータは使わず、頂点(X,Y)しか使いません。
_pos配列の中身
[コントロールポイントX1,コントロールポイントY1,頂点X1,頂点Y1,コントロールポイントX2,コントロールポイントY2,頂点X2,頂点Y2,・・・]
_pos配列の要素はコントロールポイント(X,Y)と頂点(X,Y)の4要素を繰り返します。
// TAURI
const { invoke } = window.__TAURI__.core;
// 変数
let _canvas = document.querySelector('canvas');
let _context = _canvas.getContext('2d');
let _pos = [];
let _current = 0;
// 全てのDOMが読み込み完了したら
window.addEventListener("DOMContentLoaded", () => {
// マウスがクリックされたら頂点描画
_canvas.addEventListener("mousedown", (e) => {
let pos = getPos(e);
_pos.push(pos[0],pos[1],pos[0],pos[1]);
_current = _pos.length/4;
line();
});
});
// 図形の描画
function line() {
_context.clearRect(0,0,_canvas.width,_canvas.height);
_context.strokeStyle = 'rgb(0,0,0)';
_context.lineWidth = 10;
// 1クリックした頂点
for ( let i = 0; i < _current; i++ ) _context.strokeRect(_pos[i*4+2],_pos[i*4+3],1,1);
}
// マウス座標の取得
function getPos(event) {
return [event.pageX,event.pageY];
}
サンプルコードの解説
htmlファイルのDOMが全て読み込み完了したら、マウスダウンをフックします。マウスダウンされたらマウス座標を取得し、_pos配列にコントロールポイントとマウス座標を追加(push)し、_pos配列をもとに複数描く頂点の数を_current変数に取得し、頂点を描きます。
「line」関数で、<canvas>タグを真っ白にクリアし、頂点の矩形を_pos配列の要素の4分の1の数だけ繰り返して描画(strokeRectメソッド)します。
正確なクリック座標に
次のサンプルコードのように追記してプロジェクトをデバッグビルドして実行したら、<canvas>タグにおける正確なクリック座標を取得できます。ウィンドウとウィンドウ内のクライアント領域と、<canvas>タグの配置位置を考慮してから、マウス座標との差から正確な座標が得られます。
(前略)
// マウス座標の取得
function getPos(event) {
let clickX = event.pageX;
let clickY = event.pageY;
// 要素の位置を取得
let clientRect = _canvas.getBoundingClientRect();
let positionX = clientRect.left + window.pageXOffset;
let positionY = clientRect.top + window.pageYOffset;
// 要素内におけるクリック位置を計算
let x = clickX - positionX;
let y = clickY - positionY;
return [x,y];
}
サンプルコードの解説
単純にマウス座標(clickX,clickY)を取得します。
<canvas>タグのクライアント領域をclientRect変数に取得します。
ウィンドウの位置から考慮して<canvas>タグの左上の(positionX,positionY)を取得します。
<canvas>タグにおけるマウス座標(x,y)を取得します。
配列にした[x,y]をgetPos関数の戻り値として返します。
