同じ色を一括で処理するようにしたグラデーションスクリプト

さて、前回のエントリーの「グラデーションを作成するサンプルスクリプト」ですが、同じ色が続く部分を一括に塗る処理を入れてみました。他に、モジュール化したこと。そして、マウスのドラッグによってグラデーションの方向や長さを表す矢印を指定できるようになりました。

他のことでググってたら偶然グラデーションアルゴリズムこういうの見つけた。この記事で書いたのよりもっとシンプルに書けるみたい。でも、自分で考えて書いたっていうのも大事だよね!...そういうことにしておきます。

#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コンバータ」を利用させていただきました。今後掲載するスクリプトも、こちらのツールを使っていきたいと思います。

インフォメーション

公開日時
2007年7月28日 午後8時58分0秒
最終更新日時
2007年12月29日 午後2時1分22秒
カテゴリ
HSP