0

Tìm hiểu về Core Image: filters image

I. Giới thiệu

Core Image là một framework được viết bởi Apple sử dụng cho cả iOS và OS x. Đây là một framework rất mạnh mẽ, cung cấp cho chúng ta khả năng xử lý ảnh cực nhanh(near real-time processing). Framework này có khả năng xử lý nhiểu kiểu dữ liệu hình ảnh từ các framework khác như Core Graphics, Core Video, Image I/O,...

Core Image cung cấp cho chúng ta các hàm API đơn giản để chúng ta làm việc với tác vụ xử lý đồ họa. Thông qua các hàm API này, chúng ta không cần phải làm việc trực tiếp với các tầng xử lý đồ họa bậc thấp bên dưới(OpenGL, Open GLES). Hơn nữa, Core Image có khả năng chọn lựa xử lý hình ảnh trên CPU hoặc GPU, cung cấp cho chúng ta API để thực hiện việc xử lý đa luồng.

Trong bài này, tôi xin giới thiệu đến các bạn về filters image, tính năng quan trọng bậc nhất của Core Image. Để tiện cho việc tìm hiểu, chúng ta sẽ tìm hiểu qua app demo sau đây.

II. Demo App

1. Tạo filter

Bước đầu tiên, chúng ta mở Xcode, tạo một project: File -> New -> Project -> iOS -> Application -> Single View Application. Đặt tên cho project "FilterImageTutorial", chọn Language Swift, Devices Universal, bỏ tích các ô "Use Core Data", "Include Unit tests", "Include UI test" và tạo project.

Screen Shot 2016-05-24 at 5.20.03 PM.png

Tiếp theo, chúng ta mở file storyboard, kéo thả một UIImageView vào controller, tạo auto layout cho UIImageView với UIView Tralling, Leading, top và bottom của UIViewController như hình

Screen Shot 2016-05-24 at 5.26.57 PM.png

Chúng ta cần ảnh sample để thực hiện việc filter. Các bạn có thể lên đây down ảnh về, đổi tên thành image (cho dễ dùng 😉) và kéo vào Assets.xcassets.

Bước tiếp, chúng ta kéo Outlet cho UIImageView bên trên vào ViewController của chúng ta, đồng thời import framework Core Image:

import UIKit import CoreData

class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

Kế tiếp, chúng ta viết thêm code load ảnh vào trong imageView trong hàm viewDidLoad():

    override func viewDidLoad() {
        super.viewDidLoad()

        let image = UIImage(named: "image")
        imageView.image = image
        imageView.contentMode = UIViewContentMode.ScaleAspectFit
    }

Chạy thử project để chắc chắn là image của chúng ta bình thường:

Screen Shot 2016-05-24 at 10.10.39 PM.png

Tiếp theo, chúng ta thực hiện việc filter image bằng cách thêm vào hàm viewDidLoad() đoạn code sau:

    override func viewDidLoad() {
        super.viewDidLoad()

        let image = UIImage(named: "image")
//        imageView.image = image
        imageView.contentMode = UIViewContentMode.ScaleAspectFit

        // 1
        guard let img = image, cgImage = img.CGImage else { return }

        // 2
        let coreImage = CIImage(CGImage: cgImage)

        // 3
        let filter = CIFilter(name: "CIColorInvert")
        filter?.setValue(coreImage, forKey: kCIInputImageKey)

        // 4
        if let output = filter?.valueForKey(kCIOutputImageKey) as? CIImage {
            let filteredImage = UIImage(CIImage: output)
            imageView.image = filteredImage
        } else {
            print("filter image failed")
        }
    }

Như các bạn có thể thấy trong đoạn code trên, việc thực hiện filter ảnh bằng Core Image là rất đơn giản. Các bước thực hiện trên đoạn code trên lần lượt:

  1. kiểm tra điều kiện ảnh khác nil để thực hiện các bước kế tiếp
  2. Tạo CIImage từ cgImage để làm ảnh đầu vào cho filter
  3. Tạo object filter và gán các property vào các key cho filter. Như ví dụ trên đây, filter của tôi là "CIColorInvert" filter, filter này chỉ có 1 property cần gán cho key kCIInputImageKey
  4. Chúng ta lấy ảnh đầu ra từ filter bằng cách lấy property từ key kCIOutputImageKey của filter. Trong bước này, chúng ta gán convert ngược lại từ CIImage đầu ra thành dạng UIImage, rồi gán trả lại vào imageView

