最も基本となる、画像の転送関数です。
■ コード
各属性ごとに描画ルーチンを用意しています。
それぞれのアルゴリズムについては、後で解説します。
#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));