您的位置:首页 > 其它

区域填充:扫描线种子填充算法应用(类似魔术棒功能)

2012-08-29 11:23 309 查看
/* < 扫描线种子填充算法 >
*
* 算法的基本过程如下:
*  当给定种子点(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
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: