如何实现PS中使用选择框、套索等工具后形成的蚂蚁线效果

用过PS的同学都知道使用选择框、套索、魔棒工具选择区域后,边线会有一个黑白条纹交替移动产生的动画,俗称蚂蚁线,作用是更明显的突出选择区域范围。

---原理---

通过观察PS,发现:一、线条可以任意复杂;二、并且不是只往线条指向的一个方向移动;三、不同位置角度的黑白线段长度不同;四、可以跟随图片缩放自然的产生不同效果;五、当生成很多线条时,CPU占用率也很低(比如在复杂图像中使用魔棒时)。

如何做到的呢?一开始我把这些"线"当成一条条黑白相间的线条处理,发现:一、达不到PS的效果;二、当线条复杂时,处理的复杂性大大增加以至于难以完成;三、很难跟随图片缩放自然变化;四、线段多时CPU占用偏高。

继续观察PS,发现在拖动选择区域时,动画效果会暂停,但向不同方向拖动时还是会产生某种动画效果,并且方向不同效果也不同,这让我想起以前看过的一个系列视频——把一块带有特定形状缝隙的板子放到画有明暗条纹背景的平面上,以一定的速度沿着一定方向拖动板子,会形成某种好玩的动画效果。这启发我想到PS会不会也是这样实现的动画效果?——画布是板子,边线是板子上缝隙,画布下面是斜45度黑白条纹排列的平面背景(见[图1]),画布拖不动,反向拖背景效果一样,于是拖动背景产生动画效果。

上述推测对不对呢?单条"缝"太窄看不清,撬开"板子"看看吧——我想到的办法是在PS中左右来回画一些上下距离一个像素的直线,当这些"缝隙"密密麻麻连起来时,也就起到了撬开"板子"的效果。鼠标+直尺?太蛋疼手一抖就连成一片前功尽弃了。想起以前用过一个叫按键精灵的软件,可以通过脚本控制鼠标键盘,正合适在这里用。于是下载安装写脚本保存退出F10运行OK——成功撬开,看看结果(见[图2])——斜45度4个像素宽的黑白相间条纹不停滚动形成动画,证实了我前面的推测是正确的。

---实现---

知道了原理,实现应该就不难了,但如果要像PS里一样高效率的实现还需要一点技巧。我们知道在电脑里不用真的拖动什么东西,只要模拟出相同效果即可。想像一下,要模拟拖动效果,只要在一定时间间隔内用正确的颜色不断填充所有"缝隙",然后不断循环应该就可以达到了。产生循环简单:只要一个定时器即可;重点是如何正确高效的在不同位置计算并显示相应颜色,显示颜色也比较简单可以直接用SetPixel函数,或者使用更快的方法:比如在GDI中直接写DIB内存。

现在问题就剩下——给定任意一个坐标点,如何快速准确的计算该点颜色值。一时没有头绪,画个草图看看(见[图3]同时参考[图1]),首先注意到的是外面的边线(即x/y轴,左上角为原点):0-3是黑色、4-7是白色、8-11黑色、12-15是白色,依此类推可以通过简单的公式计算出边线点是黑色还是白色:((x or y) % 8) < 4 为黑色,否则是白色。边线上点的问题解决了,那其他位置的点有没有简单的计算方法呢?继续观察[图3]:因为所有斜边都是45度,所以和x/y轴组成了一个等腰直角三角形,而等腰直角三角形有如下性质:所有斜边上的点的x坐标加y坐标的和都相等,利用这个性质可以很容易的求出与任意点在同一斜边上的边线点的坐标值,这样结合上面的边线点颜色公式,可以得到任意点颜色公式:((x + y) % 8) < 4 为黑色,否则是白色。

总结一下:先得到边线点(x/y轴、直角边)的颜色公式(很直观),再根据等腰直角三角形斜边的性质,将任意点转化成同一斜边上的边线点,使未知的求任意点颜色值的问题简化成已知的求边线点颜色值的问题,最后综合并简化步骤得到任意点的颜色公式。

---代码---

可以写代码了,鉴于黑(0)白(0FFFFFFh)两色的特殊性,最后还可以把颜色判断优化掉
上面啰嗦了那么多,代码其实很简单,用c甚至只要一行:
Color = ((((X + Y - PixShift) & 4) >> 2) - 1) & 0xFFFFFF; // 用定时器让PixShift在0-7之间循环,更简单:PixShift = (++PixShift) & 7;

具体实现看代码中以下两个回调函数:
LineDDProc
Lasso_TimerProc

汇编版的demo中有一些关于原理的演示,点击右键观看。

---参考和学习资料--- (其实写代码没参考到什么,都是写完代码以后,写这篇文章的时候找的,然后中文资料有用的基本没有)

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的方法

