JavaScript
14. ベジエ曲線を利用する
ベジエ曲線は、直線や円弧とは異なる複雑な図形の描画も可能です。
指定する座標が多く、また描かれる曲線の姿が想像しにくい面もありますが、上手く利用すると多様な表情の図形を描くことができます。
制御点を規則的に変化させる
window.addEventListener( "load", loadFunc, false );
function loadFunc() {
var canvas = document.getElementById( "stage" );
var ctx = canvas.getContext( "2d" );
var leftX = 50; //始点のx座標
var topY = 50; //始点のy座標
var rightX = 450; //終点のx座標
var bottomY = 450; //終点のy座標
var repeatNum = 50; //繰返しの回数
var controlPitch = ( rightX - leftX ) / repeatNum; //制御点を移動させる幅を、横幅を繰返しの回数で割って求める
var controlSpan = 100;
ctx.strokeStyle = "#cc3333";
for( var i = 0; i < repeatNum; i++ ) {
var controlX = leftX + i * controlPitch;
var controlY = bottomY - i * controlPitch;
ctx.beginPath();
ctx.moveTo( leftX, topY );
ctx.bezierCurveTo( controlX, controlY - controlSpan,
controlX, controlY + controlSpan,
rightX, bottomY );
ctx.stroke();
//ctx.fillRect( controlX - 2, controlY - 2 - controlSpan, 4, 4 );
//ctx.fillRect( controlX - 2, controlY - 2 + controlSpan, 4, 4 );
}
}
ベジエ曲線を練習する時、始点や終点、制御点を規則的に変化させると、どのような性質を持った曲線なのかが理解しやすくなります。
25行目〜27行目、長くなるので改行していますが、1行に書いても同じことです。
24行目、ベジエ曲線の始点は、x座標leftX、y座標topYに固定です。27行目、ベジエ曲線の描き終わりもx座標rightX、y座標bottomYに固定です。
21、22行目で、ベジエ曲線の2つの制御点の座標の基準を計算で求めていますが、画面左下から画面右上に等間隔で変化するようにしています。
25、26行目、21、22行目で求めた点から実際の制御点の位置を、y方向に-controlSpan、+ controlSpanしています。つまり上下に100ピクセルずらしています。
制御点の間隔は、描画全体の幅( rightX - leftX )を繰返しの回数で割って求めていますので、repeatNumの値さえ変えればベジエ曲線の本数を変えることができます。
29、30行目をコメントではなくすると、制御点の位置に小さな四角が描かれます。

ベジエ曲線は、始点、終点、制御点を変えると、ちょっとした変更でも大きくその表情を変えます。色々試してみて欲しいと思います。
window.addEventListener( "load", loadFunc, false );
function loadFunc() {
var canvas = document.getElementById( "stage" );
var ctx = canvas.getContext( "2d" );
var leftX = 50; //始点のx座標
var topY = 50; //始点のy座標
var rightX = 450; //終点のx座標
var bottomY = 450; //終点のy座標
var repeatNum = 50; //繰返しの回数
var controlPitch = ( rightX - leftX ) / repeatNum; //制御点を移動させる幅を、横幅を繰返しの回数で割って求める
ctx.strokeStyle = "#cc3333";
for( var i = 0; i < repeatNum; i++ ) {
var controlX1 = leftX + i * controlPitch;
var controlX2 = rightX - i * controlPitch;
ctx.beginPath();
ctx.moveTo( controlX1, topY );
ctx.bezierCurveTo( controlX1, 1000,
controlX2, -500,
controlX2, bottomY );
ctx.stroke();
}
}

