見出し画像

Pyxelでアフィン変換実装してみました。

追記20240828:
なんと!
「Pyxel 2.2.0をリリースしました。画像の回転と拡大縮小が可能になりました! サンプル16をご参照ください。」
画像の回転と拡大縮小が実装されたようです。
イメージバンクにあるものを描画の条件付きですが。

pset画像なら下記で実現可能では無いかと。


前回、画像をPyxel Editorにドラッグ&ドロップしたら自動減色されながらもEditor内に取り込むことができたので、その画像使って何かできないかなと試行錯誤していました。

画像回転させてみようと思ったのが発端です。

いざ回転となるとアフィン変換だろうといろいろ検索しては何かないかと探していましたが、どれもpythonのライブラリを使うものがほとんど。できればライブラリを使わずに実装したいと思いさらに調査。

見つけました。

C言語ですが全く問題無し。
すぐに使えそうな感じです。

次に、
Pyxel Editorのイメージバンクに取り込んだ画像から画像情報を取得したい。
イメージバンクから直接データは拾えないので、一旦描画領域に転送させて、pget使えばピクセルの色を取得できるとか。

当初、RGBの色データが取得できるのかと思っていたので、パレット構造再構築とか面倒だなと思っていたら、0〜15のパレットの色番号でした。なのでこのまま使えそうです。

最初に元画像情報を取得して保存しておいて、数値演算したのちの出力画像を描画すれば実現できそう!

というわけで処理速度は気にせず実装してみました。

画像データは前回同様

元画像

これをPixel Editorに読み込ませて一旦描画

取り込み後

何も考えずに256dot x 256dotで実装してみました。
おまけで拡大縮小させてます。

回転拡大縮小

微妙に重い・・・

さらに高速化はできそうです。
用途しては全画面ではなく、キャラクタだけとかなら使えそうかも。

しかし、これだけでは面白くないので他にも何かできないか実験してみようかと思います。

追記0810
応用編

juria animation

いろいろいじってて画像関係は色数少ないと楽しくないのでここまでとします。アフィン変換に関するソースを貼り付けておきます。
参考にしたC言語ソースをpyxel化(python化)してアニメ処理加えただけですが参考になれば・・・(クラス不使用)

import pyxel

WORK_SIZE = 10
WORK = [0 for i in range(WORK_SIZE)]
_step = 0		#制御ステップ
_angle = 1		#角度0~359
_scale = 2		#0.1~2.0とする
_scalef = 3		#拡大縮小方法(0/1 = 拡大/縮小)
_movex = 4		#横方向移動量
_movey = 5		#縦方向移動量

SCREEN_WIDTH = 256
SCREEN_HEIGHT = 256
IMAGE_SIZE = SCREEN_WIDTH*SCREEN_HEIGHT
TEMP = [0 for i in range(IMAGE_SIZE)]	#入力画像色データ
OUT = [0 for i in range(IMAGE_SIZE)]	#出力画像色データ

