Tổng hợp các kỹ thuật Resizing Image mà ai cũng có thể làm được

Có nhiều cách để resizing image nhưng không phải ai cũng biết hết. Thế nên chúng tôi đã tổng hợp đầy đủ các kỹ thuật resizing image chính xác và dễ hiểu mà mà ai cũng có thể làm được. Cùng xem ngay thôi!

Tổng hợp các ky thuật resizing image

Vì sao phải scale images?

- Chúng ta cần scale image khi mà ảnh mà chúng ta cần hiển thị có kích thước nhỏ hơn so với ảnh chúng ta có được.

- UIImageView tự động scale và crop image theo trình tự đã được định sẵn bời contentMode property và trong các case .scaleAspectFit, .scaleAspectFill, hoặc .scaleToFill.

imageView.contentMode = .scaleAspectFit

imageView.image = image

RAM:

- Kỹ thuật này được biết đến như là downsampling và nó thể cải thiện rất nhiều hiệu năng của app trong trường hợp sử dụng nó.

- Ngày nay một số app có thể load những tấm ảnh lớn mà không khác xa với ảnh gốc. Để thực hiện được việc này, cùng tham khảo 1 số phương pháp resizing nhé!

Những kỹ thuật resizing images

Tổng hợp các kỹ thuật Resizing Image mà ai cũng có thể làm được 2

Có nhiều cách khác nhau để có thể resizing image, mỗi cách khác nhau mang đến những hiệu năng khác nhau. Chúng ta có thể tìm kiếm những cách thức này ở low/high-level từ Core GraphicsvImageImage I/OCore Graphics và cả UIKit:

Tuy nhiên, để nhất quán, mỗi một kỹ thuật sau sẽ dùng chung 1 mẫu:

func resizedImage(at url: URL, for size: CGSize) -> UIImage? { ... }

imageView.image = resizedImage(at: url, for: size)

Ở đây, size là cách đo point-size thay vì dùng pixel-size. Để tính toán pixel-size cho việc scale, resizing size chúng ta dùng scale của UIScreen:

let scaleFactor = UIScreen.main.scale

let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)

let size = imageView.bounds.size.applying(scale)

Nếu bạn đang load asynchronously 1 tấm ảnh lớn hãy sử dụng transition để có tấm ảnh mờ trước trong imageview. Ví dụ:

class ViewController: UIViewController {

@IBOutlet var imageView: UIImageView!




override func viewWillAppear(_ animated: Bool) {

super.viewWillAppear(animated)




let url = Bundle.main.url(forResource: "Blue Marble West",

withExtension: "tiff")!




DispatchQueue.global(qos: .userInitiated).async {

let image = resizedImage(at: url, for: self.imageView.bounds.size)




DispatchQueue.main.sync {

UIView.transition(with: self.imageView,

duration: 1.0,

options: [.curveEaseOut, .transitionCrossDissolve],

animations: {

self.imageView.image = image

})

}

}

}

}

Sử dụng UIGraphicsImageRenderer

Highest-level API hỗ trợ việc resizing ở trong UIKit framework. UIImage cho phép bạn có thể vẽ ở trong UIGraphicsImageRenderer context để render và thu nhỏ bức ảnh:

import UIKit




// Technique #1

func resizedImage(at url: URL, for size: CGSize) -> UIImage? {

guard let image = UIImage(contentsOfFile: url.path) else {

return nil

}




let renderer = UIGraphicsImageRenderer(size: size)

return renderer.image { (context) in

image.draw(in: CGRect(origin: .zero, size: size))

}

}

UIGraphicsImageRenderer là một API tương đối mới được giới thiệu trong iOS10 để thay thế cho những  API UIGraphicsBeginImageContextWithOptions / UIGraphicsEndImageContext. Bạn xây dựng UIGraphicsImageRenderer bằng point-size. Method image sẽ tạo 1 closure và trả về kết quả những bitmap từ closure. Trong trường hợp này, kết quả trả về là bức ảnh gốc được thu nhỏ lại trong giới hạn chỉ định.

UIGraphicsImageRenderer hữu ích trong việc thu nhỏ kích thước ban đầu bức ảnh để phù hợp với khung ảnh hiển thị mà không thay đổi tỉ lệ gốc. AVMakeRect(aspectRatio:insideRect:) là function trong AVFoundation đảm trách tính toán này cho bạn:

import func AVFoundation.AVMakeRect

let rect = AVMakeRect(aspectRatio: image.size, insideRect: imageView.bounds)

Sử dụng Core Graphics

