2016년 1월 27일 수요일

Visual C# (3) 서로 겹쳐지지 않는 원 그리기 (Non Overlapped Circle Drawing)

안녕하세요?

이곳에 포스팅을 한지도 벌써 일년이 넘었군요 ^^;; 

간만에 글을 남기긴 하지만 내용 자체는 지난 포스팅에 이어 계속 원을 그리는 것을 계속 해보도록 하겠습니다.

 이번에 포스팅할 내용은 원을 여러개 그리는데 서로 겹쳐지지 않게끔 그리는 것이 목표입니다. 이번에는 클래스를 이용해서 프로그램을 짤 건데요, 원의 중심점과 반지름 등을 이용하여 Circle 객체를 생성 한 후 각 객체가 서로 겹쳐지지 않게끔 검사를 해주고 최종적으로 각 원이 차지하는 픽셀을 White로 칠해주는 프로그램입니다.

 Circle 객체를 생성할 때에는 객체가 가지는 요소에 원의 중심점, 원의 반지름의 값을 설정하면 그려질 원이 차지하는 픽셀 영역의 집합을 계산하는 메소드를 넣는거 까지 Circle class가 하게 될 역할 입니다.

 그리고 CircleDraw class도 만들어 줄 건데요, 이 CircleDraw class는 원이 생성될 갯수를 설정해주면 각 객체가 생성될 때 마다 이전에 생성된 Circle 객체와 서로 겹쳐지지 않는 지를 검사하고 겹쳐지지 않는 것이 확인되면 최종적으로 원들이 차지하는 픽셀 영역의 집합에 새로운 원의 영역을 추가하는 것으로 마무리를 짓습니다.

뭐 코드는 안 보여드리고 쓸데없이 서두가 길었는데요,

일단 코드를 쭉 보시죠~


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


namespace NonOverlapCircleDrawing
{       
    class Circle
    {     
        double x0 { get; set; }
        double y0 { get; set; } //원의 중심 좌표
        double R { get; set; }  //원의 반경

        public HashSet hashCircleAreaIndex { get; set; }

        public Circle(double x, double y, double Radius)
        {
            this.x0 = x;
            this.y0 = y;
            this.R = Radius;
        }

        //원의 중심 좌표와 반경만 주어지면 원이 그려질 픽셀을 hashset에 넣어주는 기능을 하는 메소드
        //List, Array list 등을 사용할 수도 있지만 contain 검색 및 삭제 추가 시 시간이 더 많이 소요됨.
        public void CircleGeneration(int ImgW, int ImgH)
        {
            hashCircleAreaIndex = new HashSet();
            for (int Y = (int)(y0 - R); Y < (y0 + R); Y++)
            {
                for (int X = (int)(x0 - R); X < (x0 + R); X++)
                {
                    if (X >= ImgW || X < 0) continue;
                    if (Y >= ImgH || Y < 0) continue;
                    if ((X - x0) * (X - x0) + (Y - y0) * (Y - y0) < R * R) hashCircleAreaIndex.Add(ImgW * Y + X);
                }
            }
        }
    }


    class CircleDraw
    {        
        Circle[] cir { get; set; }
        double[] rawImage { get; set; }
        int ImgW { get; set; }
        int ImgH { get; set; }

        public List listCircleAreaIndex { get; set; } //중복되지 않는 Random Number를 생성하기 위해 꼭 필요함
        public HashSet hashCircleAreaIndex { get; set; } //Circle 객체를 차례로 생성함에 따라 각 객체가 차지하는 pixel index를 저장하는 hashset
        public HashSet hashTest; //listCircleAreaIndex와 연동하기 위한 hashset. List 하나로 처리해도 구동은 가능하나 contain 검색시나 foreach 구문 시 속도가 너무 느려 그 때만 이 hashset 이용

        public CircleDraw(int ImgW, int ImgH)
        {
            this.ImgW = ImgW;
            this.ImgH = ImgH;
        }

        //listCircleAreaIndex와 hashTest를 모든 픽셀 인덱스로 차례로 채워 넣기 위한 기능 수행
        public void ListGen()
        {
            int k = 0;
            listCircleAreaIndex = new List();
            hashTest = new HashSet();

            for (int j = 0; j < ImgH; j++)
            {
                for (int i = 0; i < ImgW; i++)
                {
                    listCircleAreaIndex.Add(k);
                    hashTest.Add(k);
                    k++;
                }
            }
        }

