昨日テストで出した画像拡大用線形補間ルーチンの全体図

というわけで、さらにテストで関数丸ごとで出してみます。

MMXを使って画像を線形拡大する

BOOL StretchBitmapMMX(void *lpDest,long lDestWidth,long lDestHeight,long lDestPitch,const void *lpSrc,long lSrcWidth,long lSrcHeight,long lSrcPitch)
{
__m64 mm0,mm1,mm2,mm3,mm4,mm5,mm6,mm7;
unsigned char *dst,*src,*buf,*spn; int *ptr1,*ptr2;
long sx,sy,sw,sh,dx,dy; long x,y,ddx,ddy;
MMVAL _mmalpha; MMVAL _mmalphac(0x0100010001000100ull);
dst = (unsigned char *)lpDest; src = (unsigned char *)lpSrc;
ddx = (lSrcWidth << 8) / lDestWidth; ddy = (lSrcHeight << 8) / lDestHeight; sw = lSrcWidth - 1; sh = lSrcHeight - 1; mm6 = _mm_cvtsi32_si64(8); mm7 = _mm_setzero_si64(); for(sy = y = 0,dy = lDestHeight;dy > 0;dy–,sy += ddy,dst += lDestPitch){
buf = dst; src = (unsigned char *)lpSrc + lSrcPitch * y;
if(y < sh){ spn = src + lSrcPitch; _mmalpha.setu16((unsigned short)(sy & 0x000000ff)); } else{ spn = src; _mmalpha.setu16(0x0000); } mm4.m64_u64 = _mmalphac.u64; mm5.m64_u64 = _mmalpha.u64; mm4 = _mm_sub_pi16(mm4,mm5); for(sx = 0,dx = lDestWidth;dx > 0;dx–,sx += ddx,buf += sizeof(unsigned long)){
x = sx >> 8; _mmalpha.setu16((unsigned short)(sx & 0x000000ff));
ptr1 = (int *)(src + sizeof(unsigned long) * x);
ptr2 = (int *)(spn + sizeof(unsigned long) * x);
mm0 = _mm_cvtsi32_si64(*ptr1);
mm2 = _mm_cvtsi32_si64(*ptr2);
if(x < sw){ mm1 = _mm_cvtsi32_si64(*(ptr1 + 1)); mm3 = _mm_cvtsi32_si64(*(ptr2 + 1)); } else{ mm1 = mm0; mm3 = mm2; } mm0 = _mm_unpacklo_pi8(mm0,mm7); mm1 = _mm_unpacklo_pi8(mm1,mm7); mm2 = _mm_unpacklo_pi8(mm2,mm7); mm3 = _mm_unpacklo_pi8(mm3,mm7); mm0 = _mm_mullo_pi16(mm0,mm4); mm1 = _mm_mullo_pi16(mm1,mm4); mm2 = _mm_mullo_pi16(mm2,mm5); mm3 = _mm_mullo_pi16(mm3,mm5); mm0 = _mm_add_pi16(mm0,mm2); mm1 = _mm_add_pi16(mm1,mm3); mm0 = _mm_srl_pi16(mm0,mm6); mm1 = _mm_srl_pi16(mm1,mm6); mm2.m64_u64 = _mmalphac.u64; mm3.m64_u64 = _mmalpha.u64; mm2 = _mm_sub_pi16(mm2,mm3); mm0 = _mm_mullo_pi16(mm0,mm2); mm1 = _mm_mullo_pi16(mm1,mm3); mm1 = _mm_add_pi16(mm0,mm1); mm0 = _mm_srl_pi16(mm1,mm6); mm0 = _mm_packs_pu16(mm0,mm7); *(int *)buf = _mm_cvtsi64_si32(mm0); } } _mm_empty(); return TRUE; } [/cpp]

さっと解説すると、sxとsyは24.8の固定小数点でSourceの位置、dyとdyはDestのループカウンタ、ddxとddyはDest1PixelあたりのSourceの移動量です。

mm0~mm7はMMXのレジスタを模しています。MMVALは64bitの構造体の変数で、各ワード(16bit)や各ダブルワード(32bit)に同じ値を設定できるようにしたヘルパ構造体です。

引数やらそれ以外は意味に合うように推察してください。一応名前そのまんまのつもりです。

元々は全部をx86のアセンブラコードで組もうかと思っていたコードでしたが、ループ制御の部分がややこしすぎたのでmmintrinでの実装に切り替えました。

計算のやり方はほかの線形補間のページで解説されているとおりのことしかやっていない(1Pixelの幅を256分割してそれをα乗算することで4点の補間処理を行っている)です。

一応MMX命令を使用しているのである程度の高速化の効果が期待できるルーチンになっています。

もちろん、このままでは無駄がいろいろとありますので、実際に実装しているものはもう少し高速化を図ってあります。

目安としては、

  • 256分割ではなく、16分割にしてPixel一回分のx乗算+シフトの演算を削除する
  • 計算が不要なコードを削る(終端部のポインタが一致する部分など)

があります。

さて、このコードは私が作成したプログラムに実装されていますが・・・。

それはとりあえず明日の話ということで。それでは。

・・・やっぱりはてなの方がプログラムコードの表示がきれいですね。

しばらく使ってみて乗り換えか併用か考えてみたいと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL