물론 C++같은 고속(?) 언어에 비하면 맞는 말이긴 하지만, 그렇다고 못쓸정도로 느리진 않다.
이렇게 이미지 프로세싱이 느리다고 알려진것은 SetPixel() / GetPixel() 이 대량의 픽셀을 처리하기엔 너무 느리기 때문이다.
C# 으로도 포인터를 사용하여 메모리를 직접 컨트롤 하면 제법 준수한 속도를 뽑아 준다.
그러나, 이경우 unsafe 라는 컴파일 속성을 지정해 줘야 하기 때문에 이미지 프로세싱이 프로젝트에서 극히 일부분에만 사용될 경우는 사용하기가 꺼려진다.
어쨌든 "unsafe" 니까...
하지만!!
궂이 "Unsafe" 로 포인터를 사용하지 않고도 메모리 엑세스 방식으로 처리할수 있는 방법이 있으니 바로 LockBits() 와 BitmapData 의 조합이다.
뭐, 메모리상에 고정된 영역을 사용하도록 지정한다는둥... 뭔가 기술적인 복잡한 설명이 있지만, 그런게 궁금하다면 그런건 MSDN 에서 찾아 보도록하자.
MSDN 에 가면 아주 잘 설명되어 있다.
참고 : http://msdn.microsoft.com/ko-kr/library/5ey6h79d(v=vs.110).aspx
쉽고 간단하게 두가지 방식으로 이미지 변환을 해보겠다.
하나는 SetPixel() / GetPixel() 를 이용하여 흰색이 아닌 부분을 붉은색으로 바꾸는 방식.
하나는 LockBits() / BitmapData 을 이용하여 흰색이 아닌 부분을 붉은색으로 바꾸는 방식.
LockBits() / BitmapData 에 비해 SetPixel() / GetPixel() 방식이 훨씬 쉽고 간결하게 소스가 작성된다.
그러나, 작업 시간을 비교해 보면 LockBits() 방식이 SetPixel() / GetPixel() 에 비해 훨씬 빠른것을 알수 있다.
쉽게 말해, 픽셀 한두개 바꿀 경우라면 SetPixel() / GetPixel() 를, 넗은 영역의 이미지를 컨트롤할 경우라면 LockBits() / BitmapData 를 사용하는 것이 정신건강에 좋을 것이다.
[ LockBits() ]
private void btnChangColor_Click(object sender, EventArgs e) { Stopwatch sw = new Stopwatch(); sw.Start(); Rectangle rect = new Rectangle(0, 0, img.Width, img.Height); System.Drawing.Imaging.BitmapData bmpData = img.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, img.PixelFormat); // Get the address of the first line. IntPtr ptr = bmpData.Scan0; // Declare an array to hold the bytes of the bitmap. int bytes = Math.Abs(bmpData.Stride) * img.Height; byte[] rgbValues = new byte[bytes]; System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); int numBytes = 0; for (int y = 0; y < img.Height; y++) { for (int x = 0; x < img.Width; x++) { numBytes = (y * (img.Width * 4)) + (x * 4); if (rgbValues[numBytes] != 255 && rgbValues[numBytes + 1] != 255 && rgbValues[numBytes + 2] != 255) { rgbValues[numBytes] = 0; rgbValues[numBytes + 1] = 0; rgbValues[numBytes + 2] = 255; } } } // Copy the RGB values back to the bitmap System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes); // Unlock the bits. img.UnlockBits(bmpData); pictureBox1.Invalidate(); sw.Stop(); MessageBox.Show(sw.ElapsedMilliseconds.ToString() + "ms"); }[SetPixel() / GetPixel()]
private void btnChangColor02_Click(object sender, EventArgs e) { Stopwatch sw = new Stopwatch(); sw.Start(); Color co; for (int y = 0; y < img.Height; y++) { for (int x = 0; x < img.Width; x++) { co = img.GetPixel(x, y); if (co.R != 255 && co.G != 255 && co.B != 255) { co = Color.FromArgb(255, 0, 0); img.SetPixel(x, y, co); // 해당 좌표 픽셀의 컬러값을 변경 } } } pictureBox1.Invalidate(); sw.Stop(); MessageBox.Show(sw.ElapsedMilliseconds.ToString() + "ms"); }[원본]
[ LockBits() ]
[SetPixel() / GetPixel()]