本文是筆者用來記錄學(xué)習(xí) iOS
圖像處理相關(guān)的文章截亦,內(nèi)容可能有部分不對额划,可以通過評論或私信交流工闺。本文所有代碼都是 Swift 3.0
前言
一直以來度苔,筆者很想實現(xiàn)這么一個功能:獲取一張圖片,用戶可以從圖片中進行取色暂吉,更進一步胖秒,可以通過相機對現(xiàn)實中的景色進行取色;
現(xiàn)在就以從圖片中取色這么一個功能來作為開篇慕的。
實現(xiàn)
實現(xiàn)效果
筆者思路
最開始阎肝,對于這么一個功能,筆者的思路是:獲取圖片所有像素肮街,將用戶點擊的位置轉(zhuǎn)換為圖片中的像素索引风题,獲取目標像素。
獲取圖片所有像素:
public extension UIImage {
/// 根據(jù)圖片大小提取像素
///
/// - parameter size: 圖片大小
///
/// - returns: 像素數(shù)組
public func extraPixels(in size: CGSize) -> [UInt32]? {
guard let cgImage = cgImage else {
return nil
}
let width = Int(size.width)
let height = Int(size.height)
// 一個像素 4 個字節(jié)嫉父,則一行共 4 * width 個字節(jié)
let bytesPerRow = 4 * width
// 每個像素元素位數(shù)為 8 bit沛硅,即 rgba 每位各 1 個字節(jié)
let bitsPerComponent = 8
// 顏色空間為 RGB,這決定了輸出顏色的編碼是 RGB 還是其他(比如 YUV)
let colorSpace = CGColorSpaceCreateDeviceRGB()
// 設(shè)置位圖顏色分布為 RGBA
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
var pixelsData = [UInt32](repeatElement(0, count: width * height))
guard let content = CGContext(data: &pixelsData, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
return nil
}
content.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
return pixelsData
}
}
根據(jù)用戶點擊位置獲取顏色:
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
// 獲取用戶點擊位置在圖像上的相對位置
let point = touch.location(in: imageView0)
// 將點擊位置轉(zhuǎn)換為像素索引(實際應(yīng)用時此處應(yīng)做空判斷)
let pixelIndex = imageView0.pixelIndex(for: point) ?? 0
// 根據(jù)圖像大小提取圖像像素(實際應(yīng)用時此處應(yīng)做空判斷)
let pixels = imageView0.pixels ?? []
// 獲取目標像素(實際應(yīng)用時此處應(yīng)做范圍判斷)
view.backgroundColor = imageView0.extraColor(for: pixels[pixelIndex])
}
public extension UIImageView {
/// 圖像像素
public var pixels: [UInt32]? {
return image?.extraPixels(in: bounds.size)
}
/// 將位置轉(zhuǎn)換為像素索引
///
/// - parameter point: 位置
///
/// - returns: 像素索引
public func pixelIndex(for point: CGPoint) -> Int? {
let size = bounds.size
guard point.x > 0 && point.x <= size.width
&& point.y > 0 && point.y <= size.height else {
return nil
}
return (Int(point.y) * Int(size.width) + Int(point.x))
}
/// 將像素值轉(zhuǎn)換為顏色
///
/// - parameter pixel: 像素值
///
/// - returns: 顏色
public func extraColor(for pixel: UInt32) -> UIColor {
let r = Int((pixel >> 0) & 0xff)
let g = Int((pixel >> 8) & 0xff)
let b = Int((pixel >> 16) & 0xff)
let a = Int((pixel >> 24) & 0xff)
return UIColor(intRed: r, green: g, blue: b, alpha: a)
}
}
需要注意:
上面
touchesBegan
中是每次點擊都去根據(jù)當前UIImageView
的大小獲取圖像所有像素绕辖,實際應(yīng)用中可在初始化時獲取并保存摇肌,點擊的時候直接調(diào)用即可;提取圖像像素時需要根據(jù)當前顯示的大小進行提取仪际,這是因為使用
UIImageView
顯示圖像時围小,圖像會根據(jù)顯示大小進行縮放偷拔,此時顯示的像素分布與UIImage
的像素分布是完全不同的桑包,因此在獲取某一點的像素锨络,必須根據(jù)當前顯示大小進行處理升熊;
另一種實現(xiàn)
上面的實現(xiàn)需要先獲取圖像的所有像素數(shù)據(jù),比較占內(nèi)存晤郑;有另一種實現(xiàn)书斜,不需要獲取所有像素數(shù)據(jù):
public extension UIView {
/// 獲取特定位置的顏色
///
/// - parameter at: 位置
///
/// - returns: 顏色
public func pickColor(at position: CGPoint) -> UIColor? {
// 用來存放目標像素值
var pixel = [UInt8](repeatElement(0, count: 4))
// 顏色空間為 RGB牡借,這決定了輸出顏色的編碼是 RGB 還是其他(比如 YUV)
let colorSpace = CGColorSpaceCreateDeviceRGB()
// 設(shè)置位圖顏色分布為 RGBA
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
guard let context = CGContext(data: &pixel, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo) else {
return nil
}
// 設(shè)置 context 原點偏移為目標位置所有坐標
context.translateBy(x: -position.x, y: -position.y)
// 將圖像渲染到 context 中
layer.render(in: context)
return UIColor(red: CGFloat(pixel[0]) / 255.0,
green: CGFloat(pixel[1]) / 255.0,
blue: CGFloat(pixel[2]) / 255.0,
alpha: CGFloat(pixel[3]) / 255.0)
}
}
根據(jù)用戶點擊位置獲取顏色:
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {
return
}
// 獲取用戶點擊位置在圖像上的相對位置
let point = touch.location(in: imageView0)
view.backgroundColor = imageView0.pickColor(at: point)
}
可以看到筆者將 pickColor
作為 UIView
的擴展伦连,因為這適用于所有擁有 layer
層的對象雨饺。