图像缩放算法
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
图像缩放算法
摘要:首先给出一个基本的图像缩放算法,然后一步一步的优化其速度和缩放质量;
高质量的快速的图像缩放全文分为:
上篇近邻取样插值和其速度优化
中篇二次线性插值和三次卷积插值
下篇三次线性插值和MipMap链
正文:
为了便于讨论,这里只处理32bit的ARGB颜色;
代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005;
为了代码的可读性,没有加入异常处理代码;
测试使用的CPU为AMD64x2 4200+(2.37G) 和 Intel Core2 4400(2.00G);
速度测试说明:
只测试内存数据到内存数据的缩放
测试图片都是800*600缩放到1024*768; fps表示每秒钟的帧数,值越大表示函数越快
//////////////////////////////////////////////////////////////////////// ////////
//Windows GDI相关函数参考速度:
//====================================================================== ========
// BitBlt 544.7 fps //is copy 800*600 to 800*600
// BitBlt 331.6 fps //is copy 1024*1024 to 1024*1024
// StretchBlt 232.7 fps //is zoom 800*600 to 1024*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<Dst.width;++x)
{
for (long y=0;y<Dst.height;++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.4 fps
//////////////////////////////////////////////////////////////////////// ////////
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<Dst.height;++y)
{
for (long x=0;x<Dst.width;++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.1 fps
//////////////////////////////////////////////////////////////////////// ////////
b.“(x*Sr
c.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
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1; //16.16格式定点数
unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height+1; //16.16格式定点数
//可证明: (Dst.width-1)*xrIntFloat_16<Src.width成立
for (unsigned long y=0;y<Dst.height;++y)
{
for (unsigned long x=0;x<Dst.width;++x)
{
unsigned long srcx=(x*xrIntFloat_16)>>16;
unsigned long srcy=(y*yrIntFloat_16)>>16;
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom2 185.8 fps
//////////////////////////////////////////////////////////////////////// ////////
c. 在x的循环中y一直不变,那么可以提前计算与y相关的值; 1.可以发现srcy的值和x变量无关,可以提前到x轴循环之前;2.展开Pixels函数,优化与y相关的指针计算;
void PicZoom3(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsigned long yrIntFloat_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<Dst.height;++y)
{
TARGB32*
pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16))); unsigned long srcx_16=0;
for (unsigned long x=0;x<dst_width;++x)
{
pDstLine[x]=pSrcLine[srcx_16>>16];
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom3 414.4 fps
//////////////////////////////////////////////////////////////////////// ////////
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;
double xrFloat=1.000000001/((double)Dst.width/Src.width);
double yrFloat=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<Dst.height;++y)
{
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width* ((long)srcy)));
/**//*
double srcx=0;
for (unsigned long x=0;x<dst_width;++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<dst_width;++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.2 fps
//////////////////////////////////////////////////////////////////////// ////////
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<dst_width;++x)//生成表 SrcX_Table
{
SrcX_Table[x]=(x*Src.width/Dst.width);
}
TARGB32* pDstLine=Dst.pdata;
for (unsigned long y=0;y<Dst.height;++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<dst_width;++x)
pDstLine[x]=pSrcLine[SrcX_Table[x]];
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
delete [] SrcX_Table;
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom3_Table 390.1 fps
//////////////////////////////////////////////////////////////////////// ////////
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;
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsigned long yrIntFloat_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<Dst.height;++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
mov ecx,dst_width
xor ebp,ebp //srcx_16=0
and ecx, (not 3) //循环4次展开
TEST ECX,ECX //nop
jle EndWriteLoop
lea edi,[edi+ecx*4]
neg ecx
//todo: 预读
WriteLoop:
mov eax,ebp
shr eax,16 //srcx_16>>16
lea ebx,[ebp+edx]
movd mm0,[esi+eax*4]
shr ebx,16 //srcx_16>>16
PUNPCKlDQ mm0,[esi+ebx*4]
lea ebp,[ebp+edx*2]
// movntq qword ptr [edi+ecx*4], mm0 //不使用缓存的写入指令
asm _emit 0x0F asm _emit 0xE7 asm _emit 0x04 asm _emit 0x8F
mov eax,ebp
shr eax,16 //srcx_16>>16
lea ebx,[ebp+edx]
movd mm1,[esi+eax*4]
shr ebx,16 //srcx_16>>16
PUNPCKlDQ mm1,[esi+ebx*4]
lea ebp,[ebp+edx*2]
// movntq qword ptr [edi+ecx*4+8], mm1 //不使用缓存的写入指令
asm _emit 0x0F asm _emit 0xE7 asm _emit 0x4C asm _emit 0x8F asm _emit 0x08
add ecx, 4
jnz WriteLoop
//sfence //刷新写入
asm _emit 0x0F asm _emit 0xAE asm _emit 0xF8 emms
EndWriteLoop:
mov ebx,ebp
pop ebp
//处理边界循环次数为0,1,2,3;(这个循环可以展开,做一个跳转表,略)
mov ecx,dst_width
and ecx,3
TEST ECX,ECX
jle EndLineZoom
lea edi,[edi+ecx*4]
neg ecx
StartBorder:
mov eax,ebx
shr eax,16 //srcx_16>>16
mov eax,[esi+eax*4]
mov [edi+ecx*4],eax
add ebx,edx
inc ECX
JNZ StartBorder
EndLineZoom:
}
//
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
//===================================================================== //鉴于有读者反映汇编代码阅读困难,这里给出一个使用intel提供的函数调用方式的实现,
//读者可以相互对照来阅读代码
//要编译PicZoom3_SSE_mmh,需要#include <mmintrin.h> #include <xmmintrin.h> //并且需要编译器支持
//函数PicZoom3_SSE_mmh速度为 593.7 fps
void PicZoom3_SSE_mmh(const TPicRegion& Dst,const TPicRegion& Src)
{
//警告: 函数需要CPU支持MMX和movntq指令
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height+1;
unsigned long dst_width=Dst.width;
TARGB32* pDstLine=Dst.pdata;
unsigned long srcy_16=0;
unsigned long for4count=dst_width/4*4;
for (unsigned long y=0;y<Dst.height;++y)
{
TARGB32*
pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16))); unsigned long srcx_16=0;
unsigned long x;
for (x=0;x<for4count;x+=4)//循环4次展开
{
__m64 m0=_m_from_int(*(int*)(&pSrcLine[srcx_16>>16]));
srcx_16+=xrIntFloat_16;
m0=_m_punpckldq(m0,
_m_from_int(*(int*)(&pSrcLine[srcx_16>>16])) );
srcx_16+=xrIntFloat_16;
__m64 m1=_m_from_int(*(int*)(&pSrcLine[srcx_16>>16]));
srcx_16+=xrIntFloat_16;
m1=_m_punpckldq(m1,
_m_from_int(*(int*)(&pSrcLine[srcx_16>>16])) );
srcx_16+=xrIntFloat_16;
_mm_stream_pi((__m64 *)&pDstLine[x],m0); //不使用缓存的写入指令 _mm_stream_pi((__m64 *)&pDstLine[x+2],m1); //不使用缓存的写入指令 }
for (x=for4count;x<dst_width;++x)//处理边界
{
pDstLine[x]=pSrcLine[srcx_16>>16];
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
_m_empty();
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom3_SSE 711.7 fps
//////////////////////////////////////////////////////////////////////// ////////
E: 缩放效果图:
原图放大图(x轴放大8倍,y轴放大12倍)
原图缩小图(缩小到0.66倍) 放大图(放大到1.6倍)
F: 把测试成绩放在一起:
//////////////////////////////////////////////////////////////////////// ////////
//CPU: AMD64x2 4200+(2.1G) zoom 800*600 to 1024*768
//====================================================================== ========
// BitBlt 544.7 fps //is copy 800*600 to 800*600
// BitBlt 331.6 fps //is copy 1024*1024 to 1024*1024
// StretchBlt 232.7 fps //is zoom 800*600 to 1024*1024
//
// PicZoom0 19.4 fps
// PicZoom1 30.1 fps
// PicZoom2 185.8 fps
// PicZoom3 414.4 fps
// PicZoom3_float 286.2 fps
// PicZoom3_Table 390.1 fps
// PicZoom3_SSE 711.7 fps
//////////////////////////////////////////////////////////////////////// ////////
补充Intel Core2 4400上的测试成绩:
//////////////////////////////////////////////////////////////////////// ////////
//CPU: Intel Core2 4400(2.00G) zoom 800*600 to 1024*768
//====================================================================== ========
// PicZoom0 15.0 fps
// PicZoom1 63.9 fps
// PicZoom2 231.2 fps
// PicZoom3 460.5 fps
// PicZoom3_float 422.5 fps
// PicZoom3_Table 457.6 fps
// PicZoom3_SSE 1099.7 fps
//////////////////////////////////////////////////////////////////////// ////////
摘要:首先给出一个基本的图像缩放算法,然后一步一步的优化其速度和缩放质量;
高质量的快速的图像缩放全文分为:
上篇近邻取样插值和其速度优化
中篇二次线性插值和三次卷积插值
下篇三次线性插值和MipMap链
正文:
为了便于讨论,这里只处理32bit的ARGB颜色;
代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005;
为了代码的可读性,没有加入异常处理代码;
测试使用的CPU为AMD64x2 4200+(2.37G) 和 Intel Core2 4400(2.00G);
速度测试说明:
只测试内存数据到内存数据的缩放
测试图片都是800*600缩放到1024*768; fps表示每秒钟的帧数,值越大表示函数越快A:近邻取样插值、二次线性插值、三次卷积插值缩放效果对比
原图近邻取样缩放到0.6倍近邻取样缩放到1.6倍
二次线性插值缩放到0.6倍二次线性插值缩放到1.6倍
三次卷积插值缩放到0.6倍三次卷积插值缩放到1.6倍
原图近邻取样缩放到8倍二次线性插值缩放到8倍三次卷积插值缩放到8倍二次线性插值(近似公式)
近邻取样插值缩放简单、速度快,但很多时候缩放出的图片质量比较差(特别是对于人物、景色等),
图片的缩放有比较明显的锯齿;使用二次或更高次插值有利于改善缩放效果;
B: 首先定义图像数据结构:
#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];
}
二次线性插值缩放:
C: 二次线性插值缩放原理和公式图示:
缩放后图片原图片
(宽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
聚焦看看(Sx,Sy)坐标点(Sx,Sy为浮点数)附近的情况;
对于近邻取样插值的缩放算法,直接取Color0颜色作为缩放后点的颜色;
二次线性插值需要考虑(Sx,Sy)坐标点周围的4个颜色值Color0\Color1\Color2\Color3,把(Sx,Sy)到A\B\C\D坐标点的距离作为系数来把4个颜色混合出缩放后点的颜色;
( u=Sx-floor(Sx); v=Sy-floor(Sy); 说明:floor函数的返回值为小于等于参数的最大整数 )
二次线性插值公式为:
tmpColor0=Color0*(1-u) + Color2*u;
tmpColor1=Color1*(1-u) + Color3*u;
DstColor =tmpColor0*(1-v) + tmpColor2*v;
展开公式为:
pm0=(1-u)*(1-v);
pm1=v*(1-u);
pm2=u*(1-v);
pm3=u*v;
则颜色混合公式为:
DstColor = Color0*pm0 + Color1*pm1 + Color2*pm2 + Color3*pm3;
参数函数图示:
二次线性插值函数图示
对于上面的公式,它将图片向右下各移动了半个像素,需要对此做一个修正;
=> Sx=(Dx+0.5)*SW/DW-0.5; Sy=(Dy+0.5)*SH/DH-0.5;
而实际的程序,还需要考虑到边界(访问源图片可能超界)对于算法的影响,边界的处理可能有各种方案(不处理边界或边界回绕或边界饱和或边界映射或用背景颜色混合等;文章中默认使用边界饱和来处理超界);
比如:边界饱和函数:
//访问一个点的函数,(x,y)坐标可能超出图片边界; //边界处理模式:边界饱和
inline TARGB32 Pixels_Bound(const TPicRegion& pic,long x,long y)
{
//assert((pic.width>0)&&(pic.height>0));
bool 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.heigh t-1; IsInPic=false; }
TARGB32 result=Pixels(pic,x,y);
if (!IsInPic) result.a=0;
return result;
}
D: 二次线性插值缩放算法的一个参考实现:PicZoom_BilInear0
该函数并没有做什么优化,只是一个简单的浮点实现版本;
inline void Bilinear0(const TPicRegion& pic,float fx,float fy,TARGB3 2* result)
{
long x=(long)fx; if (x>fx) --x; //x=floor(fx);
long y=(long)fy; if (y>fy) --y; //y=floor(fy);
TARGB32 Color0=Pixels_Bound(pic,x,y);
TARGB32 Color2=Pixels_Bound(pic,x+1,y);
TARGB32 Color1=Pixels_Bound(pic,x,y+1);
TARGB32 Color3=Pixels_Bound(pic,x+1,y+1);
float u=fx-x;
float v=fy-y;
float pm3=u*v;
float pm2=u*(1-v);
float pm1=v*(1-u);
float pm0=(1-u)*(1-v);
result->a=(pm0*Color0.a+pm1*Color1.a+pm2*Color2.a+pm3*Color3.a); result->r=(pm0*Color0.r+pm1*Color1.r+pm2*Color2.r+pm3*Color3.r); result->g=(pm0*Color0.g+pm1*Color1.g+pm2*Color2.g+pm3*Color3.g); result->b=(pm0*Color0.b+pm1*Color1.b+pm2*Color2.b+pm3*Color3.b); }
void PicZoom_Bilinear0(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;
TARGB32* pDstLine=Dst.pdata;
for (unsigned long y=0;y<Dst.height;++y)
{
float srcy=(y+0.4999999)*Src.height/Dst.height-0.5;
for (unsigned long x=0;x<dst_width;++x)
{
float srcx=(x+0.4999999)*Src.width/Dst.width-0.5;
Bilinear0(Src,srcx,srcy,&pDstLine[x]);
}
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom_BilInear0 8.3 fps
//////////////////////////////////////////////////////////////////////// ////////
E: 把PicZoom_BilInear0的浮点计算改写为定点数实现:PicZoom_BilInear1
inline void Bilinear1(const TPicRegion& pic,const long x_16,const lo ng y_16,TARGB32* result)
{
long x=x_16>>16;
long y=y_16>>16;
TARGB32 Color0=Pixels_Bound(pic,x,y);
TARGB32 Color2=Pixels_Bound(pic,x+1,y);
TARGB32 Color1=Pixels_Bound(pic,x,y+1);
TARGB32 Color3=Pixels_Bound(pic,x+1,y+1);
unsigned long u_8=(x_16 & 0xFFFF)>>8;
unsigned long v_8=(y_16 & 0xFFFF)>>8;
unsigned long pm3_16=(u_8*v_8);
unsigned long pm2_16=(u_8*(unsigned long)(256-v_8));
unsigned long pm1_16=(v_8*(unsigned long)(256-u_8));
unsigned long pm0_16=((256-u_8)*(256-v_8));
result->a=((pm0_16*Color0.a+pm1_16*Color1.a+pm2_16*Color2.a+pm3_ 16*Color3.a)>>16);
result->r=((pm0_16*Color0.r+pm1_16*Color1.r+pm2_16*Color2.r+pm3_ 16*Color3.r)>>16);
result->g=((pm0_16*Color0.g+pm1_16*Color1.g+pm2_16*Color2.g+pm3_ 16*Color3.g)>>16);
result->b=((pm0_16*Color0.b+pm1_16*Color1.b+pm2_16*Color2.b+pm3_ 16*Color3.b)>>16);
}
void PicZoom_Bilinear1(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
long xrIntFloat_16=((Src.width)<<16)/Dst.width+1;
long yrIntFloat_16=((Src.height)<<16)/Dst.height+1;
const long csDErrorX=-(1<<15)+(xrIntFloat_16>>1);
const long csDErrorY=-(1<<15)+(yrIntFloat_16>>1);
unsigned long dst_width=Dst.width;
TARGB32* pDstLine=Dst.pdata;
long srcy_16=csDErrorY;
long y;
for (y=0;y<Dst.height;++y)
{
long srcx_16=csDErrorX;
for (unsigned long x=0;x<dst_width;++x)
{
Bilinear1(Src,srcx_16,srcy_16,&pDstLine[x]); //border
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom_BilInear1 17.7 fps
//////////////////////////////////////////////////////////////////////// ////////
F: 二次线性插值需要考略边界访问超界的问题,我们可以将边界区域和内部区域分开处理,这样就
可以优化内部的插值实现函数了:比如不需要判断访问超界、减少颜色数据复制、减少一些不必要的重复坐标计算等等
inline void Bilinear2_Fast(TARGB32* PColor0,TARGB32* PColor1,unsigne d long u_8,unsigned long v_8,TARGB32* result)
{
unsigned long pm3_16=u_8*v_8;
unsigned long pm2_16=(u_8<<8)-pm3_16;
unsigned long pm1_16=(v_8<<8)-pm3_16;
unsigned long pm0_16=(1<<16)-pm1_16-pm2_16-pm3_16;
result->a=((pm0_16*PColor0[0].a+pm2_16*PColor0[1].a+pm1_16*PColo r1[0].a+pm3_16*PColor1[1].a)>>16);
result->r=((pm0_16*PColor0[0].r+pm2_16*PColor0[1].r+pm1_16*PColo r1[0].r+pm3_16*PColor1[1].r)>>16);
result->g=((pm0_16*PColor0[0].g+pm2_16*PColor0[1].g+pm1_16*PColo r1[0].g+pm3_16*PColor1[1].g)>>16);
result->b=((pm0_16*PColor0[0].b+pm2_16*PColor0[1].b+pm1_16*PColo r1[0].b+pm3_16*PColor1[1].b)>>16);
}
inline void Bilinear2_Border(const TPicRegion& pic,const long x_16,c onst long y_16,TARGB32* result)
{
long x=(x_16>>16);
long y=(y_16>>16);
unsigned long u_16=((unsigned short)(x_16));
unsigned long v_16=((unsigned short)(y_16));
TARGB32 pixel[4];
pixel[0]=Pixels_Bound(pic,x,y);
pixel[1]=Pixels_Bound(pic,x+1,y);
pixel[2]=Pixels_Bound(pic,x,y+1);
pixel[3]=Pixels_Bound(pic,x+1,y+1);
Bilinear2_Fast(&pixel[0],&pixel[2],u_16>>8,v_16>>8,result);
}
void PicZoom_Bilinear2(const TPicRegion& Dst,const TPicRegion& Src)
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
long xrIntFloat_16=((Src.width)<<16)/Dst.width+1;
long yrIntFloat_16=((Src.height)<<16)/Dst.height+1;
const long csDErrorX=-(1<<15)+(xrIntFloat_16>>1);
const long csDErrorY=-(1<<15)+(yrIntFloat_16>>1);
unsigned long dst_width=Dst.width;
//计算出需要特殊处理的边界
long border_y0=-csDErrorY/yrIntFloat_16+1; //y0+y*yr>=0 ; y0=csDErrorY => y>=-csDErrorY/yr
if (border_y0>=Dst.height) border_y0=Dst.height;
long border_x0=-csDErrorX/xrIntFloat_16+1;
if (border_x0>=Dst.width ) border_x0=Dst.width;
long border_y1=(((Src.height-2)<<16)-csDErrorY)/yrIntFloat_16+1; //y 0+y*yr<=(height-2) => y<=(height-2-csDErrorY)/yr
if (border_y1<border_y0) border_y1=border_y0;
long border_x1=(((Src.width-2)<<16)-csDErrorX)/xrIntFloat_16+1;
if (border_x1<border_x0) border_x1=border_x0;
TARGB32* pDstLine=Dst.pdata;
long Src_byte_width=Src.byte_width;
long srcy_16=csDErrorY;
long y;
for (y=0;y<border_y0;++y)
{
long srcx_16=csDErrorX;
for (unsigned long x=0;x<dst_width;++x)
{
Bilinear2_Border(Src,srcx_16,srcy_16,&pDstLine[x]); //border srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
for (y=border_y0;y<border_y1;++y)
{
long srcx_16=csDErrorX;
long x;
for (x=0;x<border_x0;++x)
{
Bilinear2_Border(Src,srcx_16,srcy_16,&pDstLine[x]);//border srcx_16+=xrIntFloat_16;
}
{
unsigned long v_8=(srcy_16 & 0xFFFF)>>8;
TARGB32* PSrcLineColor= (TARGB32*)((TUInt8*)(Src.pdata)+Src_ byte_width*(srcy_16>>16)) ;
for (unsigned long x=border_x0;x<border_x1;++x)
{
TARGB32* PColor0=&PSrcLineColor[srcx_16>>16];
TARGB32* PColor1=(TARGB32*)((TUInt8*)(PColor0)+Src_byte_ width);
Bilinear2_Fast(PColor0,PColor1,(srcx_16 & 0xFFFF)>>8,v_8 ,&pDstLine[x]);
srcx_16+=xrIntFloat_16;
}
}
for (x=border_x1;x<dst_width;++x)
{
Bilinear2_Border(Src,srcx_16,srcy_16,&pDstLine[x]);//border srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
for (y=border_y1;y<Dst.height;++y)
{
long srcx_16=csDErrorX;
for (unsigned long x=0;x<dst_width;++x)
{
Bilinear2_Border(Src,srcx_16,srcy_16,&pDstLine[x]); //border srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
//////////////////////////////////////////////////////////////////////// ////////
//速度测试:
//====================================================================== ========
// PicZoom_BilInear2 43.4 fps
//////////////////////////////////////////////////////////////////////// ////////
(F'补充:
如果不想处理边界访问超界问题,可以考虑扩大源图片的尺寸,加一个边框(“哨兵”优化);
这样插值算法就不用考虑边界问题了,程序写起来也简单很多!
如果对缩放结果的边界像素级精度要求不是太高,我还有一个方案,一个稍微改变的缩放公式: Sx=Dx*(SW-1)/DW; Sy=Dy*(SH-1)/DH; (源图片宽和高:SW>=2;SH>=2) 证明这个公式不会造成内存访问超界:
要求Dx=DW-1时: sx+1=int( (dw-1)/dw*(dw-1) ) +1 <= (sw-1)
有: int( (sw-1)*(dw-1)/dw ) <=sw-2
(sw-1)*(dw-1)/dw <(sw-1)
(dw-1) /dw<1
(dw-1) <dw
比如,按这个公式的一个简单实现: (缩放效果见前面的"二次线性插值(近似公式)"图示)
void PicZoom_ftBilinear_Common(const TPicRegion& Dst,const TPicRegion& S rc)
{
if ( (0==Dst.width)||(0==Dst.height)
||(2>Src.width)||(2>Src.height)) return;
long xrIntFloat_16=((Src.width-1)<<16)/Dst.width;
long yrIntFloat_16=((Src.height-1)<<16)/Dst.height;
unsigned long dst_width=Dst.width;
long Src_byte_width=Src.byte_width;
TARGB32* pDstLine=Dst.pdata;
long srcy_16=0;
for (unsigned long y=0;y<Dst.height;++y)
{
unsigned long v_8=(srcy_16 & 0xFFFF)>>8;
TARGB32* PSrcLineColor= (TARGB32*)((TUInt8*)(Src.pdata)+Src_byte _width*(srcy_16>>16)) ;
long srcx_16=0;
for (unsigned long x=0;x<dst_width;++x)
{
TARGB32* PColor0=&PSrcLineColor[srcx_16>>16];
Bilinear_Fast_Common(PColor0,(TARGB32*)((TUInt8*)(PColor0)+S rc_byte_width),(srcx_16 & 0xFFFF)>>8,v_8,&pDstLine[x]);
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
)
G:利用单指令多数据处理的MMX指令一般都可以加快颜色的运算;在使用MMX改写之前,利用
32bit寄存器(或变量)来模拟单指令多数据处理;
数据储存原理:一个颜色数据分量只有一个字节,用2个字节来储存单个颜色分量的计算结果,
对于很多颜色计算来说精度就够了;那么一个32bit寄存器(或变量)就可以储存2个计算出的
临时颜色分量;从而达到了单个指令两路数据处理的目的;
单个指令两路数据处理的计算:
乘法: ((0x00AA*a)<<16) | (0x00BB*a) = 0x00AA00BB * a
可见只要保证0x00AA*a和0x00BB*a都小于(1<<16)那么乘法可以直接使用无符号数乘法了加法: ((0x00AA+0x00CC)<<16) | (0x00BB+0x00DD) = 0x00AA00BB + 0x00CC00DD 可见只要0x00AA+0x00CC和0x00BB+0x00DD小于(1<<16)那么加法可以直接使用无符号数加法了
(移位、减法等稍微复杂一点,因为这里没有用到就不推倒运算公式了)
inline void Bilinear_Fast_Common(TARGB32* PColor0,TARGB32* PColor1,u nsigned long u_8,unsigned long v_8,TARGB32* result)
{
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);
}
inline void Bilinear_Border_Common(const TPicRegion& pic,const long
x_16,const long y_16,TARGB32* result)
{
long x=(x_16>>16);
long y=(y_16>>16);
unsigned long u_16=((unsigned short)(x_16));
unsigned long v_16=((unsigned short)(y_16));
TARGB32 pixel[4];。