Обновление
Итак, похоже, что vImageMatrixMultiply_ARGB8888 ожидает значения в следующем порядке:
let rotationMatrix = [
1, 0, 0, 0, // A
0, 1.9692307692307693, 0, 0, // R
0, 0, 2.226086956521739, 0, // G
0, 0, 0, 2.585858585858586 // B
].map { return Int16($0 * Float(divisor)) } // divisor = 0x1000
Это не соответствует примеру кода Apple, который подразумевает BGRA. Возможно, эта матрица изначально предназначалась для использования с другим форматом pixelBuffer - или какой-то другой проблемой.
Я считаю, что другая проблема, с которой вы сталкиваетесь, заключается в том, что CIFilter использует CIContext по умолчанию. Когда я запускаю это, я получаю linearSRGB workingColorSpace.
let context = CIContext()
print(context.workingColorSpace ?? "")
Вывод:
<CGColorSpace 0x7fe4abd06d30> (kCGColorSpaceICCBased; kCGColorSpaceModelRGB; sRGB Linear)
Таким образом, вы можете либо указать CGColorSpaceCreateDeviceRGB () в качестве CIContext workingColorSpace для вашего CIFilter, либо установить rgbDestinationImageFormat colorSpace для .linearSRGB в вашем использовании vImage. Я полагаю, что между этим и исправленным порядком матриц ваши выходные изображения теперь будут совпадать.
// error handling removed for brevity
let ciImage = CIImage(cgImage: cgImage)
let colorMatrix = CIFilter(name: "CIColorMatrix")!
colorMatrix.setDefaults()
colorMatrix.setValue(ciImage, forKey: "inputImage")
colorMatrix.setValue(CIVector(x: 1.9692307692307693, y: 0, z: 0, w: 0), forKey: "inputRVector")
colorMatrix.setValue(CIVector(x: 0, y: 2.226086956521739, z: 0, w: 0), forKey: "inputGVector")
colorMatrix.setValue(CIVector(x: 0, y: 0, z: 2.585858585858586, w: 0), forKey: "inputBVector")
colorMatrix.setValue(CIVector(x: 0, y: 0, z: 0, w: 1), forKey: "inputAVector")
let context = CIContext(options: [.workingColorSpace : CGColorSpaceCreateDeviceRGB()]) // without this, colorSpace defaults to linearSRGB
return context.createCGImage(colorMatrix.outputImage!, from: rect)!
В этом сценарии, вероятно, нет необходимости в смещении до и после, если у вас нет другой причины для этого.
Наконец, вы можете извлечь выгоду из большего делителя. В то время как данные пикселей составляют 8 бит на канал, аккумулятор 32-битный, а значения матрицы 16-битные. Большая часть ссылочного кода использует делитель 0x1000 (4096).
func rgbAdjustmentWithMatrix(image sourceImage: NSImage, rotationMatrix: [Int16]) -> CGImage? {
var rect = CGRect(x: 0, y: 0, width: sourceImage.size.width, height: sourceImage.size.height)
guard
let cgImage = sourceImage.cgImage(forProposedRect: &rect, context: nil, hints: nil),
let sourceImageFormat = vImage_CGImageFormat(cgImage: cgImage),
let rgbDestinationImageFormat = vImage_CGImageFormat(
bitsPerComponent: 8,
bitsPerPixel: 32,
colorSpace: CGColorSpaceCreateDeviceRGB(),
// can match CIFilter default with this:
// colorSpace: CGColorSpace(name: CGColorSpace.linearSRGB)!,
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
renderingIntent: .defaultIntent)
else {
print("Unable to initialize cgImage or colorSpace.")
return nil
}
guard
var sourceBuffer = try? vImage_Buffer(cgImage: cgImage),
var rgbDestinationBuffer = try? vImage_Buffer(width: Int(sourceBuffer.width),
height: Int(sourceBuffer.height),
bitsPerPixel: rgbDestinationImageFormat.bitsPerPixel)
else {
fatalError("Error initializing source and destination buffers.")
}
defer {
sourceBuffer.free()
rgbDestinationBuffer.free()
}
do {
let toRgbConverter = try vImageConverter.make(sourceFormat: sourceImageFormat, destinationFormat: rgbDestinationImageFormat)
try toRgbConverter.convert(source: sourceBuffer, destination: &rgbDestinationBuffer)
} catch {
fatalError(error.localizedDescription)
}
let divisor: Int32 = 0x1000 // matrix values are 16 bit and accumulator is 32 bit
// 4096 gives us decent overhead for the matrix operation
// 12-bit color
let preBias: [Int16] = [0, 0, 0, 0] // or simply pass nil
let postBias: [Int32] = [0, 0, 0, 0] // or simply pass nil
let error = vImageMatrixMultiply_ARGB8888(&rgbDestinationBuffer,
&rgbDestinationBuffer,
rotationMatrix,
divisor,
preBias,
postBias,
vImage_Flags(kvImageNoFlags))
if error != kvImageNoError { print("Error: \(error)") }
let result = try? rgbDestinationBuffer.createCGImage(format: rgbDestinationImageFormat)
return result
}
Предыдущий ответ
Глядя на ваш код и выходные изображения, я Я полагаю, что у вас поменялись коэффициенты красного и синего. Если это так, вы бы заполнили матрицу vImage следующим образом:
rotationMatrix = [2.585858585858586, 0, 0, 0 // b
0, 2.226086956521739, 0, 0, // g
0, 0, 1.9692307692307693, 0, // r
0, 0, 0, 1].map { // a
return Int16(Float($0) * Float(divisor)) // divisor = 256
}
Это будет соответствовать образцу кода Apple vImage для обесцвечивания выбранной части изображения .
let desaturationMatrix = [
0.0722, 0.0722, 0.0722, 0,
0.7152, 0.7152, 0.7152, 0,
0.2126, 0.2126, 0.2126, 0,
0, 0, 0, 1
].map {
return Int16($0 * Float(divisor))
}
Обратите внимание, что коэффициенты преобразования для Rec709 в Luma имеют вид:
Y = R * 0.2126 + G * 0.7152 + B * 0.0722