def update():
	#元画像から色データ取得
	if( WORK[_step] == 0 ):
		#画像を描画領域に転送
		pyxel.blt( 0, 0, 0, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
		#色を取得(パレット番号)
		for _oY in range( SCREEN_HEIGHT ):
			for _oX in range( SCREEN_WIDTH ):
				TEMP[_oY * SCREEN_WIDTH + _oX] = pyxel.pget(_oX, _oY)
		#初期値設定
		WORK[_scale] = 1.0
		WORK[_angle] = 0
		WORK[_movex] = 0.0
		WORK[_movey] = 0.0
		
		WORK[_step] = 1

	#回転
	elif( WORK[_step] == 1 ):
		#出力画像をクリア
		for _i in range( IMAGE_SIZE ):
			OUT[_i] = 0

		#アフィン変換の行列の4要素(2x2行列)
		_a0 = pyxel.cos( WORK[_angle] ) * WORK[_scale]
		_a1 = pyxel.sin( WORK[_angle] ) * WORK[_scale] * (-1.0)
		_a2 = pyxel.sin( WORK[_angle] ) * WORK[_scale]
		_a3 = pyxel.cos( WORK[_angle] ) * WORK[_scale]
		_det = 0.0
		#逆行列の計算
		det = _a0 * _a3 - _a1 * _a2
		if( det == 0 ):
			return

		_ia0 = _a3 / det
		_ia1 = (-1.0*_a1) / det
		_ia2 = (-1.0*_a2) / det
		_ia3 = _a0 / det

		for _oY in range( SCREEN_HEIGHT ):
			#原点0基準の値に変換
			_noY = (float)(_oY - (int)(SCREEN_HEIGHT / 2))

			for _oX in range( SCREEN_WIDTH ):
				#原点0基準の値に変換
				_noX = (float)(_oX - (int)(SCREEN_WIDTH / 2))
				
				#元画像における横方向座標を計算
				#座標変換を行ってから中心点基準の値に変換
				_rx = ( _noX * _ia0 ) + ( _noY * _ia1 ) - ( WORK[_movex] * _ia0 ) - ( WORK[_movey] * _ia1 ) + (float)( SCREEN_WIDTH / 2 )

				#最近傍補間
				_ix = (int)( _rx + 0.5 )

				#元画像をはみ出る画素の場合は次の座標に飛ばす
				if( _ix >= SCREEN_WIDTH or _ix < 0 ):
					continue

				#元画像における縦方向座標を計算
				#座標変換を行ってから中心点基準の値に変換
				_ry = ( _noX * _ia2 ) + ( _noY * _ia3 ) - ( WORK[_movex] * _ia2 ) - ( WORK[_movey] * _ia3 ) + (float)( SCREEN_HEIGHT / 2 )

 				#最近傍補間
				_iy = (int)( _ry + 0.5 )

				#元画像をはみ出る画素の場合は次の座標に飛ばす
				if( _iy >= SCREEN_HEIGHT or _iy < 0 ):
					continue;

				#アフィン変換後画像の座標(om, on)に対応する元画像の座標(im, in)の画素値をコピー
				OUT[ _oY * SCREEN_WIDTH + _oX ] = TEMP[ _iy * SCREEN_WIDTH + _ix ]


		WORK[_angle] += 1
		if( WORK[_angle] >= 360 ):
			WORK[_angle] -= 360


		if( WORK[_scalef] == 0 ):
			WORK[_scale] += 0.01
			if( WORK[_scale] > 2.0 ):
				WORK[_scale] = 2.0
				WORK[_scalef] = 1
		else:
			WORK[_scale] -= 0.01
			if( WORK[_scale] < 0.1 ):
				WORK[_scale] = 0.1
				WORK[_scalef] = 0
			
		#NEXT描画へ
		WORK[_step] = 2

	#描画
	elif( WORK[_step] == 2 ):
		#演算へ
		WORK[_step] = 1

def draw():
	#描画
	if( WORK[_step] == 2 ):
		pyxel.cls(0)
		for _oY in range( SCREEN_HEIGHT ):
			for _oX in range( SCREEN_WIDTH ):
				#_colnum = TEMP[_oY * SCREEN_WIDTH + _oX]
				_colnum = OUT[_oY * SCREEN_WIDTH + _oX]
				pyxel.pset( _oX, _oY, _colnum )


pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, fps=60)
#イメージバンクNo.0に画像ファイルを読み込む
#読み込むと同時に16色に減色される
pyxel.images[0].load(0, 0, "Taushubetsu2024.jpg")
#ワーククリア
for _i in range( WORK_SIZE ):
	WORK[_i] = 0
#画像データワーククリア
for _i in range( IMAGE_SIZE ):
	TEMP[_i] = 0
	OUT[_i] = 0

pyxel.run(update, draw)

コード見直してて、、、結局リソースファイル使ってなかったなと(汗)


この記事が気に入ったらサポートをしてみませんか?