开启成长之旅!这是我参加「日新方案 12 月更文应战」的第27天,点击检查活动详情

本案例的目的是了解如何用Metal完成色彩空间转化作用滤镜,转化在不同色彩空间生成的图画;


Demo

  • HarbethDemo地址
  • iDay每日共享文档地址

实操代码

// 色彩空间转化滤镜
let filter = C7ColorSpace.init(with: .rgb_to_yuv)
// 方案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

作用对比图

  • 不同参数下作用
rgb_to_yiq yiq_to_rgb rgb_to_yuv
Metal每日分享,不同色彩空间转换滤镜效果
Metal每日分享,不同色彩空间转换滤镜效果
Metal每日分享,不同色彩空间转换滤镜效果

完成原理

  • 过滤器

这款滤镜采用并行计算编码器设计.compute(kernel: type.rawValue)

/// 色彩空间转化
public struct C7ColorSpace: C7FilterProtocol {
    public enum SwapType: String, CaseIterable {
        case rgb_to_yiq = "C7ColorSpaceRGB2YIQ"
        case yiq_to_rgb = "C7ColorSpaceYIQ2RGB"
        case rgb_to_yuv = "C7ColorSpaceRGB2YUV"
        case yuv_to_rgb = "C7ColorSpaceYUV2RGB"
    }
    private let type: SwapType
    public var modifier: Modifier {
        return .compute(kernel: type.rawValue)
    }
    public init(with type: SwapType) {
        self.type = type
    }
}
  • 着色器

每条通道乘以各自偏移求和得到Y,用Y作为新的像素rgb;

kernel void C7ColorSpaceRGB2Y(texture2d<half, access::write> outputTexture [[texture(0)]],
                              texture2d<half, access::read> inputTexture [[texture(1)]],
                              uint2 grid [[thread_position_in_grid]]) {
    const half4 inColor = inputTexture.read(grid);
    const half Y = half((0.299 * inColor.r) + (0.587 * inColor.g) + (0.114 * inColor.b));
    const half4 outColor = half4(Y, Y, Y, inColor.a);
    outputTexture.write(outColor, grid);
}
// See: https://en.wikipedia.org/wiki/YIQ
kernel void C7ColorSpaceRGB2YIQ(texture2d<half, access::write> outputTexture [[texture(0)]],
                                texture2d<half, access::read> inputTexture [[texture(1)]],
                                uint2 grid [[thread_position_in_grid]]) {
    const half4 inColor = inputTexture.read(grid);
    const half3x3 RGBtoYIQ = half3x3({0.299, 0.587, 0.114}, {0.596, -0.274, -0.322}, {0.212, -0.523, 0.311});
    const half3 yiq = RGBtoYIQ * inColor.rgb;
    const half4 outColor = half4(yiq, inColor.a);
    outputTexture.write(outColor, grid);
}
kernel void C7ColorSpaceYIQ2RGB(texture2d<half, access::write> outputTexture [[texture(0)]],
                                texture2d<half, access::read> inputTexture [[texture(1)]],
                                uint2 grid [[thread_position_in_grid]]) {
    const half4 inColor = inputTexture.read(grid);
    const half3x3 YIQtoRGB = half3x3({1.0, 0.956, 0.621}, {1.0, -0.272, -0.647}, {1.0, -1.105, 1.702});
    const half3 rgb = YIQtoRGB * inColor.rgb;
    const half4 outColor = half4(rgb, inColor.a);
    outputTexture.write(outColor, grid);
}
// See: https://en.wikipedia.org/wiki/YUV
kernel void C7ColorSpaceRGB2YUV(texture2d<half, access::write> outputTexture [[texture(0)]],
                                texture2d<half, access::read> inputTexture [[texture(1)]],
                                uint2 grid [[thread_position_in_grid]]) {
    const half4 inColor = inputTexture.read(grid);
    const half3x3 RGBtoYUV = half3x3({0.299, 0.587, 0.114}, {-0.299, -0.587, 0.886}, {0.701, -0.587, -0.114});
    const half3 yuv = RGBtoYUV * inColor.rgb;
    const half4 outColor = half4(yuv, inColor.a);
    outputTexture.write(outColor, grid);
}
kernel void C7ColorSpaceYUV2RGB(texture2d<half, access::write> outputTexture [[texture(0)]],
                texture2d<half, access::read> inputTexture [[texture(1)]],
                uint2 grid [[thread_position_in_grid]]) {
  const half4 inColor = inputTexture.read(grid);
  const half3x3 YUVtoRGB = half3x3({1.0, 0.0, 1.28033}, {1.0, -0.21482, -0.38059}, {1.0, 2.21798, 0.0});
  const half3 rgb = YUVtoRGB * inColor.rgb;
  const half4 outColor = half4(rgb, inColor.a);
  outputTexture.write(outColor, grid);
}

色彩空间

  • YIQ

在YIQ体系中,是NTSC(National Television Standards Committee)电视体系规范;

  • Y是供给是非电视及彩色电视的亮度信号Luminance,即亮度Brightness;
  • I代表In-phase,色彩从橙色到青色;
  • Q代表Quadrature-phase,色彩从紫色到黄绿色;

Metal每日分享,不同色彩空间转换滤镜效果

转化公式如下:

Metal每日分享,不同色彩空间转换滤镜效果

  • YUV

YUV是在工程师想要在是非基础设施中运用彩色电视时发明的。他们需要一种信号传输办法,既能与是非 (B&W) 电视兼容,又能添加色彩。亮度重量现已作为是非信号存在;他们将紫外线信号作为解决方案添加到其中。

由于 U 和 V 是色差信号,因此在直接 R 和 B 信号上挑选色度的 UV 表明。换句话说,U 和 V 信号告知电视在不改动亮度的情况下改动某个点的色彩。
或许 U 和 V 信号告知显示器以献身另一种色彩为代价使一种色彩更亮,以及它应该移动多少。
U 和 V 值越高(或负值越低),斑点的饱和度(色彩)就越高。
U 值和 V 值越接近零,色彩偏移越小,这意味着红、绿和蓝光的亮度会更均匀,然后产生更灰的点。
这是运用色差信号的优点,即不是告知色彩有多少红色,而是告知红色比绿色或蓝色多多少。
反过来,这意味着当 U 和 V 信号为零或不存在时,它只会显示灰度图画。
假如运用 R 和 B,即使在是非场景中,它们也将具有非零值,需要一切三个数据承载信号。
这在前期的彩色电视中很重要,因为旧的是非电视信号没有 U 和 V 信号,这意味着彩色电视开箱后只会显示为是非电视。
此外,是非接收器能够接收 Y' 信号并疏忽 U 和 V 色彩信号,使 YUV 向后兼容一切现有的是非设备、输入和输出。
假如彩色电视规范不运用色差信号,这或许意味着彩色电视会从 B& 中产生风趣的色彩 W 播送,否则需要额定的电路将是非信号转化为彩色。
有必要为色度通道分配较窄的带宽,因为没有可用的额定带宽。
假如某些亮度信息是经过色度通道到达的(假如运用 RB 信号而不是差分 UV 信号,就会呈现这种情况),是非分辨率就会受到影响。

YUV 模型界说了一个亮度重量 (Y),表明物理线性空间亮度,以及两个色度重量,别离称为 U(蓝色投影)和 V(红色投影)。它可用于在 RGB 模型之间进行转化,并具有不同的色彩空间

Metal每日分享,不同色彩空间转换滤镜效果

转化公式如下:

Metal每日分享,不同色彩空间转换滤镜效果

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地址
  • 喜欢的老板们能够点个星,谢谢各位老板!!!

✌️.