各位開發者朋友們好。
我第一次在 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
}
}
The most depressing thing is that you can't even output anything to the console. It is unclear how to look for errors. I will be grateful for any hints.
PS: Xcode 13.1, iOS 14-15. SwiftUI life cycle.
GitHub: https://github.com/VKostin8311/MetalKernelsTestApp
uj5u.com熱心網友回復:
歡迎!
內核代碼的主要問題是整數文字的使用:
Metal Shading Language 基于 C ,它沒有與 Swift 相同的型別推斷系統。所以,當你寫1/3它實際上整數除法演出,讓像float t = adjustedHue 1/3就等于adjustedHue 0。你必須在這里使用浮動文字:adjustedHue 1.0/3.0。
我在您的示例專案上創建了一個拉取請求,其中包含各種修復和改進。如果有不清楚的地方,請告訴我。
至于除錯:不幸的是,使用斷點和直接列印陳述句無法除錯內核代碼。我通常使用像素顏色進行printf除錯,如下所示:
if (<condition I want to check>) { return float4(1.0, 0.0, 0.0, 1.0); }
在這種情況下,所有滿足條件的像素都變為紅色,您可以使用它來驗證假設等。
uj5u.com熱心網友回復:
最郁悶的是連控制臺都不能輸出任何東西。目前尚不清楚如何查找錯誤。我將不勝感激任何提示。
為了開發和除錯金屬著色器,您應該使用Xcode shader profiler。
我現在正在用手機寫,從我看到的著色器代碼中有一個錯誤:
if(maxComp == minComp)
浮點數應始終與 epsilon 值進行比較。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/350498.html
標籤:ios swift metal core-image xcode13
