In Depth Banner
Skip Navigation Links

Select your preferred language

The MapFill class

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;

    }

  }

}

 

Return to the article.

Copyright © Bob Powell 2003-2009. All rights reserved