Canvasでのパス描画と塗りつぶしの基本技術

Canvasを使用した2Dグラフィックス描画において、パスの定義や線のスタイル、塗りつぶし方法は非常に重要です。本記事では、パスの起点設定、線の描画・塗りつぶし、矩形の描画、非零正交ルールおよび奇偶ルールといった基本的な概念を解説し、実際の折れ線グラフや棒グラフの作成例も紹介します。

パスの基本操作と状態管理

Canvasの描画には、まず描画の開始点を指定する必要があります。
  • ctx.moveTo(x, y):描画の出発点を設定します(座標は左上原点からの相対値)。
  • ctx.lineTo(x, y):現在の位置から指定された点まで線を引きます。
  • ctx.beginPath():新しいパスの開始を宣言。既存のパスとの区別が可能になります。
  • ctx.closePath():最後の点と最初の点を自動的に結び、閉じた領域を作成します。

描画スタイルの設定

  • ctx.stroke():定義されたパスに沿って輪郭線(ストローク)を描画します。色はstrokeStyleで設定。
  • ctx.fill():閉じられたパスの内部を塗りつぶします。fillStyleで色やグラデーションを指定。
  • ctx.setLineDash([5, 10]):線のダッシュパターンを設定(例:5ピクセル実線 → 10ピクセル空白)。
  • ctx.lineDashOffset = 10:ダッシュパターンのオフセット値を調整。

矩形の描画手法

  • ctx.rect(x, y, width, height):矩形のパスを定義しますが、描画は行いません。
  • ctx.fillRect(x, y, width, height):矩形を即座に塗りつぶします。
  • ctx.strokeRect(x, y, width, height):矩形の輪郭線を即座に描画します。
  • ctx.clearRect(x, y, width, height):指定領域の内容をクリア(消去)します。

実践例:複数の図形の描画

以下のコードは、異なる形状とスタイルを組み合わせて描画するサンプルです。

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// 1. 直線の描画
ctx.moveTo(20, 20);
ctx.lineTo(180, 20);
ctx.strokeStyle = '#3a7';
ctx.lineWidth = 2;
ctx.stroke();

// 2. 虚線の描画(ダッシュパターン)
ctx.beginPath();
ctx.moveTo(20, 50);
ctx.lineTo(180, 50);
ctx.setLineDash([6, 4]);
ctx.lineDashOffset = 2;
ctx.stroke();

// 3. 三角形の描画(閉じる)
ctx.beginPath();
ctx.moveTo(250, 30);
ctx.lineTo(300, 100);
ctx.lineTo(200, 100);
ctx.closePath();
ctx.fillStyle = 'rgba(255, 100, 50, 0.7)';
ctx.fill();
ctx.strokeStyle = '#f00';
ctx.stroke();

// 4. 円環の描画(非零正交ルール)
ctx.beginPath();
ctx.arc(400, 60, 50, 0, Math.PI * 2, false); // 外側
ctx.arc(400, 60, 30, 0, Math.PI * 2, true);  // 内側(逆時計回り)
ctx.fillStyle = 'gold';
ctx.fill(); // 非零正交ルールにより、内側は塗りつぶされない

塗りつぶしルール:非零正交と奇偶ルール

パスの内部を塗りつぶす際に使用されるルールは2種類あります。

非零正交ルール(default)

任意の点から外へ放射線を引き、その線がパスの辺と交差する回数をカウント。順時計回りは+1、逆時計回りは-1。最終的な合計が0でなければ、その領域は塗りつぶされます。


// 円環の描画例
ctx.beginPath();
ctx.arc(500, 60, 50, 0, Math.PI * 2, false);     // 外円(時計回り)
ctx.arc(500, 60, 30, 0, Math.PI * 2, true);      // 内円(逆時計回り)
ctx.fillStyle = 'red';
ctx.fill(); // 内部は空洞になる

奇偶ルール(evenodd)

放射線がパスと交差する回数が奇数なら塗りつぶし、偶数なら塗りつぶしません。


ctx.beginPath();
ctx.arc(600, 60, 50, -Math.PI/2, Math.PI*3/2, false);
ctx.moveTo(600, 10);
ctx.lineTo(550, 50);
ctx.lineTo(600, 90);
ctx.lineTo(650, 50);
ctx.closePath();
ctx.fillStyle = 'blue';
ctx.fill('evenodd'); // 塗りつぶされる領域が異なる

棒グラフの実装例

以下は、データ配列に基づいて棒グラフを描画するクラスの実装です。


class BarChart {
    constructor(ctx) {
        this.ctx = ctx;
        this.m = 10; // グリッド間隔
        this.width = ctx.canvas.width;
        this.height = ctx.canvas.height;
        this.cols = Math.floor(this.width / this.m);
        this.rows = Math.floor(this.height / this.m);
        this.barWidth = 30;
        this.originX = 50;
        this.originY = 350;
    }

    draw(data) {
        this.drawGrid();
        this.drawAxes();
        this.drawBars(data);
    }

    drawGrid() {
        this.ctx.strokeStyle = '#eee';
        for (let i = 1; i < this.rows; i++) {
            this.ctx.beginPath();
            this.ctx.moveTo(0, i * this.m + 0.5);
            this.ctx.lineTo(this.width, i * this.m + 0.5);
            this.ctx.stroke();
        }
        for (let j = 1; j < this.cols; j++) {
            this.ctx.beginPath();
            this.ctx.moveTo(j * this.m + 0.5, 0);
            this.ctx.lineTo(j * this.m + 0.5, this.height);
            this.ctx.stroke();
        }
    }

    drawAxes() {
        const x = this.originX;
        const y = this.originY;
        const lenX = 600, lenY = 300;

        this.ctx.beginPath();
        this.ctx.moveTo(x, y);
        this.ctx.lineTo(x + lenX, y);
        this.ctx.lineTo(x + lenX - 10, y - 5);
        this.ctx.lineTo(x + lenX - 10, y + 5);
        this.ctx.fill();

        this.ctx.beginPath();
        this.ctx.moveTo(x, y);
        this.ctx.lineTo(x, y - lenY);
        this.ctx.lineTo(x - 5, y - lenY + 10);
        this.ctx.lineTo(x + 5, y - lenY + 10);
        this.ctx.fill();

        this.ctx.strokeStyle = '#000';
        this.ctx.stroke();
    }

    drawBars(data) {
        data.forEach(item => {
            const barHeight = item.value * 2; // スケーリング
            this.ctx.fillStyle = item.color;
            this.ctx.fillRect(
                this.originX + item.x,
                this.originY - barHeight,
                this.barWidth,
                barHeight
            );
        });
    }
}

// データ
const bars = [
    { x: 0, value: 25, color: 'red' },
    { x: 60, value: 120, color: 'blue' },
    { x: 120, value: 75, color: 'green' },
    { x: 180, value: 200, color: 'purple' }
];

// 実行
new BarChart(document.getElementById('canvas').getContext('2d')).draw(bars);

タグ: Canvas javascript 2D Graphics Path Drawing Fill Rule

9月10日 00:20 投稿