画面はピクセルのマス目
コンピュータグラフィックスでは、画面を小さなマス目の集まりとして扱います。このマス目の1つをピクセルと呼びます。
直線も三角形も、どのピクセルを塗るかを計算して描きます。このレッスンでは、座標でピクセルの場所を指し、直線が通るピクセルを求めます。
このレッスンのゴール
メモ
ピクセルの座標を指定し、直線が通るピクセルを求められる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| ピクセル | 画面を作る1つのマス | |
| 座標 | マスの場所を示す、x と y の番号 | (3, 1) |
| 原点 | 座標が (0, 0) の場所。画面では左上 | |
| 傾き | x が1進むとき、y が進む量 | |
| 四捨五入 | 近い整数にそろえること | |
| 格子 | マスを縦と横に並べたもの |
学生が入力するセルは、このプレビューでは空白です。
画面の場所は x と y の番号で指す
画面は、横の列と縦の行が並んだマス目です。1つのマスがピクセルで、場所は座標 (x, y) で指します。
x は左から数えた列の番号、y は上から数えた行の番号です。番号は 0 から始まり、左上が原点 (0, 0) です。y は下へ行くほど大きくなります。
ピクセルの座標の例
| 場所 | 座標 (x, y) | 意味 |
|---|---|---|
| 左上の角 | (0, 0) | 原点 |
| 右へ3つ | (3, 0) | x が増える |
| 下へ2つ | (0, 2) | y が増える。向きは下 |
| 右へ3つ、下へ2つ | (3, 2) | x と y の両方が増える |
学生が入力するセルは、このプレビューでは空白です。
直線は x を1つずつ進めて y を決める
直線を描くには、x を1ずつ進めながら、そのたびに y を求めます。y は、傾きに進んだ量をかけて、はじめの y に足した値です。
たとえば (0, 0) から (5, 2) へ引く線では、傾きは 2 ÷ 5 で 0.4 です。x が1進むと、y は 0.4 進みます。
傾き 0.4 の線は、y が少しずつ増える
| x | y の正確な値 | 四捨五入した y |
|---|---|---|
| 0 | 0.0 | 0 |
| 1 | 0.4 | 0 |
| 2 | 0.8 | 1 |
| 3 | 1.2 | 1 |
| 4 | 1.6 | 2 |
| 5 | 2.0 | 2 |
学生が入力するセルは、このプレビューでは空白です。
x ごとの y を計算する
x0 = 0
y0 = 0
x1 = 5
y1 = 2
slope = (y1 - y0) / (x1 - x0)
for step in range(x1 - x0 + 1):
x = x0 + step
y = y0 + slope * step
print(x, round(y, 1), round(y))
# x1 を 10、y1 を 3 に変えて、もう一度実行します。
# 点は何個になりますか。
ピクセルは整数の場所にしかない
ピクセルは整数の場所にしかないので、y は整数にそろえます。そのため、直線は階段のように見えます。
次のコマンドは、(0, 0) から (15, 7) の線について、x ごとの y を表示します。傾きは 7 ÷ 15 です。y は 0 から 7 まで、2回に1回ほど上がります。
(15, 7) の線の x ごとの y
docker exec jupyter python3 -c "import math; print([[x, math.floor(7 * x / 15 + 0.5)] for x in range(16)])"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
Python の round は、ふつうの四捨五入と同じと考えがちです。実際は、ちょうど 0.5 のとき偶数へ丸め、round(0.5) は 0 になります。直線では 0.5 を切り上げたいので、math.floor(値 + 0.5) を使います。
Pillow で直線を描いて保存する
Pillow の ImageDraw は、直線を描く道具を持っています。次のコマンドは、16 × 8 の黒い画像に、左上から右下へ白い線を引きます。
画像は 10 倍に拡大して line.png に保存し、白いピクセルの数を表示します。JupyterLab で line.png を開くと、黒い地に白い階段状の線が見えます。
白い線を引いて line.png に保存
docker exec jupyter python3 -c "from PIL import Image, ImageDraw; im = Image.new('L', (16, 8), 0); ImageDraw.Draw(im).line([(0, 0), (15, 7)], fill=255); im.resize((160, 80), Image.NEAREST).save('line.png'); print(sum(1 for p in im.getdata() if p)); print(im.getpixel((0, 0)), im.getpixel((15, 7)), im.getpixel((15, 0)))"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。