如何實現PS中使用選擇框、套索等工具后形成的螞蟻線效果


用過PS的同學都知道使用選擇框、套索、魔棒工具選擇區域后,邊線會有一個黑白條紋交替移動產生的動畫,俗稱螞蟻線,作用是更明顯的突出選擇區域范圍。


---原理---

通過觀察PS,發現:一、線條可以任意復雜;二、並且不是只往線條指向的一個方向移動;三、不同位置角度的黑白線段長度不同;四、可以跟隨圖片縮放自然的產生不同效果;五、當生成很多線條時,CPU占用率也很低(比如在復雜圖像中使用魔棒時)。

如何做到的呢?一開始我把這些"線"當成一條條黑白相間的線條處理,發現:一、達不到PS的效果;二、當線條復雜時,處理的復雜性大大增加以至於難以完成;三、很難跟隨圖片縮放自然變化;四、線段多時CPU占用偏高。

繼續觀察PS,發現在拖動選擇區域時,動畫效果會暫停,但向不同方向拖動時還是會產生某種動畫效果,並且方向不同效果也不同,這讓我想起以前看過的一個系列視頻——把一塊帶有特定形狀縫隙的板子放到畫有明暗條紋背景的平面上,以一定的速度沿着一定方向拖動板子,會形成某種好玩的動畫效果。這啟發我想到PS會不會也是這樣實現的動畫效果?——畫布是板子,邊線是板子上縫隙,畫布下面是斜45度黑白條紋排列的平面背景(見[圖1]),畫布拖不動,反向拖背景效果一樣,於是拖動背景產生動畫效果。

                   圖1

上述推測對不對呢?單條"縫"太窄看不清,撬開"板子"看看吧——我想到的辦法是在PS中左右來回畫一些上下距離一個像素的直線,當這些"縫隙"密密麻麻連起來時,也就起到了撬開"板子"的效果。鼠標+直尺?太蛋疼手一抖就連成一片前功盡棄了。想起以前用過一個叫按鍵精靈的軟件,可以通過腳本控制鼠標鍵盤,正合適在這里用。於是下載安裝寫腳本保存退出F10運行OK——成功撬開,看看結果(見[圖2])——斜45度4個像素寬的黑白相間條紋不停滾動形成動畫,證實了我前面的推測是正確的。

                                                   圖2
---實現---

知道了原理,實現應該就不難了,但如果要像PS里一樣高效率的實現還需要一點技巧。我們知道在電腦里不用真的拖動什么東西,只要模擬出相同效果即可。想像一下,要模擬拖動效果,只要在一定時間間隔內用正確的顏色不斷填充所有"縫隙",然后不斷循環應該就可以達到了。產生循環簡單:只要一個定時器即可;重點是如何正確高效的在不同位置計算並顯示相應顏色,顯示顏色也比較簡單可以直接用SetPixel函數,或者使用更快的方法:比如在GDI中直接寫DIB內存。

現在問題就剩下——給定任意一個坐標點,如何快速准確的計算該點顏色值。一時沒有頭緒,畫個草圖看看(見[圖3]同時參考[圖1]),首先注意到的是外面的邊線(即x/y軸,左上角為原點):0-3是黑色、4-7是白色、8-11黑色、12-15是白色,依此類推可以通過簡單的公式計算出邊線點是黑色還是白色:((x or y) % 8) < 4 為黑色,否則是白色。邊線上點的問題解決了,那其他位置的點有沒有簡單的計算方法呢?繼續觀察[圖4]:因為所有斜邊都是45度,所以和x/y軸組成了一個等腰直角三角形,而等腰直角三角形有如下性質:所有斜邊上的點的x坐標加y坐標的和都相等,利用這個性質可以很容易的求出與任意點在同一斜邊上的邊線點的坐標值,這樣結合上面的邊線點顏色公式,可以得到任意點顏色公式:((x + y) % 8) < 4 為黑色,否則是白色。

               

                                                   圖3                                                                                                                             圖4

總結一下:先得到邊線點(x/y軸、直角邊)的顏色公式(很直觀),再根據等腰直角三角形斜邊的性質,將任意點轉化成同一斜邊上的邊線點,使未知的求任意點顏色值的問題簡化成已知的求邊線點顏色值的問題,最后綜合並簡化步驟得到任意點的顏色公式。


