닷넷프로그래밍정복 김상형저 가메출판사

 

Image class

GDI+가 GDI에 비해 월등히 우수한 점은 압축이미지를 다룰수 있다는 점이다. GDI는 이미지는 처리할수 있지만, 압축하지 않은 거대한 BMP파일만 쓸수 있어 활용도가 제한적이다. 반면에 GDI+는 JPG, GIF, PNG, TIFF 등의 압축효율이 높고 대중적인 파일들을 자유롭게 사용할수 있다 Image 클래스는 성성자가 제공되지 않는 추상클래스이므로 Image 타입의 객체를 직접생성할수 없다. 다만 대표타입으로 파생클래스인  Bitmap이나 Metafile 클래스의 객체를 가르키기만 한다.

ex.

 Image i = Image.FromFile("tiffany01.jpg");
            e.Graphics.DrawImage(i, 0, 0);

예제 구현의 편의상 Paint이벤트에 파일을 직접 출력했지만 이미지 같은 거대한 객체는 Load이벤트에서 미리 읽어두어야 한다.

 

DrawImage

 

비트맵 - Image로부터 파생되는 Bitamp클래스는 비트맵 그 자체를 표현하며 비트맵을 구성하느느 래스터 데이터를 관리한다. Image는 존재하는 이미지만 출력하고 과리하는 일반적인 클래스인데 비해 Bitmap은 이미지를 생성하거나 직접 조작할수 있다는 점에거 기능이 더 많다.

이러한 비트맵은 다양한 용도로 쓰이지만 주로 메모리상의 임시적인 출력표면으로 사용된다. 화면으로 출력을 내보내듯이 비트맵의 표면으로 출력을 내 보낼수 있다. 이런 목적으로 사용되는 비트맵을 오프스크린 비트맵 또는 가상화면이라고 한다. 가상 화면으로 출력을 하려면 먼저 Graphics 클랫의 다음 정적 메소드로 비트맵을 출력표면으로 하는 Graphics객체 얻어야 한다.

ex. MouseClick 핸들러에서

            if (e.Button == MouseButtons.Left)
            {
                Graphics G = Graphics.FromImage(bm);
                G.Clear(BackColor);
                Random R = new Random();
                for (int i = 0; i < 500; i++)
                {
                    SolidBrush Br = new SolidBrush(Color.FromArgb(R.Next(256),
                        R.Next(256), R.Next(256), R.Next(256)));
                    G.FillEllipse(Br, R.Next(600), R.Next(400), R.Next(70) + 30, R.Next(70) + 30);

                }
                Invalidate();
            }

이 이벤트를 Paint에서 처리한다면?

 e.Graphics.Clear(BackColor);
            Random R = new Random();
            for (int i = 0; i < 500; i++)
            {
                SolidBrush Br = new SolidBrush(Color.FromArgb(R.Next(256),
                    R.Next(256), R.Next(256), R.Next(256)));
                e.Graphics.FillEllipse(Br, R.Next(600), R.Next(400), R.Next(70) + 30, R.Next(70) + 30);

            }

이렇게 처리했을때 문제점은

  1. 그림을 그리는 중간 과정이 사용자 눈에 적나라하게 보인다는 점인데 시간이 오래 걸리는 것은 할수 없지만 중간과정을 보이는 것은 깔끔한 동작이 아니다. 백그라운드에서 이 작업을 미리 내놓는다면 더 빠르게 출력할수도 있다
  2. 이미 완성한 그림을 Paint 이벤트를 받을때마다 다시 그려야 한다는 점이다. 아무리 복잡한 그림이라도 메모리상의 비트맵에 미리 준비해두었다면 다시 그리기를 할떄는 완성된 그림만 전송하여 훨씬 더 빠르게 그릴수 있을 것이다.

 

 

더블버퍼링.

ex

  public partial class JohnForm : Form
    {
        int ex = 10, ey = 100;
        const int r = 15;


        public JohnForm()
        {
            InitializeComponent();
            timer1.Start();
        }

        private void JohnForm_Paint(object sender, PaintEventArgs e)
        {
            int x, y;

            for (x = 0; x < ClientRectangle.Right; x += 10)
            {
                e.Graphics.DrawLine(Pens.Black, x, 0, x, ClientRectangle.Bottom);
            }

            for (y = 0; y < ClientRectangle.Bottom; y += 10)
            {
                e.Graphics.DrawLine(Pens.Black, 0,y, ClientRectangle.Right,y);
            }

            e.Graphics.FillEllipse(new SolidBrush(Color.LightGreen), ex - r, ey - r, r * 2, r * 2);
            e.Graphics.DrawEllipse(new Pen(Color.Blue, 5), ex - r, ey - r, r * 2, r * 2);
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            ex += 6;
            if (ex > ClientRectangle.Right) ex = 0;
            Invalidate();
        }
    }

