이미지에 필터를 적용하는 작업을 해볼겁니다
Core Image를 사용합니다.
제일 쉬운 블러를 빠르게 적용해보고
(이건 UIKit에서도 제공해서 적용하긴쉽긴하지만 커스텀하기엔 더 low level인 Core Image 좋습니다.)
모자이크와 색감필터 효과들도 적용해보려합니다
Core Image란?

Core Image는 정지 영상 및 동영상에 대한 실시간에 가까운 처리를 제공하도록 설계된 이미지 처리 및 분석 기술입니다.
Core Graphics, Core Video, Image I/O 프레임워크의 이미지 데이터 유형을 기반으로 작동하며, GPU 또는 CPU 렌더링 경로를 사용합니다.
Core Image는 사용하기 쉬운 애플리케이션 프로그래밍 인터페이스(API)를 제공함으로써 저수준 그래픽 처리의 세부 사항을 숨깁니다.
GPU의 성능을 활용하기 위해 OpenGL, OpenGL ES 또는 Metal의 세부 사항을 알 필요도 없으며, 멀티코어 처리의 이점을 얻기 위해 Grand Central Dispatch(GCD)에 대해 알 필요도 없습니다.
Core Image가 세부 사항을 처리해 줍니다.
앞으로 사용하게될 CIImage는 이미지가 아니라 이미지를 생성하는데 필요한 정보입니다.
About Core Image
About Core Image Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either
developer.apple.com
Core Image Filter Reference
Core Image Filter Reference
developer.apple.com
필터 정보나 Core Image 가이드는 여기서 확인하세요!
CIFilter이란..?
CIImage → CIFilter → output CIImage → CIFilter → output CIImage → … 방식으로
CIImage에 효과를 적용하는데 사용하는 타입입니다.
2번째링크를 눌러서 필터종류중에 블러쪽을 보면 이렇게생겼습니다.


CIFilter를 만드는 방법은 2가지가 있습니다.
import CoreImage.CIFilterBuiltins
func applyBlur(originImage: CIImage) {
let blurFilter = CIFilter.boxBlur()
blurFilter.inputImage = originImage
blurFilter.radius = 10
}
func applyBlur2(originImage: CIImage) {
let filter = CIFilter(name: "CIBoxBlur")
filter?.setValue(originImage, forKey: kCIInputImageKey) // 적용할 이미지
filter?.setValue(10, forKey: kCIInputRadiusKey) // 강도
}
1. import CoreImage.CIFilterBuiltins를 통해 CIFilter의 타입값으로 사용하는 방법
2. 직접 필터 문자열을 적용해서 kCI 상수값을 사용하는방법

결과는 똑같으니 취향에 맞게 사용하면 좋을거 같아요
만약에 여러 필터를 적용하고 싶다면??
체이닝을 통해서 적용 하면 됩니다.

