NSView의 내용을 이미지로 캡쳐하기

 

NSView의 내용을 비트맵 그래픽 파일로 캡쳐하는 방법에 대해 설명하겠다. 일전에 간략히 적어둔 내용이 있었는데, 잘못된 부분도 있고 예전엔 돼었는데 제대로 동작 안하는 부분도 있어서 다시 정리한다.

PDF로 캡쳐하는 방법도 있으니 살펴보도록 하자.

이미지를 만든다기 보다는 특정한 포맷으로 표현할 수 있은 이미지 표현형을 획득할 수 있으면(NSBitmapImageRep) 이 클래스의 representation(using:properties:)를 사용해서 이미지 파일을 위한 데이터를 생성할 수 있다. 여기에는 크게 두 가지 방법이 있다.

  1. 뷰 자체를 캡쳐하는 NSBitmapImageRep의 이니셜라이저를 사용하기
  2. 뷰의 비트맵 캐시를 추출하기

첫번째 방법은 NSBitmapImageRep의 이니셜라이저 중에서 init(focusedViewRect:)를 사용하는 것이다. Xcode9 기분으로 이 메소드는 뷰의 draw(_:) 내에서 쓸 때에만 유효한 표현형 값이 생성되고, 그 외의 컨텍스트에서는 (제아무리 해당 뷰에 대해서 lockFocus()를 호출하더라도) 제대로 된 데이터가 생성되지 않는다.

두 번째 방법은 뷰의 비트맵 캐시를 추출하는 것이다.

// in NSView's Subclass
func imageRep() -> NSBitmapImageRep? {
  if let rep = bitmapImageRepForCachingDisplay(in: bounds) {
    cacheDisplay(in: bounds, to: rep)
    return rep
  }
  return nil
}

흔히 하는 실수가 bitmapImageRepForCachingDisplay(in:) 을 호출한 결과를 바로 리턴하는 것인데, 이 메소드는 뷰 캐싱을 위한 이미지 표현형 객체를 생성할 뿐이지 그 속에 실제 콘텐츠를 그리지는 않는다. 실제 콘텐츠를 그려넣는 cacheDisplay(in:to:)를 사용해서 콘텐츠를 복사한 후에 이를 사용한다.

비트맵 캐시를 추출할 수 있다면 다음과 같은 식으로 저장하기 메소드를 만들 수 있다.

@IBAction func saveAsPNG(_ sender: Any?) {
  guard let rep = imageRep() else { return  }
  let panel = NSSavePanel()
  panel.allowedFileTypes = ["png"]
  
  let handler: (NSApplication.ModalResponse) -> Void = { res in 
    if res == .OK,
      let data = rep.representation(using:.png, properties:[:])
    {
      do {
        try data.write(to: panel.url!)
      } catch {
        NSLog("Error while saving file.")
      }
  }
  
  if let window = self.window {
    panel.beginSheetModal(for:window, completionHandler: handler)
  }
}

만약 draw(_:) 내에서 그래픽 컨텍스트를 이용하여 그림을 그리는 함수를 가지고 있다면, 비트맵 이미지 표현형을 생성한 후에 이를 바탕으로 이미지를 그려서 만들어도 된다. (이는 기본적으로 비트맵 그래픽 컨텍스트를 만들고 여기에 그림을 그려서 CGImage를 생성하는 방법과 완전히 동일하다.)

func offscreenImage(ofSize repSize: CGSize) -> NSImage {
  let offscreenRep = NSBitmapImageRep(
      bitmapDataPlanes: nil, // nil을 넘기면 자동으로 할당한다.
      pixelsWide: Int(repSize.width),
      pixelsHigh: Int(repSize.height),
      bitsPerSample: 8,
      samplesPerPixel: 4,
      hasAlpha: true,
      isPlaner: false,
      colorSpaceName: NSColorSpaceName.diviceRGB,
      bitmapFormat: .alphaFirst,
      bytesPerRow: 0,   // 8 * 4 * width / 8 인데 계산하지 않고 0 을 넘긴다.
      bitsPerPixel: 0)  // 역시 계산하지 않고 0을 넘긴다.
  
  let g = NSGraphicsContext(bitmapImageRep: offscreenRep!)
  NSGraphicsContext.saveGraphicsState()
  NSGraphicsContext.current = g

  let ctx = g!.cgContext
  let imageFrame = CGRect(origin: CGPoint.zero, size: repSize)
  ctx.setFillColor(NSColor.red.cgColor)
  ctx.fillEllipse(in: imageFrame)
  ...
  
  let image = NSImage()
  image.size = repSize
  image.addRepresentation(offscreenRep!)
  return image
}

참고자료

관련내용

UIView를 UIImage로 캡쳐하는 방법도 있다.

[Python101] 002. 변수, 변수를 출력해보다.

파이썬 강좌 세 번째 시간이다. 이번에는 변수란 무엇이고 변수는 출력과 무슨 관계가 있는지를 알아보도록 하자.

초등학교 국어책에는 사진을 보여주고 빈칸에 알맞는 낱말을 넣는 문제들이 종종 나온다.

철수가  ____  봅니다.

이 빈칸에 무엇을 추가해주면 그 때마다 이 문장은 다른 문장이 될 수가 있다.

  1. 철수가 만화책을 봅니다.
  2. 철수가 영화를 봅니다.
  3. 철수가 재미를 봅니다.
  4. 철수가 미쳤나 봅니다.

아무래도 상황에 맞게 가변적인 내용을 출력해주는게, 조금 더 유연하고 쓸모있는 프로그램이 될 수 있는 소지가 클 것이다. 매번 똑같은 말만 나오는 건 좀 재미가 없으니까.

변수

그래서 변수에 대해 알아보도록 하자. 변수는 어떤 값을 담아두는 상자 같은 것이다. 담아두는 값에 따라서 변수는 그 내용이 달라질 수 있다. 따라서 가변적인 처리, 계산등을 할 수 있는 것이다. 변수는 프로그래밍에서 너무나 기본적인 것이라 뭐라 적절하게 설명할 말을 못 찾겠다. 아무튼 우리는 앞으로 변수는 많이, 정말 많이 사용하게 될 것이다.

그런데 조금 주의할 게 있다. 앞서 “변수는 어떤 값을 담아두는 상자”라고 했는데, 이는 사실 나중에 혼란을 줄 수 있는 틀린 표현이다. 개념적으로는 “어떤 값에 대해 이름표를 붙여둔다”라고 생각하는 것이 정확하다.

서점이나 도서관을 예로 들어보자. 도서관에는 많은 책이 있다. 정말 정말 많은 책이 있다. 우리가 도서관에 가서 책을 찾아보고자 할 때 검색을 해 보면 “서지번호”라는 것을 찾게 된다. 이 서지번호에 따라서 책을 찾을 수 있게 된다. 즉 변수는 ‘서지 번호’에 해당한다. 어떤 값에 이름표를 붙여두고, 나중에 다시 찾기 쉽게 표시를 해 두는 것이다. 사실상 ‘그릇에 담는’ 비유와 실질적, 체감적으로는 차이를 인지하기 어려울 수도 있는데, 일단은 그렇게 알아두자. 변수는 값을 담는 그릇이라기보다는 “값을 가리키는” 이정표나 명찰에 더 가까운 개념이다.

변수는 먼저 정의해두고 나중에 그 이름으로 값을 사용하게 된다. 변수를 정의하는 방법은 다음과 같다.

변수이름 = 값

즉 변수 이름에 값을 대입할 때는 등호를 사용한다. 이는 두 개가 같다는 의미가 아니라 변수에 값을 대입한다는 의미이다. (나중에 알게 되겠지만 두 개가 같다는 의미는 == 로 표현한다.) 자 그럼 이제 두 번째 예제인 ex02.py를 작성해 보도록 하자. 요상하게 생긴 표현들이 좀 나온다만 당황하지 말고 우선 “정확하게 타이핑”하고 보도록 하자.

ex02.py

#-*-coding:utf-8

number_of_apple = 50

print number_of_apple
print "Number of apples: ", number_of_apple

people = 5
print "Number of people: ", people
print number_of_apple / people

apple_per_person = number_of_apple / people
print "We can have ", apple_per_person, "apples for everyone."

print
print

# use formatting string
print "There are %d apples in the box" % number_of_apple
print "And here are %d people." % people
print "So, we can have %d apples for everyone." % apple_per_person

print
print

# use formatting string together
print "There are %d apples and we are %d person. \nSo, we can have %d apples for everyone." % (number_of_apple, people, number_of_apple / people)

실행해보면 다음과 같은 결과를 볼 수 있다.

50
Number of apples:  50
Number of people:  5
10
We can have  10 apples for everyone.

There are 50 apples in the box
And here are 5 people.
So, we can have 10 apples for everyone.

There are 50 apples and we are 5 person.
So, we can have 10 apples for everyone.

결과를 보고 나서 다시 한 번 작성한 코드를 이해할 수 있는지 찬찬히 읽어보자. 코드를 읽어보고 무슨 의미일지 생각해본 다음 아래 설명을 보도록 한다.

1) number_of_apple = 50 : 이 문장은 number_of_apple 이라는 변수에 50이라는 값을 대입한다. 즉 숫자값 50에 number_of_apple 이라고 이름표를 붙여 주었다.

변수의 이름은 영문자와 언더스코어(흔히 언더바라고 부르는 _ )그리고 숫자로 구성될 수 있다. 변수 이름에 숫자를 쓰는 것은 그리 좋은 습관이 아니며, 가능하면 간략하고 실제로 어떤 문맥적인 의미를 갖는 한개 혹은 두세개의 단어를 사용하는 것이 좋다. 귀찮다는 이유로 a = 50, u = 70 이런 식으로 쓰는 사람이 많은데, 나중에 이 변수가 무슨 의미인지를 도통 알 수 없게 되는 경우가 많기 때문에 가능하면 단어를 그대로 쓰는 것을 추천한다. 또한 여러 단어를 하나로 합성할 때에는 공백 대신 언더스코어를 써서 변수 이름 자체는 한 단어가 되도록 해야 한다. 혹은 이런 방법 대신에 numberOfApple 과 같은 식으로 두 번째 단어부터는 각 단어의 첫글자를 대문자로 쓰는 표기법도 있다. (아이폰 앱을 만드는 Objective-C 와 같은 언어는 이런 표기법을 추천하고 있다.) 또한 변수명은 숫자로 시작해서는 안된다. 대부분의 프로그래밍 언어가 숫자로 시작하는 이름을 허용하지 않고 있다.

2) 5행은 따옴표 없이 변수 이름을 사용하여 print 하고 있다. 이는 변수의 이름이 아닌 변수의 내용물, 즉 변수가 가리키는 값을 출력해주도록 한다. 사실 print 문은 “계산식”의 결과를 출력하는 개념이라고 이해하면 된다. 변수의 이름만 쓴 계산식은 결국 변수의 값을 말하는 것이니까. print 문은 계산식의 결과를 표시하므로 당연히 다음의 구문을 쓸 수 있다.

print number_of_apple * 10

3) 2)번을 이해했다면 6번 행은 보다 명쾌하게 이해가 될 것이다. 즉 문자열을 출력하고 컴마로 연결했는데, 이이서 변수의 값을 출력해주고 있다.

4) 8~10 행은 다른 변수, people 이라는 변수를 정의하고 출력한다. 그런 다음, 사과의 수를 사람 수로 나눈 수식값을 출력한다. 13행은 문자열과 계산식(변수값), 다시 문자열을 컴마로 연결하여 출력해주는 예를 보여준다.

5) 19~21행은 되게 이상해보이는 구성을 가지고 있다. 먼저 문자열의 내용을 살펴보면 문자열 안에 숫자가 들어가야 할 부분에 %d 라고 써 두었다. 그런 다음 문자열의 뒤에 % 를 붙이고 변수 명을 넣었다.

우리는 이런 것을 ‘문자열 포맷팅’이라고 부른다. 문자열에 특정한 서식을 적용하고, 그 서식에 맞는 값을 치환하여 표시하도록 하는 것이다. 즉 %d 라는 문자는 numbers_of_apple의 숫자값이 치환되어 표시된다. 나머지 두 행도 동일한 방식으로 표시하고 있다.

문자열 포맷팅에서 %d는 digit의 의미로, 숫자를 의미한다. 이와 비슷한 포맷 문자에는 몇 가지가 있는데 가장 흔하게 쓰이는 것들을 소개하겠다.

  • %d : 숫자
  • %f : 소수점이 있는 숫자.
  • %c : 한 글자
  • %s : 문자열
  • %r : 뭐든 상관없음. 변수를 print 한 결과로 치환됨.

6) 이러한 포맷팅이 유요한 것은 마지막 27행에서 드러난다. 27행은 하나의 문자열 안에 이런 치환자를 3개 쓰고 있다. 그리고 문자열 뒤에는 % 를 쓰고 다시 괄호속에 순서대로 들어갈 값을 써주고 있다. 이 때 치환자의 개수와 괄호안의 수식의 개수는 같아야 한다.

다시 한 번 말하지만 포맷팅은 처음에는 이상하게 보여도, 출력하는 용도등으로 쓸 텍스트를 ‘합성’해내는 좋은 방법이다. 익숙해지면 그리 이상하게 보이지도 않고, 또한 어렵지도 않다.

문자열 포매팅

방금 ex02.py를 통해 문자열을 포매팅하는 법을 잠깐 살펴보았다. 포매팅은 문자를 출력하는 것 외에도 문자열을 합성하는데도 유용하게 쓸 수 있다고 했다.

포맷팅 문자열을 만드는 방법은 다음과 같다.

“%d….%f… %r…. %s…” % 치환될 값1, 치환될 값2 ,….)

문자열에 치환자를 포함시키고 문자열 뒤에 %를 표시해서 치환될 값의 묶음과 연결해주면 된다. 그리고 치환될 값이 숫자인지, 소수점을 포함하는 숫자인지, 문자열인지 등에 따라 적절한 치환자를 쓸 수 있으면 되는 것이다.

그럼 이번에는 몇 가지 포매팅을 활용하는 기법을 좀 더 보도록 하자.

ex03.py

ex03.py는 포맷팅을 사용하여 문자열을 합성하고 출력하는 방법을 보여준다. 특이할만한 점은 ‘포맷팅 문자열’ 자체를 변수에 담아두고 출력할 때 합성하는 방법을 쓸 수도 있다는 점이다.

#-*-coding:utf-8

my_name = "sooop"
height = 180
weight = 100
eye = 1.5

print "My name is %s." % my_name
print "I'm %dcm tall and weight %d kg." % (height, weight)
print "My Eyes are %f. I don't need glasses." % eye

print

# string with format
greeting = "Hi, my name is %s" % my_name
print greeting
print

# use format string
more_greet = "Hi, my name is %s. \nI'm %dcm tall and weight %dkg."
print more_greet % (my_name, height, weight)

위의 ex02.py와 마찬가지로 코드의 각 부분이 어떤 의미인지 유추해보고 어떻게 표시될 지 추측해보자. 그리고 실행해보면 다음과 같은 결과가 보이는데, 예상대로 인지 확인해보자.

My name is sooop.
I'm 180cm tall and weight 100 kg.
My Eyes are 1.500000. I don't nee

Hi, my name is sooop

Hi, my name is sooop.
I'm 180cm tall and weight 100kg.

1) 3행에서 이번에는 my_name 이라는 변수에 문자열을 대입했다. 그리고 이를 8행에서 출력한다.

2) 중요한 부분은 15행부터이다. 15행은 print 문이 아니다. greeting 이라는 변수를 정의하면서 이 변수에 포맷 문자열에 다른 변수 값을 합성하여 그 결과를 대입하고 있다. 따라서 greeting은 이 때 “Hi, my name is sooop.”이라는 문자열을 가리키게 된다.

3) 20행에서는 포맷 문자열 그자체를 변수에 대입했다. 21행에서 more_greet 변수 뒤에 치환될 값을 붙여주었다. 따라서 출력되는 시점에 문자열이 합성되고 이를 출력하게 되었다.

ex04.py

한가지 예제를 더 살펴보기로 하자.
# -*-coding:utf-8-*-
print "Mary had a little lamb."
print "Its fleece was white as %s." % 'snow'
print "And everywhere that Mary went."
print "." * 10 # ????

end1 = "C"
end2 = "h"
end3 = "e"
end4 = "e"
end5 = "s"
end6 = "e"
end7 = "B"
end8 = "u"
end9 = "r"
end10 = "g"
end11 = "e"
end12 = "r"

print end1 + end2 + end3 + end4 + end5 + end6,
      print end7 + end8 + end9 + end10 + end11 + end12

당연하다고 생각할 수도 있겠지만, 파이썬에서는 “문자열에 대한 연산”을 지원한다. 사실 포매팅 문자열 역시 문자열에 대한 연산인 셈이다. (포맷 문자열만 변수에 저장했다가 print 시에 연산하는 것 처럼 이해할 수 있다.) 3행을 보면 실제로 포매팅 문자열에 치환 값으로 변수가 아닌 문자열 상수를 넣고 있다. 이런 식으로 2개의 문자열을 합성하는 것이 가능하다.

특히 문자열에 대해 +, * 연산이 가능하다. 문자열을 더하면 문자열끼리 연결되고, 숫자를 곱하면 곱한 숫자값만큼 반복된다는 것을 알 수 있다. 표시되는 결과는 다음과 같다.

Mary had a little lamb.
Its fleece was white as snow.
And everywhere that Mary went.
..........
Cheese Burger

포맷팅 문자열을 사용하는 것은 상당히 쓸모가 많은데, 다음 ex05.py를 통해서 한가지만 더 살펴보기로 하자.

ex05.py

formatter = "%r %r %r %r"

print formatter % (1, 2, 3, 4)
print formatter % ("one", "two", "three", "four")
print formatter % (True, False, False, True)
print formatter % (formatter,   formatter,  formatter,  formatter)
print formatter % (
        "I had this thing.",
        "That you could type up right.",
        "But it didn't sing.",
        "So I said goodnight."
        )

포맷팅 문자열을 변수에 대입한 후 여기에 다양한 치환값을 넣어 매번 다르게 표시하고 있는 것을 확인할 수 있다. 이 예제의 출력은 쉽게 예상할 수 있을 것이다.

1 2 3 4
'one' 'two' 'three' 'four'
True False False True
'%r %r %r %r' '%r %r %r %r' '%r %r %r %r' '%r %r %r %r'
'I had this thing.' 'That you could type up right.' "But it didn't sing." 'So I said goodnight.'

상당히 긴 글이 된 관계로 잠깐 요약을 하고 마무리하겠다. 기본적인 원리와 문법에 대해서는 꼭 기억해야 한다.

  • 문자열 포맷팅은 문자열과 다른 값을 합성하는 방법이다.
  • 문자열 포매팅과 더하기, 곱하기 등은 문자열에 대한 연산이다.
  • 문자열 포매팅은 문자열 % (치환값, 치환값…)의 문법을 사용한다.
  • 변수는 어떤 값을 가리키는 이름표이다.

[Python101] 001. 문자를 출력해보다.

출력해보기

아마도 C언어가 세상에 알려질 때 “Hello World!”라는 문장을 출력하는 시연을 했던 것이 시초가 되어 프로그래밍 언어를 시작하는 개발자들은 맨 먼저 이 문장을 출력해보는 코드를 작성하곤 한다고 한다. 이는 단순히 관습이라기보다는 보다 중요한 의미를 갖는다. 프로그램은 기본적으로 “어떤 처리를 해주는 기계 장치”에 가까운 개념이다. 그러니까 입력→처리→출력의 과정을 통해서 작업을 수행한다.

문자열을 찍어서 표시하는 기능은 이 출력의 중요한 부분을 차지하게 된다. 그런데 문제는 뭔가를 ‘출력’할 때는 그 표현 양식이 중요하다는 점이다. 그래서 프로그래밍 언어마다 문자열을 포매팅(서식화)하는 부분은 나름 상당히 중요하게 다뤄지고 있고, 이 부분은 깊게 공부하고자 한다면 상당히 살펴야 하는 분량이 많다. (문자열을 다루는 것 자체가 생각보다 범위가 많고 또 그만큼 중요하다.) 따라서 프로그래밍을 통해 실제로 쓸모 있는 것을 만드는 것을 가정한다면 입출력과 관련한 부분은 꽤나 중요하다고 볼 수 있다. 그래서 다른 개념들에 앞서서 이 부분을 좀 먼저 다뤄 봐야 할 것 같다.

ex01

가장 먼저 print 명령에 대해 살펴보도록 하겠다. print 명령은 정해진 문자열을 콘솔 화면에 출력해주는 명령이다.

print {출력할 것}

위와 같은 형식으로 쓰며, 실제로 중괄호 {}는 쓰지 않는다. 이 중괄호는 명령이나 구문에서 직접 넣어줘야 하는 것을 의미하는 형식상의 표현이라 보면 된다.

우리가 (공식적으로는) 처음으로 작성해볼 ex01.py는 간단히 텍스트를 출력해주는 프로그램이다. 한줄 한줄 정확하게 타이핑해 보도록 하자. (화면이 좁아 읽기 불편하다면 텍스트 편집기에 붙여 넣은 다음, 실제로 그 내용을 보면서 꼭 직접 타이핑하도록 한다. 맥용 IDLE에서는 한글 입력이 안되는 문제가 있는데, 불가피한 경우에는 텍스트 에디터에서 작성 후 IDLE에서 붙여 넣자.)

#!/usr/bin/python
#-*-coding:utf-8

# 1번 행은 터미널에서 바로 실행될 수 있도록 함
# 2번 행은 주석이나 문자열에 한글을 쓸 수 있도록함
# 윈도의 경우에는 -*-coding:cp949 로 해야 함

# 문자열을 출력할 때는 print 명령을 사용한다.
# 문자열은 겹따옴표나 홑따옴표로 둘러싸게 된다.

print "Hello World!"
print 'Hello World!'

# 위 두 문장은 똑같은 결과를 출력한다. 차이가 있는지?

print
print
# print 만 쓴 문장은 빈줄을 출력한다.

print "You're typing your first python program."
print 'He said "Good morning." and waved to me.'

# 위 두 문장은 무슨 차이가 있는지? 따옴표의 종류에 주목할 것

# using escape characters

print "That's The \"GREATEST\" program."

# another escape charaters
print
print
print "This line is printed in \n2 lines."
print "\tAnd this line is printed with indentation."
print "You can also print\n\t* a kind of\n\t* list\n\t* like this way."
print "\n\nIf you want to print 'back slash', use escape character : \\\\"

# 파이썬은 긴 문자열을 출력할 때 이렇게 쓸 수도 있음

print """You can print multi-line text, with triple double-quotes
like this. print 2, 3, 4 ,5 or more line as you type.
You can also using \\ escape character \\ in this representation.
and "Qouted Sentence using 'qoutes' is also available."."""

# 마지막으로 재밌는 표현하나더
print '.' * 20

위 코드를 정확히 타이핑했다면, 실행 후 다음과 같은 내용을 볼 수 있을 것이다.

Hello World!
Hello World!

You're typing your first python program.
He said "Good morning." and waved to me.
That is 'The "GREATEST" program.

This line is printed in
2 lines.
And this line is printed with indentation.
You can also print
    * a kind of
    * list
    * like this way.

If you want to print 'back slash', use escape character : \\
You can print multi-line text, with triple double-quotes
like this. print 2, 3, 4 ,5 or more line as you type.
You can also using \ escape character \ in this representation.
and "Qouted Sentence using 'qoutes' is also available.".
....................

1) # 문자로 시작하는 행은 주석(코멘트)이다. 주석은 프로그램 실행에 아무런 영향을 주지 않는다. 대신 나중에 작성된 코드를 다시 읽어볼 때 이 부분은 무엇을 하는 부분인지 쉽게 알아보기 위해 메모를 할 때 사용할 수 있고, 혹은 문제가 있는 코드를 무력화할 때 사용하기도 한다. 주석에 대한 내용은 뒤에서 다시 살펴보게 될 것이다.

2) 처음 2줄의 주석은 파이썬에서 특별한 장치이다. 1행은 파이썬이 설치된 경로를 알려주어 터미널에서 파이썬 코드가 바로 실행될 수 있도록 한다. (이 부분 역시 다음에 따로 설명하겠다.) 2행은 이 코드의 문자가 UTF-8로 인코딩된 문자라는 것을 나타낸다. 주석등에 영문자, 숫자 등이 아닌 한글이나 한자, 일본어 등이 들어갈 때 파이썬에게 알파벳 말고 다른 문자가 있다는 것을 알려준다고 보면 된다.

3) Hello World! 를 출력하는 각각의 print 명령문이 있다. 두 개는 결과적으로 동일한 출력을 하지만 따옴표를 겹따옴표(“)를 썼는지 홑따옴표(‘)를 썼는지 차이가 있다. 이 따옴표의 차이는 다시 아래에서 볼 수 있다.

