using
System;
using
System.Collections;
using
System.Runtime.InteropServices;
using
System.Drawing;
using
System.Drawing.Drawing2D;
using
System.Drawing.Imaging;
namespace
WellFormed
{
///
<summary>
/// Fills a bitmap
using a non-recursive flood-fill.
///
</summary>
public class
MapFill
{
public
MapFill()
{
}
static
Stack stack=new Stack();
///
<summary>
///
Checks to make sure a pixel is in an image.
///
</summary>
///
<param name="pos">The
position to check</param>
///
<param name="bmd">The
BitmapData from which the bounds are determined</param>
///
<returns>True
if the point is in the image</returns>
private
static bool
CheckPixel(Point pos, BitmapData bmd)
{
return
(pos.X>-1) && (pos.Y>-1) && (pos.X<bmd.Width) && (pos.Y<bmd.Height);
}
///
<summary>
///
Returns the color at a specific pixel
///
</summary>
///
<param name="pos">The
position of the pixel</param>
///
<param name="bmd">The
locked bitmap data</param>
///
<returns>The
color of the pixel under the nominated point</returns>
private
static Color GetPixel(Point pos, BitmapData bmd)
{
if
(CheckPixel(pos, bmd))
{
//always
assumes 32 bit per pixels
int
offset=pos.Y*bmd.Stride+(4*pos.X);
return
Color.FromArgb(
Marshal.ReadByte(bmd.Scan0,offset+2),
Marshal.ReadByte(bmd.Scan0,offset+1),
Marshal.ReadByte(bmd.Scan0,offset));
}
else
return
Color.FromArgb(0,0,0,0);
}
///
<summary>
///
Sets a pixel at a nominated point to a specified color
///
</summary>
///
<param name="pos">The
coordinate of the pixel to set</param>
///
<param name="bmd">The
locked bitmap data</param>
///
<param name="c">The
color to set</param>
private
static void
SetPixel(Point pos, BitmapData bmd, Color c)
{
if
(CheckPixel(pos,bmd))
{
//always
assumes 32 bit per pixels
int
offset=pos.Y*bmd.Stride+(4*pos.X);
Marshal.WriteByte(bmd.Scan0,offset+2,c.B);
Marshal.WriteByte(bmd.Scan0,offset+1,c.G);
Marshal.WriteByte(bmd.Scan0,offset,c.R);
Marshal.WriteByte(bmd.Scan0,offset+3,255);
}
}
///
<summary>
///
Fills a pixel and its un-filled neigbors with a specified color
///
</summary>
///
<param name="pos">The
position at which to begin</param>
///
<param name="bmd">The
locked bitmap data</param>
///
<param name="c">The
color with which to fill the area</param>
///
<param name="org">The
original colour of the point. Filling stops when all connected pixels of this
color are exhausted</param>
private
static void
FillPixel(Point pos, BitmapData bmd, Color c, Color org)
{
Point
currpos=new Point(0,0);
stack.Push(pos);
do
{
currpos=(Point)stack.Pop();
SetPixel(currpos,bmd,c);
if
(GetPixel(new
Point(currpos.X+1,currpos.Y),bmd)==org)
stack.Push(new
Point(currpos.X+1,currpos.Y));
if
(GetPixel(new
Point(currpos.X,currpos.Y-1),bmd)==org)
stack.Push(new
Point(currpos.X,currpos.Y-1));
if
(GetPixel(new
Point(currpos.X-1,currpos.Y),bmd)==org)
stack.Push(new
Point(currpos.X-1,currpos.Y));
if
(GetPixel(new
Point(currpos.X,currpos.Y+1),bmd)==org)
stack.Push(new
Point(currpos.X,currpos.Y+1));
}
while (stack.Count>0);
}
///
<summary>
///
Fills a bitmap with color.
///
</summary>
///
<remarks>If a
non 32-bit image is passed to this routine and only 32 bit image will be
created, the original image will be copied to the new image and filling will
take place on the new image which will be handed back when complete.
</remarks>
///
<param name="img">The
image to fill</param>
///
<param name="pos">The
position to begin filling at</param>
///
<param name="color">The
color to fill</param>
///
<returns>A
Bitmap object with the filled area.</returns>
public
static Bitmap Fill(Image img, Point pos, Color
color)
{
//Ensure
the bitmap is in the right format
Bitmap
bm=(Bitmap)img;
if
(img.PixelFormat!=PixelFormat.Format32bppArgb)
{
//if
it isn't, convert it.
bm=new
Bitmap(img.Width,img.Height,PixelFormat.Format32bppArgb);
Graphics g=Graphics.FromImage(bm);
g.InterpolationMode=InterpolationMode.NearestNeighbor;
g.DrawImage(img,new
Rectangle(0,0,bm.Width,bm.Height),0,0,img.Width,img.Height,GraphicsUnit.Pixel);
g.Dispose();
}
//Lock
the bitmap data
BitmapData
bmd=bm.LockBits(new
Rectangle(0,0,bm.Width,bm.Height),ImageLockMode.ReadWrite,bm.PixelFormat);
//get
the color under the point. This is the original.
Color
org=GetPixel(pos,bmd);
//Fill
the first pixel and recursively fill all it's neighbors
FillPixel(pos,bmd,color,org);
//unlock
the bitmap
bm.UnlockBits(bmd);
return
bm;
}
}
}
Public Class
MapFill
Public
Sub New()
End
Sub 'New
Private
Shared stack As
New Stack()
'/
<summary>
'/
Checks to make sure a pixel is in an image.
'/
</summary>
'/
<param name="pos">The position to check</param>
'/
<param name="bmd">The BitmapData from which the bounds are determined</param>
'/
<returns>True if the point is in the image</returns>
Private
Shared Function
CheckPixel(pos As Point, bmd
As BitmapData) As
Boolean
Return
pos.X > - 1 AndAlso pos.Y > - 1
AndAlso pos.X < bmd.Width
AndAlso pos.Y < bmd.Height
End
Function 'CheckPixel
'/
<summary>
'/
Returns the color at a specific pixel
'/
</summary>
'/
<param name="pos">The position of the pixel</param>
'/
<param name="bmd">The locked bitmap data</param>
'/
<returns>The color of the pixel under the nominated point</returns>
Private
Shared Function
GetPixel(pos As Point, bmd
As BitmapData) As
Color
If
CheckPixel(pos, bmd) Then
'always
assumes 32 bit per pixels
Dim
offset As Integer
= pos.Y * bmd.Stride + 4 * pos.X
Return
Color.FromArgb(Marshal.ReadByte(bmd.Scan0, offset + 2),
Marshal.ReadByte(bmd.Scan0, offset + 1), Marshal.ReadByte(bmd.Scan0, offset))
Else
Return
Color.FromArgb(0, 0, 0, 0)
End
If
End
Function 'GetPixel
'/
<summary>
'/
Sets a pixel at a nominated point to a specified color
'/
</summary>
'/
<param name="pos">The coordinate of the pixel to set</param>
'/
<param name="bmd">The locked bitmap data</param>
'/
<param name="c">The color to set</param>
Private
Shared Sub
SetPixel(pos As Point, bmd
As BitmapData, c As
Color)
If
CheckPixel(pos, bmd) Then
'always
assumes 32 bit per pixels
Dim
offset As Integer
= pos.Y * bmd.Stride + 4 * pos.X
Marshal.WriteByte(bmd.Scan0, offset + 2, c.R)
Marshal.WriteByte(bmd.Scan0, offset + 1, c.G)
Marshal.WriteByte(bmd.Scan0, offset, c.B)
Marshal.WriteByte(bmd.Scan0, offset + 3, CByte(255))
End
If
End
Sub 'SetPixel
'/
<summary>
'/
Fills a pixel and its un-filled neigbors with a specified color
'/
</summary>
'/
<param name="pos">The position at which to begin</param>
'/
<param name="bmd">The locked bitmap data</param>
'/
<param name="c">The color with which to fill the area</param>
'/
<param name="org">The original colour of the point. Filling stops when all
connected pixels of this color are exhausted</param>
Private
Shared Sub
FillPixel(pos As Point, bmd
As BitmapData, c As
Color, org As Color)
Dim
currpos As New
Point(0, 0)
stack.Push(pos)
Do
currpos
= CType(stack.Pop(), Point)
SetPixel(currpos,
bmd, c)
If
GetPixel(New Point(currpos.X + 1, currpos.Y),
bmd).Equals(org) Then
stack.Push(New
Point(currpos.X + 1, currpos.Y))
End
If
If
GetPixel(New Point(currpos.X, currpos.Y - 1),
bmd).Equals(org) Then
stack.Push(New
Point(currpos.X, currpos.Y - 1))
End
If
If
GetPixel(New Point(currpos.X - 1, currpos.Y),
bmd).Equals(org) Then
stack.Push(New
Point(currpos.X - 1, currpos.Y))
End
If
If
GetPixel(New Point(currpos.X, currpos.Y + 1),
bmd).Equals(org) Then
stack.Push(New
Point(currpos.X, currpos.Y + 1))
End
If
Loop
While stack.Count > 0
End
Sub 'FillPixel
'/
<summary>
'/
Fills a bitmap with color.
'/
</summary>
'/
<remarks>If a non 32-bit image is passed to this routine and only 32 bit image
will be created, the original image will be copied to the new image and filling
will take place on the new image which will be handed back when complete.
</remarks>
'/
<param name="img">The image to fill</param>
'/
<param name="pos">The position to begin filling at</param>
'/
<param name="color">The color to fill</param>
'/
<returns>A Bitmap object with the filled area.</returns>
Public
Shared Function
Fill(img As Image, pos
As Point, color As Color)
As Bitmap
'Ensure
the bitmap is in the right format
Dim
bm As Bitmap = CType(img,
Bitmap)
If
img.PixelFormat <> PixelFormat.Format32bppArgb Then
'if
it isn't, convert it.
bm =
New Bitmap(img.Width, img.Height,
PixelFormat.Format32bppArgb)
Dim
g As Graphics = Graphics.FromImage(bm)
g.InterpolationMode
= InterpolationMode.NearestNeighbor
g.DrawImage(img,
New Rectangle(0, 0, bm.Width, bm.Height), 0, 0,
img.Width, img.Height, GraphicsUnit.Pixel)
g.Dispose()
End
If
'Lock
the bitmap data
Dim
bmd As BitmapData = bm.LockBits(New
Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, bm.PixelFormat)
'get
the color under the point. This is the original.
Dim
org As Color = GetPixel(pos, bmd)
'Fill
the first pixel and recursively fill all it's neighbors
FillPixel(pos,
bmd, color, org)
'unlock
the bitmap
bm.UnlockBits(bmd)
Return
bm
End
Function 'Fill
End Class
'MapFill