[Cocoa] 여러 이미지를 이어 붙이기

찾으면 찾으면 있겠지만, 손쉽게 여러 장의 이미지를 가로나 세로로 이어 붙여서 하나로 만들어주는 그런 앱이 있으면 참 좋겠다고 생각하다가, 까짓거 하나 만들면 되지 않겠냐 -_- 고 생각이 들어서 써보는 그런 포스팅

이미지 이어 붙이기

앱을 처음부터 끝까지 만들어보기에는 너무 힘든 포스트가 될 것 같아서, 여러 개의 NSImage를 이어붙이는 부분에 대해서만 살펴보기로 하자

대략의 구상

그러니까 레이어(CGLayer)같은 곳에는 이미지를 원하는 크기로 붙여넣을 수 있으니, 이미지(NSImage)들을 CGImage로 바꾸고, 최종 크기의 비트맵 컨텍스트를 만들어 여기에 이미지들을 각각 그려서 이 컨텍스트로부터 비트맵 이미지(CGImage)를 얻어서 저장한다…라고 생각했다. 언뜻 생각하면 상당히 귀찮을 것 같지만 될 것 같다.

기본으로 돌아가서

하지만 이리 저리 생각해본 끝에 방법을 바꾸기로 했다. NSImage도 하나의 뷰이기 때문에 여기에 각 이미지의 drawInRect:fromRect:operation:fraction: 메소드를 사용해서 그냥 그리면 되지 않을까?

  1. 이어붙일 이미지들의 배열을 하나 준비한다.
  2. 세로로 이어 붙이는 경우, 고정된 가로 크기 값을 정한다.
  3. 각 이미지들을 가로 크기로 변경했을 때의 세로크기들에 대해 그 총합을 구한다. 이는 최종 결과물의 세로 크기가 된다.
  4. 가로/세로 크기를 구했으니, NSImage 객체를 initWithSize: 를 사용해서 하나 생성한다.
  5. 새로 생성한 이미지에 lockfocus 한다.
  6. 각 이미지들을 순서대로 적절한 위치에 그려넣는다. 이때, 가로크기가 줄어드는 비율에 대해 세로크기도 함께 줄여주어야 한다. 또한, OSX에서는 왼쪽 아래가 원점이므로, 맨 위에서 부터 그리도록 한다. 빈 이미지가 포커스되었으므로, 이 이미지들은 빈 이미지에 차곡차곡 붙어서 그려진다.
  7. 다 그렸으면 unlockfocus 한다.
  8. 최종 생성된 파노라마 이미지를 데이터로 만들어 저장한다.

실제 구현

먼저, 이어붙일 이미지들은 배열에 담겨있다. 이 배열은 imageList라는 인스턴스 변수로 참조한다. 이들을 담아둘 이미지의 크기를 구해야 한다. 이는 매크로를 사용하여 RESULT_IMAGE_WIDTH 로 지정했다고 가정한다. 우선 최종 생성될 이미지의 크기를 구해야 한다. 가로는 정해졌으니, 세로 크기를 구해보자.

-(float)getResultImageHeight {
    float totalHeight = 0;
    for(NSImage* anImage in imageList) {
        NSSize theSize = anImage.size;
        totalHeight += theSize.height * RESULT_IMAGE_WIDTH / theSize.width;
    }
    return totalHeight;
}

이제 이미지를 만들고 여기에 이미지들을 하나 하나 그려넣으면 된다. 노가다일뿐 어렵지 않다.

-(NSImage *)compositeImage {
    NSSize resultImageSize;
    resultImageSize.width = RESULT_IMAGE_WIDTH;
    resultImageSize.height = [self getResultImageHeight];
    NSImage *resultImage = [[NSImage alloc] initWithSize:resultImageSize];

    NSPoint startPoint;
    startPoint.x = 0;
    startPoint.y = theSize.height;

    [resultImage lockFocus];
    for (NSImage *anImage in imageList) {
        NSSize drawnSize;
        drawnSize.width = RESULT_IMAGE_WIDTH;
        startPoint.y -= drawnSize.height;
        drawnSize.height = anImage.size.height * RESULT_IMAGE_WIDTH / anImage.size.width;
        NSRect drawRect = NSMakeRect(startPoint.x,startPoint.y,drawnSize.width,drawnSize.height);
        [anImage drawInRect:drawRect 
                   fromRect:NSZeroRect 
                  operation:NSCompositeSourceOver 
                   fraction:1.0];
    }
    [resultImage unlockFocus]
        return resultImage;
}

1) lockFocus는 해당 뷰를 포커스된 뷰로 만들어 준다. 이후에 일어나는 드로잉 메소드는 모두 이 곳에 그림을 그리게 된다. 첨엔 “눈에 보이는 뷰”에 대해서만 가능한 줄 알았는데, 그거랑은 상관 없더라.

2) NSZeroRect는 0*0 크기의 사각형인데, 때문에 fromRect:는 이미지 자신의 전체 영역을 그리게 된다.

3) operation은 컴포지션 방법을 정의한다. 만약 비어있지 않은 이미지에 다른 이미지를 그린다면 겹치는 픽셀을 어떻게 표현할 것인지 지정한다.

4) fraction은 덧그려지는 이미지의 불투명도를 0~1 사이의 값으로 지정한다.