キャンバス描画(4)・オーバーサンプリング最適化

オーバーサンプリング最適化
前回は、「オーバーサンプリング」によるキャンバス描画を行いましたが、今回は、そのコードを最適化してもう少し高速化させます。

では、まずオーバーサンプリングのループ部分をもう一度見てみましょう。

for(jy = 0, sfy_j = sfy; jy < SUBPIXEL; jy++, sfy_j += incfxy_j)
{
    for(jx = 0, sfx_j = sfx; jx < SUBPIXEL; jx++, sfx_j += incfxy_j)
    {
        sx = sfx_j >> 18;
        sy = sfy_j >> 18;
        
        if(sx < 0) sx = 0;
        else if(sx >= srcimg->w) sx = srcimg->w - 1;
        
        if(sy < 0) sy = 0;
        else if(sy >= srcimg->h) sy = srcimg->h - 1;
    
        psrc = srcimg->pixbuf + sy * srcimg->w + sx;

ソース画像の座標 sx, sy を求める計算を毎ループで行っていますが、sxsy を別々に見てみると、それぞれの座標は SUBPIXEL 通りの数値 (SUBPIXEL = 4 なら、4通り) であることがわかります。

X, Y のそれぞれのループでは、それぞれの座標値は一定なわけですから、ループ内で毎回計算せずに、X, Y それぞれの座標値をテーブル化して、ループ内ではそれを参照してやれば計算回数を減らせます。

テーブルを使った場合のコードは、以下のようになります。

int tblX[SUBPIXEL],tblY[SUBPIXEL];

/* Y のテーブル */

for(jy = 0, sfy_j = sfy; jy < SUBPIXEL; jy++, sfy_j += incfxy_j)
{
    sy = sfy_j >> 18;

    if(sy < 0) sy = 0;
    else if(sy >= srcimg->h) sy = srcimg->h - 1;
    
    tblY[jy] = sy;
}

/* X のテーブル */

for(jx = 0, sfx_j = sfx; jx < SUBPIXEL; jx++, sfx_j += incfxy_j)
{
    sx = sfx_j >> 18;

    if(sx < 0) sx = 0;
    else if(sx >= srcimg->w) sx = srcimg->w - 1;
    
    tblX[jx] = sx;
}

/* オーバーサンプリング*/

for(jy = 0; jy < SUBPIXEL; jy++)
{
    sy = tblY[jy];

    for(jx = 0; jx < SUBPIXEL; jx++)
    {
        sy = tblX[jx];
        
        psrc = srcimg->pixbuf + sy * srcimg->w + sx;

SUBPIXEL x SUBPIXEL」回分行っていた計算が、「SUBPIXEL + SUBPIXEL」回分の計算で済みました。
Y のテーブルをポインタ値に
上記のコードに、もう少し手を加えます。

Y のテーブル値は、ソース座標の Y 位置ですが、これをピクセルバッファのポインタ位置に変えてみます。

ソース画像のバッファ位置は以下の計算で求めていますが、

psrc = srcimg->pixbuf + sy * srcimg->w + sx;

Y のテーブル値を、バッファの Y 位置のポインタにすれば、ここの計算も省略できます。

SPTK_PIX_RGBA *tblY[SUBPIXEL];
int tblX[SUBPIXEL];

/* Y のテーブル : ポインタ */

for(jy = 0, sfy_j = sfy; jy < SUBPIXEL; jy++, sfy_j += incfxy_j)
{
    sy = sfy_j >> 18;

    if(sy < 0) sy = 0;
    else if(sy >= srcimg->h) sy = srcimg->h - 1;
    
    tblY[jy] = srcimg->pixbuf + sy * srcimg->w;
}

/* X のテーブル : 座標 */

for(jx = 0, sfx_j = sfx; jx < SUBPIXEL; jx++, sfx_j += incfxy_j)
{
    sx = sfx_j >> 18;

    if(sx < 0) sx = 0;
    else if(sx >= srcimg->w) sx = srcimg->w - 1;
    
    tblX[jx] = sx;
}

/* オーバーサンプリング*/

for(jy = 0; jy < SUBPIXEL; jy++)
{
    for(jx = 0; jx < SUBPIXEL; jx++)
    {
        psrc = tblY[jy] + tblX[jx];

これで、だいぶループ内を軽量化できました。
さらなる最適化
実は、上記のコードにはまだ最適化の余地があります。

上記の、Y テーブル作成部分を見てください。

これは ix (キャンバス座標 X) のループ内で毎回計算されていますが、ix のループ内では、ソース画像の Y 位置は常に一定です。

ということは、Y のテーブルは、ix のループ前に一度だけ計算してやれば OK だということです。

for(iy = 0; iy < CANVAS_H; iy++)
{
    /* Y のテーブル */

    for(jy = 0, sfy_j = sfy; jy < SUBPIXEL; jy++, sfy_j += incfxy_j)
    {
        sy = sfy_j >> 18;

        if(sy < 0) sy = 0;
        else if(sy >= srcimg->h) sy = srcimg->h - 1;
        
        tblY[jy] = srcimg->pixbuf + sy * srcimg->w;
    }
    
    sfx = sfx_left;

    for(ix = 0; ix < CANVAS_W; ix++, sfx += incfxy, pdst += winimg->bpp)
    {

これで、かなり計算数を減らせました。
ソースコード
以下が、最適化後の全コードです。

017_oversamp2.c
#include <stdio.h>
#include "sptk.h"

#define SCRW      16
#define CANVAS_W  250
#define CANVAS_H  250
#define SUBPIXEL  4

SPTK_WIDGET *scrh,*scrv;
SPTK_IMAGE *winimg;
SPTK_IMAGE32 *srcimg;

int scrx = 0,scry = 0,zoom = 100;
int update_canvas = 0;

void draw_canvas()
{
    int ix,iy,jx,jy,pitchd,sfx,sfy,sfx_left,incfxy;
    int sx,sy,r,g,b,incfxy_j,subf,tblX[SUBPIXEL];
    uint8_t *pdst;
    SPTK_PIX_RGBA *psrc,*tblY[SUBPIXEL];
        
    pdst = winimg->pixbuftop;
    pitchd = winimg->pitch_dir - CANVAS_W * winimg->bpp;
    
    incfxy   = (1 << 18) * 100 / zoom;
    incfxy_j = incfxy / SUBPIXEL;
    
    sfx_left = scrx * incfxy;
    
    sfy = scry * incfxy;
    
    for(iy = 0; iy < CANVAS_H; iy++)
    {
        /* Y テーブル作成 */
        
        for(jy = 0, subf = sfy; jy < SUBPIXEL; jy++, subf += incfxy_j)
        {
            sy = subf >> 18;

            if(sy < 0) sy = 0;
            else if(sy >= srcimg->h) sy = srcimg->h - 1;

            tblY[jy] = srcimg->pixbuf + sy * srcimg->w;
        }
        
        /*----- ix ループ -----*/
    
        sfx = sfx_left;

        for(ix = 0; ix < CANVAS_W; ix++, sfx += incfxy, pdst += winimg->bpp)
        {
            /* 範囲外 */
            
            sx = sfx >> 18;
            sy = sfy >> 18;
            
            if(sx < 0 || sx >= srcimg->w || sy < 0 || sy >= srcimg->h)
            {
                sptk_image_setpixel_buf_rgb(winimg, pdst, 0xcc, 0xcc, 0xcc);
                continue;
            }
            
            /* X テーブル作成 */
        
            for(jx = 0, subf = sfx; jx < SUBPIXEL; jx++, subf += incfxy_j)
            {
                sx = subf >> 18;

                if(sx < 0) sx = 0;
                else if(sx >= srcimg->w) sx = srcimg->w - 1;

                tblX[jx] = sx;
            }

            /* オーバーサンプリング */
            
            r = g = b = 0;
        
            for(jy = 0; jy < SUBPIXEL; jy++)
            {
                for(jx = 0; jx < SUBPIXEL; jx++)
                {
                    psrc = tblY[jy] + tblX[jx];
                    
                    r += psrc->r;
                    g += psrc->g;
                    b += psrc->b;
                }
            }
            
            r /= SUBPIXEL * SUBPIXEL;
            g /= SUBPIXEL * SUBPIXEL;
            b /= SUBPIXEL * SUBPIXEL;
        
            sptk_image_setpixel_buf_rgb(winimg, pdst, r, g, b);
        }
        
        sfy += incfxy;
        pdst += pitchd;
    }
}

void update_handle()
{
    char m[16];
    
    if(update_canvas)
    {
        draw_canvas();
        
        sprintf(m, "%d", zoom);
        sptk_image_text(winimg, 4, 4, m, -1, 0xffffff); 
        sptk_image_text(winimg, 3, 3, m, -1, 0);
        
        update_canvas = 0;
    }
}

void update_screen(int time)
{
    update_canvas = 1;

    sptk_update(NULL, 0, 0, CANVAS_W, CANVAS_H, time);
}

void change_zoom()
{
    /* スクロール最大値変更 */

    sptk_scrollbar_set_status(scrh, 0, srcimg->w * zoom / 100, CANVAS_W);
    sptk_scrollbar_set_status(scrv, 0, srcimg->h * zoom / 100, CANVAS_H);
    
    /* 位置を再取得 */
    
    scrx = sptk_scrollbar_get_pos(scrh);
    scry = sptk_scrollbar_get_pos(scrv);
}

void winhandle(SPTK_EVENT *ev)
{
    switch(ev->type)
    {
        case SPTK_EVENT_WINDOW_KEYDOWN:
            if(ev->key.code == 'U')
            {
                if(zoom < 100)
                    zoom += 10;
                else if(zoom != 1000)
                    zoom += 100;
                
                change_zoom();
                update_screen(0);
            }
            else if(ev->key.code == 'D')
            {
                if(zoom > 100)
                    zoom -= 100;
                else if(zoom != 10)
                    zoom -= 10;
                
                change_zoom();
                update_screen(0);
            }
            break;
        case SPTK_EVENT_WINDOW_CLOSE:
            sptk_quit();
            break;
    }
}

void scroll_handle(SPTK_WIDGET *wg,int type,int pos)
{
    if(wg->id == 0)
        scrx = pos;
    else
        scry = pos;
    
    update_screen(5);
}

int main()
{
    sptk_init("test", CANVAS_W + SCRW, CANVAS_H + SCRW);
    
    sptk_window_set_handle(winhandle);
    sptk_set_update_handle(update_handle);
    
    winimg = sptk_window_get_image();
    
    srcimg = sptk_image32_load_bitmap("bitmap1.bmp");
    if(!srcimg) sptk_errexit("cannot load bitmap file");
        
    scrh = sptk_widget_scrollbar_create(0, 0, CANVAS_H, CANVAS_W, SCRW, 0, scroll_handle);
    scrv = sptk_widget_scrollbar_create(1, CANVAS_W, 0, SCRW, CANVAS_H, 1, scroll_handle);

    change_zoom();
    update_screen(-1);
    
    sptk_run();
    
    sptk_image32_free(srcimg);

    return 0;
}