鼠标画笔去抖动的简单解决方案

今天在社区中看到这样的问题http://topic.csdn.net/u/20100826/09/b024f18d-6a4a-4048-8bbe-ef5131b18a6f.html,颇感兴趣。所以花了点时间研究了一下,发现这其中还是有不少值得思考的问题,所以借这篇博文整理一下思路与大家一起分享,希望大家多拍砖,希望CSDN中的xdjm们能提供更好的解决办法。

一:问题描述


PPT中可以用鼠标在电脑屏幕上画出线条来,这样的线条比较光滑,可以将鼠标的抖动去除,得到比较好的视觉效果。效果对比如下图,第一幅图是直接记录鼠标移动点连线后得到的图,而第二幅是经过光滑处理后的图。本文的目的就是探讨如何从抖动较多的图(如第一幅图)得到较平滑的图(如第二幅图)。

鼠标画笔去抖动的简单解决方案_第1张图片鼠标画笔去抖动的简单解决方案_第2张图片

二:原因分析及解决思路


这与计算机图形学中去锯齿的问题有相似之处又有不同,antialias问题是处理连续的位置量显示到离散的光学栅格上时出现的病态问题,而本文所描述问题,是由于手部的鼠标在画线过程中由于抖动而造成曲线在局部位置出现的小幅震荡问题。虽然两者都会表现出锯齿样负面视觉效应,但是锯齿所在位置的正确程度是不一样的。antialias问题中的锯齿效应是由于真实位置不为整数坐标位置而造成的,所以锯齿出现的位置基本上紧挨着正确位置(在单像素范围内偏移),而本文描述的问题中的锯齿的位置是由于抖动造成,所以位置的偏移量一般会超过单像素范围。所以我们会看到antialias问题中的锯齿会比较小,而本文描述的问题中的锯齿相对较大。

 

解决这个问题的一个比较简单的想法是增大鼠标轨迹记录点之间的步长(那些较近的相邻点会被去除),然后将这些剩余的记录点用连线函数连接起来,这样就能消除绝大多数的由于鼠标抖动而造成的震荡,接下来当然和一般的antialias问题一样,为了美观需要用带反锯齿功能的函数连线。在GDI中CDC的lineto函数不带反锯齿功能,所以还要找到一个能够支持在GDI中绘制反锯齿直线的函数实现,笔者有幸在CodeProject中找到了一个这样的函数:void DrawWuLine( CDC *pDC, int X0, int Y0, int X1, int Y1, COLORREF clrLine )。

 

三:主要代码


代码写得有点匆忙所以比较乱,没有好好整理,但是能正确运行。

实验用VS2005建的MFC对话框工程。

1:对话框类头文件中添加了记录鼠标轨迹点的链表成员。
 CList<CPoint> m_list;

2:对话框CPP文件中添加函数DrawWuLine。

// GDI中绘制反锯齿直线,不支持线宽。 void DrawWuLine( CDC *pDC, int X0, int Y0, int X1, int Y1, COLORREF clrLine ) { /* Make sure the line runs top to bottom */ if (Y0 > Y1) { int Temp = Y0; Y0 = Y1; Y1 = Temp; Temp = X0; X0 = X1; X1 = Temp; } /* Draw the initial pixel, which is always exactly intersected by the line and so needs no weighting */ pDC->SetPixel( X0, Y0, clrLine ); int XDir, DeltaX = X1 - X0; if( DeltaX >= 0 ) { XDir = 1; } else { XDir = -1; DeltaX = 0 - DeltaX; /* make DeltaX positive */ } /* Special-case horizontal, vertical, and diagonal lines, which require no weighting because they go right through the center of every pixel */ int DeltaY = Y1 - Y0; if (DeltaY == 0) { /* Horizontal line */ while (DeltaX-- != 0) { X0 += XDir; pDC->SetPixel( X0, Y0, clrLine ); } return; } if (DeltaX == 0) { /* Vertical line */ do { Y0++; pDC->SetPixel( X0, Y0, clrLine ); } while (--DeltaY != 0); return; } if (DeltaX == DeltaY) { /* Diagonal line */ do { X0 += XDir; Y0++; pDC->SetPixel( X0, Y0, clrLine ); } while (--DeltaY != 0); return; } unsigned short ErrorAdj; unsigned short ErrorAccTemp, Weighting; /* Line is not horizontal, diagonal, or vertical */ unsigned short ErrorAcc = 0; /* initialize the line error accumulator to 0 */ BYTE rl = GetRValue( clrLine ); BYTE gl = GetGValue( clrLine ); BYTE bl = GetBValue( clrLine ); double grayl = rl * 0.299 + gl * 0.587 + bl * 0.114; /* Is this an X-major or Y-major line? */ if (DeltaY > DeltaX) { /* Y-major line; calculate 16-bit fixed-point fractional part of a pixel that X advances each time Y advances 1 pixel, truncating the result so that we won't overrun the endpoint along the X axis */ ErrorAdj = ((unsigned long) DeltaX << 16) / (unsigned long) DeltaY; /* Draw all pixels other than the first and last */ while (--DeltaY) { ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ ErrorAcc += ErrorAdj; /* calculate error for next pixel */ if (ErrorAcc <= ErrorAccTemp) { /* The error accumulator turned over, so advance the X coord */ X0 += XDir; } Y0++; /* Y-major, so always advance Y */ /* The IntensityBits most significant bits of ErrorAcc give us the intensity weighting for this pixel, and the complement of the weighting for the paired pixel */ Weighting = ErrorAcc >> 8; ASSERT( Weighting < 256 ); ASSERT( ( Weighting ^ 255 ) < 256 ); COLORREF clrBackGround = ::GetPixel( pDC->m_hDC, X0, Y0 ); BYTE rb = GetRValue( clrBackGround ); BYTE gb = GetGValue( clrBackGround ); BYTE bb = GetBValue( clrBackGround ); double grayb = rb * 0.299 + gb * 0.587 + bb * 0.114; BYTE rr = ( rb > rl ? ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( rb - rl ) + rl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( rl - rb ) + rb ) ) ); BYTE gr = ( gb > gl ? ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( gb - gl ) + gl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( gl - gb ) + gb ) ) ); BYTE br = ( bb > bl ? ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( bb - bl ) + bl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( bl - bb ) + bb ) ) ); pDC->SetPixel( X0, Y0, RGB( rr, gr, br ) ); clrBackGround = ::GetPixel( pDC->m_hDC, X0 + XDir, Y0 ); rb = GetRValue( clrBackGround ); gb = GetGValue( clrBackGround ); bb = GetBValue( clrBackGround ); grayb = rb * 0.299 + gb * 0.587 + bb * 0.114; rr = ( rb > rl ? ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( rb - rl ) + rl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( rl - rb ) + rb ) ) ); gr = ( gb > gl ? ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( gb - gl ) + gl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( gl - gb ) + gb ) ) ); br = ( bb > bl ? ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( bb - bl ) + bl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( bl - bb ) + bb ) ) ); pDC->SetPixel( X0 + XDir, Y0, RGB( rr, gr, br ) ); } /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */ pDC->SetPixel( X1, Y1, clrLine ); return; } /* It's an X-major line; calculate 16-bit fixed-point fractional part of a pixel that Y advances each time X advances 1 pixel, truncating the result to avoid overrunning the endpoint along the X axis */ ErrorAdj = ((unsigned long) DeltaY << 16) / (unsigned long) DeltaX; /* Draw all pixels other than the first and last */ while (--DeltaX) { ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ ErrorAcc += ErrorAdj; /* calculate error for next pixel */ if (ErrorAcc <= ErrorAccTemp) { /* The error accumulator turned over, so advance the Y coord */ Y0++; } X0 += XDir; /* X-major, so always advance X */ /* The IntensityBits most significant bits of ErrorAcc give us the intensity weighting for this pixel, and the complement of the weighting for the paired pixel */ Weighting = ErrorAcc >> 8; ASSERT( Weighting < 256 ); ASSERT( ( Weighting ^ 255 ) < 256 ); COLORREF clrBackGround = ::GetPixel( pDC->m_hDC, X0, Y0 ); BYTE rb = GetRValue( clrBackGround ); BYTE gb = GetGValue( clrBackGround ); BYTE bb = GetBValue( clrBackGround ); double grayb = rb * 0.299 + gb * 0.587 + bb * 0.114; BYTE rr = ( rb > rl ? ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( rb - rl ) + rl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( rl - rb ) + rb ) ) ); BYTE gr = ( gb > gl ? ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( gb - gl ) + gl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( gl - gb ) + gb ) ) ); BYTE br = ( bb > bl ? ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( bb - bl ) + bl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?Weighting:(Weighting ^ 255)) ) / 255.0 * ( bl - bb ) + bb ) ) ); pDC->SetPixel( X0, Y0, RGB( rr, gr, br ) ); clrBackGround = ::GetPixel( pDC->m_hDC, X0, Y0 + 1 ); rb = GetRValue( clrBackGround ); gb = GetGValue( clrBackGround ); bb = GetBValue( clrBackGround ); grayb = rb * 0.299 + gb * 0.587 + bb * 0.114; rr = ( rb > rl ? ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( rb - rl ) + rl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( rl - rb ) + rb ) ) ); gr = ( gb > gl ? ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( gb - gl ) + gl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( gl - gb ) + gb ) ) ); br = ( bb > bl ? ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( bb - bl ) + bl ) ) : ( ( BYTE )( ( ( double )( grayl<grayb?(Weighting ^ 255):Weighting) ) / 255.0 * ( bl - bb ) + bb ) ) ); pDC->SetPixel( X0, Y0 + 1, RGB( rr, gr, br ) ); } /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */ pDC->SetPixel( X1, Y1, clrLine ); } 
    

3:添加MouseMove,LButtonDown,LButtonUp函数

 

void CnewDlg::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CDC *dc = GetDC(); CPen pen1(PS_SOLID,1,RGB(0,0,0)); dc->SelectObject(&pen1); //不优化的效果 POSITION pos = m_list.GetHeadPosition(); CPoint begin,end; for(int i = 0; i < m_list.GetCount() - 1; i++) { if(pos != NULL) begin = m_list.GetNext(pos); if(pos != NULL) end = m_list.GetAt(pos); dc->MoveTo(begin); dc->LineTo(end); } //略去很近的相邻点后的效果 pos = m_list.GetHeadPosition(); bool begintrue = false; bool endtrue = false; while(pos!=NULL) { begintrue = false; endtrue = false; if(pos != NULL) { begintrue = true; begin = m_list.GetNext(pos); } else break; if(pos != NULL) { endtrue = true; end = m_list.GetAt(pos); } else break; //当开始点与它的邻近点的距离小于某个值时略过该值继续找下一个点,否则找到改点。 while((abs(begin.x-end.x)+abs(begin.y-end.y))<8) { endtrue = false; if(pos!=NULL) m_list.GetNext(pos); else break; if(pos!=NULL) end = m_list.GetAt(pos); else break; } if((abs(begin.x-end.x)+abs(begin.y-end.y))>=8) { endtrue = true; } if(begintrue && endtrue) { begin.y += 200; end.y +=200; dc->MoveTo(begin); dc->LineTo(end); } else break; } //略去很近的相邻点后再实施去锯齿化的效果 pos = m_list.GetHeadPosition(); begintrue = false; endtrue = false; while(pos!=NULL) { begintrue = false; endtrue = false; if(pos != NULL) { begintrue = true; begin = m_list.GetNext(pos); } else break; if(pos != NULL) { endtrue = true; end = m_list.GetAt(pos); } else break; while((abs(begin.x-end.x)+abs(begin.y-end.y))<8) { endtrue = false; if(pos!=NULL) m_list.GetNext(pos); else break; if(pos!=NULL) end = m_list.GetAt(pos); else break; } if((abs(begin.x-end.x)+abs(begin.y-end.y))>=8) { endtrue = true; } if(begintrue && endtrue) { DrawWuLine ( dc, begin.x, begin.y+400, end.x, end.y+400, RGB(0,0,0)); } else break; } ReleaseDC(dc); CDialog::OnLButtonUp(nFlags, point); } void CnewDlg::OnMouseMove(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 if(nFlags == MK_LBUTTON) { //添加一个轨迹点 m_list.AddTail(point); } CDialog::OnMouseMove(nFlags, point); } void CnewDlg::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 //将鼠标轨迹点全部去除 m_list.RemoveAll(); CDialog::OnLButtonDown(nFlags, point); }  
四:实验结果

鼠标画笔去抖动的简单解决方案_第3张图片
五:不足和可改进之处


 1:由于DrawWuLine函数不支持线宽,所以如果有线宽则需要将DrawWuLine函数做一个升级改造。计算机图形学的能人们,期待你们的后续工作,哈。
2:由于较近的相邻点会被去除,这样对于比较短的轨迹来说处理后有可能轨迹消失了,如上面实验结果中的"china"中的字母"i"上面的一点没有了。这个可以通过检测链表将这样的情况预先排除掉,然后单独处理。希望练手的朋友们,你们可以发挥一下你们的聪明才智了。
3:没有实现边画边显示的功能,只能够画完一笔再显示(鼠标放开后才显示),刚学习了MFC的新手们可以练练手了。
4:整体效果虽然有很大改善,不过相对于PPT中的效果来说还是差了不少,不过对于我们来说,通过这样的一个简单操作就可以达到目前的效果已经很经济了。我们不是老是讲性价比吗?这个解决方法的性价比就不错。


六:如何结合拟合方法解决本问题


给定一系列点求一条曲线(曲线事先要建模,比如直线,二次曲线等等),使得该曲线能够与这一些离散点尽可能接近,但并不一定要经过(否则就是差值的问题了)。参数曲线的可导性很好,直观上说就是曲线非常光滑,没有毛刺。如果能够有这样的曲线,那么结果将非常理想。这种方法看似非常诱人,但是在解这个问题的时候却可能需要非常巧妙的算法来保证以下一些方面:

1:算法的效率要高,保证实时。

2:曲线需要和轨迹上的每一个点都非常接近。

3:绘制的曲线是任意的,怎么样对曲线建模?

4:需要分段拟合吗?怎么分?如何保证分段间的连续性和光滑性?……

 

这些都需要精通数学的你来参与,共同讨论其解决方法。

 

 

法律声明:本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。若您需要转载,请务必注明文章出处为CSDN以保障网站的权益;请务必注明文章作者为swimmingfish2004(http://blog.csdn.net/swimmingfish2004 ),并向[email protected]发送邮件,标明文章位置及用途。转载时请将此法律声明一并转载,谢谢!

你可能感兴趣的:(算法,null,mfc,each,byte,图形)