본문 바로가기
연구하기/iOS App Dev

iOS 에서 raw 픽셀 데이터로부터 이미지 개체 생성하기

by 썰렁황제 2012. 7. 8.

iOS 에서 raw 픽셀 데이터로부터 이미지 개체 생성하기

* iOS 가 업데이트되면서 크게 변경된 내용이 있습니다. http://gcempire.tistory.com/552  를 참고해 주세요.


 * 여기서 raw 픽셀 데이터란, 디지털 카메라의 raw 포맷과는 의미가 좀 다르므로 유의하시기 바랍니다.

  iOS 의 그래픽스와 이미지 체계는 꽤나 복잡하다. 특히 이쪽 계통의 문제는 Objective-C 가 아닌 C 형식의 문법을 지니고 있는 것이 많다는 것. iOS 5.0 에 와서는 Core Image 라이브러리가 추가되면서 Objective-C 레벨로 어느 정도 올라오는 듯 보였지만, UI 라이브러리에서 직접 사용할 수 있는 이미지 생성은 불가능하고, 결국 화면상에 표시를 위해서는 Core Graphics 를 거쳐야만 하기 때문에 여전히 불편하다. 퍼포먼스 문제도 크고.

  어쨌거나, 그림 그리는 툴을 만들려고 한다면, 현재 사용자가 그린 것들을 바로 화면상에 보여주는 동시에 저장할 때에는 저장도 해 주어야 한다.

  화면상에 보여주는 건, 별도의 데이터 공간에 보존하고 매번 일일이 픽셀 단위로 점을 찍어서 보여주는 방법도 있곘지만, 이 방법은 퍼포먼스 면에서 좋다고 보기는 뭐하고, 무엇보다 이런 방식으로는 이미지를 파일이나 기타 다른 방식으로 저장하거나 하는 것이 불가능하다.

  이 때 가장 좋은 것은 이러한 화면상에 작업한 픽셀 데이터를 즉시 이미지화 하여 화면상에 찍어주는 것이다. 더불어 이미지화 된 데이터는 특정 포맷으로 저장하는 즉시 이미지 파일이 되니 얼마나 좋은가!


  일단 이야기는 여기까지 하고, 실제 이미지 생성하는 과정을 보도록 하자.

1. Core Graphics 를 활용한 방법

  먼저 데이터 공간을 만들자. 크기 dataRequiredBytes 는 아래와 같다.

  int dataRequiredBytes = imageWidth * imageHeight * pixelPerBytes;

  imageWidth 는 폭, imageHeight 는 높이, pixelPerBytes 는 픽셀당 바이트 수이다. 일반적으로는 32비트 이미지를 사용하고 (알파채널 8비트, Red, Green, Blue 각각 8비트) 8비트가 1바이트이므로 최종적으로는 이 값은 4가 된다. 따라서 다음과 같이 메모리를 할당할 수 있겠다.

  uint8_t *rawImage = (uint8_t *)malloc(dataRequiredBytes);

  이렇게 할당된 공간을 바탕으로 NSData 를 만든다.

  NSData *dataImage = [[NSData alloc] initWithBytes:rawImage length:dataRequiredBytes freeWhenDone:YES];

   freeWhenDone 을 설정해 두면 개체와 연결되어 있으므로 나중에 처리하기 편하다. 만약 저 부분이 파일로부터 읽는 것이라면 NSData 에서 제공하는 다양한 함수들이 있으므로 이를 활용하면 매우 유용하다.

  이제 NSData 를 기반으로 이미지 생성 시 데이터를 제공할 DataProvider 를 만든다. 데이터 제공자 패턴을 아시는 분이라면 익숙한 이름일 것이다.

  CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)dataImage);

 CFDataRef 와 NSData 가 1대 1로 대응된다는 것은 이미 잘 알고 있으리라 본다. 이제 색공간을 만든다.

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

디바이스 기반의 RGB 를 만드는 것으로, 일반적인 전자 디스플레이는 RGB 색공간을 사용하며, 특히 현대에는 32비트 4채널을 사용하는 색공간을 사용하는 것이 일반적이다. (실제로는 RGB 3채널이고 알파 채널은 가상이지만) 과거 시절에는 16색 혹은 256색 동시 디스플레이에 팔레트 형식의 색공간도 많이 사용됐고, 65536 색을 표현하는 채널당 4비트 혹은 RGB 각각 5비트에 알파채널 1비트와 같은 것도 많이 사용되었다. 그 외에도 전자 디스플레이와는 다른 CMYK 색공간이라든가 RGB 색역을 넘는 sRGB 색공간, Lab Color 라 불리는 전 색역을 모두 표현하는 공간 등 여러 색공간이 존재하므로 만약 전문적인 작업도구를 만들고 싶다면 이 부분에 대해서는 별도로 공부를 해 두도록 한다.

어쨌거나 이제 이미지를 생성해 보자

CGImageRef cgImg = CGImageCreate(imageWidth, imageHeight, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpace, bitmapImfo, dataProvider, decode, shouldInterpolate, intent);

아이고메 길다.

못보던 값이 보일텐데 다음과 같다.

bitsPerComponent : 색 구성요소당 bit 이다. 우리가 일반적으로 쓰는 32비트 4채널 색상이라면 당연히 하나의 요소는 1바이트, 즉 8비트가 될 것이다. 8이라고 쓰면 된다. 만약 채널당 4비트 규격이었다면 4가 되겠지.

