图片缩放旋转.docx
《图片缩放旋转.docx》由会员分享,可在线阅读,更多相关《图片缩放旋转.docx(50页珍藏版)》请在冰豆网上搜索。
![图片缩放旋转.docx](https://file1.bdocx.com/fileroot1/2022-11/20/b8f69ec6-a7f4-4968-b6d9-943d844ab3d4/b8f69ec6-a7f4-4968-b6d9-943d844ab3d41.gif)
图片缩放旋转
高质量的快速的图像缩放上篇近邻取样插值和其速度优化
正文:
为了便于讨论,这里只处理32bit的ARGB颜色;
代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005;
为了代码的可读性,没有加入异常处理代码;
测试使用的CPU为AMD64x24200+(2.37G) 和IntelCore24400(2.00G);
速度测试说明:
只测试内存数据到内存数据的缩放
测试图片都是800*600缩放到1024*768;fps表示每秒钟的帧数,值越大表示函数越快
////////////////////////////////////////////////////////////////////////////////
//WindowsGDI相关函数参考速度:
//==============================================================================
//BitBlt 544.7fps //iscopy800*600to800*600
//BitBlt 331.6fps //iscopy1024*1024to1024*1024
//StretchBlt 232.7fps //iszoom800*600to1024*1024
////////////////////////////////////////////////////////////////////////////////
A:
首先定义图像数据结构:
#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];
}
B:
缩放原理和公式图示:
缩放后图片 原图片
(宽DW,高DH) (宽SW,高SH)
(Sx-0)/(SW-0)=(Dx-0)/(DW-0) (Sy-0)/(SH-0)=(Dy-0)/(DH-0)
=> Sx=Dx*SW/DW Sy=Dy*SH/DH
C:
缩放算法的一个参考实现
//给出一个最简单的缩放函数(插值方式为近邻取样,而且我“尽力”把它写得慢一些了:
D)
//Src.PColorData指向源数据区,Dst.PColorData指向目的数据区
//函数将大小为Src.Width*Src.Height的图片缩放到Dst.Width*Dst.Height的区域中
void PicZoom0(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
for (long x=0;x {
for (long y=0;y {
long srcx=(x*Src.width/Dst.width);
long srcy=(y*Src.height/Dst.height);
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
//PicZoom0 19.4fps
////////////////////////////////////////////////////////////////////////////////
D:
优化PicZoom0函数
a.PicZoom0函数并没有按照颜色数据在内存中的排列顺序读写(内部循环递增y行
索引),将造成CPU缓存预读失败和内存颠簸导致巨大的性能损失,(很多硬件都有这种特性,
包括缓存、内存、显存、硬盘等,优化顺序访问,随机访问时会造成巨大的性能损失)
所以先交换x,y循环的顺序:
void PicZoom1(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
for (long y=0;y {
for (long x=0;x {
long srcx=(x*Src.width/Dst.width);
long srcy=(y*Src.height/Dst.height);
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
//PicZoom1 30.1fps
////////////////////////////////////////////////////////////////////////////////
b.“(x*Src.Width/Dst.Width)”表达式中有一个除法运算,它属于很慢的操作(比一般
的加减运算慢几十倍!
),使用定点数的方法来优化它;
void PicZoom2(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
//函数能够处理的最大图片尺寸65536*65536
unsignedlongxrIntFloat_16=(Src.width<<16)/Dst.width+1; //16.16格式定点数
unsignedlongyrIntFloat_16=(Src.height<<16)/Dst.height+1; //16.16格式定点数
//可证明:
(Dst.width-1)*xrIntFloat_16 for(unsignedlongy=0;y {
for(unsignedlongx=0;x {
unsignedlongsrcx=(x*xrIntFloat_16)>>16;
unsignedlongsrcy=(y*yrIntFloat_16)>>16;
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
//PicZoom2 185.8fps
////////////////////////////////////////////////////////////////////////////////
c. 在x的循环中y一直不变,那么可以提前计算与y相关的值;1.可以发现srcy的值和x变量无关,可以提前到x轴循环之前;2.展开Pixels函数,优化与y相关的指针计算;
voidPicZoom3(constTPicRegion&Dst,constTPicRegion&Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsignedlongxrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsignedlongyrIntFloat_16=(Src.height<<16)/Dst.height+1;
unsignedlongdst_width=Dst.width;
TARGB32*pDstLine=Dst.pdata;
unsignedlongsrcy_16=0;
for(unsignedlongy=0;y {
TARGB32*pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
unsignedlongsrcx_16=0;
for(unsignedlongx=0;x {
pDstLine[x]=pSrcLine[srcx_16>>16];
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
//PicZoom3 414.4fps
////////////////////////////////////////////////////////////////////////////////
d.定点数优化使函数能够处理的最大图片尺寸和缩放结果(肉眼不可察觉的误差)受到了一
定的影响,这里给出一个使用浮点运算的版本,可以在有这种需求的场合使用:
void PicZoom3_float(const TPicRegion& Dst,const TPicRegion& Src)
{
//注意:
该函数需要FPU支持
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
doublexrFloat=1.000000001/((double)Dst.width/Src.width);
doubleyrFloat=1.000000001/((double)Dst.height/Src.height);
unsigned short RC_Old;
unsigned short RC_Edit;
asm //设置FPU的取整方式 为了直接使用fist浮点指令
{
FNSTCW RC_Old // 保存协处理器控制字,用来恢复
FNSTCW RC_Edit // 保存协处理器控制字,用来修改
FWAIT
OR RC_Edit, 0x0F00 // 改为 RC=11 使FPU向零取整
FLDCW RC_Edit // 载入协处理器控制字,RC场已经修改
}
unsigned long dst_width=Dst.width;
TARGB32* pDstLine=Dst.pdata;
double srcy=0;
for (unsigned long y=0;y {
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*((long)srcy)));
/**//*
double srcx=0;
for (unsigned long x=0;x {
pDstLine[x]=pSrcLine[(unsigned long)srcx];//因为默认的浮点取整是一个很慢
//的操作!
所以才使用了直接操作FPU的内联汇编代码。
srcx+=xrFloat;
}*/
asm fld xrFloat //st0==xrFloat
asm fldz //st0==0 st1==xrFloat
unsigned long srcx=0;
for (long x=0;x {
asm fist dword ptr srcx //srcx=(long)st0
pDstLine[x]=pSrcLine[srcx];
asm fadd st,st
(1) //st0+=st1 st1==xrFloat
}
asm fstp st
asm fstp st
srcy+=yrFloat;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
asm //恢复FPU的取整方式
{
FWAIT
FLDCW RC_Old
}
}
////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
//PicZoom3_float 286.2fps
////////////////////////////////////////////////////////////////////////////////
e.注意到这样一个事实:
每一行的缩放比例是固定的;那么可以预先建立一个缩放映射表格
来处理缩放映射算法(PicZoom3_Table和PicZoom3_float的实现等价);
void PicZoom3_Table(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsigned long dst_width=Dst.width;
unsigned long* SrcX_Table = new unsigned long[dst_width];
for (unsigned long x=0;x {
SrcX_Table[x]=(x*Src.width/Dst.width);
}
TARGB32* pDstLine=Dst.pdata;
for (unsigned long y=0;y {
unsigned long srcy=(y*Src.height/Dst.height);
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*srcy));
for (unsigned long x=0;x pDstLine[x]=pSrcLine[SrcX_Table[x]];
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
delete [] SrcX_Table;
}
////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
//PicZoom3_Table 390.1fps
////////////////////////////////////////////////////////////////////////////////
f.为了加快缩放,可以采用根据缩放比例动态生成函数的方式来得到更快的缩放函数;这
有点像编译器的工作原理;要实现它需要的工作量比较大(或比较晦涩)就不再实现了;
(动态生成是一种不错的思路,但个人觉得对于缩放,实现它的必要性不大)
g.现代CPU中,在读取数据和写入数据时,都有自动的缓存机制;很容易知道,算法中生
成的数据不会很快再次使用,所以不需要写入缓存的帮助;在SSE指令集中增加了movntq
等指令来完成这个功能;
(尝试过利用CPU显式prefetcht0、prefetchnta预读指令或直接的mov读取指令等速度反
而略有下降:
( 但预读在copy算法中速度优化效果很明显)
void PicZoom3_SSE(const TPicRegion& Dst,const TPicRegion& Src)
{
//警告:
函数需要CPU支持MMX和movntq指令
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsignedlongxrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsignedlongyrIntFloat_16=(Src.height<<16)/Dst.height+1;
unsigned long dst_width=Dst.width;
TARGB32* pDstLine=Dst.pdata;
unsigned long srcy_16=0;
for (unsigned long y=0;y {
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
asm
{
push ebp
mov esi,pSrcLine
mov edi,pDstLine
mov edx,xrIntFloat_16