NSImage와 이미지 표현형에 대해 – Cocoa

코코아에서 이미지를 표현하는 클래스로 기본적으로 NSImage를 쓴다. 앱킷에서 이 클래스는 다양한 기본 포맷의 이미지에 대해서 사용할 수 있고, 이미지를 로드하거나, 이미지를 그리는 등의 거의 모든 작업에서 주로 사용되는 클래스이다.

사실 NSImage는 어떤 이미지값을 감싸는 wrapper인데, 우리의 상식과는 달리 이 클래스 자체는 자신의 내부에 들어있는 이미지 데이터에 대해서 별로 아는게 없다. 어떤 이미지는 단일 이미지 내에 표현형에 따라서 여러벌의 이미징 데이터를 가지고 있는 경우가 있기 때문에, NSImage는 이러한 데이터를 담는 일종의 배열처럼 동작할 뿐이고, 실제로 렌더링되는 이미지 데이터는 이미지 표현형 객체에 의해서 다뤄진다. 이러한 이미지 표현형 객체에는 NSImageRep의 여러 서브 클래스들이 있고, 대표적으로 TIFF, JPEG 등의 비트맵 이미지 데이터를 위한 NSBitmapImageRep 클래스가 많이 쓰인다.

NSImage가 하는 일

NSImage는 이러한 진실(?)에도 불구하고 대부분의 경우에 코드상에서는 ‘이미지 객체’를 대표한다라고 취급될 수 있다. 그리고 다음과 같은 작업에는 NSImage 클래스를 사용한다.

  • 특정 URL 및 디스크의 파일로부터 이미지 데이터를 로딩한다.
  • 페이스트보드에 복사된 이미지데이터를 로딩한다.
  • 뷰나 그래픽 컨텍스트에 실제 이미지를 그린다.
  • CALayer 객체의 콘텐츠를 제공해주는 역할을 담당할 수 있다.
  • 일련의 드로잉 명령을 캡쳐하여 새로운 이미지를 만들 수 있다.
  • 특정 이미지에 대해서 여러 가지 다른 파일 포맷 버전을 만들 수 있다.

표현형

NSImage 객체의 representations 속성을 이용하여 해당 이미지가 가지고 있는 표현형들의 목록을 알아낼 수 있다. 이미지 표현형은 주로 NSImageRep 의 서브 클래스의 인스턴스이다. 표현형은 소스데이터로부터 특정한 타입의 이미지를 그릴 때 사용되며, NSImage 가 실제로는 알지 못하는 이미지에 대한 정보들을 제공하기도 한다. 결국 NSIamgedraw(*...) 족 메소드들은 모두 해당 이미지의 표현형의 동일 메소드를 호출하는 것으로 보면 된다. 그리고 이를 위해서 NSImag는 특정한 상황에서 최적의 ㅍ현형을 자동으로 선택하는 기능을 탑재하고 있다.

NSBitmapImageRep

NSBitmapImageRep은 GIF, JPG, PNG, TIFF 포맷을 포함한 여러 비트맵 데이터를 렌더링하기 위해 사용되는 클래스이다. 위에서 언급한 포맷의 비트맵 데이터를 통해서 생성하거나, CGImage, CIImage로 부터 변환될 수 있고, 포커스가 잠긴 (즉 현재 그래픽 컨텍스트와 연결된) 뷰의 특정 영역을 캡쳐하는 방식으로 생성될 수 있다.

특정 뷰의 내용을 캡쳐하기

init(focusedViewRect:)로 생성할 수 있고, 이 때 현재 포커스 고정1 된 뷰의 내용을 캡쳐하여 비트맵 이미지로 만들 수 있다. 다음 함수는 특정한 뷰를 받아서 해당 뷰의 내용을 캡쳐한 PNG 데이터를 생성하여 리턴하는 함수이다.

func getCapturedPNGImage(from targetView: NSView) -> Data?
{
  var pngData: Data?
  targetView.lockFocus()
  if let rep = NSBitmapImageRep(focusedViewRect: targetView.bounds)
  {
    pngData = rep.representation(using:.png, properties:[:])
  }
  targetView.unlockFocus()
  return pngData
}

뷰 내용을 캡쳐하는 다른 방법

뷰 내용을 캡쳐하는 다른 방법들도 있다. NSView의 내용을 PDF로 획득한 후 해당 데이터로부터 이미지 표현형을 생성하는 방법도 있다. 그외에도 NSImage를 포커스 고정하고 뷰의 내용을 가져다 그리거나, 레이어 기반 뷰의 경우에는 이미지 객체의 컨텍스트에다가 해당 레이어를 렌더하게 하는 방법등 방법 자체는 매우 여러가지가 있다.

파일포맷과 표현형의 차이

NSBitmapImageRep 클래스는 표시되는 이미지의 표현형(representation)을 의미한다고 했는데, 이것이 파일 포맷을 그대로 가리키지는 않는다. 표현형은 특정한 데이터로부터 표현되는 이미지를 말하는 것이며, 파일 포맷은 이미지 데이터를 파일에 저장하거나 전송하기 위해 특정한 형식에 맞춰 정리하는 개념이다.

따라서 NSImage가 두 개 이상의 표현형을 가질 수 있다는 말은, 예를 들어 썸네일과 원본 이미지의 두 개의 표현형을 가지고, 렌더링 되어야 하는 뷰의 크기에 따라서 알맞은 표현형을 선택한다는 맥락에서 이해할 수 있으며, 이 때 이미지의 정보를 담고 있는 데이터 자체는 표현형 마다 고유하게 하나의 데이터가 존재한다.

단일 표현형에 대해서 이미지 파일로 이미지를 저장하고자 할 때, 파일 포맷을 선택해야 하며, JPEG, PNG, BMP, GIF, TIFF 등의 파일 포맷을 선택할 수 있다.

참고로 NSImageRep이 제공하는 파일 포맷의 종류는 NSBitmapImageRep.FileType에 정의되어 있다. (https://developer.apple.com/documentation/appkit/nsbitmapimagerep.filetype)

UIImage와의 차이

iOS를 위한 코코아 터치에서는 NSImage를 사용하지 않고 UIImage를 사용한다. 이 클래스는 iOS기기에서 지원가능한 모든 파일 포맷에 대하여 내부적으로 지원할 수 있으며, 표현형을 이미지 클래스와 분리하지 않고 하나의 클래스로 결합해두었다.

따라서 뷰 캡쳐를 위해서 사용할 수 있는 표현형 클래스는 따로 존재하지 않으며 대신 UIGraphicsGetImageFromCurrentImageContext()라는 프레임워크 레벨의 함수에 의해서 캡쳐된 드로잉명령들을 이용해서 이미지에 그림을 그릴 수 있으며, 파일을 생성하고자 하는 경우에는 UIImagePNGRepresentation(_:)UIImageJPEGRepresentation(_:)을 이용해서 이미지 클래스를 파일 데이터로 변환할 수 있다.


  1. 포커스가 고정되었다는 의미는 first responder가 되어 포커스링이 생긴 뷰라는 의미가 아니라, 앱 킷의 drawing 명령을 받는 상태가 되었다는 의미이다. 예를 들어 NSView를 커스텀하게 되면 draw(in:) 메소드를 오버라이드 하게 되는데, 이 때 이 메소드 내부의 모든 드로잉명령은 “현재 그래픽 컨텍스트”에 대해서 적용된다는 것을 가정하며, 해당 메소드가 호출되기 직전에 앱 킷은 그 뷰를 포커스 뷰로 만든 후 호출한다.