【Pyxel】短いプログラムで遊ぶ⑤（高速化追記）

20250308追記：描画高速化

描画処理
```
#描画
def draw():
	pyxel.cls(0)
	_screen_ptr = pyxel.screen.data_ptr()
	for _i in range(SCREEN_HEIGHT*SCREEN_WIDTH):
		_screen_ptr[_i] = pix[_i]
```
data_ptrに格納する方法をスライスにするとさらに高速化できます。
（「スライス」は今までもなんとなく使ってましたが、最近知りました(^^;）
変更前の描画速度の平均は、約0.007くらい

補足：速度計測方法
```
import time
...
	start = time.time()
（処理）
	end = time.time()
    time_diff = end - start
    print(time_diff)
```

一行ずつ格納に変更
```
#描画
def draw():
	pyxel.cls(0)
	_screen_ptr = pyxel.screen.data_ptr()
	for _i in range(SCREEN_HEIGHT):
		_screen_ptr[_i*SCREEN_WIDTH:_i*SCREEN_WIDTH+(SCREEN_WIDTH)] = pix[_i*SCREEN_WIDTH:_i*SCREEN_WIDTH+(SCREEN_WIDTH)]
```
変更後の描画速度の平均は、約0.005くらい
で、ここで気づいたことは、全部スライスでいいんじゃ・・・
ということで最終形態
```
def draw():
	pyxel.cls(0)
	_screen_ptr = pyxel.screen.data_ptr()
	_screen_ptr[0:SCREEN_HEIGHT*SCREEN_WIDTH] = pix[0:SCREEN_HEIGHT*SCREEN_WIDTH]
```
最終形態の描画速度の平均は、約0.003くらい
変更前から２倍以上の高速化に成功！（とはいえ微々たるものですが・・・計算処理を高速化しないと！）

[動作確認はこちら：Pyxel実験室](https://sanbunnoichi1962.web.fc2.com/pyxel_experiment.html#mandelzoom2)
----------------------------------追記ここまで

「16色を超える色」を使ってマンデルブロート集合を描画します。
深度を上げると表示に時間がかかります。

操作方法：
マウスでカーソル位置を変更して
マウス右クリックで拡大
マウス左クリックで縮小
Cキーでグラデカラーに切り替え
Wキーで別のグラデカラーに切り替え
Gキーでグレーカラーに切り替え
1～9,0キーで深度切り替え
Rキーで初期画面にもどる

前準備として、
内部で作成したパレットファイル my_resource.pyxpalを使用するため、本プログラムの実行前にPyxelエディタを実行（"pyxel edit"）、ファイル保存して空のリソースファイル：my_resource.pyxresを作成してください。

画像サンプル
![mandelzoom1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927925/796cccd6-0bbe-93ec-4417-bcccf209e006.png)
![mandelzoom2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927925/80d79a72-421e-d75d-4881-180861aa0f80.png)
![mandelzoom3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3927925/41826dbd-b9c5-10e6-789c-2befddfdeeb3.png)

```
import pyxel
import math
import numpy as np

SCREEN_WIDTH = 256
SCREEN_HEIGHT = 256

max_i = 0x100
xmin = -2.0
xmax = 1.2
ymin = -1.2
ymax = 1.2
pix = [0 for tbl in range(SCREEN_WIDTH * SCREEN_HEIGHT)]
#-----------------------------------------------------------------
#計算
def cal_mbrot():
	global max_i
	global xmin
	global xmax
	global ymin
	global ymax
	global pix

	w = SCREEN_WIDTH
	h = SCREEN_HEIGHT
	i = j = k = 0
	x = y = z = x0 = y0 = 0.0
	w1 = 1.0 / w
	h1 = 1.0 / h

	bailout=4.0
	LOG2 = math.log(2)

	p = 0
	for j in range( h ):
		y0 = ymin + (ymax - ymin) * j * h1
		for i in range( w ):
			k = 0
			x0 = xmin + (xmax - xmin) * i * w1
			zx = 0.0
			zy = 0.0
			radius2 = 0.0
			iteration = 0
			while iteration < max_i and radius2 <= bailout:
				zx, zy = zx*zx - zy*zy + x0, 2*zx*zy + y0
				radius2 = zx*zx + zy*zy
				iteration += 1

			alpha = 0
			outside = iteration<max_i
			if outside:
				# smoothing
				log_zn = math.log(radius2) / 2
				nu = math.log(log_zn / LOG2) / LOG2
				alpha = iteration + 1 - nu

				alpha *= 0.05
				alpha = (alpha%1)*255

			k = int(alpha)
			k &= 0xff
			if( k >= 255 ):		#255以上は指定できない
				k = 254
			pix[p] = k
			p+=1
			if( p >= (SCREEN_WIDTH * SCREEN_HEIGHT) ):
				return
#-----------------------------------------------------------------
#マウスで任意の位置を選択（拡大縮小）した時の動作
def zoom_set( isZoom ):
	global xmin
	global xmax
	global ymin
	global ymax

	#マウスカーソル位置をセット
	x = pyxel.mouse_x
	y = pyxel.mouse_y

	xc = xmin + (xmax - xmin) * x / SCREEN_WIDTH
	yc = ymin + (ymax - ymin) * y / SCREEN_HEIGHT

	if( isZoom != 0 ):
		xstep = (xmax - xmin) * 2.0 * 0.5
		ystep = (ymax - ymin) * 2.0 * 0.5
	else:
		xstep = (xmax - xmin) * 0.5 * 0.5
		ystep = (ymax - ymin) * 0.5 * 0.5

	xmin = xc - xstep
	xmax = xc + xstep
	ymin = yc - ystep
	ymax = yc + ystep

	cal_mbrot()
#-----------------------------------------------------------------
#マウスボタン入力
#-----------------------------------------------------------------
def getInputML():
	if pyxel.btnp(pyxel.MOUSE_BUTTON_LEFT):
		return 1
	else:
		return 0

def getInputMR():
	if pyxel.btnp(pyxel.MOUSE_BUTTON_RIGHT):
		return 1
	else:
		return 0
#-----------------------------------------------------------------
#更新
def update():
	global max_i
	global xmin
	global xmax
	global ymin
	global ymax

	#深度変更：キー1,2,3,4,5,6,7,8,9,0
	if pyxel.btnp( pyxel.KEY_1 ):
		max_i = 1<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_2 ):
		max_i = 2<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_3 ):
		max_i = 3<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_4 ):
		max_i = 4<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_5 ):
		max_i = 5<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_6 ):
		max_i = 6<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_7 ):
		max_i = 7<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_8 ):
		max_i = 8<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_9 ):
		max_i = 9<<8
		cal_mbrot()
	elif pyxel.btnp( pyxel.KEY_0 ):
		max_i = 10<<8
		cal_mbrot()

	#拡大縮小
	if getInputML():
		zoom_set(0)
	elif getInputMR():
		zoom_set(1)

	#C/Gキーでグラデカラー/グレーカラー切り替え
	if pyxel.btnp(pyxel.KEY_C):
		pyxel.load("my_resource.pyxres", excl_images=True, excl_tilemaps=True, excl_sounds=True, excl_musics=True)
		cal_mbrot()
	elif pyxel.btnp(pyxel.KEY_G):
		pyxel.load("glaycolor.pyxres", excl_images=True, excl_tilemaps=True, excl_sounds=True, excl_musics=True)
		cal_mbrot()
	elif pyxel.btnp(pyxel.KEY_W):
		pyxel.load("getcol.pyxres", excl_images=True, excl_tilemaps=True, excl_sounds=True, excl_musics=True)
		cal_mbrot()

	#Rキーで初期画面にもどる
	elif pyxel.btnp(pyxel.KEY_R):
		max_i = 0x100
		xmin = -2.0
		xmax = 1.2
		ymin = -1.2
		ymax = 1.2
		cal_mbrot()
#-----------------------------------------------------------------
#描画
def draw():
	pyxel.cls(0)
	_screen_ptr = pyxel.screen.data_ptr()
	for _i in range(SCREEN_HEIGHT*SCREEN_WIDTH):
		_screen_ptr[_i] = pix[_i]
#-----------------------------------------------------------------
#パレットファイルを作成する
#-----------------------------------------------------------------
def get_color(alpha):
    shift = 0.0
    color = np.empty((3,), np.float32)
    color[0] = (np.cos((alpha*2.0-1.0+shift)*np.pi) + 1.0)
    color[1] = (np.cos((alpha*2.0-0.75+shift)*np.pi) + 1.0)
    color[2] = (np.cos((alpha*2.0-0.5+shift)*np.pi) + 1.0)
    return (color*(0.5*255)).astype(np.float32)
#-----------------------------------------------------------------
def make_palet():
	with open('./my_resource.pyxpal', mode='w') as f:
		#No.0の色を設定
		f.write( str(0)+'\n' )
		density = 0.35
		for y in range(1,255):
			alpha = y * 0.05 * density
			alpha = math.log(alpha+1)
			col = get_color( alpha )
			c0 = int(col[0])
			c1 = int(col[1])
			c2 = int(col[2])
			if(c0 > 255):
				c0 = 255
			if(c1 > 255):
				c1 = 255
			if(c2 > 255):
				c2 = 255
			coldata = c0*0x10000 + c1*0x100 + c2
			f.write( hex(coldata).removeprefix('0x')+'\n' )
#-----------------------------------------------------------------
pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT)
#パレットファイルを作成する
make_palet()
#255色カラーデータを読み込むためリソース読み込み
pyxel.load("my_resource.pyxres")
#初期画面作成
cal_mbrot()
#マウスカーソル表示
pyxel.mouse( visible = True )
#実行
pyxel.run(update, draw)
```