Есть два ключевых вопроса.
Код вычисляет все красные значения для каждого пикселя в градациях серого и создает четыре байта PixelData
для каждого (даже если заполнен только красный канал) и добавляет его в массив pixelsData
. Затем он повторяет это для зеленых значений, а затем снова для синих значений. Это дает в три раза больше данных, чем нужно для изображения, и используются только данные красного канала.
Вместо этого мы должны вычислить значения RGBA один раз, создать PixelData
для каждого и повторить этот пиксель за пикселем.
premultipliedFirst
означает ARGB. Но ваша структура использует RGBA, поэтому вы хотите premultipliedLast
.
Таким образом:
func generateTintedImage(completion: @escaping (UIImage?) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let image = self.tintedImage()
DispatchQueue.main.async {
completion(image)
}
}
}
private func tintedImage() -> UIImage? {
let tintRed = tintColor.red
let tintGreen = tintColor.green
let tintBlue = tintColor.blue
let tintAlpha = tintColor.alpha
let data = pixels.map { pixel -> PixelData in
let red = UInt8((Float(pixel) / 255) * tintRed)
let green = UInt8((Float(pixel) / 255) * tintGreen)
let blue = UInt8((Float(pixel) / 255) * tintBlue)
let alpha = UInt8(tintAlpha)
return PixelData(r: red, g: green, b: blue, a: alpha)
}.withUnsafeBytes { Data($0) }
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let bitsPerComponent = 8
let bitsPerPixel = 32
guard
let providerRef = CGDataProvider(data: data as CFData),
let cgImage = CGImage(width: width,
height: height,
bitsPerComponent: bitsPerComponent,
bitsPerPixel: bitsPerPixel,
bytesPerRow: width * MemoryLayout<PixelData>.stride,
space: rgbColorSpace,
bitmapInfo: bitmapInfo,
provider: providerRef,
decode: nil,
shouldInterpolate: true,
intent: .defaultIntent)
else {
return nil
}
return UIImage(cgImage: cgImage)
}
Я также переименовал несколько переменных, использовал stride
вместо size
, заменил dimension
на width
и height
, чтобы я мог обрабатывать неквадратные изображения и т. Д.
Я бы также советовал не использовать вычисляемое свойство для чего-либо такого сложного в вычислительном отношении, поэтому я дал этому асинхронный метод, который вы могли бы использовать следующим образом:
let map = Map(with: image)
map.generateTintedImage { image in
self.tintedImageView.image = image
}
В любом случае, приведенное выше дает следующее, где самым правым изображением является ваше тонированное изображение:
Излишне говорить, что для преобразования вашей матрицы в массив pixels
вы можете просто сгладить массив массивов:
let matrix: [[Pixel]] = [
[0, 0, 125],
[10, 50, 255],
[90, 0, 255]
]
pixels = matrix.flatMap { $0 }
Вот распараллеленное воспроизведение, которое также немного более эффективно по отношению к буферу памяти:
private func tintedImage() -> UIImage? {
let tintAlpha = tintColor.alpha
let tintRed = tintColor.red / 255
let tintGreen = tintColor.green / 255
let tintBlue = tintColor.blue / 255
let alpha = UInt8(tintAlpha)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue
let bitsPerComponent = 8
let bytesPerRow = width * MemoryLayout<PixelData>.stride
guard
let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
let data = context.data
else {
return nil
}
let buffer = data.bindMemory(to: PixelData.self, capacity: width * height)
DispatchQueue.concurrentPerform(iterations: height) { row in
let start = width * row
let end = start + width
for i in start ..< end {
let pixel = pixels[i]
let red = UInt8(Float(pixel) * tintRed)
let green = UInt8(Float(pixel) * tintGreen)
let blue = UInt8(Float(pixel) * tintBlue)
buffer[i] = PixelData(r: red, g: green, b: blue, a: alpha)
}
}
return context.makeImage()
.flatMap { UIImage(cgImage: $0) }
}