Someone has recently been using C # to write a Windows-like drawing tool that is stuck in the color-filled section. Employers and employees want him to use the seed fill algorithm coloring (do not call the Windows provided API, otherwise exercise a yarn), now I have implemented this function, the program is highly efficient. Here's a little bit about how to do this.
The program is written in vb.net, C # is similar (and does not require the use of marshal classes to access unmanaged resources, more convenient). The results of the program's operation are as follows:
Seed filling algorithm Plainly is the width first search algorithm (BFS), if you do not know what this is, it means that your data structure is not learned at all, please add the appropriate knowledge.
The first step: to implement the "pencil" tool
We define the following global variables (private members of the form Class) to see what the name means:
Copy Code code as follows:
Private Enum DrawStyle
Drawing = 0
Fill = 1
Drawdragging = 2
End Enum
Private _fillcolor () as Color = {Color.Blue, color.green, Color.Red, Color.lightgray, Color.lightpink, Color.lightskyblue, _
Color.greenyellow, Color.gold, Color.lightseagreen}
Private _drawstyle as DrawStyle = drawstyle.drawing
Private _imgmain as Bitmap
Private _g as Graphics
Private _lastposition as Point
Private _drawingpen as Pen
The color of the fill in this program is randomly determined (all too lazy to do a color-selective function), the color can be filled in the _fillcolor array. _drawstyle defines the current drawing mode (drawing means the pencil tool, but not pressed, fill indicates ready to fill, drawdragging means the mouse is pressed and dragged).
_imgmain is the picture that is drawn, _g is the graphics object created on this bitmap.
It should be noted that the drawing and Drawing2D classes do not provide a way to draw points, and we need to simulate them by drawing a line or drawing a rectangle. As for the role of _lastposition, because of the mouse drag and drop process, if the speed is too fast, then MouseMove events in the coordinate points (each MouseMove event is triggered) is not continuous, so we need to the current point and the previous mouse position between a line, Otherwise, the lines drawn are discontinuous.
MouseDown, MouseMove, and MouseUp implement the basic functions of the Pencil tool, as follows:
Copy Code code as follows:
Private Sub Picturebox1_mousedown (sender as Object, E as MouseEventArgs) Handles Picturebox1.mousedown
If checkbox1.checked Then _drawstyle = drawstyle.fill Else _drawstyle = drawstyle.drawing
If _drawstyle = Drawstyle.fill Then
Call FillRegion (E.location, _fillcolor) (New Random (). Next (_fillcolor.count))
Else
_drawstyle = drawstyle.drawdragging
_lastposition = E.location
End If
End Sub
Private Sub picturebox1_mousemove (sender as Object, E as MouseEventArgs) Handles Picturebox1.mousemove
If _drawstyle = drawstyle.drawdragging Then
_g.drawline (_drawingpen, _lastposition, e.location)
_lastposition = E.location
pictureBox1.Image = _imgmain
End If
End Sub
Private Sub picturebox1_mouseup (sender as Object, E as MouseEventArgs) Handles Picturebox1.mouseup
_drawstyle = drawstyle.drawing
End Sub
Second, the topic--the realization of seed filling algorithm
It said a lot of nonsense, and now finally can start to implement the fill algorithm.
When a user clicks on a point in the image, it is necessary to populate the other points adjacent to the point with the same color. Why do you call it "seed filling"? It's probably like this: You sow a seed at that point in the point, it blossoms (the color becomes the target color), and then it sows a new seed (the point that is adjacent to it and the color equals the original color); The new seed blossoms again (change color), sowing new seeds ... So reciprocating, until there is no place to sow, the algorithm is over.
You can use a loop queue as a data structure, as you normally do in BFS. For the BFS algorithm, the required storage space is large, the specific needs of how much is really difficult to estimate. Here to give you a reference, my program picture frame size is 832*450, about 370,000 pixels, the capacity of the loop queue set to 1600 to meet the demand (all coloring). If you have a larger picture frame, you can first take a larger number (such as 8000), and then gradually shrink, try again and again.
The implementation of this loop queue directly defined as a one-dimensional array can be, there is no need to use the Concurrentqueue class, otherwise the performance will be reduced, there is no need for this.
First, we can define a fill_direction array because we want to fill in four directions, in order to avoid similar code repetition that causes the program to be ugly:
Copy Code code as follows:
Dim fill_direction () as Point = {new Point ( -1, 0), New Point (1, 0), new Point (0,-1), new Point (0, 1)}
This allows you to complete four-direction operations with a for loop.
According to the first idea, the implementation of the program is very simple: first click on the point of the team, record the color of this point. Then use a loop, take out the first element of the team, and Sow in four directions (the same color, and no more out of the picture frame boundary), change the color of each seed into a target color into the team. So reciprocating until the queue is empty. The code is as follows:
Copy Code code as follows:
Private Sub FillRegion2 (sourcepoint as Point, destinationcolor as Color)
Dim new_bitmap as Bitmap = DirectCast (pictureBox1.Image, bitmap)
Dim Source_color as color = New_bitmap. GetPixel (Sourcepoint.x, Sourcepoint.y)
Dim min_x As Integer = 0, min_y As Integer = 0
Dim max_x As Integer = picturebox1.width-1, max_y As Integer = picturebox1.height-1
Dim Fill_queue (max_fill_queue) as Point
Dim fill_direction () as Point = {new Point ( -1, 0), New Point (1, 0), new Point (0,-1), new Point (0, 1)}
Dim queue_head as Integer = 0
Dim Queue_tail as Integer = 1
Fill_queue (queue_tail) = Sourcepoint
Do While Queue_head <> Queue_tail
Queue_head = (queue_head + 1) Mod Max_fill_queue
Dim Current_point as Point = Fill_queue (Queue_head)
For i as Integer = 0 to 3
Dim new_point_x as Integer = Current_point. X + fill_direction (i). X
Dim new_point_y as Integer = Current_point. Y + fill_direction (i). Y
If new_point_x < min_x OrElse new_point_y < min_y OrElse new_point_x > max_x OrElse new_point_y > Max_y Then C Ontinue for
If New_bitmap. GetPixel (new_point_x, new_point_y) = Source_color Then
New_bitmap. SetPixel (new_point_x, new_point_y, Destinationcolor)
Queue_tail = (queue_tail + 1) Mod Max_fill_queue
Fill_queue (queue_tail) = New Point (new_point_x, new_point_y)
End If
Next
Loop
pictureBox1.Image = New_bitmap
End Sub
There may be a problem, that is, the first point should be changed to the target color before the team, but I do not change here. The effect is actually the same, because next to the point in the seed when the point is found that the color has not changed, or will it team (note: If there is only one point to fill, that is, the starting point does not have adjacent points, it will cause this point is not filled into the target color, please improve the algorithm itself). We are ignoring this little problem here.
Running the program, you can find that you can implement the function of filling.
Note: If the target color is the same color as the starting point, and the starting point has adjacent, same-colored points, it causes the same point to be queued repeatedly, resulting in a queue overflow. At this point the team head pointer equals the tail pointer, and the program thinks the queue is empty and terminates the fill, so the end result is unchanged (if not using a loop queue, it causes the program to die). To avoid this situation, you should determine whether the target color is the same as the origin color before filling, and ends directly. I did not make such a judgment here.
Third, enhance the efficiency
When running the program, I found a problem, if the fill area is too large (such as directly fill the entire picture frame), the program will be very slow, about 2 seconds to fill out. The main reason for this problem is that the performance of GetPixel and SetPixel is not high, and every time you call both methods, you do a lot of extra work, which is the problem when I used assembly language to invoke DOS interrupt points.
To this end, m$ provides a lockbits and Unlockbits method. The LockBits method can lock a picture into memory to modify it directly by accessing memory. In C # we can directly use the pointer to access this piece of data, but for VB is not possible, because VB does not allow the use of pointers, we can access the System.Runtime.InteropServices.Marshal class directly to the function of memory.
A detailed description of LockBits can refer to this log: http://www.bobpowell.net/lockingbits.htm
One important point is figuring out how to calculate the memory address of a point in the picture.
As shown in this image (the picture comes from that blog post), the address of the point in memory of the coordinates (X,Y) is Scan0 + (Y * Stride) + X * k. K is related to the bytes occupied by each point in the picture, we use 32-bit ARPG, 4 bytes per pixel, so k is 4. Also note that stride is not necessarily n*k (n indicates that n pixels are stored per row) because there may be extra bits at the end to align the array (the word length of the processor). In any case, we can get the Stride property of the BitmapData object.
Since a ARGB value is 4 bytes, we need to invoke the ReadInt32 and WriteInt32 methods of the Marshal class to read and write to the color of each pixel. What we want to manipulate is the ARGB value of the color rather than the color object.
Then the above code slightly modified, you can write the following program:
Copy Code code as follows:
Private Sub fillregion (sourcepoint as Point, destinationcolor as Color)
Dim new_bitmap as Bitmap = DirectCast (pictureBox1.Image, bitmap)
Dim Source_color_int as Integer = New_bitmap. GetPixel (Sourcepoint.x, Sourcepoint.y). ToArgb
Dim Bitmap_data as BitmapData = New_bitmap. LockBits (New Rectangle (0, 0, Picturebox1.width, picturebox1.height), _
Imaging.ImageLockMode.ReadWrite, New_bitmap. PixelFormat)
Dim Stride as Integer = Math.Abs (bitmap_data. Stride)
Dim scan0 as IntPtr = Bitmap_data. Scan0
Dim bytes as Integer = Stride * New_bitmap. Height
Dim min_x As Integer = 1, min_y As Integer = 1
Dim max_x As Integer = picturebox1.width-1, max_y As Integer = picturebox1.height-1
Dim Fill_queue (max_fill_queue) as Point
Dim fill_direction () as Point = {new Point ( -1, 0), New Point (1, 0), new Point (0,-1), new Point (0, 1)}
Dim Destination_color_int as Integer = Destinationcolor.toargb
Dim queue_head as Integer = 0
Dim Queue_tail as Integer = 1
Fill_queue (queue_tail) = Sourcepoint
Do While Queue_head <> Queue_tail
Queue_head = (queue_head + 1) Mod Max_fill_queue
Dim Current_point as Point = Fill_queue (Queue_head)
For i as Integer = 0 to 3
Dim new_point_x as Integer = Current_point. X + fill_direction (i). X
Dim new_point_y as Integer = Current_point. Y + fill_direction (i). Y
If new_point_x < min_x OrElse new_point_y < min_y OrElse new_point_x > max_x OrElse new_point_y > Max_y Then C Ontinue for
Dim offset as Integer = (new_point_y * Stride) + new_point_x * 4
Dim Current_color_int as Integer = System.Runtime.InteropServices.Marshal.ReadInt32 (scan0, offset)
If current_color_int = Source_color_int Then
System.Runtime.InteropServices.Marshal.WriteInt32 (Scan0, offset, destination_color_int)
Queue_tail = (queue_tail + 1) Mod Max_fill_queue
Fill_queue (queue_tail) = New Point (new_point_x, new_point_y)
End If
Next
Loop
New_bitmap. Unlockbits (Bitmap_data)
pictureBox1.Image = New_bitmap
End Sub
Of course, if you have other better ways to implement, but also please advise. (Ah, don't tell me to use Windows API ...) Now run the program and find that efficiency is rising dramatically. I tested it, and on my computer, it took about 370,000 pixels to 50~60 milliseconds, and the efficiency was satisfying.