「横で読み込んで斜めに描画する方向性」でそのまま進んだらどうなったのか?が気になりまして…
まず、第4回で回転できる所まで作っているので、そちらを確認して頂きたいのですが…
「横で読み込んで斜めに描画する方向性」の良い点:きれいに描画できる
従来型だと斜め45度に近づくと画像がギザギザに崩れていきます。
これはこの付近が斜め読み込みした際に最も誤差が出るためであり、横で読み込む場合は画像データの並びと読み込みの方向が一致している為すべての読み込みが均質になり、ズレが起きないので綺麗に出力できます。
「横で読み込んで斜めに描画する方向性」の悪い点:モワレが発生する。
描画時に誤差が出た場合に画像に穴が開く現象です。
で、この穴ですが、よく見ると1ドットずつしか空いていません。
※赤い部分が穴
斜めに描画する方法ではブレゼンハムアルゴリズムで作成した斜線を束ねてて面にしています。この為、段差が出来た所で上図のような隙間ができると思われます。
…で、あれば、予め穴の空きそうなドットに付近の画像を転送しておけばよくね?
というわけで、穴の空きそうなドットですが…
こんな感じ。
斜線を引いて段差が出来た時に赤で示したドットの部分を埋めていく方針。
最初は360度法で45度ずつ区切らないといけないかと思いましたが、最終的にはこのようなコードになりました。
※相変わらず割り算も実数も使っていません
ソースここから
import pyxel
SCREEN_SIZE_W = 160
SCREEN_SIZE_H = 120
CENTER_X = 80
CENTER_Y = 60
EMPTY = -32767
ALPHA_NON = 0
ALPHA_HALF_1 = 1
ALPHA_HALF_2 = 2
ALPHA_QUARTER_1 = 3
ALPHA_QUARTER_2 = 4
ALPHA_QUARTER_3 = 5
ALPHA_QUARTER_4 = 6
my_table = (
0, 6, 13, 19, 25, 31, 37, 43, 49, 55, 60, 66, 71, 76, 81, 86,
91, 95, 99, 103, 106, 110, 113, 116, 118, 121, 122, 124, 126, 127, 127, 128,
128, 128, 127, 127, 126, 124, 122, 121, 118, 116, 113, 110, 106, 103, 99, 95,
91, 86, 81, 76, 71, 66, 60, 55, 49, 43, 37, 31, 25, 19, 13, 6,
0, -6, -13, -19, -25, -31, -37, -43, -49, -55, -60, -66, -71, -76, -81, -86,
-91, -95, -99,-103,-106,-110,-113,-116,-118,-121,-122,-124,-126,-127,-127,-128,
-128,-128,-127,-127,-126,-124,-122,-121,-118,-116,-113,-110,-106,-103,- 99, -95,
-91, -86, -81, -76, -71, -66, -60, -55, -49, -43, -37, -31, -25, -19, -13, -6,
0, 6, 13, 19, 25, 31, 37, 43, 49, 55, 60, 66, 71, 76, 81, 86,
91, 95, 99, 103, 106, 110, 113, 116, 118, 121, 122, 124, 126, 127, 127, 128
)
def my_sin(angle,r):
angle &= 127
return r * my_table[angle]>>7
def my_cos(angle,r):
angle &= 127
angle += 32
return r * my_table[angle]>>7
class VBresenham:
def __init__(self):
self.l_side = 0
self.s_side = 0
# horizonatial side is longer
self.b_long_hs = True
# Direction (1 or -1)
self.dct_x = 1
self.dct_y = 1
self.counter = 0
self.adder_x = 0
self.adder_y = 0
self.start_x = 0
self.start_y = 0
self.bLaneChange = False
# start x,y terminus x,y
def setup(self,s_x,s_y,t_x,t_y):
self.b_long_hs = True
self.dct_x = 1
self.dct_y = 1
self.counter = 0
self.adder_x = 0
self.adder_y = 0
self.bLaneChange = False
self.start_x = s_x
self.start_y = s_y
if (s_x > t_x):
self.l_side = s_x - t_x
self.dct_x = -1
else:
self.l_side = t_x - s_x
if (s_y > t_y):
self.s_side = s_y - t_y
self.dct_y = -1
else:
self.s_side = t_y - s_y
if (self.s_side > self.l_side):
# swap
self.s_side,self.l_side = self.l_side,self.s_side
self.b_long_hs = False
self.l_side += 1
self.s_side += 1
return self.l_side
def step(self):
self.counter += self.s_side
i = self.counter - self.l_side
ret_x = self.start_x+self.adder_x
ret_y = self.start_y+self.adder_y
ret_LC = self.bLaneChange
self.bLaneChange = False
if self.b_long_hs:
self.adder_x += self.dct_x
if i >= 0:
self.counter = i
self.adder_y += self.dct_y
self.bLaneChange = True
else:
self.adder_y += self.dct_y
if i >= 0:
self.counter = i
self.adder_x += self.dct_x
self.bLaneChange = True
return ret_x,ret_y,ret_LC
class Bresenham:
def __init__(self):
self.l_side = 0
self.s_side = 0
self.counter = 0
self.adder = 0
# Src side is longer
self.b_long_ss = True
def setup(self,Dest_side,Src_side):
self.counter = 0
self.adder = 0
if Dest_side > Src_side:
self.b_long_ss = False
self.l_side = Dest_side+1
self.s_side = Src_side+1
return self.l_side
self.b_long_ss = True
self.l_side = Src_side+1
self.s_side = Dest_side+1
return self.s_side
def step(self):
ret = self.adder
if self.b_long_ss:
self.counter += self.s_side
i = self.counter - self.l_side
if i >= 0:
self.counter = i
self.adder += 1
else:
while self.counter <= self.l_side:
self.adder += 1
self.counter += self.s_side
self.counter -= self.l_side
return ret
class RollBlt:
def __init__(self):
self.vbre_h = VBresenham()
self.bre_h = Bresenham()
self.vbre_w = VBresenham()
self.bre_w = Bresenham()
self.tile = {
ALPHA_NON :self._non,
ALPHA_HALF_1 :self._half1,
ALPHA_HALF_2 :self._half2,
ALPHA_QUARTER_1 :self._quarter1,
ALPHA_QUARTER_2 :self._quarter2,
ALPHA_QUARTER_3 :self._quarter3,
ALPHA_QUARTER_4 :self._quarter4,
}
def setup(self):
pass
# タイル転送用内部関数群
def _non(self,x,y,u,v):
pyxel.blt(x, y, 0, u, v, 1, 1)
def _half1(self,x,y,u,v):
if x&1 == y&1:
pyxel.blt(x, y, 0, u, v, 1, 1)
def _half2(self,x,y,u,v):
if x&1 != y&1:
pyxel.blt(x, y, 0, u, v, 1, 1)
def _quarter1(self,x,y,u,v):
if (x&1) + (y&1)==0:
pyxel.blt(x, y, 0, u, v, 1, 1)
def _quarter2(self,x,y,u,v):
if ((x+1)&1) + (y&1)==0:
pyxel.blt(x, y, 0, u, v, 1, 1)
def _quarter3(self,x,y,u,v):
if (x&1) + ((y+1)&1)==0:
pyxel.blt(x, y, 0, u, v, 1, 1)
def _quarter4(self,x,y,u,v):
if (x&1) + (y&1)==2:
pyxel.blt(x, y, 0, u, v, 1, 1)
# 原点座標中央位置補正コピー
def blt_c(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle, scale_w=128, scale_h=128,alpha=ALPHA_NON):
h = src_h * scale_h >> 8
w = src_w * scale_w >> 8
x = dest_x - my_sin(angle,h) - my_sin(angle+32,w)
y = dest_y - my_cos(angle,h) - my_cos(angle+32,w)
self.blt(x, y, bank, src_x, src_y, src_w, src_h, angle, scale_w, scale_h, alpha)
def blt(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle, scale_w=128, scale_h=128, alpha=ALPHA_NON):
# 角度のまるめ
angle &= 127
# 原点から見た縦方向の端の座標
h = src_h * scale_h >> 7
tv_x = my_sin(angle,h-1)+dest_x
tv_y = my_cos(angle,h-1)+dest_y
# 原点から見た横方向の端の座標
w = src_w * scale_w >> 7
th_x = my_sin(angle+32,w-1)+dest_x
th_y = my_cos(angle+32,w-1)+dest_y
# 画面内に収まっているかの判定
top = dest_y
if tv_y < top:
top = tv_y
if th_y < top:
top = th_y
if (tv_y + th_y - dest_y) < top:
top = tv_y + th_y - dest_y
bottom = dest_y
if tv_y > bottom:
bottom = tv_y
if th_y > bottom:
bottom = th_y
if (tv_y + th_y - dest_y) > bottom:
bottom = tv_y + th_y - dest_y
# 画面外
if top < 0 and bottom < 0:
return
if top >= SCREEN_SIZE_H and bottom >= SCREEN_SIZE_H:
return
left = dest_x
if tv_x < left:
left = tv_x
if th_x < left:
left = th_x
if (tv_x + th_x - dest_x) < left:
left = tv_x + th_x - dest_x
right = dest_x
if tv_x > right:
right = tv_x
if th_x > right:
right = th_x
if (tv_x + th_x - dest_x) > right:
right = tv_x + th_x - dest_x
# 画面外
if left < 0 and right < 0:
return
if left >= SCREEN_SIZE_W and right >= SCREEN_SIZE_W:
return
# 一旦横方向は原点を引いてベクトルとしないといけない、意味が変わるので注意
th_x -= dest_x
th_y -= dest_y
base_reg = self.vbre_h.setup(dest_x,dest_y,tv_x,tv_y)
self.bre_h.setup(src_h-1,base_reg-1)
match angle:
case a if a < 32:
for h in range (base_reg):
base_x,base_y,bDummy = self.vbre_h.step()
add_src_y = self.bre_h.step()
reg = self.vbre_w.setup(base_x,base_y,base_x+th_x,base_y+th_y)
self.bre_w.setup(src_w-1,reg-1)
old_x = base_x
old_y = base_y
for w in range (reg):
x,y,bLC = self.vbre_w.step()
add_src_x = self.bre_w.step()
if x < 0 or y >= SCREEN_SIZE_H:
continue
if x >= SCREEN_SIZE_W:
break
if bLC:
self.tile[alpha](x,old_y,src_x+add_src_x,src_y+add_src_y)
if y < 0:
break
self.tile[alpha](x,y,src_x+add_src_x,src_y+add_src_y)
old_x = x
old_y = y
case a if a < 64:
for h in range (base_reg):
base_x,base_y,bDummy = self.vbre_h.step()
add_src_y = self.bre_h.step()
reg = self.vbre_w.setup(base_x,base_y,base_x+th_x,base_y+th_y)
self.bre_w.setup(src_w-1,reg-1)
old_x = base_x
old_y = base_y
for w in range (reg):
x,y,bLC = self.vbre_w.step()
add_src_x = self.bre_w.step()
if y >= SCREEN_SIZE_H or x >= SCREEN_SIZE_W:
continue
if y < 0:
break
if bLC:
self.tile[alpha](old_x,y,src_x+add_src_x,src_y+add_src_y)
if x < 0:
break
self.tile[alpha](x,y,src_x+add_src_x,src_y+add_src_y)
old_x = x
old_y = y
case a if a < 96:
for h in range (base_reg):
base_x,base_y,bDummy = self.vbre_h.step()
add_src_y = self.bre_h.step()
reg = self.vbre_w.setup(base_x,base_y,base_x+th_x,base_y+th_y)
self.bre_w.setup(src_w-1,reg-1)
old_x = base_x
old_y = base_y
for w in range (reg):
x,y,bLC = self.vbre_w.step()
add_src_x = self.bre_w.step()
if x >= SCREEN_SIZE_W or y < 0:
continue
if x < 0:
break
if bLC:
self.tile[alpha](x,old_y,src_x+add_src_x,src_y+add_src_y)
if y >= SCREEN_SIZE_H:
break
self.tile[alpha](x,y,src_x+add_src_x,src_y+add_src_y)
old_x = x
old_y = y
case _:
for h in range (base_reg):
base_x,base_y,bDummy = self.vbre_h.step()
add_src_y = self.bre_h.step()
reg = self.vbre_w.setup(base_x,base_y,base_x+th_x,base_y+th_y)
self.bre_w.setup(src_w-1,reg-1)
old_x = base_x
old_y = base_y
for w in range (reg):
x,y,bLC = self.vbre_w.step()
add_src_x = self.bre_w.step()
if y < 0 or x < 0:
continue
if y >= SCREEN_SIZE_H:
break
if bLC:
self.tile[alpha](old_x,y,src_x+add_src_x,src_y+add_src_y)
if x >= SCREEN_SIZE_W:
break
self.tile[alpha](x,y,src_x+add_src_x,src_y+add_src_y)
old_x = x
old_y = y
class App:
def __init__(self):
# 画面サイズ 160x120 で初期化
pyxel.init(SCREEN_SIZE_W, SCREEN_SIZE_H, title="RollBlt")
self.add_x = 0
pyxel.load("yajirushi.pyxres")
self.rb = RollBlt()
self.angle = 0
self.angle2 = 0
pyxel.run(self.update, self.draw)
def update(self):
self.angle += 1
self.angle &= 127
self.angle2 -= 1
self.angle2 &= 127
def draw(self):
# 画面を黒(色番号0)でクリア
pyxel.cls(0)
#self.rb.blt(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, 16,512,512)
#self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,256,256)
#self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,512,512)
#self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=64,scale_h=64)
#self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=256,scale_h=256)
"""
self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512, alpha=ALPHA_HALF_1)
self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle2,scale_w=512,scale_h=512, alpha=ALPHA_HALF_2)
"""
#"""
self.rb.blt_c(CENTER_X-30, CENTER_Y-20, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_1)
self.rb.blt_c(CENTER_X-30, CENTER_Y+20, 0, 0, 0, 32, 16, self.angle2,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_4)
self.rb.blt_c(CENTER_X+30, CENTER_Y-20, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_2)
self.rb.blt_c(CENTER_X+30, CENTER_Y+20, 0, 0, 0, 32, 16, self.angle2,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_3)
#"""
App()
ソースここまで
これまでのソースでは斜め線を求めるブレゼンハムアルゴリズムのクラス VBresenham は1ステップ進むたびに座標だけを返していましたが、段差ができたかのフラグも追加で返す事にしました。
で、出力ですが…
絵の出力は完璧。ソースコードも短くなりました。
ただ、斜めに読み込み横に書き込む方法は、最初に外枠とその間をどう補完するのかをあらかじめ求めてラインバッファに保管し、その後に一気に描画する方法で描画や判定処理が少なく抑えられています。
対して今回の方法はバッファに一時保管するといった処理を行いませんが、そもそもモワレが発生しない浅い角度の時は予防として穴埋めしている処理が全て無駄になる(上書きされる)事になりますし、1ドットを転送する為にかかる処理が大変重かったりします。
セレロン2コアのパソコンでデバッグしている分には体感できるレベルで処理落ちしており、実測では2~3割ほど処理が重いようです。
普通画像転送といえば見栄えを重視しますからこちらの方が正解なのですが、エフェクト等のブレても良いものなら以前のものの方が良いのかもしれません。




