화면은 주기적으로 지워졌다 그려졌다를 반복하며 사용자 눈에 지워진 화면과 그려진 화면이 교대로 보이는 것이다. 이 문제를 해결하는 가장 간단한 방법은 폼의 DoubleBuffered 프로퍼티를 true로 바꾸는 것이다. 닷넷2.0에서 추가되었다. 하지만 프레임워크에 의해 자동으로 더블 버퍼링을 하다보니 속도가 느리며 언커버될때나 복구될때 다시 그릴필요가 없을떄도 완전히 다시 그린후 더블버퍼링을 하므로 효율적이지 못하다.

ex.

      int ex = 10, ey = 100;
        const int r = 15;
        Bitmap bm;


        public JohnForm()
        {
            InitializeComponent();
            timer1.Start();
        }

        private void JohnForm_Paint(object sender, PaintEventArgs e)
        {
            if (bm != null)
            {
                e.Graphics.DrawImage(bm, 0, 0);
            }         
        }
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //base.OnPaintBackground(e);
        }
        private void timer1_Tick(object sender, EventArgs e)
        {          
            int x, y;
            if (bm == null || bm.Width != ClientSize.Width || bm.Height != ClientSize.Height)
            {
                bm = new Bitmap(ClientSize.Width, ClientSize.Height);
            }
            Graphics g = Graphics.FromImage(bm);
            g.Clear(SystemColors.Window);

            ex += 6;

            if (ex > ClientRectangle.Right) ex = 0;

            for (x = 0; x < ClientRectangle.Right; x += 10)
            {
                g.DrawLine(Pens.Black, x, 0, x, ClientRectangle.Bottom);
            }
            for (y = 0; y < ClientRectangle.Bottom; y += 10)
            {
                g.DrawLine(Pens.Black, 0,y,  ClientRectangle.Right, y);
            }

            g.FillEllipse(new SolidBrush(Color.LightGreen), ex - r, ey - r, r * 2, r * 2);
            g.DrawEllipse(new Pen(Color.Blue, 5), ex - r, ey - r, r * 2, r * 2);

            Invalidate();
        }

타이머이벤트는 비트맵 객체를 생성하고 이 비트맵에 화면을 그린다.  Paint에서는 타이머이벤트로 생성한 화면을 고속전송하여 보여주기만 할뿐이다.

 

메타파일.

이미지를 저장하는 방식은 크게 래스터벡터 두가지로 구분된다. 래스터 이미지는 점의 집합으로 이미지의 모양을 기억하는 방식으로 BMP, JPG 등 흔히 사용하는 그래픽 파일이 모두 이 방식을 사용한다. 이미지를 저장하는 방식이 비디로 램의 물리적인 구조와 유사하므로 출력속도가 빠르고 얼마든지 복잡한 이미지도   표현할수 있다.

이에 비해 벡터능 이미지를 그리는 명령들을 부호화하여 저장하는 방식이며 명령들을 순서대로 재쟁하여 원래의 이미지를 복원한다. 명령을 재생할때 일정한 비율을 곱하여 확대할수 있으며 확대를 해도 이미지가 전혀 손상되지 않는다. 뙤 각 도형을 그리는 명령들이 부호화되어 저장되어 있으므로 개별 도형들을 분리하여 편집할수 있다. 하지만 벡터는 사진 같은 정교한 이미지를 표현하기에는 적합하지 않으며 명령을 일일이 재생해야 하므로 속도가 느린 것이 단점이다.

GDI+에서 벡터이미지를 표현하는 클래스는 Metafile이며 Image로부터 파생된다.

 

 

이미지고급

포맷변환 - 메모리상에서 생성한 이미지를 파일로 만들수도 있고 파일로부터 읽은 이미지를 원본과 다른 포맷으로 변환할수 있다. 어차피 그래픽 파일의 포맷은 압축방식이 조금씩 다를뿐이지 저장하는 대상은 동일하므로 상호변환할수 있다.

 