试了几个能实现蚂蚁线的图形软件,还是PS实现的好:准确、流畅、不占用资源,就是不知道具体如何实现的。

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 }

时间: 2024-10-25 05:08:41

如何实现PS中使用选择框、套索等工具后形成的蚂蚁线效果的相关文章

cocos2dx2.2.2登录场景中Checkbox选择框的实现

在前两篇文章中,我们介绍了在注册场景中需要用到的输入框及弹出框的实现方式,这两篇文章中介绍的内容在登录场景同样会用到.而我们经常会在登录场景中见到的另一种元素就是自动登录或者记住密码的Checkbox选择框.那么,接下来就让我们看看这个选择框如何实现. 首先,我们先看一下效果 我们需要的就是一个Checkbox选择框,后面加上“自动登录”或者其他的文字.效果就是点击选择框或文字时,Checkbox的状态会进行切换:同时在程序中还要知道当前选择框的状态,只要我们能够实现这几点,这个功能就完成了.

如何在vue+element中实现选择框和穿梭框的根据拼音以及拼音首字母以及汉字的模糊搜索

1.汉字: 直接添加对应的 filterable 2.拼音: 穿梭框和选择器的实现方式有所不同 选择器: <1>下载pinyin-match:   npm i --save pinyin-match <2>在main.js引入并注册为全局属性 import PinyinMatch from 'pinyin-match'; Vue.prototype.$pinyinmatch = PinyinMatch; <3>为需要的选择器添加自定义过滤方法 :filter-metho

js中,表单中的选择框学习与使用总结

选择框脚本 选择框是通过<select>和<option>元素来创建.下面从对选择框的几个操作,来学习,总结Js中操作选择框的方法 一.选择选项 (1)只允许选择一项的选择框,访问选项中的最简单方式,就是使用选择框的selectIndex属性. selectedIndex 属性可设置或返回下拉列表中被选选项的索引号.设置selectedIndex会导致取消以前的所有选项并选择指定的那一项,而读取selectedIndex则只会返回选中项第一项的索引值.注意:若允许多重选择,则仅会返

左右选择框 js插件

随着项目的进展,测试工程师在更多的浏览器中兼容性测试中,发现有些浏览器不支持option的触发事件,这就造成了先前一篇博文bootstrap 左右框多项选择示例 中左右选择框的失效,于是我就由原先的select-option结构 改成了 现在的 ul-li 结构,并写成了js插件的形式,方便以后调用和修改,并在多个浏览器测试中得到验证: 实现的页面如下: jsp页面的修改如下: 1 <div id="province_dchannel"> 2 <div class=&

jquery.chosen.js下拉选择框美化插件项目实例

由于之前使用的bootstrap-select插件是建立在bootstrap基础上的,实际使用到项目中的时候,与我们使用的ace-admin(基于bootstrap)存在样式冲突,导致下拉框的样式发生变化.为了界面的美观,不得已查资料寻找另外的插件. 使用jquery.chosen.js下拉选择框美化插件同样也能达到类似效果 完成效果如下 实现步骤如下 1.导入相关文件 <link rel="stylesheet" href="${ctxStatic}/css/chos

20140527-在jQuery中设置文本框回车事件

20140527-在jQuery中设置文本框回车事件 该代码要完成的效果是,用户在文本框输入完毕以后,按下回车键,立即触发"搜索"的单击事件. 例如: $("#search_user_name").keydown(function (e) {         // search_user_name为文本框ID         var curKey = e.which;         if (curKey == 13) {             // search

点击事件中实现弹出一个选择框(如选择网络设置、选择电话短信联系方式)

1.网络设置 public void checkNetwork(){ //获取连接的管理对象 ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); //获取当前正在使用的网络 NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); //判断网络是

解决v-for中遍历多个el-select时,下拉选择框同步选择问题

当v-for遍历多个el-select时,由于v-model绑定的值为同一个,导致下拉选择时多个下拉选择框同步选择问题 如图是v-model绑定的同一个seatValue数据: 解决办法: 一.为el-select绑定不同的v-mode值,将v-for传递的index值绑定在v-model的参数上( v-model="seatValue[index]" ) 二.通过axios获取数据时,创建一个新的seatValue空数组,通过map遍历获取到的fourthContentArr里的每一

表单中单选、多选、选择框值的获取及表单的序列化

总结了下在表单处理中单选.多选.选择框值的获取及表单的序列化,写成了一个对象.如下: 1 var formUtil = { 2 // 获取单选按钮的值,如有没有选的话返回null 3 // elements为radio类的集合的引用 4 getRadioValue:function(elements) { 5 var value = null; // null表示没有选中项 6 // 非IE浏览器 7 if(elements.value != undefined && elements.v