图形图像处理-之-任意角度的高质量的快速的图像旋转 下篇 补充话题
tag:图像旋转,任意角度,图像缩放,速度优化,定点数优化,近邻取样插值,二次线性插值,
三次卷积插值,MipMap链,三次线性插值,MMX/SSE优化,CPU缓存优化,AlphaBlend,颜色混合,并行
摘要: 该文章是《任意角度的高质量的快速的图像旋转》的一些高级补充话题;
给出了一个完整的Alpha混合的插值旋转实现;并尝试将旋转函数并行化,从而在多核电脑上获得更快的速度;添加优化预读缓冲区的函数实现版本,提高超大图片的旋转的速度;
任意角度的高质量的快速的图像旋转 全文 分为:
上篇 纯软件的任意角度的快速旋转
中篇 高质量的旋转
下篇 补充话题
(2007.07.15 添加对大图片旋转的预读缓冲区优化版本)
正文:
为了便于讨论,这里只处理32bit的ARGB颜色;
代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005;
为了代码的可读性,没有加入异常处理代码;
测试使用的CPU为AMD64x2 4200+(2.37G) 和 Intel Core2 4400(2.00G);
(基础代码参考《图形图像处理-之-任意角度的高质量的快速的图像旋转》系列前面的文章)
A:完整的Alpha混合的双线性插值旋转实现
《高质量的旋转》中已经涉及到了边界的AlphaBlend的问题,这里顺水推舟的实现一个支持全图片Alpha通道Blend混合的双线性插值旋转函数;
首先给出带完整Alpha通道的源图片:
这张图片是带有8比特Alpha的32比特RGB真彩bmp图片;
带的Alpha通道在工具里可能显示不出来,单独提取出来的图示:
函数实现:
void BilInear_BlendBorder_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;
pixel[2]=Pixels_Bound(pic,x0,y0+1,IsInPic);
if (!IsInPic) pixel[2].a=0;
pixel[1]=Pixels_Bound(pic,x0+1,y0,IsInPic);
if (!IsInPic) pixel[1].a=0;
pixel[3]=Pixels_Bound(pic,x0+1,y0+1,IsInPic);
if (!IsInPic) pixel[3].a=0;
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_BlendLine_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_in_x0;++x)
{
TARGB32 src_color;
BilInear_BlendBorder_MMX(SrcPic,srcx0_16,srcy0_16,&src_color);
if (src_color.a>0)
pDstLine[x]=AlphaBlend_MMX(pDstLine[x],src_color);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
for (x=dst_in_x0;x<dst_in_x1;++x)
{
TARGB32 src_color;
BilInear_Fast_MMX(SrcPic,srcx0_16,srcy0_16,&src_color);
if (src_color.a==255)
pDstLine[x]=src_color;
else if (src_color.a>0)
pDstLine[x]=AlphaBlend_MMX(pDstLine[x],src_color);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
for (x=dst_in_x1;x<dst_border_x1;++x)
{
TARGB32 src_color;
BilInear_BlendBorder_MMX(SrcPic,srcx0_16,srcy0_16,&src_color);
if (src_color.a>0)
pDstLine[x]=AlphaBlend_MMX(pDstLine[x],src_color);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
asm emms
}
void PicRotaryBlendBilInear_MMX(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,1)) 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)
{
PicRotary_BilInear_BlendLine_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<Dst.height)
{
PicRotary_BilInear_BlendLine_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);
}
}
}
效果图:
B:在双核上并行三次卷积插值旋转的一个简单实现
(假设图片旋转绘制到目的图片的中间)
这里利用CWorkThreadPool来并行执行任务;
(参见我的文章《并行计算简介和多核CPU编程Demo》,里面有CWorkThreadPool类的完整源代码)
最容易想到的方案就是分成上下两部分分别调用PicRotaryThreeOrder_MMX,从而并行执行;
{
const TPicRegion* Dst;
const TPicRegion* Src;
double RotaryAngle;
double ZoomX;
double ZoomY;
double move_x;
double move_y;
};
void RotaryThreeOrder_callback(void* wd)
{
TRotaryThreeOrder_WorkData* WorkData=(TRotaryThreeOrder_WorkData*)wd;
PicRotaryThreeOrder_MMX(*WorkData->Dst,*WorkData->Src,WorkData->RotaryAngle,WorkData->ZoomX,WorkData->ZoomY,WorkData->move_x,WorkData->move_y);
}
void PicRotaryThreeOrder_MMX_parallel2(const TPicRegion& Dst,const TPicRegion& Src,double RotaryAngle,double ZoomX,double ZoomY,double move_x,double move_y)
{
TRotaryThreeOrder_WorkData work_list[2];
TRotaryThreeOrder_WorkData* pwork_list[2];
for (long i=0;i<2;++i)
{
work_list[i].Src=&Src;
work_list[i].RotaryAngle=RotaryAngle;
work_list[i].ZoomX=ZoomX;
work_list[i].ZoomY=ZoomY;
work_list[i].move_x=move_x;
work_list[i].move_y=move_y;
pwork_list[i]=&work_list[i];
}
TPicRegion dst_up=Dst;
dst_up.height=Dst.height/2;
work_list[0].Dst=&dst_up;
TPicRegion dst_down=Dst;
dst_down.pdata=&Pixels(Dst,0,dst_up.height);
dst_down.height=Dst.height-dst_up.height;
work_list[1].Dst=&dst_down;
work_list[1].move_y=move_y-Dst.height/2;
CWorkThreadPool::work_execute(RotaryThreeOrder_callback,(void**)&pwork_list,2);
}
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值
//////////////////////////////////////////////////////////////////////////////////
//速度测试: CPU: AMD64x2 4200+
//==============================================================================
// PicRotaryThreeOrder_MMX_parallel2 87.6 fps
//==============================================================================
//////////////////////////////////////////////////////////////////////////////////
//速度测试: CPU: Intel Core2 4400(2.00G)
//==============================================================================
// PicRotaryThreeOrder_MMX_parallel2 89.3 fps
////////////////////////////////////////////////////////////////////////////////
并行化的实现PicRotaryThreeOrder_MMX_parallel2比PicRotaryThreeOrder_MMX的44.2fps快了98.2%! (Intel Core2 4400上快了94.6%)
在双核CPU上执行速度几乎是单核上的2倍!
B':一个通用的针对任意多核并行的一个简单实现
有了上面的并行基础,我们来实现一个更加通用一些的版本;根据CPU核心数来动态分配任务;
实现方式为直接按照扫描行来分配(但这样处理可能不利于内存的高效访问),就懒得去估算任务量了:)
{
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,2)) return;
TARGB32* pDstLine=Dst.pdata;
((TUInt8*&)pDstLine)+=(Dst.byte_width*rcData.out_dst_down_y);
long run_part_i=0;
while (true) //to down
{
long y=rcData.out_dst_down_y;
if (y>=Dst.height) break;
if (y>=0)
{
if (run_part_i%part_count==part_i)
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);
++run_part_i;
}
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)
{
if (run_part_i%part_count==part_i)
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);
++run_part_i;
}
}
}
struct TRotaryThreeOrder_part_WorkData
{
const TPicRegion* Dst;
const TPicRegion* Src;
double RotaryAngle;
double ZoomX;
double ZoomY;
double move_x;
double move_y;
long part_i;
long part_count;
};
void RotaryThreeOrder_part_callback(void* wd)
{
TRotaryThreeOrder_part_WorkData* WorkData=(TRotaryThreeOrder_part_WorkData*)wd;
PicRotaryThreeOrder_MMX_part(*WorkData->Dst,*WorkData->Src,WorkData->RotaryAngle,WorkData->ZoomX,WorkData->ZoomY,
WorkData->move_x,WorkData->move_y,WorkData->part_i,WorkData->part_count);
}
void PicRotaryThreeOrder_MMX_parallel(const TPicRegion& Dst,const TPicRegion& Src,double RotaryAngle,double ZoomX,double ZoomY,double move_x,double move_y)
{
long work_count=CWorkThreadPool::best_work_count();
std::vector<TRotaryThreeOrder_part_WorkData> work_list(work_count);
std::vector<TRotaryThreeOrder_part_WorkData*> pwork_list(work_count);
long i;
for (i=0;i<work_count;++i)
{
work_list[i].Dst=&Dst;
work_list[i].Src=&Src;
work_list[i].RotaryAngle=RotaryAngle;
work_list[i].ZoomX=ZoomX;
work_list[i].ZoomY=ZoomY;
work_list[i].move_x=move_x;
work_list[i].move_y=move_y;
work_list[i].part_i=i;
work_list[i].part_count=work_count;
pwork_list[i]=&work_list[i];
}
CWorkThreadPool::work_execute(RotaryThreeOrder_part_callback,(void**)&pwork_list[0],work_count);
}
//注:测试图片都是800*600的图片旋转到1004*1004的图片中心 测试成绩取各个旋转角度的平均速度值
//////////////////////////////////////////////////////////////////////////////////
//速度测试: CPU: AMD64x2 4200+
//==============================================================================
// PicRotaryThreeOrder_MMX_parallel 81.0 fps
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//速度测试: CPU: Intel Core2 4400(2.00G)
//==============================================================================
// PicRotaryThreeOrder_MMX_parallel 89.5 fps
////////////////////////////////////////////////////////////////////////////////
这个实现能应付大多数时候的并行需求了,包括以后的4核8核...
(注意:这里的并行任务分割方案仅仅是简单的举例(用了代码改动最小的方案),你应该根据你的需求来更好的并行化你的任务; 如果分割后的单个任务太小,并行的优势可能就体现不出来,甚至于更慢;)
C:超大图片旋转优化
1.使用PicRotary*、PicRotaryBilInear*、PicRotaryThreeOrder*等函数在旋转大图片的时候,会出现一个速度变慢问题:就是旋转不同的角度,速度差异巨大(甚至达到8倍以上!)
速度测试:
//注:CPU: AMD64x2 4200+(2.37G)
////////////////////////////////////////////////////////////////////////////////
// 800x600的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 304.2 fps 250.7 fps 565.3 fps
// PicRotaryBilInear_MMX 100.2 fps 87.8 fps 130.3 fps
// PicRotaryThreeOrder_MMX 44.2 fps 41.4 fps 49.7 fps
////////////////////////////////////////////////////////////////////////////////
// 3200x2400的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 12.2 fps 4.6 fps 36.3 fps
// PicRotaryBilInear_MMX 5.0 fps 1.1 fps 8.7 fps
// PicRotaryThreeOrder_MMX 2.6 fps 0.9 fps 3.5 fps
////////////////////////////////////////////////////////////////////////////////
//注:CPU: Intel Core2 4400(2.00G)
////////////////////////////////////////////////////////////////////////////////
// 800x600的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 449.3 fps 250.5 fps 753.4 fps
// PicRotaryBilInear_MMX 109.5 fps 95.3 fps 132.4 fps
// PicRotaryThreeOrder_MMX 45.9 fps 41.5 fps 50.3 fps
////////////////////////////////////////////////////////////////////////////////
// 3200x2400的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 18.3 fps 12.0 fps 44.8 fps
// PicRotaryBilInear_MMX 6.7 fps 3.5 fps 8.7 fps
// PicRotaryThreeOrder_MMX 2.9 fps 2.2 fps 3.4 fps
////////////////////////////////////////////////////////////////////////////////
在我的AMD64下 4200+ CPU上800x600的源图片对旋转的角度不是很敏感,但当源图片为3200x2400的时候,最小速度和平均速度差异巨大;(Intel Core2 4400上稍好)
2.先来分析一下问题出现的原因,对于某些角度(比如90度和270度),按以前的函数实现,访问源图片内存的方式将是列方向的,当图片比较大的时候,内 存的读取访问将变得非常低效;一般CPU访问内存的时候都会一次性读取连续相邻的64字节放到缓存,但很明显对于某些角度,预读的大部分数据都没有用(甚 至只使用了其中的4个字节,浪费了60字节完全没有用就被新的数据挤出了缓存);对于小缓存的CPU和较大的源图片,这种情况会更严重;
能想到的一些解决方案:a.使用CPU的预读指令来手工预读数据,但是没有办法指定预读的内存块大小从而避免带宽浪费;而且以后的硬件趋势也只会朝一次读 取更大的块发展,所以该方案不可行;b.针对不同的角度方向分别编码,使读取的内存方向尽量按行方向(从而使预读生效),比如靠近90度旋转的时候写内存 的方向将变为列方向,因为写内存指令中可以禁止写缓存,应该可以降低列写入带来的性能损失;该方案还有一个缺点是代码编写稍嫌麻烦:) C:利用内存访问的局部性来使缓存的数据有效,就是分成小块来处理旋转算法,使内存访问在任何角度时都有相关性;
3.分块局部性旋转算法的实现方案;
以前的扫描算法图示 新的分块扫描算法图示
为了实现新的扫描路径,有一个简单的改进办法,把以前的扫描行(起始和结束位置)先保存起来,然后在处理这是扫描行;代码就很简单了,如下:
(没有给出的基础代码参见该系列的其他文章)
4.近邻取样插值的分块扫描函数实现PicRotarySSE2_Block
{
public:
TARGB32* pdst;
long width_border0;
long width_in;
long width_border1;
long src_x0_16;
long src_y0_16;
TBlockLineWork(TARGB32* _pdst,long _width_in,long _src_x0_16,long _src_y0_16)
:pdst(_pdst),width_in(_width_in),src_x0_16(_src_x0_16),src_y0_16(_src_y0_16),width_border0(0),width_border1(0) {}
TBlockLineWork(TARGB32* _pdst,long _width_border0,long _width_in,long _width_border1,long _src_x0_16,long _src_y0_16)
:pdst(_pdst),width_in(_width_in),src_x0_16(_src_x0_16),src_y0_16(_src_y0_16),width_border0(_width_border0),width_border1(_width_border1) {}
};
typedef std::vector<TBlockLineWork> TBlockWork;
//分小块遍历
void do_PicRotarySSE2_Block(TBlockWork& BlockWork,const TPicRegion& Src,long Ax_16,long Ay_16)
{
//我测试的分成64x64的小块比较合适,也可以尝试一下其它块大小
const long rotary_block_width=64;
const long rotary_block_height=rotary_block_width;
long height=BlockWork.size();
for (long y=0;y<height;y+=rotary_block_height)
{
long cur_block_height;
if (rotary_block_height<=(height-y))
cur_block_height=rotary_block_height;
else
cur_block_height=(height-y);
bool is_line_filish=false;
while (!is_line_filish)
{
is_line_filish=true;
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_in;
if (cur_block_width>0)
{
is_line_filish=false;
if (cur_block_width>rotary_block_width)
cur_block_width=rotary_block_width;
PicRotarySSE2_CopyLine(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
BlockLine->pdst=&BlockLine->pdst[cur_block_width];
BlockLine->width_in-=cur_block_width;
BlockLine->src_x0_16+=(Ax_16*cur_block_width);
BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
}
}
}
void PicRotarySSE2_Block(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,0)) return;
TBlockWork BlockWork;
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.out_dst_x0_in;
BlockWork.push_back(TBlockLineWork(&pDstLine[x0],rcData.out_dst_x1_in-x0,rcData.out_src_x0_16,rcData.out_src_y0_16));
}
if (!rcData.next_clip_line_down()) break;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
for (long sleft=0,sright=BlockWork.size()-1;sleft<sright;++sleft,--sright)
std::swap(BlockWork[sleft],BlockWork[sright]);
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.out_dst_x0_in;
BlockWork.push_back(TBlockLineWork(&pDstLine[x0],rcData.out_dst_x1_in-x0,rcData.out_src_x0_16,rcData.out_src_y0_16));
}
}
do_PicRotarySSE2_Block(BlockWork,Src,Ax_16,Ay_16);
asm sfence //刷新写入
}
5.二次线性插值的分块扫描函数实现PicRotaryBilInear_MMX_Block
这里比近邻取样的相应函数多出一些边界处理代码
long Ax_16,long Ay_16,long srcx0_16,long srcy0_16,const TPicRegion& SrcPic)
{
for (long x=0;x<width;++x)
{
BilInear_Fast_MMX(SrcPic,srcx0_16,srcy0_16,&pDstLine[x]);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
}
inline void PicRotary_BilInear_CopyLine_Border_MMX(TARGB32* pDstLine,long width,
long Ax_16,long Ay_16,long srcx0_16,long srcy0_16,const TPicRegion& SrcPic)
{
for (long x=0;x<width;++x)
{
TARGB32 src_color;
BilInear_Border_MMX(SrcPic,srcx0_16,srcy0_16,&src_color);
pDstLine[x]=AlphaBlend_MMX(pDstLine[x],src_color);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
}
void do_PicRotary_BilInear_MMX_Block(TBlockWork& BlockWork,const TPicRegion& Src,long Ax_16,long Ay_16)
{
const long rotary_block_width=64; //128
const long rotary_block_height=rotary_block_width;
long height=BlockWork.size();
for (long y=0;y<height;y+=rotary_block_height)
{
long cur_block_height;
if (rotary_block_height<=(height-y))
cur_block_height=rotary_block_height;
else
cur_block_height=(height-y);
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_border0;
if (cur_block_width>0)
{
PicRotary_BilInear_CopyLine_Border_MMX(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
BlockLine->pdst=&BlockLine->pdst[cur_block_width];
BlockLine->src_x0_16+=(Ax_16*cur_block_width);
BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
bool is_line_filish=false;
while (!is_line_filish)
{
is_line_filish=true;
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_in;
if (cur_block_width>0)
{
is_line_filish=false;
if (cur_block_width>rotary_block_width)
cur_block_width=rotary_block_width;
PicRotary_BilInear_CopyLine_Fast_MMX(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
BlockLine->pdst=&BlockLine->pdst[cur_block_width];
BlockLine->width_in-=cur_block_width;
BlockLine->src_x0_16+=(Ax_16*cur_block_width);
BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
}
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_border1;
if (cur_block_width>0)
{
PicRotary_BilInear_CopyLine_Border_MMX(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
//BlockLine->pdst=&BlockLine->pdst[cur_block_width];
//BlockLine->src_x0_16+=(Ax_16*cur_block_width);
//BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
}
asm emms
}
void PicRotaryBilInear_MMX_Block(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,1)) return;
TBlockWork BlockWork;
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)
{
BlockWork.push_back(TBlockLineWork(&pDstLine[rcData.out_dst_x0_boder],
rcData.out_dst_x0_in-rcData.out_dst_x0_boder,rcData.out_dst_x1_in-rcData.out_dst_x0_in,
rcData.out_dst_x1_boder-rcData.out_dst_x1_in,rcData.out_src_x0_16,rcData.out_src_y0_16));
}
if (!rcData.next_clip_line_down()) break;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
for (long sleft=0,sright=BlockWork.size()-1;sleft<sright;++sleft,--sright)
std::swap(BlockWork[sleft],BlockWork[sright]);
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)
{
BlockWork.push_back(TBlockLineWork(&pDstLine[rcData.out_dst_x0_boder],
rcData.out_dst_x0_in-rcData.out_dst_x0_boder,rcData.out_dst_x1_in-rcData.out_dst_x0_in,
rcData.out_dst_x1_boder-rcData.out_dst_x1_in,rcData.out_src_x0_16,rcData.out_src_y0_16));
}
}
do_PicRotary_BilInear_MMX_Block(BlockWork,Src,Ax_16,Ay_16);
}
6.三次卷积插值的分块扫描函数实现PicRotaryThreeOrder_MMX_Block
几乎就是拷贝PicRotaryBilInear_MMX_Block,然后稍做改动;
(实际项目中的代码和文章中的代码还是有很多不同的,要是实际代码中也有这么多长篇长篇的拷贝然后稍作修改的代码,那就要疯了:)
long Ax_16,long Ay_16,long srcx0_16,long srcy0_16,const TPicRegion& SrcPic)
{
for (long x=0;x<width;++x)
{
ThreeOrder_Fast_MMX(SrcPic,srcx0_16,srcy0_16,&pDstLine[x]);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
}
inline void PicRotary_ThreeOrder_CopyLine_Border_MMX(TARGB32* pDstLine,long width,
long Ax_16,long Ay_16,long srcx0_16,long srcy0_16,const TPicRegion& SrcPic)
{
for (long x=0;x<width;++x)
{
TARGB32 src_color;
ThreeOrder_Border_MMX(SrcPic,srcx0_16,srcy0_16,&src_color);
pDstLine[x]=AlphaBlend_MMX(pDstLine[x],src_color);
srcx0_16+=Ax_16;
srcy0_16+=Ay_16;
}
}
void do_PicRotary_ThreeOrder_MMX_Block(TBlockWork& BlockWork,const TPicRegion& Src,long Ax_16,long Ay_16)
{
const long rotary_block_width=64; //128
const long rotary_block_height=rotary_block_width;
long height=BlockWork.size();
for (long y=0;y<height;y+=rotary_block_height)
{
long cur_block_height;
if (rotary_block_height<=(height-y))
cur_block_height=rotary_block_height;
else
cur_block_height=(height-y);
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_border0;
if (cur_block_width>0)
{
PicRotary_ThreeOrder_CopyLine_Border_MMX(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
BlockLine->pdst=&BlockLine->pdst[cur_block_width];
BlockLine->src_x0_16+=(Ax_16*cur_block_width);
BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
bool is_line_filish=false;
while (!is_line_filish)
{
is_line_filish=true;
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_in;
if (cur_block_width>0)
{
is_line_filish=false;
if (cur_block_width>rotary_block_width)
cur_block_width=rotary_block_width;
PicRotary_ThreeOrder_CopyLine_Fast_MMX(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
BlockLine->pdst=&BlockLine->pdst[cur_block_width];
BlockLine->width_in-=cur_block_width;
BlockLine->src_x0_16+=(Ax_16*cur_block_width);
BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
}
for (long yi=y;yi<y+cur_block_height;++yi)
{
TBlockLineWork* BlockLine=&BlockWork[yi];
long cur_block_width=BlockLine->width_border1;
if (cur_block_width>0)
{
PicRotary_ThreeOrder_CopyLine_Border_MMX(BlockLine->pdst,cur_block_width,Ax_16,Ay_16,
BlockLine->src_x0_16,BlockLine->src_y0_16,Src);
//BlockLine->pdst=&BlockLine->pdst[cur_block_width];
//BlockLine->src_x0_16+=(Ax_16*cur_block_width);
//BlockLine->src_y0_16+=(Ay_16*cur_block_width);
}
}
}
asm emms
}
void PicRotaryThreeOrder_MMX_Block(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,2)) return;
TBlockWork BlockWork;
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)
{
BlockWork.push_back(TBlockLineWork(&pDstLine[rcData.out_dst_x0_boder],
rcData.out_dst_x0_in-rcData.out_dst_x0_boder,rcData.out_dst_x1_in-rcData.out_dst_x0_in,
rcData.out_dst_x1_boder-rcData.out_dst_x1_in,rcData.out_src_x0_16,rcData.out_src_y0_16));
}
if (!rcData.next_clip_line_down()) break;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
for (long sleft=0,sright=BlockWork.size()-1;sleft<sright;++sleft,--sright)
std::swap(BlockWork[sleft],BlockWork[sright]);
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)
{
BlockWork.push_back(TBlockLineWork(&pDstLine[rcData.out_dst_x0_boder],
rcData.out_dst_x0_in-rcData.out_dst_x0_boder,rcData.out_dst_x1_in-rcData.out_dst_x0_in,
rcData.out_dst_x1_boder-rcData.out_dst_x1_in,rcData.out_src_x0_16,rcData.out_src_y0_16));
}
}
do_PicRotary_ThreeOrder_MMX_Block(BlockWork,Src,Ax_16,Ay_16);
}
7.分块处理的旋转函数的速度测试:
//注:CPU: AMD64x2 4200+(2.37G)
////////////////////////////////////////////////////////////////////////////////
// 800x600的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 304.2 fps 250.7 fps 565.3 fps
// PicRotarySSE2_Block 316.6 fps 255.5 fps 495.6 fps
// PicRotaryBilInear_MMX 100.2 fps 87.8 fps 130.3 fps
// PicRotaryBilInear_MMX_Block 99.5 fps 92.7 fps 122.3 fps
// PicRotaryThreeOrder_MMX 44.2 fps 41.4 fps 49.7 fps
// PicRotaryThreeOrder_MMX_Block 43.2 fps 41.3 fps 47.8 fps
////////////////////////////////////////////////////////////////////////////////
// 3200x2400的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 12.2 fps 4.6 fps 36.3 fps
// PicRotarySSE2_Block 19.2 fps 15.9 fps 33.3 fps
// PicRotaryBilInear_MMX 5.0 fps 1.1 fps 8.7 fps
// PicRotaryBilInear_MMX_Block 6.3 fps 5.8 fps 8.3 fps
// PicRotaryThreeOrder_MMX 2.6 fps 0.9 fps 3.5 fps
// PicRotaryThreeOrder_MMX_Block 2.9 fps 2.7 fps 3.4 fps
////////////////////////////////////////////////////////////////////////////////
//注:CPU: Intel Core2 4400(2.00G)
////////////////////////////////////////////////////////////////////////////////
// 800x600的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 449.3 fps 250.5 fps 753.4 fps
// PicRotarySSE2_Block 351.3 fps 219.0 fps 436.3 fps
// PicRotaryBilInear_MMX 109.5 fps 95.3 fps 132.4 fps
// PicRotaryBilInear_MMX_Block 112.2 fps 98.1 fps 124.9 fps
// PicRotaryThreeOrder_MMX 45.9 fps 41.5 fps 50.3 fps
// PicRotaryThreeOrder_MMX_Block 47.7 fps 44.8 fps 50.1 fps
////////////////////////////////////////////////////////////////////////////////
// 3200x2400的源图片 各角度平均帧数 角度中最小帧数 角度中最大帧数
//==============================================================================
// PicRotarySSE2 18.3 fps 12.0 fps 44.8 fps
// PicRotarySSE2_Block 18.4 fps 15.5 fps 23.8 fps
// PicRotaryBilInear_MMX 6.7 fps 3.5 fps 8.7 fps
// PicRotaryBilInear_MMX_Block 7.3 fps 6.8 fps 8.3 fps
// PicRotaryThreeOrder_MMX 2.9 fps 2.2 fps 3.4 fps
// PicRotaryThreeOrder_MMX_Block 3.2 fps 2.9 fps 3.4 fps
////////////////////////////////////////////////////////////////////////////////
测试更大的源图片(6400x4800)或者CPU的缓存越小的时候*_Block优化版本优势越明显!
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 宫本茂的童心和乔布斯的叛逆--设计哲学背后的文化基因 zt
2008-02-02 13:05 任天堂的掌上游戏机NDSL里有一个叫《吉娃娃》的养狗游戏,让我这个对真狗兴趣不大的人忽然有了对宠物的兴趣。用笔可以以不同的手法去抚摸狗,不同的抚摸方式,会让那条狗作出各种可爱的动作,通过语音识别技术,我可以给狗起名字,并用说话来训练狗“坐下,…...
2024/4/21 8:52:55 - JavaActivity工作流
一、 什么是工作流 以请假为例,现在大多数公司的请假流程是这样的 员工打电话(或网聊)向上级提出请假申请——上级口头同意——上级将请假记录下来——月底将请假记录上交公司——公司将请假录入电脑 采用工作流技术的公司的请假流程是这样的 员工使用账户登录系统——点击请…...
2024/4/21 8:52:54 - hive函数 -- stddev , stddev_pop , stddev_samp , var_pop , var_samp
标准差是方差的平方根 1.方差公式: m为x1,x2...xn数列的期望值(平均数) s^2 = [(x1-m)^2 + (x2-m)^2 + ... (xn-m)^2]/n s即为标准差 s^2为方差。 2.实例: hive> select * from dim_row_num limit 10; O…...
2024/4/20 17:17:25 - 【Activiti】 Activiti工作流引擎 - 提交审批和审批处理示例详解 【精品】
在上一篇的文章中,我给大家分享了Activiti工作流引擎的API的封装代码,通过我们自己封装的代码,在实际的项目中我们就可以进行工作流相关的开发了。以下内容我们分三个部分:提交审批,审批列表查询,审批处理。一、提交审批提交审批之前,我们需要做好相关的准备工作。在上文…...
2024/5/5 12:54:29 - MFC中MessageBox()用法!
函数原型:int MessageBox( HWND hWnd, LPCTSTR lpText, LPCSTR lpCaption,UINT uStyle ); 注:应先将项目属性中的字符集属性改为多字符集(Unicode)。参数解释:hwnd:父窗口的句柄,为NULL说明消息框没有父窗口;大多数情况下可以省略不写。lpText:指向要显示字符串的指针…...
2024/4/21 8:52:55 - Ernest Adams总结50个最伟大的游戏创意
作者:Ernest Adams 50年以前,William Higinbotham用一个示波器和一些模拟电路制作了第一款电子游戏。虽然自那以后,游戏已经发生了翻天覆地的变化,但今天的AAA游戏大作的成功也部分归功于数年以前就存在的设计创新。在本文中,我将罗列我认为特别重要的50个设计创新(或某天…...
2024/4/21 8:52:50 - 图形图像处理-之-任意角度的高质量的快速的图像旋转
图形图像处理-之-任意角度的高质量的快速的图像旋转 转自:http://blog.chinaunix.net/uid-20306372-id-1707267.html 上篇 纯软件的任意角度的快速旋转 HouSisong@GMail.com 2007.04.26tag:图像旋转,任意角度,图像缩放,速度优化,定点数优化,…...
2024/4/21 8:52:49 - 基于J2EE的柔性工作流引擎的设计与实现
摘要 工作流的柔性问题日益成为研究的热点,本文在分析工作流和分布式计算技术J2EE的基础上,给出一个基于J2EE的柔性工作流引擎的设计方案及其关键部分的实现技术。 关键词 工作流引擎,柔性,J2EE1 引言在一个企业或部门的日常活动中,70%以上属于流程类的活动,如生产流程,…...
2024/4/21 8:52:48 - 【ASP.NET】2.SQL server2008卸载怎么清理注册表
标准的卸载方法无法完全清除sqlserver2008,导致重装sqlserver报错,下面介绍一下不借助任何软件完全卸载sqlserver2008数据库软件。(1)点击计算机右下角“开始”,点击“控制面板”,点击“程序”。在程序列表中找到“Microsoft SQL Server 2008”,右击 选“卸载”(或双击…...
2024/4/21 8:52:47 - 高科技公司名字的由来
1. Google - 谷歌 - googol 巨大的數字2. Microsoft - 微軟 - microcomputer and software 微電腦和軟件3. Twitter - 推特Twitter(中文称:推特)是国外的一个社交网络及微博客服务的网站。Twitter这一名称是从帽子中抓阄抓出来的。一天,旧金山的播客 (视频分享)创业公司Odeo几…...
2024/5/1 22:14:59 - SQL数据库快速入门基础
SQL(Structure Query Language,结构化查询语言)语言是国际标准化组织(ISO)采纳的标准数据库语言。 数据库就是一幢大楼,我们要先盖楼,然后再招住户(住户当然就是数据库对象,)。我们盖得大楼的基本格局设计师们已经为我们设计好,我们在创建数据库过程中,系统(设计师)…...
2024/4/21 8:52:45 - shark工作流学习(三)
Shark特征<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />Shark1.0是一款简洁、实用的完全符合WFMC规范的工作流引擎。 它具备以下几个特点:1 在使用过程中,把流程名字尝试改成中文,但是对流程的驱动有一些影响,表明sha…...
2024/4/21 8:52:45 - VC透明显示带透明度PNG图片及不带透明度的图片
一、显示带透明度的PNG图片 PNG图片的透明背景总是一片白色,后来才发现这其实是微软GDI+的设计问题,PNG图片是ARGB,使用GDI+载入图片的时候,GDI+会默认已经进行了预剩运算(PARGB),即每象素的实际值是已经和ALPHA值按比例相乘的结果,实际上它根本就没有做预乘,在使用透…...
2024/4/19 21:39:41 - Python基于类和对象实现的决斗游戏
需求: 基本任务: 1 建立角色类,角色拥有生命值的属性和攻击的方法,攻击值是随机的。 2 建立职业子类,刀客, (伤害少,血量多)剑客(伤害正常,血量正常,有几率两倍暴击), 女贼(伤害高,血量少,有几率 3 倍暴击) 3 欢迎界面,选择职业,创建角色,替电脑创建角色(…...
2024/4/20 18:21:27 - Messagebox的简单用法
实训后,让我对消息弹窗这一部分内容特别感兴趣,现在对弹窗消息做一点小小的总结: 1、头文件#include<windows.h> ,只能在windows环境下进行; 2、MessageBox(NULL,"弹窗的消息内容",“弹窗的标题”,对话框的类型常量); 3、对话框类型常量: (1)MB_OK=…...
2024/4/19 21:08:43 - Java开源工作流项目简介
1.OBE 简介:遵循WfMC所定义的规范的工作流引擎 优点:支持WfMC定义的工作流接口规范,项目开发者也比较有来头。 缺点:不支持工作流实例的持久化,缺少图形编辑环境,尚未全部完成WfMC定义的五类接口 http://www.openbusinessengine.org/index.html 2.OFBiz 简介:OFBiz本身是…...
2024/4/19 23:45:39 - CSharpGL(40)一种极其简单的半透明渲染方法
CSharpGL(40)一种极其简单的半透明渲染方法 开始 这里介绍一个实现半透明渲染效果的方法。此方法极其简单,不拖累渲染速度,但是不能适用所有的情况。 如下图所示,可以让包围盒显示为半透明效果。原理很简单,就是渲染包围盒时,只渲染坐标值为奇数(或偶数)的那些fragment。…...
2024/4/20 18:21:24 - 使用Java开源工作流jBPM开发一个简单的Demo
jBPM是jboss旗下遵守LGPL许可的java开源工作流,功能比较完善,从4.0开始引入了pvm的概念,支持jPDL、BPEL等流程定义语言。由于相关资料还比较少,开发自己的一个demo还不是太容易,本文详细讲解如何做一个简单的demo程序。 我们从http://www.jboss.org/jbossjbpm/jbpm_downlo…...
2024/4/20 18:21:23 - 科幻般的体感传感器 - kinect
Kinect是微软在2010年6月14日对XBOX360体感周边外设正式发布的名字。Natal为开发代号。伴随Kinect名称的正式发布,Kinect还推出了多款配套游戏,包括Lucasarts出品的《星球大战》、MTV推出的跳舞游戏、宠物游戏、运动游戏《Kinect Sports》、冒险游戏《Kinect Adventure》、赛…...
2024/5/1 22:22:38 - SQL Server辅助插件——SQL Prompt
前言:当我们对某个程序进行维护或者完善时,必不可少的就是跟数据库或者以前开发人员写的sql语句打交道,当我们对数据库表的结构不熟悉时,修改或者编写sql语句将是一件很痛苦的时间,而且sqlserver有没有像vs等软件可以提供强大的智能提醒。这时候我们就需要一款合适的插件来…...
2024/4/21 5:48:52
最新文章
- Android 状态栏WiFi图标的显示逻辑
1. 状态栏信号图标 1.1 WIFI信号显示 WIFI信号在状态栏的显示如下图所示 当WiFi状态为关闭时,状态栏不会有任何显示。当WiFi状态打开时,会如上图所示,左侧表示有可用WiFi,右侧表示当前WiFi打开但未连接。 当WiFi状态连接时&#x…...
2024/5/6 22:32:59 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/5/6 9:38:23 - 【stm32】I2C通信协议
【stm32】I2C通信协议 概念及原理 如果我们想要读写寄存器来控制硬件电路,就至少需要定义两个字节数据 一个字节是我们要读写哪个寄存器,也就是指定寄存器的地址 另一个字节就是这个地址下存储寄存器的内容 写入内容就是控制电路,读出内容就…...
2024/5/4 15:30:22 - 专升本-云计算
被誉为第三次信息技术革命 什么是云计算? 云计算是一种商业的计算模式,它将任务分布在大量计算机构成的资源池上,用户可以按需通过网络存储空间,计算能力和信息等服务 云计算的产生和发展: 起源:上世纪6…...
2024/5/4 11:52:52 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/4 23:54:56 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/4 23:54:56 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/5/4 23:54:56 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/6 9:21:00 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/5/4 23:54:56 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/5/4 23:55:05 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/5/4 23:54:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/5/4 23:55:16 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/5/4 23:54:56 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/6 1:40:42 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/5/4 23:54:56 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/5/4 23:55:17 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/5/4 23:55:06 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/5/4 23:54:56 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/4 23:55:06 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/5 8:13:33 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/5/4 23:55:16 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/5/4 23:54:58 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/6 21:42:42 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/5/4 23:54:56 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57