Tổng hợp các kỹ thuật Resizing Image mà ai cũng có thể làm được 3

Core Graphics / Quartz 2D là một lower-level API cho phép chúng ta có nhiều tuỳ chỉnh cá nhân hơn.

Chúng ta sẽ có CGImage, một context bitmap tạm thời được sử dụng để render các bức ảnh đã scale bằng method draw(_ in: ...):

import UIKit

import CoreGraphics




// Technique #2

func resizedImage(at url: URL, for size: CGSize) -> UIImage? {

guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),

let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)

else {

return nil

}




let context = CGContext(data: nil,

width: Int(size.width),

height: Int(size.height),

bitsPerComponent: image.bitsPerComponent,

bytesPerRow: image.bytesPerRow,

space: image.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!,

bitmapInfo: image.bitmapInfo.rawValue)

context?.interpolationQuality = .high

context?.draw(image, in: CGRect(origin: .zero, size: size))




guard let scaledImage = context?.makeImage() else { return nil }




return UIImage(cgImage: scaledImage)

}

CGContext khởi tạo yêu cầu một vài arguments để tạo context, bao gồm các tham số kích thước và dung lượng bộ nhớ cho mỗi channel với các color space . Trong ví dụ này, các parameter được lấy ra từ CGImage object.

Tiếp đó, việc setting interpolationQuality property thành .high để nội suy cho các pixel ảnh được hiển thị trung thực, Method** draw(_:in**) sẽ vẽ lại image được cung cấp size và position, cho phép bức ảnh được crop và cắt các góc cạnh cho vừa với đặc điểm yêu cầu hiển thị. Cuối cùng, method makeImage() sẽ truyền những thông tin từ context có được vừa render nó cho CGImage.

Sử dụng Image I/O

Image I/O là 1 framework mạnh mẽ để làm việc với image. Nó hoạt động độc lập với Core Graphics, nó có thể read and write giữa các format khác nhau, các loại data photo khác nhau và tiến hành xử lý các phương thức xử lý ảnh thông thường.

Framework này cho phép các hình thức encoder và decoder nhanh nhất với các cơ chế caching nâng cao, thậm chí là khả năng load từng bước image.

Một chú ý quan trọng là CGImageSourceCreateThumbnailAtIndex cung cấp API ngắn gọn với những lựa chọn khác nhau tỏ ra hữu dụng hơn với những thứ được cung cấp trong CoreGraphics.

import ImageIO




// Technique #3

func resizedImage(at url: URL, for size: CGSize) -> UIImage? {

let options: [CFString: Any] = [

kCGImageSourceCreateThumbnailFromImageIfAbsent: true,

kCGImageSourceCreateThumbnailWithTransform: true,

kCGImageSourceShouldCacheImmediately: true,

kCGImageSourceThumbnailMaxPixelSize: max(size.width, size.height)

]




guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),

let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)

else {

return nil

}




return UIImage(cgImage: image)

}

Chúng ta có CGImageSource để có thể set các tuỳ chọn, func CGImageSourceCreateThumbailAtlndex để tạo một thumbnail của bức ảnh để chúng ta có thể preview. Việc resizing hoàn thành bởi kCGImageSourceThumbnailMaxPixelSize với kích thước tối đa được sử dụng để chia tỷ lệ bức hình theo hình ảnh gốc.

Với việc setting kCGImageSourceCreateThumbnailFromImageIfAbsent hoặc kCGImageSourceCreateThumbnailFromImageAlwaysImage I/O tự động cache và scale kết quả cho lần sử dụng tiếp theo.

Sử dụng Core Image

Tổng hợp các kỹ thuật Resizing Image mà ai cũng có thể làm được 4

Core Image được xây dựng trên các function bằng cách lọc các ClLanczosScaleTransform có tên giống nhau. Mặc dù được cho là API cao cấp hơn của UIKit, việc sử dụng các key-value coding trong Core Image làm nó khó sử dụng.

Quá trình tạo bộ lọc để transform, config và render cho ra các bức ảnh không khác với quy trình của các API khác:

import UIKit

import CoreImage




let sharedContext = CIContext(options: [.useSoftwareRenderer : false])




// Technique #4

func resizedImage(at url: URL, scale: CGFloat, aspectRatio: CGFloat) -> UIImage? {

guard let image = CIImage(contentsOf: url) else {

return nil

}




let filter = CIFilter(name: "CILanczosScaleTransform")

filter?.setValue(image, forKey: kCIInputImageKey)

filter?.setValue(scale, forKey: kCIInputScaleKey)

filter?.setValue(aspectRatio, forKey: kCIInputAspectRatioKey)




guard let outputCIImage = filter?.outputImage,

let outputCGImage = sharedContext.createCGImage(outputCIImage,

from: outputCIImage.extent)

else {

return nil

}




return UIImage(cgImage: outputCGImage)

}

