さて、前回のエントリーの「グラデーションを作成するサンプルスクリプト」ですが、同じ色が続く部分を一括に塗る処理を入れてみました。他に、モジュール化したこと。そして、マウスのドラッグによってグラデーションの方向や長さを表す矢印を指定できるようになりました。
他のことでググってたら偶然グラデーションアルゴリズムこういうの見つけた。この記事で書いたのよりもっとシンプルに書けるみたい。でも、自分で考えて書いたっていうのも大事だよね!...そういうことにしておきます。
#module mod_gradation
// 左上座標の値とX方向、Y方向の増加量の計算
#deffunc local _calc \
var t, var ddx, var ddy, \
int x, int y, \
double x0, double y0, double x1, double y1, \
local dx, local dy, local a, local b
// 左上座標の値とX方向、Y方向の値の増加量を取得
dx = x1 - x0
dy = y1 - y0
a = dx * dx + dy * dy
b = dx * ( x0 - x ) + dy * ( y0 - y )
if a != 0.0 {
t = - ( b / a )
ddx = dx / a
ddy = dy / a
} else { // (x0,y0)-(x1,y1)が線分でなく点の場合
t = 0.5
ddx = 0.0
ddy = 0.0
}
return
// 同じ色が続く回数を取得
#defcfunc local _countContinuation \
double _now, double _inc, int nex, \
local now, local inc, local cont
now = _now * 255
inc = _inc * 255
cont = absf( ( double( nex ) - now ) / inc )
cont = int( cont ) + ( cont > int( cont ) )
return cont
#deffunc gradation \
int x, int y, int w, int h, \
double x0, double y0, double x1, double y1, \
local t, local ddx, local ddy, \
local __l, local _l, local l, local xcnt, local ycnt, \
local cont, local nex
_calc@mod_gradation t, ddx, ddy, x, y, x0, y0, x1, y1
__l = t
repeat h
ycnt = cnt
__l += ddy
_l = __l
repeat w
xcnt = cnt
_l += ddx
l = int( limitf( _l, 0.0, 1.0 ) * 255 )
color l, l, l
// 同じ色が続く部分を一括で塗る処理
// 列の最後までを一括で塗る
if ( ddx == 0.0 ) | ( _l <= 0.0 & ddx <= 0.0 ) | ( _l >= 1.0 & ddx >= 0.0 ) {
line x + w, ycnt + y, xcnt + x, ycnt + y
break
}
// 列の最初からを一括で塗る
if ( _l <= 0.0 ) | ( _l >= 1.0 ) {
if _l <= 0.0 : nex = 0 : else : nex = 255
cont = _countContinuation@mod_gradation( _l, ddx, nex )
cont = limit( cont, 1, w - xcnt )
line xcnt + x + cont, ycnt + y, xcnt + x, ycnt + y
_l += ddx * ( cont - 1 )
continue xcnt + cont
}
pset xcnt + x, ycnt + y
await
loop
loop
return
#global
#uselib "winmm"
#cfunc timeGetTime "timeGetTime"
#const DRAW_WIDTH 256
#const DRAW_HEIGHT 256
#const DRAW_POSX 32
#const DRAW_POSY 32
#const WND_WIDTH DRAW_WIDTH + DRAW_POSX * 2
#const WND_HEIGHT DRAW_HEIGHT + DRAW_POSY * 2
#define gbox( %1, %2, %3, %4 ) line %1, %2, %3, %2 : line %1, %4 : line %3, %4 : line %3, %2
buffer 1, WND_WIDTH, WND_HEIGHT
screen 0, WND_WIDTH, WND_HEIGHT
oncmd gosub *MoveCursor, $0200 // WM_MOUSEMOVE
oncmd gosub *StartDrag, $0201 // WM_LBUTTONDOWN
oncmd gosub *EndDrag, $0202 // WM_LBUTTONUP
redraw 0
gosub *ClearScreen
redraw 1
gosub *CopyToBuffer
stop
*DrawGradation
redraw 0
gosub *ClearScreen
time = timeGetTime()
gradation DRAW_POSX, DRAW_POSY, DRAW_WIDTH, DRAW_HEIGHT, x0, y0, x1, y1
time = timeGetTime() - time
gosub *DrawLine
redraw 1
gosub *CopyToBuffer
title ""+time+" ms"
return
*CopyToBuffer
gsel 1
pos 0, 0
gcopy 0, 0, 0, WND_WIDTH, WND_HEIGHT
gsel 0
return
*ClearScreen
color 255, 255, 255
boxf
color 0, 0, 0
gbox DRAW_POSX - 1, DRAW_POSY - 1, DRAW_POSX + DRAW_WIDTH - 1 + 1, DRAW_POSY + DRAW_HEIGHT - 1 + 1
return
*DrawLine
color 255, 0, 0
line x1, y1, x0, y0
return
*StartDrag
x0 = double( lParam & 0xFFFF )
y0 = double( (lParam >> 16) & 0xFFFF )
fDragging = 1
return
*MoveCursor
if fDragging {
x1 = double( lParam & 0xFFFF )
y1 = double( (lParam >> 16) & 0xFFFF )
redraw 0
gosub *ClearScreen
pos 0, 0
gcopy 1, 0, 0, WND_WIDTH, WND_HEIGHT
gosub *DrawLine
redraw 1
}
return
*EndDrag
oncmd 0
// グラデーションの向きや大きさを決める線分の座標
x1 = double( lParam & 0xFFFF )
y1 = double( (lParam >> 16) & 0xFFFF )
fDragging = 0
gosub *DrawGradation
oncmd 1
return
同じ色、というのはすなわちグラデーション外の黒や白の部分です。グラデーション部分に同じ灰色が並んでいたとしても、それは関係ありません。同じ色を一括といっても、横方向に走査するときに同じ色が並んでいる場合ということで、正確に言えば、「横方向に同じ色が並んでいるもの」を一度に描画します。例外的に、黒や白以外のグラデーション内の灰色でも X 方向の増加量が 0 の場合(すなわち垂直方向のグラデーション)も、同じように一度に描画します。
そういうわけで同じ色がたくさん並ぶ、グラデーションの幅が小さいときにより高速になります。逆に同じ色がほとんど並ばない、グラデーションの幅が大きいときには無駄な演算が増えるだけなので逆に速度は落ちてしまいます。
このスクリプトでは X 方向に走査するようになっています。しかし、場合に分けて Y 方向に走査するように変更すれば水平方向のグラデーションも垂直方向のように高速になるはずです。
また、ループ内の同じ計算をループ外に追い出すなどの地味な高速化はまだまだ出来そうです。
P.S.スクリプトをハイライトするのに、(X)HTMLコンバータ - プロジェクト空色鉛筆で公開されている「(X)HTMLコンバータ」を利用させていただきました。今後掲載するスクリプトも、こちらのツールを使っていきたいと思います。