ベジエ曲線を描画単位に利用する
「13. 描画の単位を考える」で行ったような描画単位にベジエ曲線を利用してみます。
どのような描画単位しようか考える際には、いきなり数多くの繰返しを行うのではなく(例えば以下のrepeatNumを1にしておくなどして)描画単位がある程度作り、その上で繰返しを行った方が良いでしょう。
描画単位:異なる大きさの波形の線
window.addEventListener( "load", loadFunc, false );
function loadFunc() {
var canvas = document.getElementById( "stage" );
var ctx = canvas.getContext( "2d" );
var repeatNum = 9;
var pitch = 50;
var shapeSize = 50;
var pitchWidth = pitch * ( repeatNum - 1 );
var leftX = ( canvas.width - pitchWidth ) / 2;
for( var i = 0; i < repeatNum; i++ ) {
for( var j = 0; j < repeatNum; j++ ) {
var drawPointX = leftX + j * pitch;
var drawPointY = leftX + i * pitch;
var lineAlpha = Math.random() / 2 + 0.5;
ctx.strokeStyle = "hsla( 240, 50%, 40%, " + lineAlpha + " )";
ctx.lineWidth = Math.floor( Math.random() * 3 ) + 1;
drawShape( ctx, drawPointX, drawPointY, shapeSize );
ctx.stroke();
}
}
}
function drawShape( argCtx, centerX, centerY, size ) {
var repeatBezNum = 5;
var ctlPointPitch = size / repeatBezNum;
var tmpX = centerX - size/2;
argCtx.beginPath();
argCtx.moveTo( tmpX, centerY );
for( var i = 0; i < repeatBezNum; i++ ) {
var yuragiX = Math.floor( Math.random() * ctlPointPitch/2 ) - ctlPointPitch/4;
var yuragiY1 = Math.floor( Math.random() * size * 2 ) + size;
var yuragiY2 = Math.floor( Math.random() * size * 2 ) + size;
tmpX = tmpX + ctlPointPitch;
argCtx.bezierCurveTo( tmpX - ctlPointPitch/2 - yuragiX , centerY - yuragiY1,
tmpX - ctlPointPitch/2 + yuragiX, centerY + yuragiY2,
tmpX, centerY );
}
}
20行目、繰返しの中で線の透明度に指定する変数の値をランダムに求めています。同様に、22行目、線の太さもランダムにしています。
40〜52行目、drawShape()内で、さらに繰返しを記述し、その回数に応じて波の形になるように、ベジエ曲線に必要な座標を変化させています。

ランダムを多用すれば良いというものでもないのですが、プログラムで手描きらしさをどのように出すか、ということも考えてみる方向の1つとしては面白いかもしれません。
描画単位:ベジエ曲線でつくる形
window.addEventListener( "load", loadFunc, false );
function loadFunc() {
var canvas = document.getElementById( "stage" );
var ctx = canvas.getContext( "2d" );
var repeatNum = 20;
var pitch = 20;
var shapeSize = 100;
var pitchWidth = pitch * ( repeatNum - 1 );
var leftX = ( canvas.width - pitchWidth ) / 2;
for( var i = 0; i < repeatNum; i++ ) {
for( var j = 0; j < repeatNum; j++ ) {
var drawPointX = leftX + j * pitch;
var drawPointY = leftX + i * pitch;
var colorHue = Math.floor( Math.random() * 360 );
ctx.fillStyle = "hsla( " + colorHue + ", 50%, 50%, 0.5 )";
drawShape( ctx, drawPointX, drawPointY, shapeSize );
ctx.fill();
}
}
}
function drawShape( argCtx, centerX, centerY, size ) {
var tmpX = Math.floor( Math.random() * size / 2 ) - size / 4;
var yuragiX = Math.floor( Math.random() * 4 ) + 1;
var topX = centerX + tmpX;
var bottomX = centerX - tmpX;
argCtx.beginPath();
argCtx.moveTo( topX, centerY - size/2 );
argCtx.bezierCurveTo( topX + yuragiX, centerY - size/4,
topX + yuragiX, centerY,
bottomX, centerY + size/2 );
argCtx.bezierCurveTo( topX - yuragiX, centerY,
topX - yuragiX, centerY - size/4,
topX, centerY - size/2 );
argCtx.closePath();
}