Core Image lọc các bộ phận có tên CILanczosScaleTransform trong parameter là inputImageinputScale và inputAspectRatio.

Thú vị hơn là một CIContext được sử dụng để tạo UIImage bằng đại diện trung gian là CGImageRef.

Với UIImage(CIImage:) không thường làm việc như mong đợi, tạo ra CIContext là một cách thay thế. Việc cache được sử dụng nhiều lần trong việc resizing.

Sử dụng vImage

Tổng hợp các kỹ thuật Resizing Image mà ai cũng có thể làm được 5

Kỹ thuật cuối cùng là scale image với vImage với các function để scale các image. Những lower-level API này hứa hẹn cho hiệu năng cao so với ít công đoạn yêu cầu. Ngoại trừ việc bạn phải quản lý việc các phần nhớ đệm bằng tay:(lol)

import UIKit

import Accelerate.vImage




// Technique #5

func resizedImage(at url: URL, for size: CGSize) -> UIImage? {

// Decode the source image

guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),

let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil),

let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],

let imageWidth = properties[kCGImagePropertyPixelWidth] as? vImagePixelCount,

let imageHeight = properties[kCGImagePropertyPixelHeight] as? vImagePixelCount

else {

return nil

}




// Define the image format

var format = vImage_CGImageFormat(bitsPerComponent: 8,

bitsPerPixel: 32,

colorSpace: nil,

bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),

version: 0,

decode: nil,

renderingIntent: .defaultIntent)




var error: vImage_Error




// Create and initialize the source buffer

var sourceBuffer = vImage_Buffer()

defer { sourceBuffer.data.deallocate() }

error = vImageBuffer_InitWithCGImage(&sourceBuffer,

&format,

nil,

image,

vImage_Flags(kvImageNoFlags))

guard error == kvImageNoError else { return nil }




// Create and initialize the destination buffer

var destinationBuffer = vImage_Buffer()

error = vImageBuffer_Init(&destinationBuffer,

vImagePixelCount(size.height),

vImagePixelCount(size.width),

format.bitsPerPixel,

vImage_Flags(kvImageNoFlags))

guard error == kvImageNoError else { return nil }




// Scale the image

error = vImageScale_ARGB8888(&sourceBuffer,

&destinationBuffer,

nil,

vImage_Flags(kvImageHighQualityResampling))

guard error == kvImageNoError else { return nil }




// Create a CGImage from the destination buffer

guard let resizedImage =

vImageCreateCGImageFromBuffer(&destinationBuffer,

&format,

nil,

nil,

vImage_Flags(kvImageNoAllocate),

&error)?.takeRetainedValue(),

error == kvImageNoError

else {

return nil

}




return UIImage(cgImage: resizedImage)

}

Accelerate APIs được sử dụng ở đây rất khó sử dụng trong việc triển khai so với các phương thức rezising khác. Bỏ qua việc các tên hàng và variable khó đọc bạn sẽ thấy phương thức triển khai này thì cách tiếp cận này cũng đáng để tìm hiểu.

- Đầu tiên: tạo bộ nhớ đệm cho bức ảnh.

- Thứ 2: tạo đường dẫn cho các bộ nhớ đệm này.

- Thứ 3: chia tỷ lệ data cho bức ảnh trong bộ đệm tới đường dẫn được chỉ định.

- Cuối cùng: Gắn lại một bức ảnh với data là kết quả trong bộ đệm nguồn.

Kết luận:

  • UIKitCore GraphicsImage I/O đều hoạt động tốt trên các phương thức scaling ở hầu hết các bức ảnh. Nếu bạn sử dụng IOS, UIGraphicsImageRenderer là sự lựa chọn hàng đầu bạn nên cân nhắc.
  • Core Image là sự lựa chọn tốt cho hiệu năng. Trên thực tế, bạn nên sử dụng CoreGraphics hoặc Image/IO để crop hoặc downsampling thay vì sử dụng Core Image.
  • Trừ khi bạn thực sữ sẵn sàng đương đầu với vImage, các công việc bổ sung để có thể sử dụng API là không cần thiết trong đa số trường hợp.

Mong rằng những thông tin về các kỹ thuật Resizing Image trên đây của Blog.kdata sẽ giúp bạn đọc thực hiện thành công và dễ dàng.