圖形圖像處理-之-任意角度的高質量的快速的圖像旋轉 上篇 純軟件的任意角度的快速旋轉

          圖形圖像處理-之-任意角度的高質量的快速的圖像旋轉 上篇 純軟件的任意角度的快速旋轉
                               
[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軸的平移量;

 一些顏色和圖片的數據定義:

#define asm __asm

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都可以在旋轉之前預先計算出來)
  改進後的函數爲:

void PicRotary1(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 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.用定點數代替浮點計算

void PicRotary2(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
)); 

    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類用於尋找旋轉需要處理的邊界範圍;算法思路是首先尋找原圖片中心點對應的;
那條掃描線,然後依次向上和向下尋找邊界;  如果想要更快速的邊界尋找算法,可以考慮利用像素的直線
  繪製原理來自動生成邊界(有機會的時候再來實現它);

                  

                       邊界尋找算法圖示

struct 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<0return 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<0return 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);
    }
};

 

void PicRotary3_CopyLine(TARGB32* pDstLine,long dstCount,long Ax_16,long Ay_16,
                        
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<0break
;
        ((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<0break
;
        ((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<0break
;
        ((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混合


(希望讀者能在這一系列的文章中不僅能學到旋轉和縮放,還能夠學到一些優化的基本技巧和思路;也歡迎指出文章中的錯誤、我沒有做到的優化、改進意見 等)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章