「这是我参与2022初次更文应战的第14天,活动概况检查:2022初次更文应战」
本文正在参与「金石计划 . 瓜分6万现金大奖」
本事例的意图是了解如何用Metal实现图画4×4色彩矩阵作用滤镜,经过4×4矩阵对RGBA像素处理;
Demo
- HarbethDemo地址
- iDay每日分享文档地址
实操代码
// 绿色通道加倍
let filter = C7ColorMatrix4x4(matrix: Matrix4x4.Color.greenDouble)
// 计划1:
ImageView.image = try? BoxxIO(element: originImage, filters: [filter, filter2, filter3]).output()
// 计划2:
ImageView.image = originImage.filtering(filter, filter2, filter3)
// 计划3:
ImageView.image = originImage ->> filter ->> filter2 ->> filter3
作用对比图
identity: 原始 | sepia: 棕褐色 | nostalgic: 怀旧作用 |
---|---|---|
retroStyle: 复古作用 | polaroid: 宝丽来五颜六色 | greenDouble: 绿色通道加倍 |
skyblue_turns_green :天蓝色变绿色 | gray: 灰度图矩阵 | remove_green_blue: 去掉绿色和蓝色 |
replaced_red_green: 赤色绿色对调方位 | rgb_to_bgr: 映射RGB到BGR | decreasingOpacity: 调整通明度 |
axix_red_rotate: 环绕赤色旋转 | axix_green_rotate: 环绕绿色旋转 | axix_blue_rotate: 环绕蓝色旋转 |
实现原理
- 过滤器
这款滤镜选用并行计算编码器设计.compute(kernel: "C7ColorMatrix4x4")
,参数因子[intensity] + offset.toFloatArray()
对外开放参数
-
intensity
: 新转化的色彩取代每个像素原始色彩的程度,默认1; -
offset
: 每个通道的色彩偏移量; -
matrix
: 色彩矩阵;
/// 4x4 color matrix.
public struct C7ColorMatrix4x4: C7FilterProtocol {
public static let range: ParameterRange<Float, Self> = .init(min: 0.0, max: 1.0, value: 1.0)
/// The degree to which the new transformed color replaces the original color for each pixel, default 1
@ZeroOneRange public var intensity: Float = range.value
/// Color offset for each channel.
public var offset: RGBAColor = .zero
public var matrix: Matrix4x4
public var modifier: Modifier {
return .compute(kernel: "C7ColorMatrix4x4")
}
public var factors: [Float] {
return [intensity] + offset.toFloatArray()
}
public func setupSpecialFactors(for encoder: MTLCommandEncoder, index: Int) {
guard let computeEncoder = encoder as? MTLComputeCommandEncoder else { return }
var factor = matrix.to_factor()
computeEncoder.setBytes(&factor, length: Matrix4x4.size, index: index + 1)
}
public init(matrix: Matrix4x4) {
self.matrix = matrix
}
}
/// RGBA色彩空间中的色彩,在`0 ~ 1`区间内
/// Color in the RGBA color space, from 0 to 1.
public struct RGBAColor {
public static let zero = RGBAColor(red: 0, green: 0, blue: 0, alpha: 0)
public static let one = RGBAColor(red: 1, green: 1, blue: 1, alpha: 1)
@ZeroOneRange public var red: Float
@ZeroOneRange public var green: Float
@ZeroOneRange public var blue: Float
@ZeroOneRange public var alpha: Float
public init(red: Float, green: Float, blue: Float, alpha: Float = 1.0) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
}
public init(color: C7Color) {
// See: https://developer.apple.com/documentation/uikit/uicolor/1621919-getred
let tuple = color.mt.toRGBA()
self.init(red: tuple.red, green: tuple.green, blue: tuple.blue, alpha: tuple.alpha)
}
}
extension RGBAColor: Convertible {
public func toFloatArray() -> [Float] {
[red, green, blue, alpha]
}
public func toRGB() -> [Float] {
[red, green, blue]
}
}
extension RGBAColor: Equatable {
public static func == (lhs: RGBAColor, rhs: RGBAColor) -> Bool {
lhs.red == rhs.red &&
lhs.green == rhs.green &&
lhs.blue == rhs.blue &&
lhs.alpha == rhs.alpha
}
}
- 着色器
对RGBA进行矩阵运算,然后加上偏移值offset,按intensity程度获取新的像素色彩;
kernel void C7ColorMatrix4x4(texture2d<half, access::write> outputTexture [[texture(0)]],
texture2d<half, access::read> inputTexture [[texture(1)]],
constant float *intensity [[buffer(0)]],
constant float *redOffset [[buffer(1)]],
constant float *greenOffset [[buffer(2)]],
constant float *blueOffset [[buffer(3)]],
constant float *alphaOffset [[buffer(4)]],
constant float4x4 *colorMatrix [[buffer(5)]],
uint2 grid [[thread_position_in_grid]]) {
const half4 inColor = inputTexture.read(grid);
const half r = inColor.r;
const half g = inColor.g;
const half b = inColor.b;
const half a = inColor.a;
const half4x4 matrix = half4x4(*colorMatrix);
const half red = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2] + a * matrix[0][3] + (*redOffset);
const half green = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2] + a * matrix[1][3] + (*greenOffset);
const half blue = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2] + a * matrix[2][3] + (*blueOffset);
const half alpha = r * matrix[3][0] + g * matrix[3][1] + b * matrix[3][2] + a * matrix[3][3] + (*alphaOffset);
const half4 color = half4(red, green, blue, alpha);
const half4 outColor = half(*intensity) * color + (1.0h - half(*intensity)) * inColor;
outputTexture.write(outColor, grid);
}
4×4矩阵
- 部分4×4色彩矩阵
extension Matrix4x4 {
/// 常见4x4色彩矩阵,考线性代数时刻
/// 第一行的值决议了赤色值,第二行决议绿色,第三行蓝色,第四行是通明通道值
/// Common 4x4 color matrix
/// See: https://medium.com/macoclock/coreimage-911-color-matrix-4x4-50a7098414f4
public struct Color { }
}
extension Matrix4x4.Color {
public static let identity = Matrix4x4(values: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
])
/// 棕褐色,老照片
public static let sepia = Matrix4x4(values: [
0.3588, 0.7044, 0.1368, 0.0,
0.2990, 0.5870, 0.1140, 0.0,
0.2392, 0.4696, 0.0912, 0.0,
0.0000, 0.0000, 0.0000, 1.0,
])
/// 怀旧作用
public static let nostalgic = Matrix4x4(values: [
0.272, 0.534, 0.131, 0.0,
0.349, 0.686, 0.168, 0.0,
0.393, 0.769, 0.189, 0.0,
0.000, 0.000, 0.000, 1.0,
])
/// 复古作用
public static let retroStyle = Matrix4x4(values: [
0.50, 0.50, 0.50, 0.0,
0.33, 0.33, 0.33, 0.0,
0.25, 0.25, 0.25, 0.0,
0.00, 0.00, 0.00, 1.0,
])
/// 宝丽来五颜六色
public static let polaroid = Matrix4x4(values: [
1.4380, -0.062, -0.062, 0.0,
-0.122, 1.3780, -0.122, 0.0,
-0.016, -0.016, 1.4830, 0.0,
-0.030, 0.0500, -0.020, 1.0,
])
/// 绿色通道加倍
public static let greenDouble = Matrix4x4(values: [
1.0, 0.0, 0.0, 0.0,
0.0, 2.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
])
/// 天蓝色变绿色,天蓝色是由绿色和蓝色叠加
public static let skyblue_turns_green = Matrix4x4(values: [
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0,
])
/// 灰度图矩阵,平均值法
public static let gray = Matrix4x4(values: [
0.33, 0.33, 0.33, 0.0,
0.33, 0.33, 0.33, 0.0,
0.33, 0.33, 0.33, 0.0,
0.00, 0.00, 0.00, 1.0,
])
/// 去掉绿色和蓝色
public static let remove_green_blue = Matrix4x4(values: [
1.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0,
])
/// 赤色绿色对调方位
public static let replaced_red_green = Matrix4x4(values: [
0.0, 1.0, 0.0, 0.0,
1.0, 0.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
])
/// 白色剪影
/// In case you have to produce a white silhouette you need to supply data to the last column of the color matrix.
public static let white_silhouette = Matrix4x4(values: [
0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 1.0,
])
/// maps RGB to BGR (rows permuted)
public static let rgb_to_bgr = Matrix4x4(values: [
0.22, 0.22, 0.90, 0.0,
0.11, 0.70, 0.44, 0.0,
0.90, 0.11, 0.11, 0.0,
0.00, 0.00, 0.00, 1.0
])
/// When you have a premultiplied image, where RGB is multiplied by Alpha, decreasing A value you decrease a whole opacity of RGB.
/// Thus, any underlying layer becomes partially visible from under our translucent image.
/// - Parameter alpha: Alpha, 0 ~ 1
public static func decreasingOpacity(_ alpha: Float) -> Matrix4x4 {
var matrix = Matrix4x4.Color.identity
matrix.values[15] = min(1.0, max(0.0, alpha))
return matrix
}
/// Rotates the color matrix by alpha degrees clockwise about the red component axis.
/// - Parameter angle: rotation degree.
/// - Returns: 4x4 color matrix.
public static func axix_red_rotate(_ angle: Float) -> Matrix4x4 {
var matrix = Matrix4x4.Color.identity
let radians = angle * Float.pi / 180.0
matrix.values[5] = cos(radians)
matrix.values[6] = sin(radians)
matrix.values[9] = -sin(radians)
matrix.values[10] = cos(radians)
return matrix
}
/// Rotates the color matrix by alpha degrees clockwise about the green component axis.
/// - Parameter angle: rotation degree.
/// - Returns: 4x4 color matrix.
public static func axix_green_rotate(_ angle: Float) -> Matrix4x4 {
var matrix = Matrix4x4.Color.identity
let radians = angle * Float.pi / 180.0
matrix.values[0] = cos(radians)
matrix.values[2] = -sin(radians)
matrix.values[7] = sin(radians)
matrix.values[9] = cos(radians)
return matrix
}
/// Rotates the color matrix by alpha degrees clockwise about the blue component axis.
/// - Parameter angle: rotation degree.
/// - Returns: 4x4 color matrix.
public static func axix_blue_rotate(_ angle: Float) -> Matrix4x4 {
var matrix = Matrix4x4.Color.identity
let radians = angle * Float.pi / 180.0
matrix.values[0] = cos(radians)
matrix.values[1] = sin(radians)
matrix.values[4] = -sin(radians)
matrix.values[5] = cos(radians)
return matrix
}
}
Harbeth功用清单
- 支撑ios体系和macOS体系
- 支撑运算符函数式操作
- 支撑多种形式数据源 UIImage, CIImage, CGImage, CMSampleBuffer, CVPixelBuffer.
- 支撑快速设计滤镜
- 支撑兼并多种滤镜作用
- 支撑输出源的快速扩展
- 支撑相机采集特效
- 支撑视频增加滤镜特效
- 支撑矩阵卷积
- 支撑运用体系 MetalPerformanceShaders.
- 支撑兼容 CoreImage.
- 滤镜部分大致分为以下几个模块:
- Blend:图画交融技术
- Blur:模糊作用
- Pixel:图画的基本像素色彩处理
- Effect:作用处理
- Lookup:查找表过滤器
- Matrix: 矩阵卷积滤波器
- Shape:图画形状巨细相关
- Visual: 视觉动态特效
- MPS: 体系 MetalPerformanceShaders.
最后
- 渐渐再弥补其他相关滤镜,喜欢就给我点个星吧。
-
滤镜Demo地址,目前包括
100+
种滤镜,同时也支撑CoreImage混合运用。 - 再附上一个开发加速库KJCategoriesDemo地址
- 再附上一个网络基础库RxNetworksDemo地址
- 喜欢的老板们能够点个星,谢谢各位老板!!!
✌️.