Windowsコード集 上のページ

#0010 GBuf_Blt 矩形転送
最も基本となる、画像の転送関数です。

■ コード

各属性ごとに描画ルーチンを用意しています。
それぞれのアルゴリズムについては、後で解説します。

#ifndef MULDIV
    #define MULDIV(a,b,c)       ((a) * (b) / (c))
#endif

BOOL WINAPI GBuf_Blt(GBUF_STRUCT *dst,GBUF_STRUCT *src,int dx,int dy,int sx,int sy,int nx,int ny)
{
    G_PIXEL *dpixel = GBuf_GetPtrPosition(dst,dx,dy),*wdpixel = dpixel;
    G_PIXEL *spixel = GBuf_GetPtrPosition(src,sx,sy),*wspixel = spixel;
    G_PIXEL mask;
    BOOL ismask = GBuf_GetMask(src,&mask);
    int dpitch = GBuf_GetPitch(dst),spitch = GBuf_GetPitch(src);
    int x = dx,y = dy,ex = dx + nx,ey = dy + ny;

    // アルファ
    if(src->flags & GF_ALPHA){
        while(y < ey){
            while(x < ex){
                wdpixel->pixel.red   = (wspixel->pixel.red   * wspixel->pixel.alpha + wdpixel->pixel.red   * (255 - wspixel->pixel.alpha)) / 255;
                wdpixel->pixel.green = (wspixel->pixel.green * wspixel->pixel.alpha + wdpixel->pixel.green * (255 - wspixel->pixel.alpha)) / 255;
                wdpixel->pixel.blue  = (wspixel->pixel.blue  * wspixel->pixel.alpha + wdpixel->pixel.blue  * (255 - wspixel->pixel.alpha)) / 255;
                wspixel++;
                wdpixel++;
                x++;
            }
            x = dx;
            y++;
            wspixel = (spixel += spitch);
            wdpixel = (dpixel += dpitch);
        }
        return TRUE;
    }

    // マスク
    if(src->flags & GF_MASK){
        while(y < ey){
            while(x < ex){
                if((wspixel->data ^ mask.data) & 0x0ffffff){
                    wdpixel->data = wspixel->data;
                }
                wspixel++;
                wdpixel++;
                x++;
            }
            x = dx;
            y++;
            wspixel = (spixel += spitch);
            wdpixel = (dpixel += dpitch);
        }
        return TRUE;
    }

    // GF_DEFAULTモード
    while(y < ey){
        while(x < ex){
            wdpixel->data = wspixel->data;
            wspixel++;
            wdpixel++;
            x++;
        }
        x = dx;
        y++;
        wspixel = (spixel += spitch);
        wdpixel = (dpixel += dpitch);
    }
    return TRUE;
}

■ GF_DEFAULTモード描画

二重のwhileループでx〜ex,y〜ey範囲にピクセルを転送する、それだけです。
wdpixel->data = wspixel->data という記述がありますが、以下のような構文を1文ですませるようにしています。

 wdpixel->pixel.red = wspixel->pixel.red;
 wdpixel->pixel.green = wspixel->pixel.green;
 wdpixel->pixel.blue = wspixel->pixel.blue;
 wdpixel->pixel.alpha = wspixel->pixel.alpha;

実際にマシン語にすると、これだけの差が生じます。
1行で書いた場合(Visual C++ 6.0SP3,最適化無し)
wdpixel->data = wspixel->data;

    mov     ecx, DWORD PTR _wdpixel$[ebp]
    mov     edx, DWORD PTR _wspixel$[ebp]
    mov     eax, DWORD PTR [edx]
    mov     DWORD PTR [ecx], eax


4行で書いた場合(Visual C++ 6.0SP3,最適化無し)
wdpixel->pixel.red   = wspixel->pixel.red;
wdpixel->pixel.green = wspixel->pixel.green;
wdpixel->pixel.blue  = wspixel->pixel.blue;
wdpixel->pixel.alpha = wspixel->pixel.alpha;

    mov  ecx, DWORD PTR _wdpixel$[ebp]
    mov  edx, DWORD PTR _wspixel$[ebp]
    mov  al,  BYTE PTR [edx+2]
    mov  BYTE PTR [ecx+2], al
    mov  ecx, DWORD PTR _wdpixel$[ebp]
    mov  edx, DWORD PTR _wspixel$[ebp]
    mov  al,  BYTE PTR [edx+1]
    mov  BYTE PTR [ecx+1], al
    mov  ecx, DWORD PTR _wdpixel$[ebp]
    mov  edx, DWORD PTR _wspixel$[ebp]
    mov  al,  BYTE PTR [edx]
    mov  BYTE PTR [ecx], al
    mov  ecx, DWORD PTR _wdpixel$[ebp]
    mov  edx, DWORD PTR _wspixel$[ebp]
    mov  al,  BYTE PTR [edx+3]
    mov  BYTE PTR [ecx+3], al