모든 Core Image 필터는 출력 CIImage 객체를 생성하므로, 이 객체를 다른 필터의 입력으로 사용할 수 있습니다.
예를 들어, 그림 1-1에 표시된 필터 시퀀스는 이미지에 색상 효과를 적용한 다음, 광채 효과를 추가하고, 마지막으로 결과물에서 일부 영역을 잘라냅니다.
Core Image는 이와 같은 필터 체인의 적용을 최적화하여 결과를 빠르고 효율적으로 렌더링합니다.
체인 내 각 CIImage 객체는 완전히 렌더링된 이미지가 아니라, 단순히 렌더링을 위한 "레시피"에 불과합니다.
Core Image는 각 필터를 개별적으로 실행하여 볼 수 없는 중간 픽셀 버퍼를 렌더링하는 데 시간과 메모리를 낭비할 필요가 없습니다.
대신 Core Image는 필터를 단일 작업으로 결합하며, 다른 순서로 적용해도 동일한 결과를 더 효율적으로 생성할 수 있다면 필터의 순서를 재구성할 수도 있습니다.
그림 1-2는 그림 1-1의 예시 필터 체인을 더 정확하게 표현한 것입니다.
그림 1-2에서 크롭 작업이 마지막에서 첫 번째로 이동했음을 확인하십시오. 이 필터는 원본 이미지의 넓은 영역이 최종 출력에서 잘려나가는 결과를 초래합니다. 따라서 해당 픽셀에는 색상 및 선명도 필터를 적용할 필요가 없습니다. 크롭 작업을 먼저 수행함으로써 Core Image는 비용이 많이 드는 이미지 처리 작업이 최종 출력에 표시될 픽셀에만 적용되도록 보장합니다.
모든영역을 필터처리하는 연산이 비싸기때문에
크롭을 먼저 적용하고 필터처리해주는 작업을 개발자가 신경쓰지않아도
비용이 적은방향으로 자동으로 최적화가 이뤄진다고합니다
원하는 영역에 필터를 적용해보는 연습을위해서
블러필터를 구현해보고
이미지의 반만 크롭해서 적용후에
비교를위해서 가운데에 선을 그려보는 구현까지
시작해보겠습니다
private func applyBlur(image: UIImage) -> UIImage {
/// 기기 사이즈에 대응하는 이미지 가로길이
let imageWidth: CGFloat = UIDevice.screenWidth
/// 기기 사이즈에 대응하는 이미지 세로길이
let imageHeight: CGFloat = imageWidth * image.size.height / image.size.width
/// 이미지뷰의 사이즈
let imageViewSize: CGSize = .init(width: imageWidth, height: imageHeight)
/// 이미지 해상도
let imageSize: CGSize = image.size
let render = UIGraphicsImageRenderer(size: .init(width: imageWidth, height: imageHeight))
let lineWidth: CGFloat = 3
let lineColor = CGColor(red: 1, green: 0, blue: 0, alpha: 0.6)
let newImage = render.image { context in
image.draw(in: .init(origin: .zero, size: imageViewSize))
/// 가로 반만 필터적용
let halfWidthPixelRect: CGRect = .init(
origin: .zero,
size: .init(width: imageSize.width/2, height: imageSize.height)
)
let cropImage = blurWithCrop(
image: image,
cropPixel: halfWidthPixelRect
)
cropImage.draw(
in: .init(
origin: .zero,
size: .init(width: imageWidth/2, height: imageHeight)
)
)
// 필터적용 구분선
context.cgContext.setStrokeColor(lineColor)
context.cgContext.setLineWidth(lineWidth)
context.cgContext.setLineDash(phase: 1, lengths: [10,10])
context.cgContext.move(to: .init(x: imageWidth / 2, y: 0))
context.cgContext.addLine(to: .init(x: imageWidth / 2, y: imageHeight))
context.cgContext.strokePath()
}
return newImage
}
private func blurWithCrop(image: UIImage, cropPixel cropRect: CGRect) -> UIImage {
if let cropImage = image.cgImage?.cropping(to: cropRect) {
let blurImage = filterFactory.applyBlur(originImage: CIImage(cgImage: cropImage))
return blurImage
}
return image
}
// ImageFilter.swift
final class ImageFilter {
let ciContext: CIContext = .init()
func applyBlur(originImage: CIImage) -> UIImage {
let blurFilter = CIFilter.boxBlur()
blurFilter.inputImage = originImage
blurFilter.radius = 10
if let ciImage = blurFilter.outputImage,
let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
return UIImage(cgImage: cgImage)
}
return UIImage(ciImage: originImage)
}
}


원본과 필터를 적용한 이미지입니다.
잘적용되는게 보이네요
이번엔 모자이크필터와 색상필터를 구현해서 적용해볼게요


