すみません。ゲーム用に調べてて作ったサンプルなのですが、動くけど中身ぜんぜん分かってません。


  1. ドットの円と楕円のサンプル
  2. ペイントっぽいサンプル
  3. そのほか

ドットの円と楕円のサンプル

まず、円を描画するアルゴリズムを調べました。すると英語版WikipediaにJavaScriptのソースが載ってたので、変数名とかを自分に分かるように書き直して使いました。

よくわからないのですが、dy の値が1だと半径が小さいときに円というより四角になってしまったので、いろいろ触ってみて2に変更することにしました。

次に、楕円を描画するアルゴリズムを調べました。こちらはStack Overflowに貼られていたものを参考にしました。触ってみても1ミリしか解らなくて魂が砕けました。ただ、この方法だと縦横の長さを入れ替えて90度回転させたとき、完全に一致する楕円にはならないらしい。

これらをお借りして、ドットで線を引けるようにいじったサンプルが以下のものです。

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8" />
	<meta name="robots" content="noarchive" />
	<title>Canvas ドットの円・楕円描画サンプル その1</title>
	<style>
html { background-color:#000; color:#eee; }
#canvas { display: block; box-sizing: border-box; margin: 5px 0 10px; border: 1px solid #666; }
	</style>
</head>
<body>
	種類: <select id="draw-type">
		<option value="circle">円</option>
		<option value="ellipse-horizontal">楕円(横)</option>
		<option value="ellipse-vertical">楕円(縦)</option>
	</select> 
	ドットの大きさ: <select id="pixel-size">
		<option value="1">1</option>
		<option value="2">2</option>
		<option value="3">3</option>
		<option value="4">4</option>
		<option value="6">6</option>
		<option value="8">8</option>
	</select>px
	<canvas id="canvas"></canvas>
	<script src="sample1.js"></script>
</body>
</html>
JavaScript
'use strict';

(() => {
	const CANVAS_WIDTH = 320;
	const CANVAS_HEIGHT = 320;
	const strokeColor = [255, 255, 255, 255];
	const canvas = document.getElementById('canvas');
	const ctx = canvas.getContext('2d');
	
	// Canvasの大きさ設定
	canvas.width  = CANVAS_WIDTH;
	canvas.height = CANVAS_HEIGHT;

	/**
	 * Canvasにサンプルを描画する
	 * @param {CanvasRenderingContext2D} ctx Canvasのコンテキスト
	 * @param {String} type - 種類 (circle|ellipse-horizontal|ellipse-vertical)
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const draw = (ctx, type, pixelSize) => {
		// 黒で塗りつぶす
		ctx.fillStyle = 'rgba(0, 0, 0, 255)';
		ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

		// 値をドットの大きさの整数倍に補正する
		const pixelize = function(value) {
			return value - (value % pixelSize);
		};
		const x = pixelize(160);
		const y = pixelize(160);
		
		// ImageData取得して書き込み
		const imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
		switch (type) {
			case 'circle':
				putPixelCircle(imageData, x, y, pixelize(10), pixelSize);
				putPixelCircle(imageData, x, y, pixelize(30), pixelSize);
				putPixelCircle(imageData, x, y, pixelize(50), pixelSize);
				putPixelCircle(imageData, x, y, pixelize(80), pixelSize);
				putPixelCircle(imageData, x, y, pixelize(110), pixelSize);
				putPixelCircle(imageData, x, y, pixelize(140), pixelSize);
				break;
			case 'ellipse-horizontal':
				putPixelEllipse(imageData, x, y, pixelize(16), pixelize(8), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(48), pixelize(24), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(80), pixelize(40), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(120), pixelize(60), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(180), pixelize(90), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(240), pixelize(120), pixelSize);
				break;
			case 'ellipse-vertical':
				putPixelEllipse(imageData, x, y, pixelize(8), pixelize(16), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(24), pixelize(48), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(40), pixelize(80), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(60), pixelize(120), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(90), pixelize(180), pixelSize);
				putPixelEllipse(imageData, x, y, pixelize(120), pixelize(240), pixelSize);
				break;
		}

		// ImageDataをcanvasに反映
		ctx.putImageData(imageData, 0, 0);
	};
	
	/**
	 * ImageDataにドットの円を描画する
	 * https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
	 * @param {ImageData} imageData
	 * @param {Number} cx - 中心X座標
	 * @param {Number} cy - 中心Y座標
	 * @param {Number} radius - 半径
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixelCircle = (imageData, cx, cy, radius, pixelSize) => {
		const diameter = radius * 2; // 直径
		let x = radius; // 半径 - 線幅
		let y = 0;
		let dx = 1;
		let dy = 2; // わかんないけど1だと半径が小さい時に四角になっちゃうから2にした
		let decisionOver2 = dx - diameter;
		
		// 0・90・180・270度の地点から両側に向かって線を伸ばしていき、計8本の弧を書く
		while (x >= y) {
			putPixel(imageData, cx + x, cy - y, pixelSize); //   0 ->  45
			putPixel(imageData, cx + y, cy - x, pixelSize); //  45 <-  90
			putPixel(imageData, cx - y, cy - x, pixelSize); //  90 -> 135
			putPixel(imageData, cx - x, cy - y, pixelSize); // 135 <- 180
			putPixel(imageData, cx - x, cy + y, pixelSize); // 180 -> 225
			putPixel(imageData, cx - y, cy + x, pixelSize); // 225 <- 270
			putPixel(imageData, cx + y, cy + x, pixelSize); // 270 -> 315
			putPixel(imageData, cx + x, cy + y, pixelSize); // 315 <- 360
			
			// わかんない
			if (decisionOver2 <= 0) {
			    y += pixelSize;
				decisionOver2 += dy * pixelSize;
				dy += 2 * pixelSize;
			}
			if (decisionOver2 > 0) {
				x -= pixelSize;
				dx += 2 * pixelSize;
				decisionOver2 += ((-diameter) + dx) * pixelSize;
			}
		}
	};

	/**
	 * ImageDataにドットの楕円を描画する
	 * https://stackoverflow.com/questions/15474122/is-there-a-midpoint-ellipse-algorithm
	 * @param {ImageData} imageData
	 * @param {Number} cx - 中心X座標
	 * @param {Number} cy - 中心Y座標
	 * @param {Number} a - 横の長さ
	 * @param {Number} b - タテの長さ
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixelEllipse = (imageData, cx, cy, a, b, pixelSize) => {
		const aa = a * a;
		const bb = b * b;
		const aa2 = 2 * aa;
		const bb2 = 2 * bb;
		let p;
		let x = 0;
		let y = b;
		let px = 0;
		let py = aa2 * y;

		// 最初の点を描画
		putPixel(imageData, cx + x, cy + y, pixelSize);
		putPixel(imageData, cx - x, cy + y, pixelSize);
		putPixel(imageData, cx + x, cy - y, pixelSize);
		putPixel(imageData, cx - x, cy - y, pixelSize);

		// 上下を描画 (わかんない)
		p = bb - (aa * b) + (0.25 * aa);
		while (px < py) {
			x += pixelSize;
			px += bb2 * pixelSize;
			if (p < 0) {
				p += bb + px * pixelSize;
			} else {
				y -= pixelSize;
				py -= aa2 * pixelSize;
				p += (bb + px - py) * pixelSize;
			}
			putPixel(imageData, cx + x, cy + y, pixelSize);
			putPixel(imageData, cx - x, cy + y, pixelSize);
			putPixel(imageData, cx + x, cy - y, pixelSize);
			putPixel(imageData, cx - x, cy - y, pixelSize);
		}

		// 左右を描画 (わかんない)
		p = bb * (x + 0.5) * (x + 0.5) + aa * (y - 1) * (y - 1) - aa * bb;
		while (y > 0) {
			y -= pixelSize;
			py -= aa2 * pixelSize;
			if (p > 0) {
				p += (aa - py) * pixelSize;
			} else {
				x += pixelSize;
				px += bb2 * pixelSize;
				p += (aa - py + px) * pixelSize;
			}
			putPixel(imageData, cx + x, cy + y, pixelSize);
			putPixel(imageData, cx - x, cy + y, pixelSize);
			putPixel(imageData, cx + x, cy - y, pixelSize);
			putPixel(imageData, cx - x, cy - y, pixelSize);
		}
	};

	/**
	 * ImageDataにドットを描画する
	 * @param {ImageData} imageData
	 * @param {Number} x - X座標
	 * @param {Number} y - Y座標
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixel = (imageData, x, y, pixelSize) => {
		// はみ出たら描画しない
		if (x < 0 || CANVAS_WIDTH <= x || y < 0 || CANVAS_HEIGHT < y) {
			return;
		}
		// 描画開始座標をドットごとのマス目の左上に補正
		x = x - (x % pixelSize);
		y = y - (y % pixelSize);

		// ImageDataにドット書き込み
		for (let offsetY = 0; offsetY < pixelSize; ++offsetY) {
			for (let offsetX = 0; offsetX < pixelSize; ++offsetX) {
				const i = 4 * ((y + offsetY) * CANVAS_WIDTH + (x + offsetX));
				imageData.data[i] = strokeColor[0];
				imageData.data[i + 1] = strokeColor[1];
				imageData.data[i + 2] = strokeColor[2];
				imageData.data[i + 3] = strokeColor[3];
			}
		}
	};
	
	// ドットの大きさ1pxで円を描画
	draw(ctx, 'circle', 1);

	// 種類・ドットの大きさ変更時に再描画するように
	document.getElementById('draw-type').addEventListener('change', (e) => {
		const type = e.target.value;
		const pixelSize = parseInt(document.getElementById('pixel-size').value, 10);
		draw(ctx, type, pixelSize);
	});
	document.getElementById('pixel-size').addEventListener('change', (e) => {
		const type = document.getElementById('draw-type').value;
		const pixelSize = parseInt(e.target.value, 10);
		draw(ctx, type, pixelSize);
	});
})();

ペイントっぽいサンプル

ペイントソフトよろしく、好きな大きさの丸を描けるサンプルも作ってみました。こっちだと、先述の縦横の挙動が一致しない感じがわりと分かります。横長で高さ1pxの丸は描けないけど、縦長で幅1pxの線が描けるあたりで顕著です。

同じ理由から、楕円の描画処理を使ってそのまま円を描こうとすると若干歪むので、縦横の長さが等しい時だけは円のアルゴリズムに切り替えるようにしてみました。

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8" />
	<meta name="robots" content="noarchive" />
	<title>Canvas ドットの円・楕円描画サンプル その2</title>
	<style>
html { background-color:#000; color:#eee; }
#canvas { display: block; box-sizing: border-box; margin: 5px 0 10px; border: 1px solid #666; }
	</style>
</head>
<body>
	マウスドラッグで丸を描けます。(タッチ操作非対応)<br />
	ドットの大きさ: <select id="pixel-size">
		<option value="1">1</option>
		<option value="2">2</option>
		<option value="3">3</option>
		<option value="4">4</option>
		<option value="6">6</option>
		<option value="8">8</option>
	</select>px
	<canvas id="canvas"></canvas>
	<script src="sample2.js"></script>
</body>
</html>
JavaScript
'use strict';

(() => {
	const CANVAS_WIDTH = 320;
	const CANVAS_HEIGHT = 320;
	const strokeColor = [255, 255, 255, 255];
	const canvas = document.getElementById('canvas');
	const ctx = canvas.getContext('2d');
	
	// Canvasの大きさ設定
	canvas.width  = CANVAS_WIDTH;
	canvas.height = CANVAS_HEIGHT;
	
	// 黒で塗りつぶし
	ctx.fillStyle = 'rgba(0, 0, 0, 255)';
	ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
	
	// 描画開始時の情報記憶用
	let imageData_org = null;
	const startPos = { x: null, y: null };
	
	// ドットの大きさ設定
	let pixelSize = parseInt(document.getElementById('pixel-size').value, 10);
	
	// ドットの大きさ変更時
	document.getElementById('pixel-size').addEventListener('change', (e) => {
		pixelSize = parseInt(e.target.value, 10);
	});
	
	// 描画開始時
	canvas.addEventListener('mousedown', (e) => {
		if (imageData_org !== null) {
			return;
		}
		e.preventDefault();
		const imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
	
		// Canvas内でのイベント発生座標を算出
		const bounds = e.target.getBoundingClientRect();
		const x = e.clientX - bounds.left;
		const y = e.clientY - bounds.top;
	
		// 描画開始時の座標とピクセル情報を保持しておく
		startPos.x = x;
		startPos.y = y;
		imageData_org = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
	
		// イベント座標からImageDataに楕円を描画
		putPixelEllipseByEventCoords(imageData, startPos.x, startPos.y, x, y, pixelSize);
		ctx.putImageData(imageData, 0, 0);
	});
	
	// 描画開始後にカーソル移動時
	canvas.addEventListener('mousemove', (e) => {
		if (imageData_org === null) {
			return;
		}
		e.preventDefault();
		const imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
	
		// 描画確定してない楕円を消す
		copyImageData(imageData_org, imageData);
	
		// Canvas内でのイベント発生座標を算出
		const bounds = e.target.getBoundingClientRect();
		const x = e.clientX - bounds.left;
		const y = e.clientY - bounds.top;
	
		// イベント座標からImageDataに楕円を描画
		putPixelEllipseByEventCoords(imageData, startPos.x, startPos.y, x, y, pixelSize);
		ctx.putImageData(imageData, 0, 0);
	});
	
	// 描画確定時
	canvas.addEventListener('mouseup', (e) => {
		if (imageData_org === null) {
			return;
		}
		e.preventDefault();
		const imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
	
		// 描画確定してない楕円を消す
		copyImageData(imageData_org, imageData);
	
		// Canvas内でのイベント発生座標を算出
		const bounds = e.target.getBoundingClientRect();
		const x = e.clientX - bounds.left;
		const y = e.clientY - bounds.top;
	
		// イベント座標からImageDataに楕円を描画
		putPixelEllipseByEventCoords(imageData, startPos.x, startPos.y, x, y, pixelSize);
		ctx.putImageData(imageData, 0, 0);
	
		// 描画開始時に保存した値を破棄
		startPos.x = startPos.y = null;
		imageData_org = null;
	});
	
	/**
	 * ImageDataのピクセル情報をコピーする
	 * @param {ImageData} src - コピー元ImageData
	 * @param {ImageData} dst - コピー先ImageData
	 */
	const copyImageData = (src, dst) => {
		for (let i = 0, len = dst.data.length; i < len; ++i) {
			dst.data[i] = src.data[i];
		}
	};
	
	/**
	 * イベント座標からドットの楕円を描画
	 * @param {ImageData} imageData
	 * @param {Number} startX - 描画開始座標X
	 * @param {Number} startY - 描画開始座標Y
	 * @param {Number} x - イベント座標X
	 * @param {Number} y - イベント座標Y
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixelEllipseByEventCoords = (imageData, startX, startY, x, y, pixelSize) => {
		// クリック座標とクリック開始座標から横とタテの長さを算出
		let a = (x - startX) / 2;
		let b = (y - startY) / 2;
	
		// クリック座標・横・タテの長さをドットの大きさの整数倍に補正
		x = x - (x % pixelSize);
		y = y - (y % pixelSize);
		a = Math.floor(a - (a % pixelSize));
		b = Math.floor(b - (b % pixelSize));
	
		// 中心座標算出
		const cx = Math.floor(startX + a);
		const cy = Math.floor(startY + b);
	
		// b辺の長さがマイナス (カーソルを上方向に動かした時) だったら補正
		b = b < 0 ? -1 * b : b;
	
		if (a === b) {
			// 縦横の長さが同じなら円を描画 (こっちの方が綺麗だから)
			putPixelCircle(imageData, cx, cy, a, pixelSize);
		} else {
			// 楕円を描画
			putPixelEllipse(imageData, cx, cy, a, b, pixelSize);
		}
	};
	
	/**
	 * ImageDataにドットの円を描画する
	 * https://en.wikipedia.org/wiki/Midpoint_circle_algorithm
	 * @param {ImageData} imageData
	 * @param {Number} cx - 中心X座標
	 * @param {Number} cy - 中心Y座標
	 * @param {Number} radius - 半径
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixelCircle = (imageData, cx, cy, radius, pixelSize) => {
		const diameter = radius * 2; // 直径
		let x = radius; // 半径 - 線幅
		let y = 0;
		let dx = 1;
		let dy = 2; // わかんないけど1だと半径が小さい時に四角になっちゃうから2にした
		let decisionOver2 = dx - diameter;
		
		// 0・90・180・270度の地点から両側に向かって線を伸ばしていき、計8本の弧を書く
		while (x >= y) {
			putPixel(imageData, cx + x, cy - y, pixelSize); //   0 ->  45
			putPixel(imageData, cx + y, cy - x, pixelSize); //  45 <-  90
			putPixel(imageData, cx - y, cy - x, pixelSize); //  90 -> 135
			putPixel(imageData, cx - x, cy - y, pixelSize); // 135 <- 180
			putPixel(imageData, cx - x, cy + y, pixelSize); // 180 -> 225
			putPixel(imageData, cx - y, cy + x, pixelSize); // 225 <- 270
			putPixel(imageData, cx + y, cy + x, pixelSize); // 270 -> 315
			putPixel(imageData, cx + x, cy + y, pixelSize); // 315 <- 360
			
			// わかんない
			if (decisionOver2 <= 0) {
				y += pixelSize;
				decisionOver2 += dy * pixelSize;
				dy += 2 * pixelSize;
			}
			if (decisionOver2 > 0) {
				x -= pixelSize;
				dx += 2 * pixelSize;
				decisionOver2 += ((-diameter) + dx) * pixelSize;
			}
		}
	};
	
	/**
	 * ImageDataにドットの楕円を描画する
	 * https://stackoverflow.com/questions/15474122/is-there-a-midpoint-ellipse-algorithm
	 * @param {ImageData} imageData
	 * @param {Number} cx - 中心X座標
	 * @param {Number} cy - 中心Y座標
	 * @param {Number} a - 横の長さ
	 * @param {Number} b - タテの長さ
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixelEllipse = (imageData, cx, cy, a, b, pixelSize) => {
		const aa = a * a;
		const bb = b * b;
		const aa2 = 2 * aa;
		const bb2 = 2 * bb;
		let p;
		let x = 0;
		let y = b;
		let px = 0;
		let py = aa2 * y;
	
		// 最初の点を描画
		putPixel(imageData, cx + x, cy + y, pixelSize);
		putPixel(imageData, cx - x, cy + y, pixelSize);
		putPixel(imageData, cx + x, cy - y, pixelSize);
		putPixel(imageData, cx - x, cy - y, pixelSize);
	
		// 上下を描画 (わかんない)
		p = bb - (aa * b) + (0.25 * aa);
		while (px < py) {
			x += pixelSize;
			px += bb2 * pixelSize;
			if (p < 0) {
				p += bb + px * pixelSize;
			} else {
				y -= pixelSize;
				py -= aa2 * pixelSize;
				p += (bb + px - py) * pixelSize;
			}
			putPixel(imageData, cx + x, cy + y, pixelSize);
			putPixel(imageData, cx - x, cy + y, pixelSize);
			putPixel(imageData, cx + x, cy - y, pixelSize);
			putPixel(imageData, cx - x, cy - y, pixelSize);
		}
	
		// 左右を描画 (わかんない)
		p = bb * (x + 0.5) * (x + 0.5) + aa * (y - 1) * (y - 1) - aa * bb;
		while (y > 0) {
			y -= pixelSize;
			py -= aa2 * pixelSize;
			if (p > 0) {
				p += (aa - py) * pixelSize;
			} else {
				x += pixelSize;
				px += bb2 * pixelSize;
				p += (aa - py + px) * pixelSize;
			}
			putPixel(imageData, cx + x, cy + y, pixelSize);
			putPixel(imageData, cx - x, cy + y, pixelSize);
			putPixel(imageData, cx + x, cy - y, pixelSize);
			putPixel(imageData, cx - x, cy - y, pixelSize);
		}
	};
	
	/**
	 * ImageDataにドットを描画する
	 * @param {ImageData} imageData
	 * @param {Number} x - X座標
	 * @param {Number} y - Y座標
	 * @param {Number} pixelSize - ドットの大きさ
	 */
	const putPixel = (imageData, x, y, pixelSize) => {
		// はみ出たら描画しない
		if (x < 0 || CANVAS_WIDTH <= x || y < 0 || CANVAS_HEIGHT < y) {
			return;
		}
		// 描画開始座標をドットごとのマス目の左上に補正
		x = x - (x % pixelSize);
		y = y - (y % pixelSize);
	
		// ImageDataにドット書き込み
		for (let offsetY = 0; offsetY < pixelSize; ++offsetY) {
			for (let offsetX = 0; offsetX < pixelSize; ++offsetX) {
				const i = 4 * ((y + offsetY) * CANVAS_WIDTH + (x + offsetX));
				imageData.data[i] = strokeColor[0];
				imageData.data[i + 1] = strokeColor[1];
				imageData.data[i + 2] = strokeColor[2];
				imageData.data[i + 3] = strokeColor[3];
			}
		}
	};
})();

そのほか

他に参考にさせていただいたところでは、以下のサイト様の解説は懇切丁寧で嬉しかったです。

次のサイト様のソースコードは、シンプルなのに意味不明で、流したら本当に○が描けて恐ろしかったです。

以下に、当該のコードをJavaScriptで動かすサンプルを置いておきます。

HTML
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8" />
	<meta name="robots" content="noarchive" />
	<title>わからん</title>
	<style>
html { background-color:#000; color:#eee; }
#canvas { display: block; box-sizing: border-box; margin: 5px 0 10px; border: 1px solid #666; }
	</style>
</head>
<body>
	<canvas id="canvas"></canvas>
	<script src="wakaran.js"></script>
</body>
</html>
JavaScript
/**
 * これは以下サイト様のソースコードをJavaScriptで動かして、目を描いてみたものです。
 * 
 * 円の描画(アルゴリズム)
 * https://www.kazetest.com/vcmemo/drawcircle/drawcircle.htm
 */
const CANVAS_WIDTH = 320;
const CANVAS_HEIGHT = 320;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

canvas.width  = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
ctx.fillStyle = 'rgba(0, 0, 0, 255)';
const imageData = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

const putPixel = (x, y) => {
	var i = 4 * (y * CANVAS_WIDTH + x);
	imageData.data[i] = 255;
	imageData.data[i + 1] = 255;
	imageData.data[i + 2] = 255;
	imageData.data[i + 3] = 255;
};

const drawCircle = (cx, cy, r) => {
	var xx = r << 7;
	var yy = 0;
	var x = 0;
	var y = 0;
	while (yy <= xx ) {
		x = xx >> 7;
		y = yy >> 7;
		putPixel(cx + x, cy + y);
		putPixel(cx - x, cy - y);
		putPixel(cx - x, cy + y);
		putPixel(cx + x, cy - y);
		putPixel(cx + y, cy + x);
		putPixel(cx - y, cy - x);
		putPixel(cx - y, cy + x);
		putPixel(cx + y, cy - x);
		yy += xx >> 7;
		xx -= yy >> 7;
	}
	ctx.putImageData(imageData, 0, 0);
};

const drawEllipse = (cx, cy, a, b) => {
	var xx = a << 6;
	var yy = 0;
	var x = 0;
	var y = 0;
	
	while (xx >= 0) {
		x = xx >> 6;
		y = yy >> 6;
		putPixel(cx + x, cy + y);
		putPixel(cx - x, cy - y);
		putPixel(cx - x, cy + y);
		putPixel(cx + x, cy - y);
		yy += xx * b / a >> 6;
		xx -= yy * a / b >> 6;
	}
	ctx.putImageData(imageData, 0, 0);
};

drawCircle(180, 170, 16);
drawCircle(160, 160, 52);
drawEllipse(160, 160, 14, 34);
drawEllipse(160, 160, 124, 62);