大家好,开发者朋友们。
这是我第一次在Stackoverflow上提问。
我第一次接触到编写自定义Metal内核以创建Core Image过滤器。
任务看起来很简单。您需要制作一个过滤器来调整图像中的颜色色相、饱和度和亮度,受到色相范围+/-22.5度的限制。就像Lightroom颜色偏移调整之类的应用程序。
算法非常简单:
- 我将原始像素颜色和色调、饱和度和亮度范围和偏移值传递给函数;
- 在函数内部,我将颜色从RGB模式转换为HSL模式;
- 我检查阴影是否处于目标范围内;如果没有击中它,我不应用偏移值,如果击中了,我将偏移值添加到转换过程中获得的色调、饱和度和亮度上;
- 我将像素颜色转换回RGB模式;
- 我返回结果。
结果证明这是一个出色的算法,在PlayGround中成功地并且没有任何问题地完成了:
以下是源代码:
struct RGB {
let r: Float
let g: Float
let b: Float
}
struct HSL {
let hue: Float
let sat: Float
let lum: Float
}
func adjustingHSL(_ s: RGB, center: Float, hueOffset: Float, satOffset: Float, lumOffset: Float) -> RGB {
// Determine the maximum and minimum color components
let maxComp = (s.r > s.g && s.r > s.b) ? s.r : (s.g > s.b) ? s.g : s.b
let minComp = (s.r < s.g && s.r < s.b) ? s.r : (s.g < s.b) ? s.g : s.b
// Convert to HSL
var inputHue: Float = (maxComp + minComp)/2
var inputSat: Float = (maxComp + minComp)/2
let inputLum: Float = (maxComp + minComp)/2
if maxComp == minComp {
inputHue = 0
inputSat = 0
} else {
let delta: Float = maxComp - minComp
inputSat = inputLum > 0.5 ? delta/(2.0 - maxComp - minComp) : delta/(maxComp + minComp)
if (s.r > s.g && s.r > s.b) {inputHue = (s.g - s.b)/delta + (s.g < s.b ? 6.0 : 0.0) }
else if (s.g > s.b) {inputHue = (s.b - s.r)/delta + 2.0}
else {inputHue = (s.r - s.g)/delta + 4.0 }
inputHue = inputHue/6
}
// Setting the boundaries of the offset hue range
let minHue: Float = center - 22.5/(360)
let maxHue: Float = center + 22.5/(360)
// I apply offsets for hue, saturation and lightness
let adjustedHue: Float = inputHue + ((inputHue > minHue && inputHue < maxHue) ? hueOffset : 0 )
let adjustedSat: Float = inputSat + ((inputHue > minHue && inputHue < maxHue) ? satOffset : 0 )
let adjustedLum: Float = inputLum + ((inputHue > minHue && inputHue < maxHue) ? lumOffset : 0 )
// Convert color to RGB
var red: Float = 0
var green: Float = 0
var blue: Float = 0
if adjustedSat == 0 {
red = adjustedLum
green = adjustedLum
blue = adjustedLum
} else {
let q = adjustedLum < 0.5 ? adjustedLum*(1+adjustedSat) : adjustedLum + adjustedSat - (adjustedLum*adjustedSat)
let p = 2*adjustedLum - q
var t: Float = 0
// Calculating red
t = adjustedHue + 1/3
if t < 0 { t += 1 }
if t > 1 { t -= 1 }
if t < 1/6 { red = p + (q - p)*6*t }
else if t < 1/2 { red = q }
else if t < 2/3 { red = p + (q - p)*(2/3 - t)*6 }
else { red = p }
// Calculating green
t = adjustedHue
if t < 0 { t += 1 }
if t > 1 { t -= 1 }
if t < 1/6 { green = p + (q - p)*6*t }
else if t < 1/2 { green = q }
else if t < 2/3 { green = p + (q - p)*(2/3 - t)*6 }
else { green = p }
// Calculating blue
t = adjustedHue - 1/3
if t < 0 { t += 1 }
if t > 1 { t -= 1 }
if t < 1/6 { blue = p + (q - p)*6*t }
else if t < 1/2 { blue = q }
else if t < 2/3 { blue = p + (q - p)*(2/3 - t)*6 }
else { blue = p }
}
return RGB(r: red, g: green, b: blue)
}
在PlayGround中的应用举例,就像这样:
let inputColor = RGB(r: 255/255, g: 120/255, b: 0/255)
// For visual perception of the input color
let initColor = UIColor(red: CGFloat(inputColor.r), green: CGFloat(inputColor.g), blue: CGFloat(inputColor.b), alpha: 1.0)
let rgb = adjustingHSL(inputColor, center: 45/360, hueOffset: 0, satOffset: 0, lumOffset: -0.2)
// For visual perception of the output color
let adjustedColor = UIColor(red: CGFloat(rgb.r), green: CGFloat(rgb.g), blue: CGFloat(rgb.b), alpha: 1.0)
相同的函数,在Xcode项目中为Metal内核重写后,给出了完全出乎意料的结果。
图像在变成黑白之后。同时,通过滑块更改输入参数也会改变图像本身。但它也很奇怪:它覆盖着小黑色或白色的正方形。
以下是在Metal内核中的源代码:
#include <metal_stdlib>
using namespace metal;
#include <CoreImage/CoreImage.h>
extern "C" {
namespace coreimage {
float4 hslFilterKernel(sample_t s, float center, float hueOffset, float satOffset, float lumOffset) {
// Convert pixel color from RGB to HSL
// Determine the maximum and minimum color components
float maxComp = (s.r > s.g && s.r > s.b) ? s.r : (s.g > s.b) ? s.g : s.b ;
float minComp = (s.r < s.g && s.r < s.b) ? s.r : (s.g < s.b) ? s.g : s.b ;
float inputHue = (maxComp + minComp)/2 ;
float inputSat = (maxComp + minComp)/2 ;
float inputLum = (maxComp + minComp)/2 ;
if (maxComp == minComp) {
inputHue = 0 ;
inputSat = 0 ;
} else {
float delta = maxComp - minComp ;
inputSat = inputLum > 0.5 ? delta/(2.0 - maxComp - minComp) : delta/(maxComp + minComp);
if (s.r > s.g && s.r > s.b) {
inputHue = (s.g - s.b)/delta + (s.g < s.b ? 6.0 : 0.0);
} else if (s.g > s.b) {
inputHue = (s.b - s.r)/delta + 2.0;
}
else {
inputHue = (s.r - s.g)/delta + 4.0;
}
inputHue = inputHue/6 ;
}
float minHue = center - 22.5/(360) ;
float maxHue = center + 22.5/(360) ;
//I apply offsets for hue, saturation and lightness
float adjustedHue = inputHue + ((inputHue > minHue && inputHue < maxHue) ? hueOffset : 0 );
float adjustedSat = inputSat + ((inputHue > minHue && inputHue < maxHue) ? satOffset : 0 );
float adjustedLum = inputLum + ((inputHue > minHue && inputHue < maxHue) ? lumOffset : 0 );
// Convert pixel color from HSL to RGB
float red = 0 ;
float green = 0 ;
float blue = 0 ;
if (adjustedSat == 0) {
red = adjustedLum;
green = adjustedLum;
blue = adjustedLum;
} else {
float q = adjustedLum < 0.5 ? adjustedLum*(1+adjustedSat) : adjustedLum + adjustedSat - (adjustedLum*adjustedSat);
float p = 2*adjustedLum - q;
// Calculating Red color
float t = adjustedHue + 1/3;
if (t < 0) { t += 1; }
if (t > 1) { t -= 1; }
if (t < 1/6) { red = p + (q - p)*6*t; }
else if (t < 1/2) { red = q; }
else if (t < 2/3) { red = p + (q - p)*(2/3 - t)*6; }
else { red = p; }
// Calculating Green color
t = adjustedHue;
if (t < 0) { t += 1; }
if (t > 1) { t -= 1; }
if (t < 1/6) { green = p + (q - p)*6*t; }
else if (t < 1/2) { green = q ;}
else if (t < 2/3) { green = p + (q - p)*(2/3 - t)*6; }
else { green = p; }
// Calculating Blue color
t = adjustedHue - 1/3;
if (t < 0) { t += 1; }
if (t > 1) { t -= 1; }
if (t < 1/6) { blue = p + (q - p)*6*t; }
else if (t < 1/2) { blue = q; }
else if (t < 2/3) { blue = p + (q - p)*(2/3 - t)*6;}
else { blue = p; }
}
float4 outColor;
outColor.r = red;
outColor.g = green;
outColor.b = blue;
outColor.a = s.a;
return outColor;
}
}
}
我想不出我可能犯了什么错误。
以防万一,我附上一个过滤器类(但它似乎运行正常):
class HSLAdjustFilter: CIFilter {
var inputImage: CIImage?
var center: CGFloat?
var hueOffset: CGFloat?
var satOffset: CGFloat?
var lumOffset: CGFloat?
static var kernel: CIKernel = { () -> CIColorKernel in
guard let url = Bundle.main.url(forResource: "HSLAdjustKernel.ci", withExtension: "metallib"),
let data = try? Data(contentsOf: url)
else { fatalError("Unable to load metallib") }
guard let kernel = try? CIColorKernel(functionName: "hslFilterKernel", fromMetalLibraryData: data)
else { fatalError("Unable to create color kernel") }
return kernel
}()
override var outputImage: CIImage? {
guard let inputImage = self.inputImage else { return nil }
return HSLAdjustFilter.kernel.apply(extent: inputImage.extent, roiCallback: { _, rect in return rect }, arguments: [inputImage, self.center ?? 0, self.hueOffset ?? 0, self.satOffset ?? 0, self.lumOffset ?? 0])
}
}
另外,调用过滤器的功能:
func imageProcessing(_ inputImage: CIImage) -> CIImage {
let filter = HSLAdjustFilter()
filter.inputImage = inputImage
filter.center = 180/360
filter.hueOffset = CGFloat(hue)
filter.satOffset = CGFloat(saturation)
filter.lumOffset = CGFloat(luminance)
if let outputImage = filter.outputImage {
return outputImage
} else {
return inputImage
}
}
最令人沮丧的是,您甚至无法向控制台输出任何内容。不清楚如何查找错误。 对于任何提示,我都将不胜感激。
PS:Xcode 13.1,iOS 14-15。SwiftUI生命周期。
GitHub:https://github.com/VKostin8311/MetalKernelsTestApp