여러 필터를 적용할수있도록 코드를 정리해서 적용해볼까요
// ViewModel.swift
@Observable
final class ViewModelOnlyFilter {
enum FilterOption {
case sepia
case mosaic
case blur
}
var maskImages: [UIImage] = []
private var images: [UIImage] = []
private let filterFactory = ImageFilter()
init() {
setupImage()
maskImages = images.map {
// 원하는 필터적용부분
applyFilter(image: $0, filter: .blur)
}
}
private func setupImage() {
self.images = [
UIImage(resource: ._8),
UIImage(resource: ._10),
UIImage(resource: .이미지6),
UIImage(resource: .이미지1),
UIImage(resource: .이미지2),
]
}
private func applyFilter(image: UIImage, filter: FilterOption) -> UIImage {
/// 기기 사이즈에 대응하는 이미지 가로길이
let imageWidth: CGFloat = UIDevice.screenWidth
/// 기기 사이즈에 대응하는 이미지 세로길이
let imageHeight: CGFloat = imageWidth * image.size.height / image.size.width
/// 이미지뷰의 사이즈
let imageViewSize: CGSize = .init(width: imageWidth, height: imageHeight)
/// 이미지 해상도
let imageSize: CGSize = image.size
let render = UIGraphicsImageRenderer(size: .init(width: imageWidth, height: imageHeight))
let lineWidth: CGFloat = 3
let lineColor = CGColor(red: 1, green: 0, blue: 0, alpha: 0.6)
/// 가로 반만 필터적용
let halfWidthPixelRect: CGRect = .init(
origin: .zero,
size: .init(width: imageSize.width/2, height: imageSize.height)
)
let cropImage: UIImage = applyFilterWithCrop(
image: image,
cropPixel: halfWidthPixelRect,
filter: filter
)
let newImage = render.image { context in
image.draw(in: .init(origin: .zero, size: imageViewSize))
cropImage.draw(
in: .init(
origin: .zero,
size: .init(width: imageWidth/2, height: imageHeight)
)
)
// 중앙 세로선 그리기
context.cgContext.setStrokeColor(lineColor)
context.cgContext.setLineWidth(lineWidth)
context.cgContext.setLineDash(phase: 1, lengths: [10,10])
context.cgContext.move(to: .init(x: imageWidth / 2, y: 0))
context.cgContext.addLine(to: .init(x: imageWidth / 2, y: imageHeight))
context.cgContext.strokePath()
}
return newImage
}
private func applyFilterWithCrop(
image: UIImage,
cropPixel cropRect: CGRect,
filter: FilterOption
) -> UIImage {
/// 필요영역만 크롭후 필터적용해서 그려주는 방법
if let cropImage = image.cgImage?.cropping(to: cropRect) {
/**
UIKit, CoreImage 좌표다름 주의
cgContext이용
해상도는 원본이미지 Pixel사이즈기준으로 크롭해야해서 해상도 변경전 rect으로해야함 주의
그릴땐 해상도 변경후 테두리좌표에 그려야함 주의
*/
switch filter {
case .sepia:
return filterFactory.applySepiaTone(image: CIImage(cgImage: cropImage))
case .mosaic:
return filterFactory.applyPixelate(image: CIImage(cgImage: cropImage))
case .blur:
return filterFactory.applyBlur(image: CIImage(cgImage: cropImage))
}
}
return image
}
}
// ImageFilter.swift
final class ImageFilter {
let ciContext: CIContext = .init()
func applySepiaTone(image: CIImage) -> UIImage {
let filter = CIFilter.sepiaTone()
filter.inputImage = image
filter.intensity = 0.8
if let ciImage = filter.outputImage,
let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
return UIImage(cgImage: cgImage)
}
return UIImage(ciImage: image)
}
func applyPixelate(image: CIImage) -> UIImage {
let pixelateFilter = CIFilter.pixellate()
pixelateFilter.inputImage = image
pixelateFilter.scale = 7
if let ciImage = pixelateFilter.outputImage,
let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
return UIImage(cgImage: cgImage)
}
return UIImage(ciImage: image)
}
func applyBlur(image: CIImage) -> UIImage {
let blurFilter = CIFilter.boxBlur()
blurFilter.inputImage = image
blurFilter.radius = 10
if let ciImage = blurFilter.outputImage,
let cgImage = ciContext.createCGImage(ciImage, from: ciImage.extent) {
return UIImage(cgImage: cgImage)
}
return UIImage(ciImage: image)
}
}
'iyOmSd > Title: Swift' 카테고리의 다른 글
| [Swift] Vision FaceDetect (0) | 2025.11.29 |
|---|---|
| [Swift] Screen Time API(DeviceActivity) (0) | 2025.10.27 |
| [Swift] Screen Time API(FamilyControls, ManagedSettings) (0) | 2025.09.26 |
| [Swift] WWDC25 Explore concurrency in SwiftUI (2) | 2025.08.28 |
| [Swift] WWDC25 Wake up to the AlarmKit API (3) | 2025.07.22 |