---代碼---

可以寫代碼了,鑒於黑(0)白(0FFFFFFh)兩色的特殊性,最后還可以把顏色判斷優化掉
上面啰嗦了那么多,代碼其實很簡單,用c甚至只要一行:
Color = ((((X + Y - PixShift) & 4) >> 2) - 1) & 0xFFFFFF; // 用定時器讓PixShift在0-7之間循環,更簡單:PixShift = (++PixShift) & 7;

具體實現看代碼中以下兩個回調函數:
LineDDAProc
Lasso_TimerProc

匯編版demo中有一些關於原理的演示,點擊右鍵觀看。(下載地址:https://files.cnblogs.com/files/hhh2000/lasso.zip)

 

效果圖1:

  

匯編版效果圖1: 

 

 匯編版效果圖2:


---一點感想---

1、使用我的這種方法只要知道點的坐標,然后通過坐標計算出點的顏色再寫入即可,操作簡單。
2、這樣實現速度也比較快,因為操作簡單,指令少了,自然就快了;同時顯示幾千條邊、百萬個點也很流暢,而且有優化余地——保存點坐標等。
3、試了幾個能實現螞蟻線的圖形軟件,還是PS實現的好:准確、流暢、不占用資源,就是不知道具體如何實現的。


---參考和學習資料--- (其實寫代碼沒參考到什么,都是寫完代碼以后,寫這篇文章的時候找的,然后中文資料有用的基本沒有)

1、wiki http://en.wikipedia.org/wiki/Marching_ants

2、一篇關於GIMP圖形處理軟件中螞蟻線簡單原理說明以及存在問題的文章 https://banu.com/blog/24/fun-with-marching-ants/

3、 一個JS網頁版的實現 http://codepen.io/sstephenson/pen/LrJIG

4、一個C#版的實現 http://www.codeproject.com/Articles/6269/The-Secret-of-Marching-Ants 原理差不多(抽板子),用的是八個8*8pixel生成patterns的方法

 

c寫的演示程序,VC++6編譯

  1 // Windows Header Files:
  2 #include <windows.h>
  3 #include <windowsx.h>
  4 
  5 // C RunTime Header Files
  6 #include "stdio.h"
  7 
  8 // Global Variables:
  9 TCHAR szBuffer[256];
 10 
 11 // Foward declarations of functions included in this code module:
 12 LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
 13 
 14 int APIENTRY WinMain(HINSTANCE hInstance,
 15                      HINSTANCE hPrevInstance,
 16                      LPSTR     lpCmdLine,
 17                      int       nCmdShow)
 18 {
 19     WNDCLASSEX wc;
 20     MSG msg;
 21     HWND hWnd;
 22 
 23     memset(&wc, 0, sizeof(wc));
 24     wc.cbSize      = sizeof(wc);
 25     wc.style       = 0;
 26     wc.lpfnWndProc = (WNDPROC)WndProc;
 27     wc.hInstance   = hInstance;
 28     wc.lpszClassName = "Draw";
 29     wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
 30 
 31     RegisterClassEx(&wc);
 32 
 33     hWnd = CreateWindowEx(0, wc.lpszClassName, wc.lpszClassName, WS_OVERLAPPEDWINDOW, 100, 150, 650, 455, NULL, NULL, hInstance, NULL);
 34 
 35     ShowWindow(hWnd, SW_SHOWNORMAL);
 36     UpdateWindow(hWnd);
 37 
 38     while (GetMessage(&msg, NULL, 0, 0))
 39     {
 40         if (!TranslateAccelerator(msg.hwnd, 0, &msg))
 41         {
 42             TranslateMessage(&msg);
 43             DispatchMessage(&msg);
 44         }
 45 
 46     }
 47     
 48     return msg.wParam;
 49 }
 50 
 51 // Global Variables:
 52 HDC hDrawDC;
 53 HGDIOBJ hBmp;
 54 HGDIOBJ hOldBmp;
 55 int* lpPt;
 56 int nPoint;
 57 int nDot;
 58 UINT PixShift;
 59 UINT* lpBits;
 60 BITMAPINFO bmi;
 61 int iWidth;
 62 
 63 // Foward declarations of functions included in this code module:
 64 void CALLBACK Lasso_TimerProc(HWND, UINT, UINT, DWORD);
 65 void SaveDot(int*, int*, POINT*);
 66 void ShowLineDot (int*, int);
 67 void CALLBACK LineDDAProc(int, int, LPARAM);
 68 HGDIOBJ GetDIB (HDC, int, int, void**);
 69 
 70 WINGDIAPI COLORREF WINAPI SetDCBrushColor(HDC, COLORREF);
 71 #define DC_BRUSH 18
 72 //////////////////////////////////////////////////////////////
 73 // 
 74 //////////////////////////////////////////////////////////////
 75 LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
 76 {
 77     POINT pt;
 78     RECT Rect;
 79     PAINTSTRUCT ps;
 80     HDC hOldDC;
 81 
 82     switch (uMsg)
 83     {
 84         case WM_CREATE:
 85             // 創建兼容DC & 位圖
 86             hOldDC = GetDC(hWnd);
 87             hDrawDC = CreateCompatibleDC(hOldDC);
 88 
 89             GetWindowRect(GetDesktopWindow(), &Rect);
 90             hBmp = GetDIB(hDrawDC, Rect.right, Rect.bottom, &lpBits);
 91             if (hBmp == 0)
 92             {
 93                 MessageBox(0, 0, "無法取得設備無關位圖", MB_APPLMODAL);
 94                 exit(0);
 95             }
 96             hOldBmp = SelectObject(hDrawDC, hBmp);
 97             ReleaseDC(hWnd, hOldDC);
 98 
 99             // 設置背景畫刷
100             SetDCBrushColor(hDrawDC, RGB(0x5B,0x5B,0x5B));
101             SelectObject(hDrawDC, GetStockObject(DC_BRUSH));
102 
103             lpPt = (int*)malloc(10000*sizeof(POINT));
104             if(!lpPt)
105             {
106                 exit(0);
107             }
108             nPoint = 0;
109             nDot = 0;
110             iWidth = bmi.bmiHeader.biWidth * 4;
111 
112             // 設置定時器
113             PixShift = SetTimer(hWnd, 1, 100, Lasso_TimerProc);            
114             break;
115         case WM_PAINT:
116             BeginPaint(hWnd, &ps);
117             
118             // 畫背景
119             CopyRect(&Rect, &ps.rcPaint);
120             InflateRect(&Rect, 1, 1);
121             Rectangle(hDrawDC, Rect.left, Rect.top, Rect.right, Rect.bottom);
122             
123             // 畫點
124             nDot = 0;
125             ShowLineDot(lpPt, nPoint);
126 
127             sprintf(szBuffer, "%d : %d", nPoint, nDot);
128             TextOut(hDrawDC, 0, 0, szBuffer, lstrlen(szBuffer));
129 
130             // 翻轉到屏幕
131             pt.x = ps.rcPaint.right - ps.rcPaint.left;
132             pt.y = ps.rcPaint.bottom - ps.rcPaint.top;
133             BitBlt(ps.hdc, ps.rcPaint.left, ps.rcPaint.top, pt.x, pt.y, hDrawDC, ps.rcPaint.left, ps.rcPaint.top, SRCCOPY);
134 
135             EndPaint(hWnd, &ps);
136             break;
137         case WM_LBUTTONDOWN:
138             SetCapture(hWnd);
139             nPoint = 0;
140             nDot = 0;
141             // 保存鼠標位置
142             pt.x = GET_X_LPARAM(lParam);
143             pt.y = GET_Y_LPARAM(lParam);
144             SaveDot(lpPt, &nPoint, &pt);
145             break;
146         case WM_MOUSEMOVE:
147             if (wParam & MK_LBUTTON && GetCapture() == hWnd)
148             {
149                 // 保存鼠標軌跡
150                 pt.x = GET_X_LPARAM(lParam);
151                 pt.y = GET_Y_LPARAM(lParam);
152                 GetClientRect(hWnd, &Rect);
153                 if (PtInRect(&Rect, pt))
154                 {
155                     SaveDot(lpPt, &nPoint, &pt);
156                 }
157                 InvalidateRect(hWnd, 0, FALSE);
158             }
159             break;
160         case WM_LBUTTONUP:
161             if (GetCapture())
162             {
163                 // 保存起始位置
164                 if (nPoint > 0)
165                 {
166                     SaveDot(lpPt, &nPoint, (POINT*) lpPt);
167                 }
168             }
169             ReleaseCapture();
170             InvalidateRect(hWnd, 0, FALSE);
171             break;
172         case WM_ERASEBKGND:
173             return TRUE;
174         case WM_DESTROY:
175             KillTimer(hWnd, 1);
176             DeleteObject(SelectObject(hDrawDC, hOldBmp));
177             DeleteDC(hDrawDC);
178             PostQuitMessage(0);
179             break;
180         default:
181             return DefWindowProc(hWnd, uMsg, wParam, lParam);
182     }
183     return 0;
184 }
185 
186 //////////////////////////////////////////////////////////////
187 // 定時器回調
188 //////////////////////////////////////////////////////////////
189 void CALLBACK Lasso_TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
190 {
191     PixShift = (++PixShift) & 7;
192     InvalidateRect(hwnd, 0, FALSE);
193 }
194 //////////////////////////////////////////////////////////////
195 // 保存坐標點
196 //////////////////////////////////////////////////////////////
197 void SaveDot(int* lpPt, int* lpnPoint, POINT* lpstPoint)
198 {
199     lpPt[*lpnPoint*2] = lpstPoint->x;
200     lpPt[*lpnPoint*2+1] = lpstPoint->y;
201     (*lpnPoint)++;
202 }
203 //////////////////////////////////////////////////////////////
204 // 線轉點並畫點
205 //////////////////////////////////////////////////////////////
206 void ShowLineDot (int* lpPt, int nPoint)
207 {
208     int i;
209     for (i = 1; i < nPoint; i++)
210     {
211         LineDDA(lpPt[i*2-2], lpPt[i*2-2+1], lpPt[i*2], lpPt[i*2+1], LineDDAProc, 0);
212     }
213 }
214 //////////////////////////////////////////////////////////////
215 // 將窗口坐標轉換為DIB段內存地址(32bit)
216 // 行的長度為四字節的倍數,不足的0補齊
217 //////////////////////////////////////////////////////////////
218 void SetDIBPixel(HDC hDrawDC, int X, int Y, COLORREF Color)
219 {
220     lpBits[(X + ((bmi.bmiHeader.biHeight - Y - 1) * bmi.bmiHeader.biWidth))] = Color;
221 }
222 //////////////////////////////////////////////////////////////
223 // 回調函數(畫點時)
224 //////////////////////////////////////////////////////////////
225 void CALLBACK LineDDAProc(int X, int Y, LPARAM lpData)
226 {
227     UINT Color;
228     // 根據位置計算顏色,形成動畫效果
229     Color = X + Y - PixShift;
230     Color = (((Color & 4) >> 2) - 1) & 0xFFFFFF;
231     // 計數 & 畫點
232     nDot++;
233     SetDIBPixel(hDrawDC, X, Y, Color);
234     //SetPixel(hDrawDC, X, Y, Color);
235 }
236 //////////////////////////////////////////////////////////////
237 // 取得DIB
238 //////////////////////////////////////////////////////////////
239 HGDIOBJ GetDIB (HDC hDrawDC, int Width, int Height, void** lpBits)
240 {
241     RECT _Rect;
242     HGDIOBJ _hBmp;
243 
244     SetRect(&_Rect, 0, 0, Width, Height);
245     // 填充DIB段結構
246     RtlZeroMemory(&bmi, sizeof(BITMAPINFO));
247     bmi.bmiHeader.biSize        = sizeof(bmi.bmiHeader);
248     bmi.bmiHeader.biHeight      = Height;
249     bmi.bmiHeader.biWidth       = Width;
250     bmi.bmiHeader.biPlanes      = 1;
251     bmi.bmiHeader.biBitCount    = 32;
252     bmi.bmiHeader.biCompression = BI_RGB;
253     // 創建hBmp
254     _hBmp = CreateDIBSection(hDrawDC, &bmi, DIB_RGB_COLORS, lpBits, 0, 0);
255     if (_hBmp == 0)
256     {
257         *lpBits = 0;
258         return 0;
259     }
260     // 取得hBmp大小
261     GetDIBits(hDrawDC, _hBmp, 0, 0, 0, &bmi, 0);
262     return _hBmp;
263 }
View Code

 

https://files.cnblogs.com/files/hhh2000/lasso.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM