[swift2] Core Image Filter 예제

Swift 2015. 11. 10. 23:42

절차지향 또는 객체지향 프로그래밍에 익숙한 상태에서, 함수형 프로그래밍에 적응하기란 쉽지 않습니다. 다른 프로그래밍 방법론을 공부했을 때처럼 많은 함수형 프로그램 코드를 읽고 이해하고, 또 많은 예제를 실제로 작성해봐야만 숙달될 수 있습니다.

swift 함수형 프로그램의 예로 Core Image 필터 예제를 소개합니다. 이 예제는 objc.io 에서 출판된 Funtional Programming in Swift 에서 발췌하였습니다.

출처: Funtional Programming in Swift

  • 이 예제에서는 MaxOSX Core Image 프레임워크의 함수형 wrapper API 작성을 목표로 합니다.
  • 또한 고차원함수(high-order function)와 같은 함수형 프로그래밍의 실질적인 활용예를 보는 것입니다.


Core Image

  • Core Image는 강력한 이미지 프로세싱 프레임워크지만 사용하기가 불편합니다.
  • Core Image API는 키밸류 코딩을 사용해서 설정할 수 있는 느슨하게 타이핑된 API 입니다.
  • 파라미터 이름을 입력할 때 실수할 수 있는 여지가 많아 런타임 에러를 발생시킬 수 있습니다.
  • 이번에 개발할 API 는 안전하고, 모듈화되었으며, (오타로 인한)런타임 에러가 발생하지 않도록 타입을 장점을 활용합니다.


Filter Type

  • Core Image의 핵심클래스는 이미지 필터를 생성하는데 사용하는 CIFilter 클래스입니다.
  • CIFilter 객체 생성시 반드시 kCIInputImageKey 키에 해당하는 input image를 전달해야합니다.
  • kCIOutputImageKey 키를 사용해서 필터가 적용된 결과 이미지를 얻어올 수 있습니다.
  • 또한 이 결과 이미지는 다른 필터의 입력으로 사용할 수 있습니다.

CIImage 타입을 파라미터로 받아 CIImage 타입의 결과 이미지를 반환하는 함수타입 Filter를 정의합니다.

typealias Filter = CIImage -> CIImage


Blur 필터 함수

  • 이미지에 블러를 적용하는 blur 함수를 정의해보겠습니다.
  • 이 블러필터는 한개의 blur radius 파라미터를 받습니다.
func blur(radius:Double) -> Filter {
    return { image in
        let parameters = [
            kCIInputRadiusKey : radius,
            kCIInputImageKey  : image
        ]
        let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters)
        return filter!.outputImage!
    }
}
  • blur 함수는 CIImage 타입의 이미지를 인자로 받고, 결과 이미지(filter!.outputImage)를 리턴하는 Filter 함수를 반환합니다.


Color Overlay 필터 함수

  • 이미지 위에 특정 컬러 레이어로 덮는 오버레이 필터를 정의해보겠습니다.
  • CoreImage 에는 오버레이 필터가 없지만 이미 있는 필터들을 조합하여 만들것입니다.
  • 컬러를 생성하는 color generator 필터와 레이어를 얹는 source-over compositing 필터를 사용하겠습니다.


Color 레이어 생성필터