Chú ý: Trong bước 3 bên trên, các bạn hãy để ý tên của filter khi chúng ta khởi tạo filter. Core Image hỗ trợ chúng ta rất nhiều loại filter để chúng ta truyền vào khi khởi tạo filter. Với mỗi loại filter khác nhau đòi hỏi chúng ta cần truyền vào các property cho các key là khác nhau. Ví dụ với "CIColorInvert" bên trên, chúng ta chỉ cần gán giá trị cho kCIInputImageKey, nhưng với "CISepiaTone", chúng ta cần gán giá trị cho cả kCIInputImageKey và kCIInputIntensityKey. Để tìm hiểu thêm về các loại filter và giá trị cần truyền vào filter, các bạn có thể tham khảo tài liệu của Apple ở đây

Build và run thử project, chúng ta sẽ được kết quả là ảnh với các pixel đảo ngược dải màu so với ảnh gốc:

Screen Shot 2016-05-24 at 10.53.47 PM.png

2. Thực hiện filter trên GPU

Bên trên, chúng ta đã thực hiện việc filter ảnh. Tuy nhiên, tác vụ filter này được thực hiện trên CPU và trên main thread. Khi chúng ta thực hiện việc này với ảnh không quá lớn và với số lượng ít, việc này không làm app của chúng ta bị ảnh hưởng nhiều. Tuy nhiên, khi phải xử lý ảnh kích thước lớn, hoặc phải xử lý nhiều ảnh đồng thời, việc filter sẽ làm app của chúng ta bị chậm, người dùng có cảm giác app bị treo. Điều này là không tốt chút nào. Core Image cho chúng ta một giải pháp rất hay, đó là sử dụng GPU để thực thi tác vụ filter. Nhiệm vụ chính của GPU là xử lý các tác vụ đồ họa, xử lý ảnh,... hoàn toàn phù hợp với việc filter ảnh của chúng ta.

Để việc filter image được thực hiện trên GPU, chúng ta thêm code vào hàm viewDidLoad() như sau:


    override func viewDidLoad() {
        super.viewDidLoad()

        let image = UIImage(named: "image")
//        imageView.image = image
        imageView.contentMode = UIViewContentMode.ScaleAspectFit

        // 1
        guard let img = image, cgImage = img.CGImage else { return }

        let openGLContext = EAGLContext(API: .OpenGLES2)    // 5
        let context = CIContext(EAGLContext: openGLContext) // 6

        // 2
        let coreImage = CIImage(CGImage: cgImage)

        // 3
        let filter = CIFilter(name: "CIColorInvert")
        filter?.setValue(coreImage, forKey: kCIInputImageKey)

        // 4
        if let output = filter?.valueForKey(kCIOutputImageKey) as? CIImage {
            let cgimgresult = context.createCGImage(output, fromRect: output.extent)    // 7
            let filteredImage = UIImage(CGImage: cgimgresult)   // 8
            imageView.image = filteredImage
        } else {
            print("filter image failed")
        }
    }

  1. Khởi tạo OpenGL ES context: Các API của OpenGL ES đều chạy trực tiếp trên GPU. Vì thế, khi chúng ta khởi tạo OpenGL ES context, context này mặc nhiên chạy trên GPU
  2. Tạo Core Image context từ OpenGL ES context ở bên trên để có thể filter image trên GPU
  3. Lấy ảnh trả về sau quá trình filter. Việc lấy ảnh trả về khi sử dụng CIContext có một chút khác biệt so với khi không dùng CIContext
  4. Tạo UIImage từ CGImage để gán trả lại imageView

Như các bạn thấy, chúng ta đã thực hiện việc chuyển tác vụ filter ảnh từ CPU sang GPU mà chỉ phải sử dụng một vài dòng code không hề phức tạp.

Build và run thử project, chúng ta vẫn nhận được ảnh filter như bên trên, nhưng có vẻ việc hiển thị ảnh trên imageView đã khá hơn (giống với hiển thị ảnh gốc ban đầu)

Screen Shot 2016-05-24 at 11.27.47 PM.png

Trong demo này, chúng ta chỉ làm việc với duy nhất 1 ảnh với kích thước không lớn, nên việc di chuyển tác vụ filter sang GPU không mang lại nhiều ý nghĩa. Tuy nhiên, trong quá trình viết app thực tế, khi phải làm việc với ảnh nặng và số lượng lớn, việc xử lý ảnh trên GPU sẽ mang lại nhiều lợi ích cho chúng ta.

III. Kết luận

Trên đây, tôi đã giới thiệu đến các bạn sơ qua về Core Image, một framework hết sức mạnh mẽ của iOS/OS x mà chúng ta cần phải biết khi xử lý ảnh. Chúng ta cũng đã tìm hiểu về việc sử dụng Core Image để filter ảnh thông qua ví dụ demo đơn giản. Hi vọng bài viết có thể giúp ích cho các bạn. Cuối cùng, xin cảm ơn các bạn đã theo dõi bài viết này!!!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí