
Introduction
Ah! Marching ants! You've gotta love them! They add a sense of quality to your software. You know marching ants? When you select something in Photoshop, Paint Shop Pro or GIMP, the selected area is indicated by an animated dashed line. It looks like there are ants marching along that boundary.
But how on earth do you implement such beasts? This article will show you how to do it.
First attempt
When I wanted to implement marching ants, my first attempt was very simple. I created an array of SBytes. This array represented the selected area. The array was initialized with 0s. A function AddRectangle() changed values in this array to 1s. The 1s indicated the pixels which belonged to the selected area. To draw the outline of the selected area, I scanned the array from top to bottom and from left to right. At each pixel, I tested whether this pixel was an edge pixel or not. Testing if a pixel was an edge pixel is very simple: if the value of the current pixel was 1 and a neighboring pixel was 0, the pixel was an edge pixel. When an edge pixel was found, I set the the value of the array cell to 2.
Now, I had an array with all edge pixels. I converted them to paths. A path consisted of one ore more lines (if you select a rectangular area and you subtract another area inside that area, you need two paths, so in this case, we would have 2 paths with 4 lines). I wrote a function that could draw those paths with dashed lines. Using a timer, I drew each path with a different offset, so that the dashes seemed to move. My ants marched.
But I didn't like my solution that much. I wondered how Photoshop and other programs did it. My solution was also slow.
Second attempt
I downloaded the GIMP source code and studied it a bit. I started from scratch and this second solution is what you can download. The screenshot above also shows what to expect. Note that I only show how to implement marching ants, I don't provide a complete program or even a complete control.
Included in the source code is a program called Fotowinkel (fotowinkel.cs). This program creates a form and adds a PaintBox control to it. (If you wanted to create some sort of paint program, you could start using this control.) PaintBox provides scrollbars for the InnerPaintBox control. InnerPaintBox contains a PaintBoxImage which, in turn, contains a Selection object (Selection.cs). PaintBoxImage draws the background and centers the current image (you cannot specify an image, but this is easy to implement). Use the mouse to select rectangular areas. Each selected area will be added to the current selected area and marching ants will indicate the edges.
Selection.cs contains the selection engine, so to speak. The Selection object contains a bitmap, which is the selection mask. When you select a rectangle, the method AddRectangle() will draw a rectangle on the selection mask. GetOutline() is called at the end of the method. GetOutline() scans the selection mask from top to bottom and then again from left to right, searching for the edges of the current selected area. The lines are animated by drawing the lines using different texture brushes. In the constructor of the Selection object, you can see how I create 8 different texture brushes. The method Animate() will change the current brush when it's called. I borrowed this technique from the GIMP.
Some thoughts
What's the difference between my first attempt and my second? In the first attempt, I have to change the values of the selection mask to indicate what pixels make up the boundary of the selection. This is annoying, because, in practice, you would create a copy of the selection mask when updating the selection outline, which means, more memory (or you could keep an array of the old pixel values, but that would create other problems, such as detecting if an edge pixel already belongs to an edge or not). The advantage of this first technique is that the ants march clockwise, in the second solution definitely not.
Another problem is performance. On my (old) computer, it takes about 2 seconds to update the selection, and we're only talking about a 256x256 selection mask! GDI+ is slow. But using GDI using DllImport is no option because GDI+ uses anti-aliasing.
The demo project allows you to select rectangular areas, but the code is able to handle all kinds of shapes because the engine doesn't know about shapes, it only knows about pixels. If you want to experiment, write a method AddEllipse(Point x, Point y) or something like that and draw an ellipse on the selection mask bitmap. Call GetOutline() and you're done!
A final thought concerns the selection mask bitmap: I use new Bitmap() to create a selection mask, but in reality, this should be a grayscale bitmap.
Update (March 2, 2004)
I updated the source code. To speed up the GetOutline() method, I created a new class called SelectionMask. SelectionMask wraps a Bitmap and provides 4 methods to manipulate that bitmap: Lock(), GetPixel(), SetPixel() and Unlock(). GetPixel() and SetPixel() access the bitmap data directly using unsafe pointers. Wow! This really gives a "turbo boost".
I also added a method AddEllipse(). The demo project now has a menu where you can choose how to select an area: rectangular or elliptical.
|
|
 |
 | what if it's complex polygon? how do we march ant along the line with different slopes scottchu.tw | 18:53 9 Dec '09 |
|
 |
If the selection is complex polygon, how to find all interioir points? It seems that the simple outline scan in your source can't solve this case properly?
I want to march ant along the line slope like splashUp did(http://www.splashup.com/splashup/). What I can think of is implementing line algorithm & draw dashed or dotted line pattern by myself.
I'm currently using Flex & ActionScript. But they are lack of line pattern. Does C sharp have better graphic API? If it provides line pattern, does it do pattern along the line slope?
|
|
|
|
 |
 | faster animate function ShyH | 0:55 15 Nov '05 |
|
 |
you can use the %(mod) operator instead of if to speed Animate func.
public void Animate(Graphics g, Point offset) { DrawOutline(g, offset); _CurMAB++; _CurMAB %= 8; }

-- modified at 5:55 Tuesday 15th November, 2005
|
|
|
|
 |
 | Is it a bug? rutmir | 3:42 14 Nov '05 |
|
 |
Hi! Thanks for so cool control! I've included it in my project. But when I was importing your code in mine, I found some a bug in OnPaintBackground of the InnerPaintBox class. This bug raised when I used non rectangle bitmaps to show in the InnerPaintBox. Instead of: // top h = (clientsize.Height - imgsize.Width) / 2; // middle left y = (clientsize.Height - imgsize.Width) / 2; // middle right y = (clientsize.Height - imgsize.Width) / 2; // bottom y = (clientsize.Height - imgsize.Width) / 2 + imgsize.Height; h = (clientsize.Height - imgsize.Width) / 2 + 1; // 7 / 2 = 3 + 4 you should change all the widths to heights. With best regards, Ruslan
|
|
|
|
 |
|
 |
Nice to hear you can use it. I asked thecodeproject to update the download file with the modifications.
|
|
|
|
 |
 | Adding straight line Craig D. | 17:01 25 Sep '05 |
|
 |
This is awesome, great job. The elipse was a nice touch, can you tell me how too add straight lines too. If it's easier, I could start with a rectangle and intersect two points with a straight line that pivits from the origin. Thanks
|
|
|
|
 |
|
 |
Add a method AddLine() to Selection (in selection.cs). Take a look at AddEllipse() and AddRectangle(): they just draw a black shape (whatever) on the selection mask. That's all there is to it. Of course, it's up to you to implement the mousing stuff. You also have to add a SelectionStyle and change the method UpdateSelection() (in Fotowinkel.cs).
|
|
|
|
 |
|
 |
Thanks, after I posted the message I did exactly that. So I have a line function now to. Do you want me to posted the code or email it to you??
What I need to look into now is how to determine if a pixel is inside or outside the selection area to do a crop. I think there is already some functionality to do that test. Then maybe display an anchor at each corner point and be able to move it. Not sure how complex I want to get.
|
|
|
|
 |
|
 |
This is a nineties theory. DOS was still used in those days. When you changed the values in the palette (256 colors), the colors on the screen immediately changed too. This technique could be used in a game for explosions or so. But now we're in the vincents (2000+). What palette would you want to change? We use 24 colors. You would have to use a 256 indexed color bitmap. And you would have to call Invalidate() anyway to update the window. And even if you had access to "the system palette", you would change ALL colors of ALL windows on the screen, not just the colors of the ants.
|
|
|
|
 |
 | even more Secret. (Color cycling) Dnt | 14:27 14 Apr '04 |
|
 |
I dont know if this is possible, but i think someone is smarter then me and can implement this.. .. this is a solution i learned from the "demo" community back in the early 1990´s.
The logic behind the solution is that its faster to change one color value in the grafic-cards DAC (or the Bitmap pallet) then to change many individual pixels.
As im a beginner to CSharp and its syntax i´ll explain in "pseudo" C#.. 
// make a Array with colors, length = 2 times the length of the "ant" // in pixels -1 (zeroBased), in this example ant.Length = 4 Color[] colors = new Color[7];
// init. colors. 1/2 Black, 1/2 White for (Int32 i=0; i<8 ; i++) { if (i<4) { colors[i] = System. (0,0,0) } else { colors[i] = System. (255,255,255) } }
// now we have a pallett looking like this: color 0: black color 1: black color 2: black color 3: black color 4: white color 5: white color 6: white color 7: white
// the Color pattern for the texture brush String pattern = new String; pattern = "01234567" + "12345670" + "23456701" + "34567012" + "45670123" + "56701234" + "67012345" + "70123456";
Now, use the pattern to fill the edges.
To get the "ants" to move all we have to do is to itterate over the first 4 colors.
loop { // change Black to White for (Int32 i=0; i<4 ; i++) { colors[i] = System. (255,255,255); colors[i+4] = System. (0,0,0); } // change white to black for (Int32 i=0; i<4 ; i++) { colors[i] = System. (0,0,0); colors[i+4] = System. (255,255,255); } }
Thats it I guess.. the ants should appere to "move" now and the performace will not be limited by the lenght(pixels) of the edge, in theory..
Thoughts are Contagious A meme is a contagious pattern of information that replicates by symbiotically infecting human minds and altering their behavior, causing them to propagate the pattern.
|
|
|
|
 |
 | Another Attempt jbialek | 0:14 25 Mar '04 |
|
 |
Hi you all, I figued another solution: I created a TextureBrush based on an Image dynamically created (white background, black bar wandering from left to right). Then I rotated the Texturebrush and voilà there are those marching ants 
Code: 
int blockpos = tick %10; Bitmap block = new Bitmap(10,10); Graphics blockg = Graphics.FromImage(block); blockg.Clear(Color.White); blockg.FillRectangle(Brushes.Black,blockpos,0,5,10); blockg.FillRectangle(Brushes.Black,blockpos-10,0,5,10); blockg.Dispose();
TextureBrush b = new TextureBrush(block); b.RotateTransform(45); Pen p = new Pen(b,1);
block.Dispose();
C is sharp
|
|
|
|
 |
 | Another marching ants technique: DashOffset Frank Hileman | 5:48 2 Mar '04 |
|
 |
The Pen class has a property called DashOffset. By incrementing this property cyclicly, you can achieve a marching ants effect on any shape you draw with that Pen. In this screenshot of a VG.net animation demo, you can see how we used DashOffset to illustrate flow through a pipe. This pipe is straight, but you can use the effect on curved shapes as well:
http://www.vgdotnet.com/build_reuse_meter.shtml#animation_sample
-Frank
check out VG.net: www.vgdotnet.com An animated vector graphics system integrated in VS.net
|
|
|
|
 |
 | slightly slow response Joel Holdsworth | 6:56 1 Mar '04 |
|
 |
Just tried out your demo - the ants certainly march quite nicly... but after drawing a rectangle the program locks for about a second on my 2.6GHz Pentium. Is there any way of improving the performance here?
Joel Holdsworth
|
|
|
|
 |
|
 |
Hi,
yes, on my computer it took about 2 seconds. I've just profiled the code and as expected, the performance hit is in GetPixel() and SetPixel(). I updated the source code and now I use unsafe pointers to manipulate the bitmap directly. Now it takes about 1 second on my computer, so 2x faster!
|
|
|
|
 |
|
 |
Much Better.. I tried this awhile ago and it was very slow but now it seems to be better 
Good job...
Matthew Hazlett Windows 2000/2003 MCSE Never got an MCSD, go figure...
|
|
|
|
 |
|
|