bitsPerPixel : 이건 픽셀당 단위이다. 구성요소당 단위와 별개인 이유는, 이걸로 구성요소 갯수를 판별하기 때문인 듯. 하지만 미묘. 어쨌거나 32비트 색상은 말 그대로 1픽셀당 32비트를 쓴다는 의미이므로 32가 되겠다.

bytesPerRow : 이미지 한 줄을 찍기 위해 필요한 바이트 수를 말한다. 통상적으로는 이미지의 가로 크기 * 픽셀당 바이트 수가 된다.

bitmapInfo : 비트맵의 정보를 설정한다. Red, Green, Blue, Alpha 채널이 각각 어디 위치하는지, 알파 채널만 있거나 혹은 그 반대인지, 색상에 알파 채널이 이미 곱해진 상태인지 등등. CGImageAlphaInfo 로 검색하면 정보가 나타나니 확인해 본다. 만약 ARGB 라면 kCGImageAlphaFirst 를 지정하면 된다.

decode : 이 파라메터는 각 채널의 영역을 어떻게 변환할지에 대한 내용이다. 예를 들어 RGB 컬러 색역이라면 이 값은 총 6개로 구성될 수 있으며, 각 쌍 (즉 2, 2, 2 이런 식으로 나뉜다는 의미) 은 각 색 요소의 최소/최대값을 설정하는 의미를 지닌다. 결과적으로 현재 지정된 픽셀 정보의 색 영역을 축소하거나 확대하는 역할을 해 준다. 만약 아무것도 하기 싫다면 (보통은 쓸일이 없으니) NULL 을 넣어주면 OK

shouldInterpolate : 인터폴레이션울 걸 것인지 묻는다. 따라서 이 값을 YES 로 지정하면, 튀는 픽셀들을 부드럽게 해주는 역할을 해 준다. 하지만 픽셀을 정확하게 디스플레이 하려 할 때에는 좋지 않다. 이미지 그대로 디스플레이 하기를 원한다면 NO 로 지정한다.

intent : 만약 현재 디바이스에 매핑할 컬러가 없는 컬러가 있을 때의 대응을 지정한다. 기본적으로는 kCGRenderingIntentDefault 를 지정한다. 기타 값들은 유사값으로 지정하는 패턴을 설정하는 것으로, 각 옵션을 읽어보면 확인할 수 있을 것이다.

이제 이미지가 생성되었다. 이미지가 생성되면서 생성에 필요한 데이터는 스스로 Retain 을 하므로, 위에서 이미 할당했던 데이터들을 Release 해 줄 필요가 있다.

CGColorSpaceRelease(colorSpace);

CGDataProviderRelease(dataProvider);

여기서 생성된 이미지 또한 사용이 완료되면 Release 해 주어야 한다.

CGImageRelease(cgImg);


2. CIImage 를 통한 이미지 생성법

iOS 5.0 에 와서, 맥 OS 에서나 있었던 CIImage 클래스가 비로소 iOS 에 추가되었다. 덕분에 개체 지향적으로 코딩할 수 있는 기능들이 대폭 추가되었고, raw 픽셀 데이터로부터 이미지를 만드는 과정도 단순화되었다. 하지만, CIImage 는 화면상에 즉시 표시하는 것이 불가능하기 때문에 결과적으로는 CGImage 형식으로 한번 변환하는 작업을 거쳐야 한다.

일단 생성법은 다음과 같다.

CIImage *image = [CIImage imageWithBitmapData:dataImage bytesPerRow:imageWidth * 4 size:CGSizeMake(imageWidth, imageHeight) format:CIFormatF colorSpace:colorSpace];

size : 이미지의 가로 세로 크기를 말한다. 이 때문에 CGSize 로 지정된다.

format : 이미지 포맷인데, RGBA 냐 BGRA 냐, RGB 64비트 이미지 (채널당 16비트) 냐를 지정한다. 애석하지만 ARGB 는 없다. kCIFormatRGBA, kCIFormatBGRA, kCIFormatRGBh 3가지 값만 들어갈 수 있다.

colorSpace : 이건 위와 완전히 동일.

UIImage 의 편의 생성자 (Convenience Constructor) 에는 CIImage 를 인자로 받는 함수가 있지만, UIImage 를 실제로 UIImageView 등에 연결시켜 화면상에 표시할 때에는 CGImage 속성만 사용하고, UIImage 는 CIImage 와 CGImage 속성을 아예 별개로 다루고 있기 때문에 CIImage 로 생성된 UIImage 는 화면상에 아무것도 그리지 못하는 문제가 있다.

이 때문에 실제로 화면상에 표시하기 위해서는 CGImage 화 시켜야 한다. 이 과정을 수행하려면 좀 복잡하다. 다음 내용을 참고해 보자

http://stackoverflow.com/questions/7788438/having-trouble-creating-uiimage-from-ciimage-in-ios5

주요 코드를 복사하면 아래와 같다.

CIImage *image = [[CIImage alloc] initWithImage:[UIImage imageNamed:@"mushroom.jpg"]];

CIContext *context = [CIContext contextWithOptions:nil];

theImageView.image = [UIImage imageWithCGImage:[context createCGImage:image fromRect:image.extent]];

요약하면, CIContext 를 통해 컨텍스트를 만들고 여기에 이미지를 넣어서 만든다. 대충 요정도가 되겠다.



반응형