4) print 문에 아무 것도 넣지 않는다면 그냥 빈 줄을 출력하게 된다.

5) 20~21 행 두 줄은 따옴표의 용도를 잘 설명해주고 있다. 문자열은 따옴표 안에 들어가게 되므로 만약 문자열 내에 겹따옴표가 있다면 홑따옴표를, 홑따옴표가 있다면 겹따옴표를 써서 문자열을 감싸야 한다. (열고 닫는 따옴표의 쌍을 제대로 맞추기 위해서이다.)

6) 만약, 문장 내에 겹따옴표와 홑따옴표를 같이 써야 한다면 어떻게 해야 할까? 만약 “That’s “The GREATEST” program.” 이라고 쓰면 따옴표는 괄호의 짝처럼 행동하므로 “That’s “The GREATEST” program.” 으로 두 개의 문자열 사이에 The GREATEST가 들어가서 오류가 나게 된다. 이렇게 파이썬에서 사용하는 문법과 관련된 문자들은 따로 특별한 처리를 하지 않으면 문자열 내에서는 사용할 수 없다. 따라서 따옴표를 문자열의 시작과 끝을 알리는 용도가 아니라 글자 그대로의 따옴표로 인식하게 하기 위해서 ‘이스케이프 문자’라는 것을 사용한다. 이는 문자 앞에 역슬래쉬(\ : 윈도 운영체제에서는 원화 표시로 표시됨)를 붙여서 사용한다. 즉 \” 이라고 쓰면 문자열을 닫는 따옴표가 아니라 문자열 내에서 따옴표 한 글자로 인식한다.

7) 32~35행은 이런 이스케이프 문자를 사용하는 몇 가지 예를 보여준다. 이스케이프 문자를 표시하는 역슬래쉬는 문장 내에서 쓰이면 마찬가지로 역슬래쉬 두개를 붙여 하나의 문자로 표시하면 된다. 이스케이프 문자는 그 외에 특수한 문자들을 대체하기도 한다. \n은 “New line”의 의미로 문자열 내에서 줄바꿈을 넣어준다. 그리고 \t는 “Tab”으로 탭 키를 눌러 들여쓰기를 한 효과를 보여준다.

8) 파이썬에서는 “여러 줄의 텍스트”를 하나의 문자열로 만드는 방법이 있다. 바로 겹 따옴표를 세번 연달아 쓴 것을 문자열의 앞,뒤에 붙여주는 것이다. 이렇게 만든 문자열은 우리가 타이핑한 그대로를 보여주기에 적합하다. 즉 여러 줄의 문장을 출력해야 하는 경우에는, 이 방법을 사용하면 print 문을 반복하지 않고 한 번에 많은 양의 텍스트를 출력할 수 있다. 물론 이 경우에도 이스케이프 문자를 사용하여 들여쓰기나 줄바꿈을 넣는 것이 가능하다.

특히 “”” ~ “”” 로 감싼 경우에는 문자열의 시작과 끝이 연속되는 따옴표 세 개 이기 때문에 겹따옴표나 홑따옴표를 이스케이프 문자로 처리하지 않고도 사용할 수 있게 된다.

파이썬 소스코드가 실행되는 방식과 import의 동작 원리

<updated> 원래 이 글은 파이썬 실행하는 방법에 대한 글이었는데, 이와 관련하여 별도로 내용을 더 자세히 정리한 글이 있어서 해당 글의 링크로 대신하고, 여기서는 파이썬 소스코드가 실행되는 방식과 내가 작성한 파이썬 파일을 import 하는 방법에 대해서 알아보기로 한다.

파이썬은 기본적으로 IDLE이라는 GUI 쉘과 편집기가 결합된 도구를 제공해주고 있다. 특히 코드 에디터를 이용해서 파이썬 코드를 파일로 저장하는 것은 같은 코드를 다른 프로그램에서 다시 작성할 필요 없이 쉽게 재사용할 수 있다. 특히 이런 코드 재사용을 위해서는 소스 코드를 조리법 식으로 작성하는 것이 아니라 함수 형태로 작성한 후, 다른 소스에서 import 구문을 사용하여 반입하는 방식으로 쉽게 재사용이 가능하다.

파이썬 소스코드가 실행되는 방식과 import의 동작 원리 더보기

[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 사이의 값으로 지정한다.