func colorGenerator(color:NSColor) -> Filter {
    return { image in
        let parameters = [kCIInputColorKey:CIColor(color: color)!]
        let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}   
  • blur 필터와 유사하지만 input 이미지가 필요하지 않다는 차이가 있습니다.
  • 생성된 컬러 레이어 이미지를 특정크기(인자로 받은 이미지의 크기)로 잘라주어야 합니다.
  • 다음으로 두 이미지를 복합하는 필터를 정의합니다.


레이어 오버필터

func compositeSourceOver(overlay:CIImage) -> Filter {
    return { image in
        let parameters = [
            kCIInputBackgroundImageKey: image,
            kCIInputImageKey: overlay
        ]
        let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}
  • 마지막에 input 이미지의 크기만큼 결과 이미지를 자릅니다.
  • 두 필터를 결합하여 color overlay 필터를 생성합니다.


Color Overlay 필터

func colorOverlay(color:NSColor) -> Filter {
    return { image in
        let overlay = colorGenerator(color)(image)
        return compositeSourceOver(overlay)(image)
    }
}
  • colorOverlay 함수는 컬러를 인자로 받아 필터함수를 반환합니다.
  • 이 필터함수에 이미지를 전달하면, 앞서 전달한 컬러로 오버레이된 이미지를 반환합니다.


Filter 복합하기

  • blur 필터와 color overlay 필터를 복합해서 사용해보겠습니다.
  • 원본 이미지 파일을 읽습니다.
let path = NSBundle.mainBundle().pathForResource("16.jpg", ofType: nil)!
let image = CIImage(contentsOfURL: NSURL(fileURLWithPath: path))!



  • blur 필터를 적용합니다.
let blurRadius = 5.0
let blurredImage = blur(blurRadius)(image)



  • 빨간색 레이어를 오버레이합니다.
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let overlaidImage =  colorOverlay(overlayColor)(blurredImage)



Function Composition

  • 위 예제처럼 두 필터함수를 한문장의 표현식으로 표현할 수 있습니다.
let result = colorOverlay(overlayColor)(blur(blurRadius)(image))
  • 하지만 여러 괄호들로 인해 조합된 필터의 수가 많아질 수록 가독성이 나타집니다.
  • 더 좋은 방법은 필터 조합을 위한 커스텀 연산자를 정의하는 것입니다.
  • 먼저 필터를 조합하는 함수를 정의해보겠습니다.
func composeFilters(filter1 filter1:Filter, filter2:Filter) -> Filter {
    return { image in filter2(filter1(image)) }
}
  • 위 함수는 두개의 필터를 인자로 받아 조합하여 새로운 필터를 정의합니다.
  • 반환된 필터 함수는 CIImage 타입의 image 인자를 filter1과 filter2에 순차적으로 전달합니다.
  • 아래와 같이 사용할 수 있습니다.
let myFilter1 = composeFilters(filter1: blur(blurRadius), filter2: colorOverlay(overlayColor))
let result1 = myFilter1(image)
  • 이제 필터 조합을 위한 연산자를 정의하여 좀더 가독성 있게 만들어보겠습니다.
  • 모든 곳에 자체 연산자를 정의할 필요는 없지만, 필터 조합은 이미지 처리 라이브러리에서 반복해서 발생하는 작업이기 때문에 그 용도가 적합합니다.
infix operator >>> { associativity left }

func >>> (filter1:Filter, filter2:Filter) -> Filter {
    return { img in filter2(filter1(img)) }
}
  • 이제 >>> 연산자를 이전에 정의했던 composeFilters 함수처럼 사용할 수 있습니다.
let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)
  • >>> 연산자를 left-associativity 로 정의했기 때문에, 유닉스 파이프처럼 왼쪽에서 오른쪽으로 필터가 적용됩니다.
  • 우리가 정의한 필터 조합은 Function Composition 의 한 예입니다.


전체소스

import Cocoa
import XCPlayground


// define Filter Type
typealias Filter = CIImage -> CIImage

// blur function
func blur(radius:Double) -> Filter {
    return { image in
        let parameters = [
            kCIInputRadiusKey : radius,
            kCIInputImageKey  : image
        ]
        let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters)
        return filter!.outputImage!
    }
}


// color layer function
func colorGenerator(color:NSColor) -> Filter {
    return { image in
        let parameters = [kCIInputColorKey:CIColor(color: color)!]
        let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}

// overlay function
func compositeSourceOver(overlay:CIImage) -> Filter {
    return { image in
        let parameters = [
            kCIInputBackgroundImageKey: image,
            kCIInputImageKey: overlay
        ]
        let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters)
        return filter!.outputImage!.imageByCroppingToRect(image.extent)
    }
}

// color overlay function
func colorOverlay(color:NSColor) -> Filter {
    return { image in
        let overlay = colorGenerator(color)(image)
        return compositeSourceOver(overlay)(image)
    }
}

// 원본 이미지 읽기
let path = NSBundle.mainBundle().pathForResource("16.jpg", ofType: nil)!
let image = CIImage(contentsOfURL: NSURL(fileURLWithPath: path))!

// apply blur
let blurRadius = 5.0
let blurredImage = blur(blurRadius)(image)

// apply color overlay
let overlayColor = NSColor.redColor().colorWithAlphaComponent(0.2)
let overlaidImage =  colorOverlay(overlayColor)(blurredImage)


// define composition function
func composeFilters(filter1 filter1:Filter, filter2:Filter) -> Filter {
    return { image in filter2(filter1(image)) }
}

let myFilter1 = composeFilters(filter1: blur(blurRadius), filter2: colorOverlay(overlayColor))
let result1 = myFilter1(image)


// define composition operator
infix operator >>> { associativity left }

func >>> (filter1:Filter, filter2:Filter) -> Filter {
    return { img in filter2(filter1(img)) }
}

let myFilter2 = blur(blurRadius) >>> colorOverlay(overlayColor)
let result2 = myFilter2(image)





'Swift' 카테고리의 다른 글

[swift2] 키워드로 보는 Swift  (0) 2015.11.30
[swift2] Currying 함수  (0) 2015.11.25
[swift2] Core Image Filter 예제  (0) 2015.11.10
[swift2.1] Xcode7.1 Playground 변경사항  (0) 2015.10.27
[swift2] Functor 와 Monad  (0) 2015.10.25
[swift2] 타입변환 연산자 (is, as, as?, as!)  (0) 2015.10.18