첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

728x90
반응형

TestProject.zip
다운로드

▶ PixelData.cs

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace TestProject
{
    /// <summary>
    /// 픽셀 데이터
    /// </summary>
    public class PixelData
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region Field

        /// <summary>
        /// 비트맵 크기
        /// </summary>
        private Size bitmapDataSize;

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region Field

        /// <summary>
        /// 픽셀 카운트
        /// </summary>
        protected readonly int pixelCount;

        /// <summary>
        /// 픽셀 배열
        /// </summary>
        protected readonly int[] pixelArray;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 비트맵 데이터 크기 - BitmapDataSize

        /// <summary>
        /// 비트맵 데이터 크기
        /// </summary>
        public Size BitmapDataSize
        {
            get
            {
                return this.bitmapDataSize;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Indexer
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 인덱서 - this[point]

        /// <summary>
        /// 인덱서
        /// </summary>
        /// <param name="point">좌표</param>
        /// <returns>픽셀 값</returns>
        public int this[Point point]
        {
            get
            {
                int index = (point.Y * this.bitmapDataSize.Width) + point.X;

                return this.pixelArray[index];
            }
            protected set
            {
                int index = (point.Y * this.bitmapDataSize.Width) + point.X;

                this.pixelArray[index] = value;
            }
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Protected

        #region 생성자 - PixelData(bitmapData)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="bitmapData">비트맵 데이터</param>
        protected PixelData(BitmapData bitmapData)
        {
            this.bitmapDataSize = new Size(bitmapData.Width, bitmapData.Height);

            this.pixelCount = this.bitmapDataSize.Width * this.bitmapDataSize.Height;

            this.pixelArray = new int[this.pixelCount];

            Marshal.Copy(bitmapData.Scan0, this.pixelArray, 0, this.pixelCount);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 생성하기 - Create(bitmap)

        /// <summary>
        /// 생성하기
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        /// <returns>픽셀 데이터</returns>
        public static PixelData Create(Bitmap bitmap)
        {
            BitmapData bitmapData = GetBitmapData(bitmap, true);

            PixelData pixelData = new PixelData(bitmapData);

            bitmap.UnlockBits(bitmapData);

            return pixelData;
        }

        #endregion
        #region 생성하기 - Create(filePath)

        /// <summary>
        /// 생성하기
        /// </summary>
        /// <param name="filePath">파일 경로</param>
        /// <returns>픽셀 데이터</returns>
        public static PixelData Create(string filePath)
        {
            using(Bitmap bitmap = new Bitmap(filePath))
            {
                BitmapData bitmapData = GetBitmapData(bitmap, true);

                PixelData pixelData = new PixelData(bitmapData);

                bitmap.UnlockBits(bitmapData);

                return pixelData;
            }
        }

        #endregion

        #region 포함 여부 구하기 - Contains(point)

        /// <summary>
        /// 포함 여부 구하기
        /// </summary>
        /// <param name="point">좌표</param>
        /// <returns>포함 여부</returns>
        public bool Contains(Point point)
        {
            return ((point.X < 0) || (point.X >= this.bitmapDataSize.Width) || (point.Y < 0) || (point.Y >= this.bitmapDataSize.Height)) ? false : true;
        }

        #endregion
        #region 색상 여부 구하기 - IsColor(point, predicate)

        /// <summary>
        /// 색상 여부 구하기
        /// </summary>
        /// <param name="point">좌표</param>
        /// <param name="predicate">판정 함수</param>
        /// <returns>색상 여부</returns>
        public bool IsColor(Point point, Predicate<int> predicate)
        {
            int pixel = this[point];

            return predicate(pixel);
        }

        #endregion
        #region 투명색 여부 구하기 - IsTransparentColor(argb)

        /// <summary>
        /// 투명색 여부 구하기
        /// </summary>
        /// <param name="argb">ARGB 값</param>
        /// <returns>투명색 여부</returns>
        public bool IsTransparentColor(int argb)
        {
            Color color = Color.FromArgb(argb);

            return (color.A == 0);
        }

        #endregion
        #region 투명색 여부 구하기 - IsTransparentColor(point)

        /// <summary>
        /// 투명색 여부 구하기
        /// </summary>
        /// <param name="point">좌표</param>
        /// <returns>투명색 여부</returns>
        public bool IsTransparentColor(Point point)
        {
            if(!Contains(point))
            {
                return true;
            }

            return IsColor(point, IsTransparentColor);
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////// Protected

        #region 비트맵 데이터 구하기 - GetBitmapData(bitmap, readOnly)

        /// <summary>
        /// 비트맵 데이터 구하기
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        /// <param name="readOnly">읽기 전용 여부</param>
        /// <returns>비트맵 데이터</returns>
        protected static BitmapData GetBitmapData(Bitmap bitmap, bool readOnly)
        {
            Rectangle rectangle = new Rectangle(new Point(0, 0), bitmap.Size);

            return bitmap.LockBits(rectangle, (readOnly) ? ImageLockMode.ReadOnly : ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
        }

        #endregion
    }
}

 

▶ PixelData2.cs

using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace TestProject
{
    /// <summary>
    /// 픽셀 데이터 2
    /// </summary>
    public class PixelData2 : PixelData
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 비트맵
        /// </summary>
        private readonly Bitmap bitmap;

        /// <summary>
        /// 비트맵 데이터
        /// </summary>
        private readonly BitmapData bitmapData;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - PixelData2(bitmap, bitmapData)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        /// <param name="bitmapData">비트맵 데이터</param>
        public PixelData2(Bitmap bitmap, BitmapData bitmapData) : base(bitmapData)
        {
            this.bitmap = bitmap;

            this.bitmapData = bitmapData;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 생성하기 - Create(bitmap)

        /// <summary>
        /// 생성하기
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        /// <returns>픽셀 데이터 2</returns>
        public static new PixelData2 Create(Bitmap bitmap)
        {
            BitmapData bitmapData = GetBitmapData(bitmap, false);

            return new PixelData2(bitmap, bitmapData);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 저장하기 - Save(targetPath)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="targetPath">타겟 경로</param>
        public void Save(string targetPath)
        {
            Marshal.Copy(pixelArray, 0, this.bitmapData.Scan0, pixelCount);

            this.bitmap.UnlockBits(this.bitmapData);

            this.bitmap.Save(targetPath);
        }

        #endregion
        #region 비트맵 구하기 - GetBitmap()

        /// <summary>
        /// 비트맵 구하기
        /// </summary>
        /// <returns>비트맵</returns>
        public Bitmap GetBitmap()
        {
            Marshal.Copy(pixelArray, 0, this.bitmapData.Scan0, pixelCount);

            this.bitmap.UnlockBits(this.bitmapData);

            return this.bitmap;            
        }

        #endregion
        #region 색상 설정하기 - SetColor(point, argb)

        /// <summary>
        /// 색상 설정하기
        /// </summary>
        /// <param name="point">좌표</param>
        /// <param name="argb">ARGB 값</param>
        public void SetColor(Point point, int argb)
        {
            base[point] = argb;
        }

        #endregion
    }
}

 

▶ BoundaryTracing.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;

namespace TestProject
{
    /// <summary>
    /// 경계선 추적
    /// </summary>
    public static class BoundaryTracing
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 생성하기 - Create(pixelData)

        /// <summary>
        /// 생성하기
        /// </summary>
        /// <param name="pixelData">픽셀 데이터</param>
        /// <returns>좌표 리스트의 리스트</returns>
        public static List<List<Point>> Create(PixelData pixelData)
        {
            Size bitmapDataSize = pixelData.BitmapDataSize;

            HashSet<Point> pointHashSet = new HashSet<Point>();

            List<Point> pointList = null;

            List<List<Point>> pointListList = new List<List<Point>>();

            bool inside = false;

            int width = bitmapDataSize.Width;

            Tuple<Func<Point, Point>, int>[] neighborhoodTupleArray = new Tuple<Func<Point, Point>, int>[]
            {
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X - 1, point.Y    ), 7),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X - 1, point.Y - 1), 7),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X    , point.Y - 1), 1),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X + 1, point.Y - 1), 1),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X + 1, point.Y    ), 3),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X + 1, point.Y + 1), 3),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X    , point.Y + 1), 5),
                new Tuple<Func<Point, Point>, int>(point => new Point(point.X - 1, point.Y + 1), 5)
            };

            for(int y = 0; y < bitmapDataSize.Height; ++y)
            {
                for(int x = 0; x < bitmapDataSize.Width; ++x)
                {
                    Point point = new Point(x, y);

                    if(pointHashSet.Contains(point) && !inside)
                    {
                        inside = true;

                        continue;
                    }

                    bool isTransparent = pixelData.IsTransparentColor(point);

                    if(!isTransparent && inside)
                    {
                        continue;
                    }

                    if(isTransparent && inside)
                    {
                        inside = false;

                        continue;
                    }

                    if(!isTransparent && !inside)
                    {
                        pointListList.Add(pointList = new List<Point>());

                        pointHashSet.Add(point); pointList.Add(point);

                        int neighborCount = 1;

                        Point startPoint = point;

                        int counter1 = 0;
                        int counter2 = 0;

                        while(true)
                        {
                            Point checkPoint = neighborhoodTupleArray[neighborCount - 1].Item1(point);

                            int newNeighborCount = neighborhoodTupleArray[neighborCount - 1].Item2;

                            if(!pixelData.IsTransparentColor(checkPoint))
                            {
                                if(checkPoint == startPoint)
                                {
                                    counter1++;

                                    if(newNeighborCount == 1 || counter1 >= 3)
                                    {
                                        inside = true;

                                        break;
                                    }
                                }

                                neighborCount = newNeighborCount;

                                point = checkPoint;

                                counter2 = 0;

                                pointHashSet.Add(point);

                                pointList.Add(point);
                            }
                            else
                            {
                                neighborCount = 1 + (neighborCount % 8);

                                if(counter2 > 8)
                                {
                                    counter2 = 0;

                                    pointList = null;

                                    break;
                                }
                                else
                                {
                                    counter2++;
                                }
                            }
                        }
                    }
                }
            }

            return pointListList;
        }

        #endregion
        #region 좌표 리스트 구하기 - GetPointList(pointListList)

        /// <summary>
        /// 좌표 리스트 구하기
        /// </summary>
        /// <param name="pointListList">좌표 리스트 리스트</param>
        /// <returns>좌표 리스트</returns>
        public static List<Point> GetPointList(List<List<Point>> pointListList)
        {
            pointListList.Sort((x, y) => x.Count.CompareTo(y.Count));

            return pointListList.Last();
        }

        #endregion
    }
}

 

▶ MainForm.cs

using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

namespace TestProject
{
    /// <summary>
    /// 메인 폼
    /// </summary>
    public partial class MainForm : Form
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainForm()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainForm()
        {
            InitializeComponent();


            Bitmap sourceBitmap = new Bitmap(400, 400, PixelFormat.Format32bppArgb);

            Graphics sourceGraphics = Graphics.FromImage(sourceBitmap);

            sourceGraphics.DrawEllipse(new Pen(Brushes.LightGray, 20), 50, 50, 300, 300);

            sourceGraphics.Dispose();

            this.pictureBox1.Image = sourceBitmap;


            PixelData2 pixelData = PixelData2.Create(sourceBitmap.Clone() as Bitmap);

            List<List<Point>> pointListList = BoundaryTracing.Create(pixelData);

            foreach(List<Point> pointList in pointListList)
            {
                foreach(Point point in pointList)
                {
                    pixelData.SetColor(point, Color.Red.ToArgb());
                }
            }

            this.pictureBox2.Image = pixelData.GetBitmap();
        }

        #endregion
    }
}
728x90
반응형
Posted by 사용자 icodebroker

댓글을 달아 주세요