        //겹치지 않는 원을 그리기 위한 실제적인 기능을 하는 메소드
        //CircleNumber는 그리려는 원의 갯수 설정
        public void CircleGen(int CircleNumber)
        {
            cir = new Circle[CircleNumber];
            hashCircleAreaIndex = new HashSet();
            
            Random rnd = new Random(); // Random number를 사용하기 위한 구문

            for (int CirNo = 0; CirNo < CircleNumber; CirNo++)
            {
                RETRY: //순차적으로 원을 그릴때 이전의 원과 겹치게 되면 다시 되돌아가는 기능을 하게 하는 goto 구문
                int iRndNumber = rnd.Next(0, listCircleAreaIndex.Count); //list가 가진 숫자내에서 랜덤  넘버를 생성하기 위해 필요
                int iPositionIndex = listCircleAreaIndex[iRndNumber]; //list 내에서 임의의 인덱스 추출. 그리고 이 임의의 인덱스는 원의 중심 좌표로 변환. 

                //리스트에서 추출한 인덱스로부터 원의 중심 좌표 계산
                int x = iPositionIndex % ImgW;
                int y = iPositionIndex / ImgW;
                int R = rnd.Next(15, ImgH / 10); // 반지름에 대한 랜덤넘버 생성

                cir[CirNo] = new Circle(x, y, R); //생성된 원의 중심과 반지름을 이용하여 Circle 객체 생성
                cir[CirNo].CircleGeneration(ImgW, ImgH);

                //생성된 Circle 객체가 차지하는 픽셀마다 검사하면서 이전에 생성한 Circle 객체와 겹치는 부분이 있는지 확인.
                //만약 겹치는 부분이 없는 것이 확인되면 해당되는 각 픽셀 포지션을 기존의 리스트(여기선 우선 hashTest)에서 삭제.
                foreach (int i in cir[CirNo].hashCircleAreaIndex)
                {
                    if (CirNo != 0)
                    {
                        if (hashCircleAreaIndex.Contains(i)) goto RETRY;
                        else hashTest.Remove(i);
                    }
                }

                //변환된 hashTest 값을 리스트에 그대로 대입
                listCircleAreaIndex = hashTest.ToList();

                //끝으로 나중에 최종 그림을 그려줄 때 쓰일 원이 차지하는 픽셀의 집합(hashCircleAreaIndex)에 새로 생성된 원의 픽셀 인덱스 추가
                foreach (int i in cir[CirNo].hashCircleAreaIndex)
                {
                    hashCircleAreaIndex.Add(i);
                }
            }
      
        }
    }
}





그리고 이 class들을 이용할 main winform 코드 입니다.

using System;
using System.Threading.Tasks;
using System.Drawing;
using System.Windows.Forms;

namespace NonOverlapCircleDrawing
{
    public partial class MAIN_FORM : Form
    {
        public Graphics g;
        Bitmap bmpCircle;

        public MAIN_FORM()
        {
            InitializeComponent();
        }

        private void BTN_CIRCLE_GENERATE_Click(object sender, EventArgs e)
        {            
            int ImgW = 1024;
            int ImgH = 1024;
            int iCircleNumber = 100;

            bmpCircle = new Bitmap(ImgW, ImgH);

            g = Graphics.FromImage(bmpCircle);
            g.FillRectangle(Brushes.Black, 0, 0, bmpCircle.Width, bmpCircle.Height);

            CircleDraw cirDraw = new CircleDraw(ImgW, ImgH);
            cirDraw.ListGen();
            cirDraw.CircleGen(iCircleNumber);

            foreach(int i in cirDraw.hashCircleAreaIndex)
            {
                int x = i % ImgW;
                int y = i / ImgW;
                if (i >= ImgW * ImgH) return;
                bmpCircle.SetPixel(x, y, Color.White);
            }

            PIC_BOX1.Image = (Image)bmpCircle.Clone();
        }
    }
}




그리고 프로그램 구동 모습입니다.



간만에 포스팅을 했더니 설명이 너무 마구리라

혹시나 이상한 점이나 수정해야할 점 더 좋은 코멘트 있으면 글 남겨 주세요.

당연히 질문도 환영이구요~ ^^

읽어주셔서 감사합니다.