以下の2行は1ライン行を転送し終わったとき、次の1ライン下に移動させるための構文です。
spitch,dpitchはそのための係数で、GBuf_GetPitchで取得した値です。

 wspixel = (spixel += spitch);
 wdpixel = (dpixel += dpitch);


■ GF_MASKモード描画

基本的にはGF_DEFAULTと同じです。
ただしマスクチェックが含まれており、if((wspixel->data ^ mask.data) & 0x0ffffff)がそれに当たります。
実際には以下のようなチェックルーチンになります。

 if(
  (wspixel->pixel.red != mask.pixel.red ) ||
  (wspixel->pixel.green != mask.pixel.green) ||
  (wspixel->pixel.blue != mask.pixel.blue )
 ){

実際にマシン語にすると、これだけの差が生じます。
if((wspixel->data ^ mask.data) & 0x0ffffff)の場合(Visual C++ 6.0SP3,最適化無し)

    mov  edx, DWORD PTR _wspixel$[ebp]
    mov  eax, DWORD PTR [edx]
    xor  eax, DWORD PTR _mask$[ebp]
    and  eax, 16777215      ; 00ffffffH
    test eax, eax
    je   SHORT $L53788

以下の構文の場合(Visual C++ 6.0SP3,最適化無し)
if(
 (wspixel->pixel.red   != mask.pixel.red  ) ||
 (wspixel->pixel.green != mask.pixel.green) ||
 (wspixel->pixel.blue  != mask.pixel.blue )
){

    mov  edx, DWORD PTR _wspixel$[ebp]
    xor  eax, eax
    mov  al, BYTE PTR [edx+2]
    mov  ecx, DWORD PTR _mask$[ebp+2]
    and  ecx, 255       ; 000000ffH
    cmp  eax, ecx
    jne  SHORT $L53788
    mov  edx, DWORD PTR _wspixel$[ebp]
    xor  eax, eax
    mov  al, BYTE PTR [edx+1]
    mov  ecx, DWORD PTR _mask$[ebp+1]
    and  ecx, 255       ; 000000ffH
    cmp  eax, ecx
    jne  SHORT $L53788
    mov  edx, DWORD PTR _wspixel$[ebp]
    xor  eax, eax
    mov  al, BYTE PTR [edx]
    mov  ecx, DWORD PTR _mask$[ebp]
    and  ecx, 255       ; 000000ffH
    cmp  eax, ecx
    je   SHORT $L53787

■ GF_ALPHAモード描画

GF_DEFAULTやGF_MASKのように、1文で済ませるという事はできません。
輝度毎に計算させて代入という計算式を行う必要があります。
各輝度の計算は、以下のような式になります。

 da = (sa * alpha / 255) + (da * (255 - alpha) / 255);
 sa : 転送画像
 da : 元画像
 alpha : アルファ値

つまり、もう少し分かりやすく書くと以下のような式になります。
転送画像と元画像それぞれの輝度に対しどれだけの分量で加算するかを求め、その合計値を新しい輝度値とする訳です。
分かりやすくするために、アルファ値を0〜100%のパーセント値としてみます。

 新しい輝度値 = (転送画像 * アルファ値[%]) + (元画像 * (100[%] - アルファ値[%]))

これが分かったところで、計算式を簡略化してみます。
上記の式を、以下のように丸め込んでしまいます。

 da = ((sa * alpha) + (da * (255 - alpha))) / 255;

割り算は遅いので、更にこのように簡略化する手もあります。

 // 前もって用意しておくテーブル
 long tbl[256 * 256];
 tbl[x][y] = x * y / 255; // x,yそれぞれ、0〜255に変化させた結果を格納しておく
 // 最適化式
 da = tbl[sa][alpha] + tbl[da][255 - alpha];

ちなみに上記式は以下のように書き変えると、コンパイラによっては更に高速になるかもしれません(笑)

 da = *(tbl + (sa << 8) + alpha) + *(tbl + (da << 8) + (255 - alpha));


上のページ