圖形圖像處理-之-任意角度的高質量的快速的圖像旋轉 上篇 純軟件的任意角度的快速旋轉
[email protected] 2007.04.26
(2009.03.09 可以到這裏下載旋轉算法的完整的可以編譯的項目源代碼: http://blog.csdn.net/housisong/archive/2009/03/09/3970925.aspx )
(2007.06.22 優化PicRotary3加快13.6%,優化PicRotarySSE加快6.1%,
嘗試了一下使用SSE2的寫緩衝優化MOVNTI)
(2007.04.29 修正一個TRotaryClipData.find_begin的bug)
(2007.05.16 更換測試用電腦和編譯器,爲了保持測試數據一致和可對比性,更新了測試數據)
tag:圖像旋轉,任意角度,圖像縮放,速度優化,定點數優化,近鄰取樣插值,二次線性插值,
三次卷積插值,MipMap鏈,三次線性插值,MMX/SSE優化,CPU緩存優化,AlphaBlend,顏色混合,並行
摘要:首先給出一個基本的圖像旋轉算法,然後一步一步的優化其速度和旋轉質量,打破不能軟件旋轉的神話!
任意角度的高質量的快速的圖像旋轉 全文 分爲:
上篇 純軟件的任意角度的快速旋轉
中篇 高質量的旋轉
下篇 補充話題(完整AlphaBlend旋轉、旋轉函數並行化、針對大圖片的預讀緩衝區優化)
正文:
爲了便於討論,這裏只處理32bit的ARGB顏色;
代碼使用C++;涉及到彙編優化的時候假定爲x86平臺;使用的編譯器爲vc2005;
爲了代碼的可讀性,沒有加入異常處理代碼;
測試使用的CPU爲賽揚2G(新的測試平臺的CPU爲AMD64x2 4200+(2.37G),測試時使用的單線程執行);
(一些基礎代碼和插值原理的詳細說明參見作者的《圖形圖像處理-之-高質量的快速的圖像縮放》系列文章)
速度測試說明:
只測試內存數據到內存數據的縮放
測試圖片都是800*600旋轉到1004*1004,測試成績取各個旋轉角度的平均速度值; fps表示每秒鐘的幀數,值越大表示函數越快
A:旋轉原理和旋轉公式:
推導旋轉公式:
旋轉示意圖
有: tg(b)=y/x ----(1)
tg(a+b)=y'/x' ----(2)
x*x + y*y = x'*x' + y'*y' ----(3)
有公式:tg(a+b) = ( tg(a)+tg(b) ) / ( 1-tg(a)*tg(b) ) ----(4)
把(1)代入(4)從而消除參數b;
tg(a)+y/x = y'/x'*( 1-tg(a)*y/x ) ----(5)
由(5)可以得x'=y'*(x-y*tg(a))/( x*tg(a)+y ) ----(6)
把(6)代入(3)從而消除參數x',化簡後求得:
y'=x*sin(a)+y*cos(a); ----(7)
把(7)代入(6),有:
x'=x*cos(a)-y*sin(a); ----(8)
OK,旋轉公式有了,那麼來看看在圖片旋轉中的應用;
假設對圖片上任意點(x,y),繞一個座標點(rx0,ry0)逆時針旋轉RotaryAngle角度後的新的座標設爲(x', y'),有公式:
(x平移rx0,y平移ry0,角度a對應-RotaryAngle , 帶入方程(7)、(8)後有: )
x'= (x - rx0)*cos(RotaryAngle) + (y - ry0)*sin(RotaryAngle) + rx0 ;
y'=-(x - rx0)*sin(RotaryAngle) + (y - ry0)*cos(RotaryAngle) + ry0 ;
那麼,根據新的座標點求源座標點的公式爲:
x=(x'- rx0)*cos(RotaryAngle) - (y'- ry0)*sin(RotaryAngle) + rx0 ;
y=(x'- rx0)*sin(RotaryAngle) + (y'- ry0)*cos(RotaryAngle) + ry0 ;
旋轉的時候還可以順便加入x軸和y軸的縮放和平移,而不影響速度,那麼完整的公式爲:
x=(x'- move_x-rx0)/ZoomX*cos(RotaryAngle) - (y'- move_y-ry0)/ZoomY*sin(RotaryAngle) + rx0 ;
y=(x'- move_x-rx0)/ZoomX*sin(RotaryAngle) + (y'- move_y-ry0)/ZoomY*cos(RotaryAngle) + ry0 ;
其中: RotaryAngle爲逆時針旋轉的角度;
ZoomX,ZoomY爲x軸y軸的縮放係數(支持負的係數,相當於圖像翻轉);
move_x,move_y爲x軸y軸的平移量;
一些顏色和圖片的數據定義:
typedef unsigned char TUInt8; // [0..255]
struct TARGB32 //32 bit color
{
TUInt8 b,g,r,a; //a is alpha
};
struct TPicRegion //一塊顏色數據區的描述,便於參數傳遞
{
TARGB32* pdata; //顏色數據首地址
long byte_width; //一行數據的物理寬度(字節寬度);
//abs(byte_width)有可能大於等於width*sizeof(TARGB32);
long width; //像素寬度
long height; //像素高度
};
//那麼訪問一個點的函數可以寫爲:
inline TARGB32& Pixels(const TPicRegion& pic,const long x,const long y)
{
return ( (TARGB32*)((TUInt8*)pic.pdata+pic.byte_width*y) )[x];
}
//判斷一個點是否在圖片中
inline bool PixelsIsInPic(const TPicRegion& pic,const long x,const long y)
{
return ( (x>=0)&&(x<pic.width) && (y>=0)&&(y<pic.height) );
}
B:一個簡單的浮點實現版本
//函數假設以原圖片的中心點座標爲旋轉和縮放的中心
void PicRotary0(const TPicRegion& Dst,const TPicRegion& Src,double RotaryAngle,double ZoomX,double ZoomY,double move_x,double move_y)
{
if ( (fabs(ZoomX*Src.width)<1.0e-4) || (fabs(ZoomY*Src.height)<1.0e-4) ) return; //太小的縮放比例認爲已經不可見
double rx0=Src.width*0.5; //(rx0,ry0)爲旋轉中心
double ry0=Src.height*0.5;
for (long y=0;y<Dst.height;++y)
{
for (long x=0;x<Dst.width;++x)
{
long srcx=(long)((x- move_x-rx0)/ZoomX*cos(RotaryAngle) - (y- move_y-ry0)/ZoomY*sin(RotaryAngle) + rx0) ;
long srcy=(long)((x- move_x-rx0)/ZoomX*sin(RotaryAngle) + (y- move_y-ry0)/ZoomY*cos(RotaryAngle) + ry0) ;
if (PixelsIsInPic(Src,srcx,srcy))
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
(調用方法比如:
PicRotary0(ppicDst,ppicSrc,PI/6,0.9,0.9,(dst_wh-ppicSrc.width)*0.5,(dst_wh-ppicSrc.height)*0.5);
//作用:將圖片ppicSrc按0.9的縮放比例旋轉PI/6幅度後繪製到圖片ppicDst的中心
)
//注:測試圖片都是800*600的圖片旋轉到1004*1004的圖片中心 測試成績取各個旋轉角度的平均速度值
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicRotary0 34.9 fps
////////////////////////////////////////////////////////////////////////////////
旋轉結果圖示(小圖):
30度 60度 90度
120度 150度 180度
210度 240度 270度
300度 330度 360度
C:優化循環內部,化簡係數
1.sin和cos函數是很慢的計算函數,可以在循環前預先計算好sin(RotaryAngle)和cos(RotaryAngle)的值:
double sinA=sin(RotaryAngle);
double cosA=cos(RotaryAngle);
2.可以將除以ZoomX、ZoomY改成乘法,預先計算出倒數:
double rZoomX=1.0/ZoomX;
double rZoomY=1.0/ZoomY;
3.優化內部的旋轉公式,將能夠預先計算的部分提到循環外(即:拆解公式):
原: long srcx=(long)((x- move_x-rx0)/ZoomX*cos(RotaryAngle) - (y- move_y-ry0)/ZoomY*sin(RotaryAngle) + rx0) ;
long srcy=(long)((x- move_x-rx0)/ZoomX*sin(RotaryAngle) + (y- move_y-ry0)/ZoomY*cos(RotaryAngle) + ry0) ;
變形爲:
long srcx=(long)( Ax*x + Bx*y +Cx ) ;
long srcy=(long)( Ay*x + By*y +Cy ) ;
其中: Ax=(rZoomX*cosA); Bx=(-rZoomY*sinA); Cx=(-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0);
Ay=(rZoomX*sinA); By=(rZoomY*cosA); Cy=(-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0);
(提示: Ax,Bx,Cx,Ay,By,Cy都可以在旋轉之前預先計算出來)
改進後的函數爲:
{
if ( (fabs(ZoomX*Src.width)<1.0e-4) || (fabs(ZoomY*Src.height)<1.0e-4) ) return; //太小的縮放比例認爲已經不可見
double rZoomX=1.0/ZoomX;
double rZoomY=1.0/ZoomY;
double sinA=sin(RotaryAngle);
double cosA=cos(RotaryAngle);
double Ax=(rZoomX*cosA);
double Ay=(rZoomX*sinA);
double Bx=(-rZoomY*sinA);
double By=(rZoomY*cosA);
double rx0=Src.width*0.5; //(rx0,ry0)爲旋轉中心
double ry0=Src.height*0.5;
double Cx=(-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0);
double Cy=(-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0);
TARGB32* pDstLine=Dst.pdata;
double srcx0_f=(Cx);
double srcy0_f=(Cy);
for (long y=0;y<Dst.height;++y)
{
double srcx_f=srcx0_f;
double srcy_f=srcy0_f;
for (long x=0;x<Dst.width;++x)
{
long srcx=(long)(srcx_f);
long srcy=(long)(srcy_f);
if (PixelsIsInPic(Src,srcx,srcy))
pDstLine[x]=Pixels(Src,srcx,srcy);
srcx_f+=Ax;
srcy_f+=Ay;
}
srcx0_f+=Bx;
srcy0_f+=By;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicRotary1 62.0 fps
////////////////////////////////////////////////////////////////////////////////
( 在AMD64x2 4200+和VC2005編譯下PicRotary1(51.8fps)比PicRotary0(27.1fps)快90%;
在AMD64x2 4200+和VC6編譯下PicRotary1(20.3fps)比PicRotary0(16.1fps)快26%;
以前在賽揚2G和VC6編譯下PicRotary1(8.4fps)反而比PicRotary0(12.7fps)慢50%! :( )
D:更深入的優化、定點數優化
(浮點數到整數的轉化也是應該優化的一個地方,這裏不再處理,可以參見
<圖形圖像處理-之-高質量的快速的圖像縮放 上篇 近鄰取樣插值和其速度優化>中的PicZoom3_float函數)
1.優化除法:
原: double rZoomX=1.0/ZoomX;
double rZoomY=1.0/ZoomY;
改寫爲(優化掉了一次除法):
double tmprZoomXY=1.0/(ZoomX*ZoomY);
double rZoomX=tmprZoomXY*ZoomY;
double rZoomY=tmprZoomXY*ZoomX;
2.x86的浮點計算單元(FPU)有一條指令"fsincos"可以同時計算出sin和cos值
//定義SinCos函數: 同時計算sin(Angle)和cos(Angle)的內嵌x86彙編函數
void __declspec(naked) __stdcall SinCos(const double Angle,double& sina,double& cosa)
{
asm
{
fld qword ptr [esp+4]//Angle
mov eax,[esp+12]//&sina
mov edx,[esp+16]//&cosa
fsincos
fstp qword ptr [edx]
fstp qword ptr [eax]
ret 16
}
}
3.用定點數代替浮點計算
{
if ( (fabs(ZoomX*Src.width)<1.0e-4) || (fabs(ZoomY*Src.height)<1.0e-4) ) return; //太小的縮放比例認爲已經不可見
double tmprZoomXY=1.0/(ZoomX*ZoomY);
double rZoomX=tmprZoomXY*ZoomY;
double rZoomY=tmprZoomXY*ZoomX;
double sinA,cosA;
SinCos(RotaryAngle,sinA,cosA);
long Ax_16=(long)(rZoomX*cosA*(1<<16));
long Ay_16=(long)(rZoomX*sinA*(1<<16));
long Bx_16=(long)(-rZoomY*sinA*(1<<16));
long By_16=(long)(rZoomY*cosA*(1<<16));
double rx0=Src.width*0.5; //(rx0,ry0)爲旋轉中心
double ry0=Src.height*0.5;
long Cx_16=(long)((-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0)*(1<<16));
long Cy_16=(long)((-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0)*(1<<16));
TARGB32* pDstLine=Dst.pdata;
long srcx0_16=(Cx_16);
long srcy0_16=(Cy_16);
for (long y=0;y<Dst.height;++y)
{
long srcx_16=srcx0_16;
long srcy_16=srcy0_16;
for (long x=0;x<Dst.width;++x)
{
long srcx=(srcx_16>>16);
long srcy=(srcy_16>>16);
if (PixelsIsInPic(Src,srcx,srcy))
pDstLine[x]=Pixels(Src,srcx,srcy);
srcx_16+=Ax_16;
srcy_16+=Ay_16;
}
srcx0_16+=Bx_16;
srcy0_16+=By_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicRotary2 134.2 fps
////////////////////////////////////////////////////////////////////////////////
E:優化內部循環的判斷函數PixelsIsInPic,將判斷展開到內部循環之外,跳過不需要處理的目標像素;
TRotaryClipData類用於尋找旋轉需要處理的邊界範圍;算法思路是首先尋找原圖片中心點對應的;
那條掃描線,然後依次向上和向下尋找邊界; 如果想要更快速的邊界尋找算法,可以考慮利用像素的直線
繪製原理來自動生成邊界(有機會的時候再來實現它);
邊界尋找算法圖示
public:
long src_width;
long src_height;
long dst_width;
long dst_height;
long Ax_16;
long Ay_16;
long Bx_16;
long By_16;
long Cx_16;
long Cy_16;
public:
long out_dst_up_y;
long out_dst_down_y;
long out_src_x0_16;
long out_src_y0_16;
private:
long out_dst_up_x0;
long out_dst_up_x1;
long out_dst_down_x0;
long out_dst_down_x1;
public:
inline long get_up_x0(){ if (out_dst_up_x0<0) return 0; else return out_dst_up_x0; }
inline long get_up_x1(){ if (out_dst_up_x1>=dst_width) return dst_width; else return out_dst_up_x1; }
inline long get_down_x0(){ if (out_dst_down_x0<0) return 0; else return out_dst_down_x0; }
inline long get_down_x1(){ if (out_dst_down_x1>=dst_width) return dst_width; else return out_dst_down_x1; }
inline bool is_in_src(long src_x_16,long src_y_16)
{
return ( ( (src_x_16>=0)&&((src_x_16>>16)<src_width) )
&& ( (src_y_16>=0)&&((src_y_16>>16)<src_height) ) );
}
void find_begin_in(long dst_y,long& out_dst_x,long& src_x_16,long& src_y_16)
{
src_x_16-=Ax_16;
src_y_16-=Ay_16;
while (is_in_src(src_x_16,src_y_16))
{
--out_dst_x;
src_x_16-=Ax_16;
src_y_16-=Ay_16;
}
src_x_16+=Ax_16;
src_y_16+=Ay_16;
}
bool find_begin(long dst_y,long& out_dst_x0,long dst_x1)
{
long test_dst_x0=out_dst_x0-1;
long src_x_16=Ax_16*test_dst_x0 + Bx_16*dst_y + Cx_16;
long src_y_16=Ay_16*test_dst_x0 + By_16*dst_y + Cy_16;
for (long i=test_dst_x0;i<=dst_x1;++i)
{
if (is_in_src(src_x_16,src_y_16))
{
out_dst_x0=i;
if (i==test_dst_x0)
find_begin_in(dst_y,out_dst_x0,src_x_16,src_y_16);
if (out_dst_x0<0)
{
src_x_16-=(Ax_16*out_dst_x0);
src_y_16-=(Ay_16*out_dst_x0);
}
out_src_x0_16=src_x_16;
out_src_y0_16=src_y_16;
return true;
}
else
{
src_x_16+=Ax_16;
src_y_16+=Ay_16;
}
}
return false;
}
void find_end(long dst_y,long dst_x0,long& out_dst_x1)
{
long test_dst_x1=out_dst_x1;
if (test_dst_x1<dst_x0) test_dst_x1=dst_x0;
long src_x_16=Ax_16*test_dst_x1 + Bx_16*dst_y + Cx_16;
long src_y_16=Ay_16*test_dst_x1 + By_16*dst_y + Cy_16;
if (is_in_src(src_x_16,src_y_16))
{
++test_dst_x1;
src_x_16+=Ax_16;
src_y_16+=Ay_16;
while (is_in_src(src_x_16,src_y_16))
{
++test_dst_x1;
src_x_16+=Ax_16;
src_y_16+=Ay_16;
}
out_dst_x1=test_dst_x1;
}
else
{
src_x_16-=Ax_16;
src_y_16-=Ay_16;
while (!is_in_src(src_x_16,src_y_16))
{
--test_dst_x1;
src_x_16-=Ax_16;
src_y_16-=Ay_16;
}
out_dst_x1=test_dst_x1;
}
}
bool inti_clip(double move_x,double move_y)
{
//計算src中心點映射到dst後的座標
out_dst_down_y=(long)(src_height*0.5+move_y);
out_dst_down_x0=(long)(src_width*0.5+move_x);
out_dst_down_x1=out_dst_down_x0;
//得到初始out_???
if (find_begin(out_dst_down_y,out_dst_down_x0,out_dst_down_x1))
find_end(out_dst_down_y,out_dst_down_x0,out_dst_down_x1);
out_dst_up_y=out_dst_down_y;
out_dst_up_x0=out_dst_down_x0;
out_dst_up_x1=out_dst_down_x1;
return (out_dst_down_x0<out_dst_down_x1);
}
bool next_clip_line_down()
{
++out_dst_down_y;
if (!find_begin(out_dst_down_y,out_dst_down_x0,out_dst_down_x1)) return false;
find_end(out_dst_down_y,out_dst_down_x0,out_dst_down_x1);
return (out_dst_down_x0<out_dst_down_x1);
}
bool next_clip_line_up()
{
--out_dst_up_y;
if (!find_begin(out_dst_up_y,out_dst_up_x0,out_dst_up_x1)) return false;
find_end(out_dst_up_y,out_dst_up_x0,out_dst_up_x1);
return (out_dst_up_x0<out_dst_up_x1);
}
};
long srcx0_16,long srcy0_16,TARGB32* pSrcLine,long src_byte_width)
{
for (long x=0;x<dstCount;++x)
{
pDstLine[x]=Pixels(pSrcLine,src_byte_width,srcx0_16>>16,srcy0_16>>16);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
}
void PicRotary3(const TPicRegion& Dst,const TPicRegion& Src,double RotaryAngle,double ZoomX,double ZoomY,double move_x,double move_y)
{
if ( (fabs(ZoomX*Src.width)<1.0e-4) || (fabs(ZoomY*Src.height)<1.0e-4) ) return; //太小的縮放比例認爲已經不可見
double tmprZoomXY=1.0/(ZoomX*ZoomY);
double rZoomX=tmprZoomXY*ZoomY;
double rZoomY=tmprZoomXY*ZoomX;
double sinA,cosA;
SinCos(RotaryAngle,sinA,cosA);
long Ax_16=(long)(rZoomX*cosA*(1<<16));
long Ay_16=(long)(rZoomX*sinA*(1<<16));
long Bx_16=(long)(-rZoomY*sinA*(1<<16));
long By_16=(long)(rZoomY*cosA*(1<<16));
double rx0=Src.width*0.5; //(rx0,ry0)爲旋轉中心
double ry0=Src.height*0.5;
long Cx_16=(long)((-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0)*(1<<16));
long Cy_16=(long)((-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0)*(1<<16));
TRotaryClipData rcData;
rcData.Ax_16=Ax_16;
rcData.Bx_16=Bx_16;
rcData.Cx_16=Cx_16;
rcData.Ay_16=Ay_16;
rcData.By_16=By_16;
rcData.Cy_16=Cy_16;
rcData.dst_width=Dst.width;
rcData.dst_height=Dst.height;
rcData.src_width=Src.width;
rcData.src_height=Src.height;
if (!rcData.inti_clip(move_x,move_y)) return;
TARGB32* pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_down_y);
while (true) //to down
{
long y=rcData.out_dst_down_y;
if (y>=Dst.height) break;
if (y>=0)
{
long x0=rcData.get_down_x0();
PicRotary3_CopyLine(&pDstLine[x0],rcData.get_down_x1()-x0,Ax_16,Ay_16,
rcData.out_src_x0_16,rcData.out_src_y0_16,Src.pdata,Src.byte_width);
}
if (!rcData.next_clip_line_down()) break;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_up_y);
while (rcData.next_clip_line_up()) //to up
{
long y=rcData.out_dst_up_y;
if (y<0) break;
((TUInt8*&)pDstLine)-=Dst.byte_width;
if (y<Dst.height)
{
long x0=rcData.get_up_x0();
PicRotary3_CopyLine(&pDstLine[x0],rcData.get_up_x1()-x0,Ax_16,Ay_16,
rcData.out_src_x0_16,rcData.out_src_y0_16,Src.pdata,Src.byte_width);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicRotary3 280.9 fps
////////////////////////////////////////////////////////////////////////////////
F:使用SSE的MOVNTQ指令優化CPU寫緩衝
(僅改寫了PicRotary3_CopyLine的實現)
void __declspec(naked) __stdcall PicRotarySSE_CopyLine(TARGB32* pDstLine,long dstCount,long Ax_16,long Ay_16,
// [esp+ 4] [esp+ 8] [esp+12] [esp+16]
long srcx0_16,long srcy0_16,TARGB32* pSrcLine,long src_byte_width)
// [esp+20] [esp+24] [esp+28] [esp+32]
{
//利用SSE的MOVNTQ指令優化寫緩衝的彙編實現
asm
{
push ebx
push esi
push edi
push ebp
//esp offset 16
mov ebx,dword ptr [esp+ 8+16]
mov esi,dword ptr [esp+32+16]
mov edi,dword ptr [esp+28+16]
mov eax,dword ptr [esp+24+16]
mov ecx,dword ptr [esp+20+16]
dec ebx
xor edx,edx
test ebx,ebx
mov dword ptr [esp+ 8+16],ebx
jle loop_bound
//jmp loop_begin
//align 16
loop_begin:
mov ebx,eax
add eax,dword ptr [esp+16+16]
sar ebx,16
imul ebx,esi
add ebx,edi
mov ebp,ecx
add ecx,dword ptr [esp+12+16]
sar ebp,16
MOVD MM0,dword ptr [ebx+ebp*4]
mov ebx,eax
add eax,dword ptr [esp+16+16]
sar ebx,16
imul ebx,esi
mov ebp,ecx
add ecx,dword ptr [esp+12+16]
sar ebp,16
add ebx,edi
MOVD MM1,dword ptr [ebx+ebp*4]
mov ebp,dword ptr [esp+ 4+16]
PUNPCKlDQ MM0,MM1
mov ebx,dword ptr [esp+ 8+16]
MOVNTQ qword ptr [ebp+edx*4],MM0
add edx,2
cmp edx,ebx
jl loop_begin
EMMS
loop_bound:
cmp edx,ebx
jne loop_bound_end
sar eax,16
imul eax,esi
sar ecx,16
add eax,edi
mov eax,dword ptr [eax+ecx*4]
mov ecx,dword ptr [esp+ 4+16]
mov dword ptr [ecx+edx*4],eax
loop_bound_end:
pop ebp
pop edi
pop esi
pop ebx
ret 32
}
}
void PicRotarySSE(const TPicRegion& Dst,const TPicRegion& Src,double RotaryAngle,double ZoomX,double ZoomY,double move_x,double move_y)
{
if ( (fabs(ZoomX*Src.width)<1.0e-4) || (fabs(ZoomY*Src.height)<1.0e-4) ) return; //太小的縮放比例認爲已經不可見
double tmprZoomXY=1.0/(ZoomX*ZoomY);
double rZoomX=tmprZoomXY*ZoomY;
double rZoomY=tmprZoomXY*ZoomX;
double sinA,cosA;
SinCos(RotaryAngle,sinA,cosA);
long Ax_16=(long)(rZoomX*cosA*(1<<16));
long Ay_16=(long)(rZoomX*sinA*(1<<16));
long Bx_16=(long)(-rZoomY*sinA*(1<<16));
long By_16=(long)(rZoomY*cosA*(1<<16));
double rx0=Src.width*0.5; //(rx0,ry0)爲旋轉中心
double ry0=Src.height*0.5;
long Cx_16=(long)((-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0)*(1<<16));
long Cy_16=(long)((-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0)*(1<<16));
TRotaryClipData rcData;
rcData.Ax_16=Ax_16;
rcData.Bx_16=Bx_16;
rcData.Cx_16=Cx_16;
rcData.Ay_16=Ay_16;
rcData.By_16=By_16;
rcData.Cy_16=Cy_16;
rcData.dst_width=Dst.width;
rcData.dst_height=Dst.height;
rcData.src_width=Src.width;
rcData.src_height=Src.height;
if (!rcData.inti_clip(move_x,move_y)) return;
TARGB32* pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_down_y);
while (true) //to down
{
long y=rcData.out_dst_down_y;
if (y>=Dst.height) break;
if (y>=0)
{
long x0=rcData.get_down_x0();
PicRotarySSE_CopyLine(&pDstLine[x0],rcData.get_down_x1()-x0,Ax_16,Ay_16,
rcData.out_src_x0_16,rcData.out_src_y0_16,Src.pdata,Src.byte_width);
}
if (!rcData.next_clip_line_down()) break;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_up_y);
while (rcData.next_clip_line_up()) //to up
{
long y=rcData.out_dst_up_y;
if (y<0) break;
((TUInt8*&)pDstLine)-=Dst.byte_width;
if (y<Dst.height)
{
long x0=rcData.get_up_x0();
PicRotarySSE_CopyLine(&pDstLine[x0],rcData.get_up_x1()-x0,Ax_16,Ay_16,
rcData.out_src_x0_16,rcData.out_src_y0_16,Src.pdata,Src.byte_width);
}
}
asm sfence //刷新寫入
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicRotarySEE 306.3 fps
////////////////////////////////////////////////////////////////////////////////
F':嘗試利用SSE2新增的MOVNTI指令優化CPU寫緩衝
void __declspec(naked) __stdcall PicRotarySSE2_CopyLine(TARGB32* pDstLine,long dstCount,long Ax_16,long Ay_16,
// [esp+ 4] [esp+ 8] [esp+12] [esp+16]
long srcx0_16,long srcy0_16,TARGB32* pSrcLine,long src_byte_width)
// [esp+20] [esp+24] [esp+28] [esp+32]
{
//利用SSE2的MOVNTI指令優化寫緩衝的彙編實現
asm
{
push ebx
push esi
push edi
push ebp
//esp offset 16
mov ebx,dword ptr [esp+ 8+16]
mov esi,dword ptr [esp+32+16]
mov edi,dword ptr [esp+28+16]
mov eax,dword ptr [esp+24+16]
mov ecx,dword ptr [esp+20+16]
dec ebx
xor edx,edx
test ebx,ebx
mov dword ptr [esp+ 8+16],ebx
jle loop_bound
jmp loop_begin
align 16
loop_begin:
mov ebx,eax
add eax,dword ptr [esp+16+16]
sar ebx,16
imul ebx,esi
add ebx,edi
mov ebp,ecx
add ecx,dword ptr [esp+12+16]
sar ebp,16
mov ebx,dword ptr [ebx+ebp*4]
mov ebp,dword ptr [esp+ 4+16]
MOVNTI dword ptr [ebp+edx*4],ebx
mov ebx,eax
add eax,dword ptr [esp+16+16]
sar ebx,16
imul ebx,esi
mov ebp,ecx
add ecx,dword ptr [esp+12+16]
sar ebp,16
add ebx,edi
mov ebx,dword ptr [ebx+ebp*4]
mov ebp,dword ptr [esp+4+16]
MOVNTI dword ptr [ebp+edx*4+4],ebx
mov ebx,dword ptr [esp+ 8+16]
add edx,2
cmp edx,ebx
jl loop_begin
loop_bound:
cmp edx,ebx
jne loop_bound_end
sar eax,16
imul eax,esi
sar ecx,16
add eax,edi
mov eax,dword ptr [eax+ecx*4]
mov ecx,dword ptr [esp+ 4+16]
mov dword ptr [ecx+edx*4],eax
loop_bound_end:
pop ebp
pop edi
pop esi
pop ebx
ret 32
}
}
void PicRotarySSE2(const TPicRegion& Dst,const TPicRegion& Src,double RotaryAngle,double ZoomX,double ZoomY,double move_x,double move_y)
{
if ( (fabs(ZoomX*Src.width)<1.0e-4) || (fabs(ZoomY*Src.height)<1.0e-4) ) return; //太小的縮放比例認爲已經不可見
double tmprZoomXY=1.0/(ZoomX*ZoomY);
double rZoomX=tmprZoomXY*ZoomY;
double rZoomY=tmprZoomXY*ZoomX;
double sinA,cosA;
SinCos(RotaryAngle,sinA,cosA);
long Ax_16=(long)(rZoomX*cosA*(1<<16));
long Ay_16=(long)(rZoomX*sinA*(1<<16));
long Bx_16=(long)(-rZoomY*sinA*(1<<16));
long By_16=(long)(rZoomY*cosA*(1<<16));
double rx0=Src.width*0.5; //(rx0,ry0)爲旋轉中心
double ry0=Src.height*0.5;
long Cx_16=(long)((-(rx0+move_x)*rZoomX*cosA+(ry0+move_y)*rZoomY*sinA+rx0)*(1<<16));
long Cy_16=(long)((-(rx0+move_x)*rZoomX*sinA-(ry0+move_y)*rZoomY*cosA+ry0)*(1<<16));
TRotaryClipData rcData;
rcData.Ax_16=Ax_16;
rcData.Bx_16=Bx_16;
rcData.Cx_16=Cx_16;
rcData.Ay_16=Ay_16;
rcData.By_16=By_16;
rcData.Cy_16=Cy_16;
rcData.dst_width=Dst.width;
rcData.dst_height=Dst.height;
rcData.src_width=Src.width;
rcData.src_height=Src.height;
if (!rcData.inti_clip(move_x,move_y)) return;
TARGB32* pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_down_y);
while (true) //to down
{
long y=rcData.out_dst_down_y;
if (y>=Dst.height) break;
if (y>=0)
{
long x0=rcData.get_down_x0();
PicRotarySSE2_CopyLine(&pDstLine[x0],rcData.get_down_x1()-x0,Ax_16,Ay_16,
rcData.out_src_x0_16,rcData.out_src_y0_16,Src.pdata,Src.byte_width);
}
if (!rcData.next_clip_line_down()) break;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_up_y);
while (rcData.next_clip_line_up()) //to up
{
long y=rcData.out_dst_up_y;
if (y<0) break;
((TUInt8*&)pDstLine)-=Dst.byte_width;
if (y<Dst.height)
{
long x0=rcData.get_up_x0();
PicRotarySSE2_CopyLine(&pDstLine[x0],rcData.get_up_x1()-x0,Ax_16,Ay_16,
rcData.out_src_x0_16,rcData.out_src_y0_16,Src.pdata,Src.byte_width);
}
}
asm sfence //刷新寫入
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicRotarySEE2 304.2 fps
////////////////////////////////////////////////////////////////////////////////
一張效果圖:
//程序使用的調用參數:
const long testcount=2000;
long dst_wh=1004;
for (int i=0;i<testcount;++i)
{
double zoom=rand()*(1.0/RAND_MAX)+0.5;
PicRotarySSE(ppicDst,ppicSrc,rand()*(PI*2/RAND_MAX),zoom,zoom,((dst_wh+ppicSrc.width)*rand()*(1.0/RAND_MAX)-ppicSrc.width),(dst_wh+ppicSrc.height)*rand()*(1.0/RAND_MAX)-ppicSrc.height);
}
//ps:如果很多時候源圖片繪製時可能落在目標區域的外面,那麼需要寫一個剪切算法快速排除不必要的繪製
一張測試函數速度的時候生成的圖像:
G:旋轉測試的結果放到一起:
//注:測試圖片都是800*600的圖片旋轉到1004*1004的圖片中心,測試成績取各個旋轉角度的平均速度值
////////////////////////////////////////////////////////////////////////////////
//速度測試: (測試CPU爲AMD64x2 4200+(2.37G),單線程)
//==============================================================================
// PicRotary0 34.9 fps
// PicRotary1 62.0 fps
// PicRotary2 134.2 fps
// PicRotary3 280.9 fps
// PicRotarySEE 306.3 fps
// PicRotarySEE2 304.2 fps
//(PicRotarySSE2_Block 316.6 fps (參見《下篇 補充話題》))
////////////////////////////////////////////////////////////////////////////////
補充Intel Core2 4400上的測試成績:
////////////////////////////////////////////////////////////////////////////////
//速度測試: (測試CPU爲Intel Core2 4400(2.00G)單線程)
//==============================================================================
// PicRotary0 58.6 fps
// PicRotary1 82.1 fps
// PicRotary2 167.9 fps
// PicRotary3 334.9 fps
// PicRotarySEE 463.1 fps
// PicRotarySEE2 449.3 fps
//(PicRotarySSE2_Block 351.3 fps (參見《下篇 補充話題》))
////////////////////////////////////////////////////////////////////////////////
//ps:文章的下篇將進一步優化圖片旋轉的質量(使用二次線性插值、三次卷積插值和MipMap鏈),並完美的處理邊緣的鋸齒,並考慮介紹顏色的Alpha Blend混合
(希望讀者能在這一系列的文章中不僅能學到旋轉和縮放,還能夠學到一些優化的基本技巧和思路;也歡迎指出文章中的錯誤、我沒有做到的優化、改進意見 等)