領域の塗りつぶし(Area Fill):スキャンラインシードの塗りつぶしアルゴリズムの適用(マジックバー機能に似ています)


/* <           >
 *  
 *          :
 *        (x, y) ,
 *                                      ,
 *             [xLeft, xRight],
 *                、                 ,
 *         。      ,      。
 *  
 *         :
 *  (1)                ,    (x, y)  ;
 *  (2)        ,          ,                   (x, y),y       ;
 *  (3)     (x, y)  ,        、       ,    。        、      xLeft xRight;
 *  (4)              y - 1 y + 1        [xLeft, xRight]    ,
 *       xLeft   xRight    ,              ,                  ,            ,
 *           (2) ;
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;

namespace MagicWandTool
{
    class MagicWandTool
    {
        #region Private Field
        private int _iThresholdValue = 0;                                                       //     ([0,255])
        private int _iImageWidth = 0;                                                           //       
        private int _iImageHeight = 0;                                                          //       
        private IntPtr _ptrSourcePoint = IntPtr.Zero;                                           //     
        private IntPtr _ptrImageStartPoint = IntPtr.Zero;                                       //     
        private Dictionary<string, Point> _dicScannedSeed = new Dictionary<string, Point>();    //           (   ,                 ,               ,      ,        )
        private Dictionary<string, Point> _dicBorderRegion = new Dictionary<string, Point>();   //       ( Dictionary          ,     ,    List, List             )
        private Stack<Point> _stackSeed = new Stack<Point>();                                   //       

        #endregion

        #region Constructor
        public MagicWandTool()
        {
            this._stackSeed = new Stack<Point>();
            this._dicBorderRegion = new Dictionary<string, Point>();
            this._dicScannedSeed = new Dictionary<string, Point>(); 
        }

        #endregion

        #region Methods

        #region Public Methods
        public Point[] GetSelectRange(Image sourceImage,Point sourcePoint,int iThrehslodValue)
        {
            List<Point> listRange = new List<Point>();

            try
            {
                #region        

                Bitmap sourceBitmap = new Bitmap(sourceImage);
                this._iThresholdValue = iThrehslodValue;
                this._iImageHeight = sourceBitmap.Height;
                this._iImageWidth = sourceBitmap.Width;
                BitmapData sourceBitmapData = sourceBitmap.LockBits(new Rectangle(0, 0, this._iImageWidth, this._iImageHeight), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
                
                unsafe
                {
                    this._ptrImageStartPoint = sourceBitmapData.Scan0;
                    this._ptrSourcePoint = (IntPtr)((byte*)(this._ptrImageStartPoint) + (sourcePoint.Y * this._iImageWidth + sourcePoint.X) * 4);
                }

                #endregion

                #region         
                if (!this.StartScanLine(sourcePoint))
                {
                    this._dicBorderRegion.Clear();
                    Console.WriteLine("GetSelectRange.StartScanLine: Failed.");
                }
                #endregion

                #region         ,    
                sourceBitmap.UnlockBits(sourceBitmapData);

                foreach (Point tp in this._dicBorderRegion.Values)
                {
                    listRange.Add(tp);
                }
                #endregion
            }
            catch (Exception ex)
            {
                Console.WriteLine("GetSelectRange.Exception: " + ex.Message);
                listRange.Clear();
            }

            return listRange.ToArray();
        }

        public Rectangle GetSelectRectangle(Image sourceImage, Point sourcePoint, int iThrehslodValue)
        {
            Rectangle rectangle = new Rectangle();

            try
            {
                #region        

                Bitmap sourceBitmap = new Bitmap(sourceImage);
                this._iThresholdValue = iThrehslodValue;
                this._iImageHeight = sourceBitmap.Height;
                this._iImageWidth = sourceBitmap.Width;
                BitmapData sourceBitmapData = sourceBitmap.LockBits(new Rectangle(0, 0, this._iImageWidth, this._iImageHeight), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

                unsafe
                {
                    this._ptrImageStartPoint = sourceBitmapData.Scan0;
                    this._ptrSourcePoint = (IntPtr)((byte*)(this._ptrImageStartPoint) + (sourcePoint.Y * this._iImageWidth + sourcePoint.X) * 4);
                }

                #endregion

                #region         
                if (!this.StartScanLine(sourcePoint))
                {
                    this._dicBorderRegion.Clear();
                    Console.WriteLine("GetSelectRectangle.StartScanLine: Failed.");
                }
                #endregion

                #region         ,    
                sourceBitmap.UnlockBits(sourceBitmapData);

                //        ,  ,  ,     ,       
                int iMinX = 0;
                int iMaxX = 0;
                int iMinY = 0;
                int iMaxY = 0;
                foreach (Point tp in this._dicBorderRegion.Values)
                {
                    if (tp.X < iMinX)
                    {
                        iMinX = tp.X;
                    }
                    if (iMaxX < tp.X)
                    {
                        iMaxX = tp.X;
                    }
                    if (tp.X < iMinX)
                    {
                        iMinX = tp.X;
                    }
                    if (iMaxX < tp.X)
                    {
                        iMaxX = tp.X;
                    }
                }

                rectangle = new Rectangle(iMinX, iMinY, iMaxX - iMinX, iMaxY - iMinY);

                #endregion
            }
            catch (Exception ex)
            {
                Console.WriteLine("GetSelectRectangle.Exception: " + ex.Message); 
                rectangle = new Rectangle();
            }

            return rectangle;
        }

        #endregion

        #region Private Methods
        private bool StartScanLine(Point sourcePoint)
        {
            try
            {
                this._dicBorderRegion.Clear();
                this._dicScannedSeed.Clear();

                this._stackSeed.Clear();
                this._stackSeed.Push(sourcePoint);                                                              //   ,     

                Point tempPoint = new Point();
                Point seed = new Point();
                int iCount = 0;
                int iLeftX = 0;
                int iRightX = 0;
                while (0 != this._stackSeed.Count)
                {
                    seed = this._stackSeed.Pop();                                                               //   ,         
                    this._dicScannedSeed[seed.ToString()] = seed;

                    iCount = 0;
                    if (seed.X - 1 >= 0)
                    {
                        iCount = ScanLineLeft(new Point(seed.X - 1, seed.Y));                                   //   ,          ,   
                    }

                    //             
                    iLeftX = seed.X - iCount;
                    iCount = 0;
                    if (seed.X + 1 < this._iImageWidth)
                    {
                        iCount = ScanLineRight(new Point(seed.X + 1, seed.Y));                                  //   ,          ,   
                    }

                    //             
                    iRightX = seed.X + iCount;

                    if (seed.Y - 1 > 0)
                    {
                        ScanLineNewSeed(new Point(iLeftX, seed.Y - 1), new Point(iRightX, seed.Y - 1));         //   ,          ,            
                    }
                    else
                    {
                        //       ,           
                        tempPoint.Y = 0;
                        for (int i = iLeftX; i <= iRightX; i++)
                        {
                            tempPoint.X = i;
                            this._dicBorderRegion[tempPoint.ToString()] = tempPoint;
                        }
                    }

                    if (seed.Y + 1 < this._iImageHeight)
                    {
                        ScanLineNewSeed(new Point(iLeftX, seed.Y + 1), new Point(iRightX, seed.Y + 1));
                    }
                    else
                    {
                        //       ,           
                        tempPoint.Y = this._iImageHeight - 1;
                        for (int i = iLeftX; i <= iRightX; i++)
                        {
                            tempPoint.X = i;
                            this._dicBorderRegion[tempPoint.ToString()] = tempPoint;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("StartScanLine.Exception: " + ex.Message);
                
                return false;
            }

            return true;
        }

        private int ScanLineLeft(Point seedPoint)
        {
            int iCount = 0;
            Point currentCheckPoint = new Point();
            currentCheckPoint.Y = seedPoint.Y;
            for (int i = seedPoint.X; i >= 0; i--)
            {
                currentCheckPoint.X = i;

                if (!this.IsPixelValid(currentCheckPoint))
                {
                    break;
                }
                iCount++;
                
                //             ,                
                if (0 == i)
                {
                    this._dicBorderRegion[currentCheckPoint.ToString()] = currentCheckPoint;
                }
            }

            return iCount;
        }

        private int ScanLineRight(Point seedPoint)
        {
            int iCount = 0;
            Point currentCheckPoint = new Point();
            currentCheckPoint.Y = seedPoint.Y;
            for (int i = seedPoint.X; i < this._iImageWidth; i++)
            {
                currentCheckPoint.X = i;
                if (!this.IsPixelValid(currentCheckPoint))
                {
                    if (i - 1 >= 0)
                    {
                        //          ,                     ,          ,         
                        currentCheckPoint.X = i - 1;
                        this._dicScannedSeed[currentCheckPoint.ToString()] = currentCheckPoint;
                    }

                    break;
                }
                iCount++;

                //             ,                
                if (this._iImageWidth - 1 == i)
                {
                    this._dicBorderRegion[currentCheckPoint.ToString()] = currentCheckPoint;
                }
            }

            return iCount;
        }

        private void ScanLineNewSeed(Point leftPoint, Point rightPoint)
        {
            bool bIsFindNewSeed = false;
            int iCurrentX = leftPoint.X;
            Point tempPoint = new Point();
            tempPoint.Y = leftPoint.Y;

            //       [leftPoint.X,rightPoint.X]   ,      ,  rightPoint.X,             
            while (iCurrentX <= rightPoint.X)
            {
                bIsFindNewSeed = false;

                //    ,                ,            (iCurrentX - 1)       .
                while (iCurrentX <= rightPoint.X)
                {
                    tempPoint.X = iCurrentX;
                    if (!this.IsPixelValid(tempPoint))
                    {
                        break;
                    }

                    bIsFindNewSeed = true;
                    iCurrentX++;
                }

                if (bIsFindNewSeed)
                {
                    tempPoint.X = iCurrentX - 1;
                    //       ,        
                    if (!this._dicScannedSeed.ContainsKey(tempPoint.ToString()))
                    {
                        this._stackSeed.Push(tempPoint);
                    }
                }

                /*          (             )*/
                //            ,  :       ,  :     
                //        ,          ,
                //        ,   iCurrentX      ,          ,         ,  iCurrentX++,       
                iCurrentX++;
                while (iCurrentX <= rightPoint.X)
                {
                    tempPoint.X = iCurrentX;
                    if (this.IsPixelValid(tempPoint))
                    {
                        //     ,   
                        break;
                    }

                    //     ,    
                    iCurrentX++;
                }
            }
        }

        private bool IsPixelValid(Point currentCheckPoint)
        {
            try
            {
                if (255 == this._iThresholdValue)
                {
                    return true;
                }

                unsafe
                {
                    byte *pCurrentColor = (byte *)(this._ptrImageStartPoint);
                    pCurrentColor += (currentCheckPoint.Y * this._iImageWidth + currentCheckPoint.X) * 4;
                    
                    if ((((((byte *)(this._ptrSourcePoint))[0] - this._iThresholdValue) <= pCurrentColor[0]) && (pCurrentColor[0] <= ((byte *)(this._ptrSourcePoint))[0] + this._iThresholdValue))
                        && (((((byte*)(this._ptrSourcePoint))[1] - this._iThresholdValue) <= pCurrentColor[1]) && (pCurrentColor[1] <= ((byte*)(this._ptrSourcePoint))[1] + this._iThresholdValue))
                        && (((((byte*)(this._ptrSourcePoint))[2] - this._iThresholdValue) <= pCurrentColor[2]) && (pCurrentColor[2] <= ((byte*)(this._ptrSourcePoint))[2] + this._iThresholdValue))
                        && (((((byte*)(this._ptrSourcePoint))[3] - this._iThresholdValue) <= pCurrentColor[3]) && (pCurrentColor[3] <= ((byte*)(this._ptrSourcePoint))[3] + this._iThresholdValue)))
                    {
                        return true;
                    }
                    else
                    {
                        this._dicBorderRegion[currentCheckPoint.ToString()] = currentCheckPoint;
                        return false;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("IsPixelValid.Exception: " + ex + "; currentCheckPoint:" + currentCheckPoint.ToString());

                return false;
            }
        }

        #endregion

        #endregion
    }
}