이미지변경 - GDI+는 간단하게나마 이미지를 편집하거나 변형하는 메소드를 제공한다.

  • 회전 - RotateFile
  • 스레쉬홀드 - SetThreshold 어떤 임계치르 ㄹ기준으로 색상 요소를 0이나 255로 내림 또는 올림하는 것이다. 임계치는 0.0~1.0까지의 값을 가지는데 0은 색상요소가 전혀 없는 것이고 1.0이면 최대 강도인 255를 의미한다. GDI+는 색상의 강도를 표현할때 0~255까지의 정수를 사용하지 않고 대신 0.0~1.0 사이의 실수를 사용한다. 각 색상요소가 임계치보다 작으면 0이되녹 임계치보다 크면 255가 되어 이미지의 색상이 훨씬 단순해진다.
  • 감마 - SetGamma 이미지의 밝기에 영향을 미치는 값이며 감마값에 따라 이미지의 밝기나 대비에 변화가 발생한다.
  • 밝기 조정 - SetColorMatrix 이미지의 색상들을 개별적으로 변경하고 싶을떄는 색상 매트릭스를 사용한다.
  • 반전 - 반전이란 모든 색상을 반대로 뒤집는 것이다.
  • 그레이스케일 - 그레이스케일은 모든 색의 삭생정보를 제거하고 밝기 정보만을 남겨 사진을 탈색시키는 것이다.
  • 색상 변경 - SetRemapTable 이미지의 특정 색상을 다른 색상으로 변경한다. 한꺼번에 여러개의 색상을 변경할수도 있다.

 

 

리소스

문자열

리소스란 코드의 반대되는 의미로 프로그램 실행ㄹ에 필요한 데이터를 말하며 문자열, 아이콘, 이미지 등이 리소스의 예이다. 프로그램의 동작을 정의한다기보다는 외형을 꾸미는 장식에 사용되는 것들을 리소스라고 한다.

문자열을 하드코딩하면 수정하기도 힘들고 다국어 적용하기도 힘들다. 그래서 리소스를 이용해서 구현하는 것이 좋다.

ex.

private void Form1_Paint(object sender, PaintEventArgs e)
        {
            string msg = Properties.Resources.String_Hello;
            e.Graphics.DrawString(msg, Font, Brushes.Black, 10, 10);
            msg = Properties.Resources.String_Anbu;
            e.Graphics.DrawString(msg, Font, Brushes.Black, 10, 30);
        }

 

이미지

앞의 예제에서는 FromFile 메소드를 이용해서 하드디스크의 이미지 파일을 읽었다. 호출형태는 간단하지만, 항상 실행파일과 같은 경로에 이미지가 반드시 있어야 하고 만약, 실수로 이미지 파일을 누락했다가거나 이미지 파일이 손상되었다면 파일을 읽지 못해 예외가 발생할 것이다. 프로그램이 제대로 동작할 것이라는 것을 보장할수가 없다. 또한 실행 중에 하드 디스크의 이미지를 읽어 들이려면 아무래도 속도가 늦다. 그래서 이미지를 실행파일의 리소스에 넣어서 관리한다.

리소스로 작성된 이미지는 컴파일할때 실행파일과 합쳐진다. 코드에서 리소스를 사용할떄는 리소스에 붙인 이름으로 읽어들이면 된다.

실행파일에 이미지가 완전히 통합되어 있으므로 별도의 부속파일없이 실행파일만으로도 잘 동작한다.

 

리소스는 링크 또는 포함 두가지 방식으로 프로젝트에 추가된다. 링크는 이미지의 실제 데이터는 외부파일에 두고 리로스에 그 경로만 기록하는 방식이다. 포함은 이미지의 실제 데이터가 텍스트 형태로 인코딩되어 리소스 파일에 기록되는 방식이다. 오해하지 말 것은 두 방식 모두 리소스를 관리하는 방법상의 차이일뿐 컴파일된 결과는 동일하다는 것이다.

 

사운드

리소스 추가하여 호출하는 방식은 다른 포맷과 동일하다.

ex,

SoundPlayer P = new SoundPlayer();

P.Stream = Properties.Resource.Dingdong;

P.Play();

 

 

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 2. 14:33