正文:
为了便于讨论,这里只处理32bit的ARGB颜色; 代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005; 为了代码的可读性,没有加入异常处理代码; 测试使用的CPU为AMD64x2 4200+(2.37G),测试时使用的单线程执行; (一些基础代码和插值原理的详细说明参见作者的《图形图像处理-之-高质量的快速的图像缩放》系列文章 旋转原理和基础参考《图形图像处理-之-任意角度的高质量的快速的图像旋转 上篇 纯软件的任意角度的快速旋转》) 速度测试说明: 只测试内存数据到内存数据的缩放 测试图片都是800*600旋转到1004*1004,测试成绩取各个旋转角度的平均速度值; fps表示每秒钟的帧数,值越大表示函数越快 A:一些颜色和图片的数据定义:#define asm __asmtypedef 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(TARGB32* pcolor,const long byte_width,const long x,const long y) { return ( (TARGB32*)((TUInt8*)pcolor+byte_width*y) )[x]; }//那么访问一个点的函数可以写为:inline TARGB32& Pixels(const TPicRegion& pic,const long x,const long y){ return Pixels(pic.pdata,pic.byte_width,x,y);}//判断一个点是否在图片中inline bool PixelsIsInPic(const TPicRegion& pic,const long x,const long y){ return ( (x>=0)&&(x=0)&&(y 0)&&(pic.height>0)); if (x<0) x=0; else if (x>=pic.width ) x=pic.width -1; if (y<0) y=0; else if (y>=pic.height) y=pic.height-1; return Pixels(pic,x,y);}inline TARGB32& Pixels_Bound(const TPicRegion& pic,long x,long y,bool& IsInPic){ //assert((pic.width>0)&&(pic.height>0)); IsInPic=true; if (x<0) {x=0; IsInPic=false; } else if (x>=pic.width ) {x=pic.width -1; IsInPic=false; } if (y<0) {y=0; IsInPic=false; } else if (y>=pic.height) {y=pic.height-1; IsInPic=false; } return Pixels(pic,x,y);}
B:实现二次线性插值的旋转
(插值原理参见我的blog文章《图形图像处理-之-高质量的快速的图像缩放 中篇 二次线性插值和三次卷积插值》)
a.首先改写用于边界扫描的类TRotaryClipData;在图片边缘插值的时候,插值的颜色数据可能
部分在图片外,部分颜色数据在图片内,所以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; long border_width;//插值边界宽度private: long cur_dst_up_x0; long cur_dst_up_x1; long cur_dst_down_x0; long cur_dst_down_x1; inline bool is_border_src(long src_x_16,long src_y_16) { return ( ( (src_x_16>=(-(border_width<<16)))&&((src_x_16>>16)<(src_width +border_width)) ) && ( (src_y_16>=(-(border_width<<16)))&&((src_y_16>>16)<(src_height+border_width)) ) ); } inline bool is_in_src(long src_x_16,long src_y_16) { return ( ( (src_x_16>=(border_width<<16))&&((src_x_16>>16)<(src_width-border_width) ) ) && ( (src_y_16>=(border_width<<16))&&((src_y_16>>16)<(src_height-border_width)) ) ); } 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_border_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_border_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=out_dst_x1_boder) ) { out_dst_x0_in=out_dst_x0_boder; out_dst_x1_in=out_dst_x1_boder; } else { long src_x_16=out_src_x0_16; long src_y_16=out_src_y0_16; long i=out_dst_x0_boder; while (i out_dst_x0_in) { src_x_16-=Ax_16; src_y_16-=Ay_16; if (is_in_src(src_x_16,src_y_16)) break; --i; } out_dst_x1_in=i; } } inline void update_out_dst_up_x() { if (cur_dst_up_x0<0) out_dst_x0_boder=0; else out_dst_x0_boder=cur_dst_up_x0; if (cur_dst_up_x1>=dst_width) out_dst_x1_boder=dst_width; else out_dst_x1_boder=cur_dst_up_x1; update_out_dst_x_in(); } inline void update_out_dst_down_x() { if (cur_dst_down_x0<0) out_dst_x0_boder=0; else out_dst_x0_boder=cur_dst_down_x0; if (cur_dst_down_x1>=dst_width) out_dst_x1_boder=dst_width; else out_dst_x1_boder=cur_dst_down_x1; update_out_dst_x_in(); }public: long out_src_x0_16; long out_src_y0_16; long out_dst_up_y; long out_dst_down_y; long out_dst_x0_boder; long out_dst_x0_in; long out_dst_x1_in; long out_dst_x1_boder;public: bool inti_clip(double move_x,double move_y,unsigned long aborder_width) { border_width=aborder_width; //计算src中心点映射到dst后的坐标 out_dst_down_y=(long)(src_height*0.5+move_y); cur_dst_down_x0=(long)(src_width*0.5+move_x); cur_dst_down_x1=cur_dst_down_x0; //得到初始扫描线 if (find_begin(out_dst_down_y,cur_dst_down_x0,cur_dst_down_x1)) find_end(out_dst_down_y,cur_dst_down_x0,cur_dst_down_x1); out_dst_up_y=out_dst_down_y; cur_dst_up_x0=cur_dst_down_x0; cur_dst_up_x1=cur_dst_down_x1; update_out_dst_up_x(); return (cur_dst_down_x0
b. 边界插值的特殊处理
对于“插值边界以外”很简单,不用处理直接跳过插值; 对于“插值边界以内”,也比较容易处理,直接调用快速的差值算法就可以了,不用担心内存访问问题; 插值实现: (从《图形图像处理-之-高质量的快速的图像缩放 中篇 二次线性插值和三次卷积插值》文章来的,后面不再说明)inline void BilInear_Fast(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { TARGB32* PColor0=&Pixels(pic,x_16>>16,y_16>>16); TARGB32* PColor1=(TARGB32*)((TUInt8*)PColor0+pic.byte_width); unsigned long u_8=(unsigned char)(x_16>>8); unsigned long v_8=(unsigned char)(y_16>>8); unsigned long pm3_8=(u_8*v_8)>>8; unsigned long pm2_8=u_8-pm3_8; unsigned long pm1_8=v_8-pm3_8; unsigned long pm0_8=256-pm1_8-pm2_8-pm3_8; unsigned long Color=*(unsigned long*)(PColor0); unsigned long BR=(Color & 0x00FF00FF)*pm0_8; unsigned long GA=((Color & 0xFF00FF00)>>8)*pm0_8; Color=((unsigned long*)(PColor0))[1]; GA+=((Color & 0xFF00FF00)>>8)*pm2_8; BR+=(Color & 0x00FF00FF)*pm2_8; Color=*(unsigned long*)(PColor1); GA+=((Color & 0xFF00FF00)>>8)*pm1_8; BR+=(Color & 0x00FF00FF)*pm1_8; Color=((unsigned long*)(PColor1))[1]; GA+=((Color & 0xFF00FF00)>>8)*pm3_8; BR+=(Color & 0x00FF00FF)*pm3_8; *(unsigned long*)result=(GA & 0xFF00FF00)|((BR & 0xFF00FF00)>>8); }
对于“插值边界”,就需要特殊处理了,很多插值旋转的实现可能都在这里打了折扣;要想完美的解决 这块区域,可以引入AlphaBlend(带Alpha通道的颜色混合) ; 其实AlphaBlend的原理也很简单,就是按不同的比例混合两种颜色: new_color=dst_color*(1-alpha)+src_color*alpha; 对于ARGB32bit颜色,需要用该公式分别处理4个颜色通道,并假设Alpha为[0..255]的整数,那么完整的实现函数为:
inline TARGB32 AlphaBlend(TARGB32 dst,TARGB32 src) { //AlphaBlend颜色混合公式(对其中的每个颜色分量分别处理): // new_color=(dst_color*(255-src_color.alpha)+src_color*src_color.alpha)/255 //提示:除法指令是很慢的操作,但vc2005可以把x/255编译为完全等价的"(x*M)>>N"类似的快速计算代码; unsigned long a=src.a; //if (a==0) return dst; //else if (a==255) return src; unsigned long ra=255-a; unsigned long result_b=(dst.b*ra+src.b*a)/255; unsigned long result_g=(dst.g*ra+src.g*a)/255; unsigned long result_r=(dst.r*ra+src.r*a)/255; unsigned long result_a=(dst.a*ra+a*a)/255; unsigned long result=(result_b) | (result_g<<8) | (result_r<<16) | (result_a<<24); return *(TARGB32*)&result; }
优化AlphaBlend,颜色处理中,也可以这样近似计算: x/255 => x>>8 ,所以有:
inline TARGB32 AlphaBlend(TARGB32 dst,TARGB32 src) { unsigned long a=src.a; unsigned long ra=255-a; unsigned long result_b=(dst.b*ra+src.b*a)>>8; unsigned long result_g=(dst.g*ra+src.g*a)>>8; unsigned long result_r=(dst.r*ra+src.r*a)>>8; unsigned long result_a=(dst.a*ra+a*a)>>8; unsigned long result=(result_b) | (result_g<<8) | (result_r<<16) | (result_a<<24); return *(TARGB32*)&result; }
(dst*(255-alpha)+src*alpha)>>8 可以近似为:(dst*(256-alpha)+src*alpha)>>8 即 (dst<<8+(src-dst)*alpha)>>8 从而用一个移位代替一个乘法 (*256会被优化为移位)
inline TARGB32 AlphaBlend(TARGB32 dst,TARGB32 src) { long a=src.a; unsigned long result_b=((unsigned long)(((long)dst.b)*256+((long)src.b-(long)dst.b)*a))>>8; unsigned long result_g=((unsigned long)(((long)dst.g)*256+((long)src.g-(long)dst.g)*a))>>8; unsigned long result_r=((unsigned long)(((long)dst.r)*256+((long)src.r-(long)dst.r)*a))>>8; unsigned long result_a=((unsigned long)(((long)dst.a)*256+((long)a-(long)dst.a)*a))>>8; unsigned long result=(result_b) | (result_g<<8) | (result_r<<16) | (result_a<<24); return *(TARGB32*)&result; }
继续优化,同时运算两路颜色分量的AlphaBlend实现:
inline TARGB32 AlphaBlend(TARGB32 dst,TARGB32 src) { unsigned long Src_ARGB=*(unsigned long*)&src; unsigned long Dst_ARGB=*(unsigned long*)&dst; unsigned long a=Src_ARGB>>24; unsigned long ra=255-a; unsigned long result_RB=((Dst_ARGB & 0x00FF00FF)*ra + (Src_ARGB & 0x00FF00FF)*a); unsigned long result_AG=(((Dst_ARGB & 0xFF00FF00)>>8)*ra + ((Src_ARGB & 0xFF00FF00)>>8)*a); unsigned long result=((result_RB & 0xFF00FF00)>>8) | (result_AG & 0xFF00FF00); return *(TARGB32*)&result; }
回到我们的主线:完美解决“旋转插值边界”
怎么利用AlphaBlend呢? 我们可以在处理边界的时候,对于颜色数据在图片内的颜色,把Alpha通道分量设置为255,在图片外的颜色数据(使用Pixels_Bound会返回最接近的一个内部颜色)的Alpha通道分量设置为0; 这个任务就交给边界插值函数了:inline void BilInear_Border(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { unsigned long x0=(x_16>>16); unsigned long y0=(y_16>>16); TARGB32 pixel[4]; bool IsInPic; pixel[0]=Pixels_Bound(pic,x0,y0,IsInPic); if (!IsInPic) pixel[0].a=0; else pixel[0].a=255; pixel[2]=Pixels_Bound(pic,x0,y0+1,IsInPic); if (!IsInPic) pixel[2].a=0; else pixel[2].a=255; pixel[1]=Pixels_Bound(pic,x0+1,y0,IsInPic); if (!IsInPic) pixel[1].a=0; else pixel[1].a=255; pixel[3]=Pixels_Bound(pic,x0+1,y0+1,IsInPic); if (!IsInPic) pixel[3].a=0; else pixel[3].a=255; TPicRegion npic; npic.pdata =&pixel[0]; npic.byte_width=2*sizeof(TARGB32); //npic.width =2; //npic.height =2; BilInear_Fast(npic,(unsigned short)x_16,(unsigned short)y_16,result); }
返回的颜色中的Alpha的值就代表了颜色的有效强度(一般介于0..255之间);
那么,对边界上的插值就可以用类似这样的代码处理好了: TARGB32 tmp_color; BilInear_Border(SrcPic,srcx0_16,srcy0_16,&tmp_color); pDstLine[x]=AlphaBlend(pDstLine[x],tmp_color);c. OK,给出完整的函数:
void PicRotary_BilInear_CopyLine(TARGB32* pDstLine,long dst_border_x0,long dst_in_x0,long dst_in_x1,long dst_border_x1, const TPicRegion& SrcPic,long srcx0_16,long srcy0_16,long Ax_16,long Ay_16){ long x; for (x=dst_border_x0;x=Dst.height) break; if (y>=0) { PicRotary_BilInear_CopyLine(pDstLine,rcData.out_dst_x0_boder,rcData.out_dst_x0_in, rcData.out_dst_x1_in,rcData.out_dst_x1_boder,Src,rcData.out_src_x0_16,rcData.out_src_y0_16,Ax_16,Ay_16); } 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
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值
//速度测试: //==============================================================================// PicRotaryBilInear 68.9 fps 二次线性插值旋转结果图示:30度 60度 90度
120度 150度 180度
210度 240度 270度
300度 330度 360度
C:用MMX指令来改进二次线性插值的旋转
inline TARGB32 AlphaBlend_MMX(TARGB32 dst,TARGB32 src) { unsigned long result; asm { PXOR MM7,MM7 MOVD MM0,src MOVD MM2,dst PUNPCKLBW MM0,MM7 PUNPCKLBW MM2,MM7 MOVQ MM1,MM0 PUNPCKHWD MM1,MM1 PSUBW MM0,MM2 PUNPCKHDQ MM1,MM1 PSLLW MM2,8 PMULLW MM0,MM1 PADDW MM2,MM0 PSRLW MM2,8 PACKUSWB MM2,MM7 MOVD result,MM2 } return *(TARGB32*)&result; } void __declspec(naked) __stdcall BilInear_Fast_MMX(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { asm { mov edx,[esp+12] //y_16 mov eax,[esp+8] //x_16 PXOR mm7,mm7 shl edx,16 shl eax,16 shr edx,24 //edx=v_8 shr eax,24 //eax=u_8 MOVD MM6,edx MOVD MM5,eax mov ecx,[esp+4]//pic mov edx,[esp+12]//y_16 mov eax,[ecx]TPicRegion.byte_width sar edx,16 imul edx,eax add edx,[ecx]TPicRegion.pdata add eax,edx mov ecx,[esp+8] //x_16 sar ecx,16 //srcx_16>>16 MOVD MM2,dword ptr [eax+ecx*4] //MM2=Color1 MOVD MM0,dword ptr [eax+ecx*4+4]//MM0=Color3 PUNPCKLWD MM5,MM5 PUNPCKLWD MM6,MM6 MOVD MM3,dword ptr [edx+ecx*4] //MM3=Color0 MOVD MM1,dword ptr [edx+ecx*4+4]//MM1=Color2 PUNPCKLDQ MM5,MM5 //mm5=u_8 PUNPCKLBW MM0,MM7 PUNPCKLBW MM1,MM7 PUNPCKLBW MM2,MM7 PUNPCKLBW MM3,MM7 PSUBw MM0,MM2 PSUBw MM1,MM3 PSLLw MM2,8 PSLLw MM3,8 PMULlw MM0,MM5 PMULlw MM1,MM5 PUNPCKLDQ MM6,MM6 //mm6=v_8 PADDw MM0,MM2 PADDw MM1,MM3 PSRLw MM0,8 PSRLw MM1,8 PSUBw MM0,MM1 PSLLw MM1,8 PMULlw MM0,MM6 mov eax,[esp+16]//result PADDw MM0,MM1 PSRLw MM0,8 PACKUSwb MM0,MM7 movd [eax],MM0 //emms ret 16 } } void BilInear_Border_MMX(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { unsigned long x0=(x_16>>16); unsigned long y0=(y_16>>16); TARGB32 pixel[4]; bool IsInPic; pixel[0]=Pixels_Bound(pic,x0,y0,IsInPic); if (!IsInPic) pixel[0].a=0; else pixel[0].a=255; pixel[2]=Pixels_Bound(pic,x0,y0+1,IsInPic); if (!IsInPic) pixel[2].a=0; else pixel[2].a=255; pixel[1]=Pixels_Bound(pic,x0+1,y0,IsInPic); if (!IsInPic) pixel[1].a=0; else pixel[1].a=255; pixel[3]=Pixels_Bound(pic,x0+1,y0+1,IsInPic); if (!IsInPic) pixel[3].a=0; else pixel[3].a=255; TPicRegion npic; npic.pdata =&pixel[0]; npic.byte_width=2*sizeof(TARGB32); //npic.width =2; //npic.height =2; BilInear_Fast_MMX(npic,(unsigned short)x_16,(unsigned short)y_16,result); }void PicRotary_BilInear_CopyLine_MMX(TARGB32* pDstLine,long dst_border_x0,long dst_in_x0,long dst_in_x1,long dst_border_x1, const TPicRegion& SrcPic,long srcx0_16,long srcy0_16,long Ax_16,long Ay_16){ long x; for (x=dst_border_x0;x=Dst.height) break; if (y>=0) { PicRotary_BilInear_CopyLine_MMX(pDstLine,rcData.out_dst_x0_boder,rcData.out_dst_x0_in, rcData.out_dst_x1_in,rcData.out_dst_x1_boder,Src,rcData.out_src_x0_16,rcData.out_src_y0_16,Ax_16,Ay_16); } 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
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值
//速度测试: //==============================================================================// PicRotaryBilInear_MMX 100.2 fps D:三次卷积插值的旋转 (实现就比较简单了,几乎就是拷贝代码,然后稍微改写几个单词:) (很多代码从《图形图像处理-之-高质量的快速的图像缩放 中篇 二次线性插值和三次卷积插值》文章来的)inline double SinXDivX(double x) { //该函数计算插值曲线sin(x*PI)/(x*PI)的值 //PI=3.1415926535897932385; //下面是它的近似拟合表达式 const float a = -1; //a还可以取 a=-2,-1,-0.75,-0.5等等,起到调节锐化或模糊程度的作用 if (x<0) x=-x; //x=abs(x); double x2=x*x; double x3=x2*x; if (x<=1) return (a+2)*x3 - (a+3)*x2 + 1; else if (x<=2) return a*x3 - (5*a)*x2 + (8*a)*x - (4*a); else return 0; } inline TUInt8 ColorBound(long Color) { if (Color<=0) return 0; else if (Color>=255) return 255; else return Color; } static long SinXDivX_Table_8[(2<<8)+1]; class _CAutoInti_SinXDivX_Table { private: void _Inti_SinXDivX_Table() { for (long i=0;i<=(2<<8);++i) SinXDivX_Table_8[i]=long(0.5+256*SinXDivX(i*(1.0/(256)))); }; public: _CAutoInti_SinXDivX_Table() { _Inti_SinXDivX_Table(); } }; static _CAutoInti_SinXDivX_Table __tmp_CAutoInti_SinXDivX_Table; void ThreeOrder_Fast(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { long u_8=(unsigned char)((x_16)>>8); long v_8=(unsigned char)((y_16)>>8); const TARGB32* pixel=&Pixels(pic,(x_16>>16)-1,(y_16>>16)-1); long pic_byte_width=pic.byte_width; long au_8[4],av_8[4]; // au_8[0]=SinXDivX_Table_8[(1<<8)+u_8]; au_8[1]=SinXDivX_Table_8[u_8]; au_8[2]=SinXDivX_Table_8[(1<<8)-u_8]; au_8[3]=SinXDivX_Table_8[(2<<8)-u_8]; av_8[0]=SinXDivX_Table_8[(1<<8)+v_8]; av_8[1]=SinXDivX_Table_8[v_8]; av_8[2]=SinXDivX_Table_8[(1<<8)-v_8]; av_8[3]=SinXDivX_Table_8[(2<<8)-v_8]; long sR=0,sG=0,sB=0,sA=0; for (long i=0;i<4;++i) { long aA=au_8[0]*pixel[0].a + au_8[1]*pixel[1].a + au_8[2]*pixel[2].a + au_8[3]*pixel[3].a; long aR=au_8[0]*pixel[0].r + au_8[1]*pixel[1].r + au_8[2]*pixel[2].r + au_8[3]*pixel[3].r; long aG=au_8[0]*pixel[0].g + au_8[1]*pixel[1].g + au_8[2]*pixel[2].g + au_8[3]*pixel[3].g; long aB=au_8[0]*pixel[0].b + au_8[1]*pixel[1].b + au_8[2]*pixel[2].b + au_8[3]*pixel[3].b; sA+=aA*av_8[i]; sR+=aR*av_8[i]; sG+=aG*av_8[i]; sB+=aB*av_8[i]; ((TUInt8*&)pixel)+=pic_byte_width; } *(unsigned long*)result=ColorBound(sB>>16) | (ColorBound(sG>>16)<<8) | (ColorBound(sR>>16)<<16)| (ColorBound(sA>>16)<<24); } void ThreeOrder_Border(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { unsigned long x0_sub1=(x_16>>16)-1; unsigned long y0_sub1=(y_16>>16)-1; long u_16_add1=((unsigned short)(x_16))+(1<<16); long v_16_add1=((unsigned short)(y_16))+(1<<16); TARGB32 pixel[16]; long i,j; for (i=0;i<4;++i) { long y=y0_sub1+i; for (j=0;j<4;++j) { long x=x0_sub1+j; bool IsInPic; pixel[i*4+j]=Pixels_Bound(pic,x,y,IsInPic); if (!IsInPic) pixel[i*4+j].a=0; else pixel[i*4+j].a=255; } } TPicRegion npic; npic.pdata =&pixel[0]; npic.byte_width=4*sizeof(TARGB32); //npic.width =4; //npic.height =4; ThreeOrder_Fast(npic,u_16_add1,v_16_add1,result); }void PicRotary_ThreeOrder_CopyLine(TARGB32* pDstLine,long dst_border_x0,long dst_in_x0,long dst_in_x1,long dst_border_x1, const TPicRegion& SrcPic,long srcx0_16,long srcy0_16,long Ax_16,long Ay_16){ long x; for (x=dst_border_x0;x=Dst.height) break; if (y>=0) { PicRotary_ThreeOrder_CopyLine(pDstLine,rcData.out_dst_x0_boder,rcData.out_dst_x0_in, rcData.out_dst_x1_in,rcData.out_dst_x1_boder,Src,rcData.out_src_x0_16,rcData.out_src_y0_16,Ax_16,Ay_16); } 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
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值
//速度测试: //==============================================================================// PicRotaryThreeOrder 22.8 fps 三次卷积插值旋转结果图示:
30度 60度 90度
120度 150度 180度
210度 240度 270度
300度 330度 360度
E:用MMX优化三次卷积插值的旋转
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值
//速度测试: //==============================================================================// PicRotaryThreeOrder_MMX 44.2 fpstypedef unsigned long TMMXData32; static TMMXData32 SinXDivX_Table_MMX[(2<<8)+1]; class _CAutoInti_SinXDivX_Table_MMX { private: void _Inti_SinXDivX_Table_MMX() { for (long i=0;i<=(2<<8);++i) { unsigned short t=long(0.5+(1<<14)*SinXDivX(i*(1.0/(256)))); unsigned long tl=t | (((unsigned long)t)<<16); SinXDivX_Table_MMX[i]=tl; } }; public: _CAutoInti_SinXDivX_Table_MMX() { _Inti_SinXDivX_Table_MMX(); } }; static _CAutoInti_SinXDivX_Table_MMX __tmp_CAutoInti_SinXDivX_Table_MMX; void __declspec(naked) _private_ThreeOrder_Fast_MMX() { asm { movd mm1,dword ptr [edx] movd mm2,dword ptr [edx+4] movd mm3,dword ptr [edx+8] movd mm4,dword ptr [edx+12] movd mm5,dword ptr [(offset SinXDivX_Table_MMX)+256*4+eax*4] movd mm6,dword ptr [(offset SinXDivX_Table_MMX)+eax*4] punpcklbw mm1,mm7 punpcklbw mm2,mm7 punpcklwd mm5,mm5 punpcklwd mm6,mm6 psllw mm1,7 psllw mm2,7 pmulhw mm1,mm5 pmulhw mm2,mm6 punpcklbw mm3,mm7 punpcklbw mm4,mm7 movd mm5,dword ptr [(offset SinXDivX_Table_MMX)+256*4+ecx*4] movd mm6,dword ptr [(offset SinXDivX_Table_MMX)+512*4+ecx*4] punpcklwd mm5,mm5 punpcklwd mm6,mm6 psllw mm3,7 psllw mm4,7 pmulhw mm3,mm5 pmulhw mm4,mm6 paddsw mm1,mm2 paddsw mm3,mm4 movd mm6,dword ptr [ebx] //v paddsw mm1,mm3 punpcklwd mm6,mm6 pmulhw mm1,mm6 add edx,esi //+pic.byte_width paddsw mm0,mm1 ret } } inline void ThreeOrder_Fast_MMX(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { asm { mov ecx,pic mov eax,y_16 mov ebx,x_16 movzx edi,ah //v_8 mov edx,[ecx+TPicRegion.pdata] shr eax,16 mov esi,[ecx+TPicRegion.byte_width] dec eax movzx ecx,bh //u_8 shr ebx,16 imul eax,esi lea edx,[edx+ebx*4-4] add edx,eax //pixel mov eax,ecx neg ecx pxor mm7,mm7 //0 //mov edx,pixel pxor mm0,mm0 //result=0 //lea eax,auv_7 lea ebx,[(offset SinXDivX_Table_MMX)+256*4+edi*4] call _private_ThreeOrder_Fast_MMX lea ebx,[(offset SinXDivX_Table_MMX)+edi*4] call _private_ThreeOrder_Fast_MMX neg edi lea ebx,[(offset SinXDivX_Table_MMX)+256*4+edi*4] call _private_ThreeOrder_Fast_MMX lea ebx,[(offset SinXDivX_Table_MMX)+512*4+edi*4] call _private_ThreeOrder_Fast_MMX psraw mm0,3 mov eax,result packuswb mm0,mm7 movd [eax],mm0 emms } } void ThreeOrder_Border_MMX(const TPicRegion& pic,const long x_16,const long y_16,TARGB32* result) { unsigned long x0_sub1=(x_16>>16)-1; unsigned long y0_sub1=(y_16>>16)-1; long u_16_add1=((unsigned short)(x_16))+(1<<16); long v_16_add1=((unsigned short)(y_16))+(1<<16); TARGB32 pixel[16]; long i,j; for (i=0;i<4;++i) { long y=y0_sub1+i; for (j=0;j<4;++j) { long x=x0_sub1+j; bool IsInPic; pixel[i*4+j]=Pixels_Bound(pic,x,y,IsInPic); if (!IsInPic) pixel[i*4+j].a=0; else pixel[i*4+j].a=255; } } TPicRegion npic; npic.pdata =&pixel[0]; npic.byte_width=4*sizeof(TARGB32); //npic.width =4; //npic.height =4; ThreeOrder_Fast_MMX(npic,u_16_add1,v_16_add1,result); }void PicRotary_ThreeOrder_CopyLine_MMX(TARGB32* pDstLine,long dst_border_x0,long dst_in_x0,long dst_in_x1,long dst_border_x1, const TPicRegion& SrcPic,long srcx0_16,long srcy0_16,long Ax_16,long Ay_16){ long x; for (x=dst_border_x0;x=Dst.height) break; if (y>=0) { PicRotary_ThreeOrder_CopyLine_MMX(pDstLine,rcData.out_dst_x0_boder,rcData.out_dst_x0_in, rcData.out_dst_x1_in,rcData.out_dst_x1_boder,Src,rcData.out_src_x0_16,rcData.out_src_y0_16,Ax_16,Ay_16); } 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
F 效果图:
//程序使用的调用参数: const long testcount=2000; long dst_wh=1004; for (int i=0;i<testcount;++i) { double zoom=rand()*(1.0/RAND_MAX)+0.5; PicRotary_XXX(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); }近邻取样插值旋转效果图:
二次线性插值旋转效果图:
三次卷积插值旋转效果图:
G:旋转测试的结果放到一起:
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心,测试成绩取各个旋转角度的平均速度值
//速度测试: (测试CPU为AMD64x2 4200+(2.37G),单线程)//==============================================================================// PicRotary3 280.9 fps// PicRotarySEE 306.3 fps// PicRotarySEE2 304.2 fps//// PicRotaryBilInear 68.9 fps// PicRotaryBilInear_MMX 100.2 fps// PicRotaryThreeOrder 22.8 fps// PicRotaryThreeOrder_MMX 44.2 fps补充Intel Core2 4400上的测试成绩:
//速度测试: (测试CPU为Intel Core2 4400(2.00G)单线程)//==============================================================================// PicRotary3 334.9 fps// PicRotarySEE 463.1 fps// PicRotarySEE2 449.3 fps//// PicRotaryBilInear 68.9 fps// PicRotaryBilInear_MMX 109.5 fps// PicRotaryThreeOrder 24.0 fps// PicRotaryThreeOrder_MMX 45.9 fps(针对大图片的预读缓冲区优化的旋转请参见《下篇 补充话题》中的优化版本)
(对于旋转的MipMap处理和三次线性插值,可以参考《图形图像处理-之-高质量的快速的图像缩放 下篇 三次线性插值和MipMap链》文章)(这里为了函数的独立性和容易理解,都是拷贝代码然后稍作修改;实际的程序中,建议把他们合并到同一个函数中,
减少代码量,提高可维护性; 对于MMX、SSE、SSE2等的使用建议用CPUID指令测试看CPU是否支持这些指令,动态决定调用不同的实现)(欢迎指出文章中的错误、我没有做到的优化、改进意见 等)
转自: