一、背景
你是否曾经想要给你的照片增添一些特殊效果,让它们更加引人注目?或者你是否对那些充满艺术感的滤镜照片羡慕不已,但却认为它们需要专业的图像处理技巧?今天,我将向你揭示一个令人惊讶的事实——图片加滤镜竟如此简单!
滤镜是一种强大而又简单的工具,它可以改变图片的色彩、亮度、对比度和纹理等特性,让你的照片脱颖而出。
在本文中,我将向你展示如何简单使用滤镜属性为你的图片增添滋味。让我们一起揭开图片加滤镜的神秘面纱,发掘那些隐藏在简单代码背后的惊艳效果。让你的照片焕发新生,让你的创造力在滤镜的魔力下绽放!让我们开始吧!
熟悉ios的朋友,如果提到滤镜,那么第一时间就会想到 GPUImage, 确实很好用,不过这个库已经太久不更新维护。
于是乎我这边基于Swift + Metal
制作一款全新的滤镜库,这边在编写的时候,当然也有借鉴GPUImage
很多滤镜算法,再次感谢大神!!!
- 先贴链接地址:github.com/yangKJ/Harb…
Animated | Still | Mix |
---|---|---|
![]() |
![]() |
![]() |
话不多说,先简单介绍滤镜库 Harbeth 支持功能:
- 支持ios系统和macOS系统;
- 支持运算符函数式操作;
- 支持多种模式数据源 UIImage, CIImage, CGImage, CMSampleBuffer, CVPixelBuffer
- 支持使用系统 MetalPerformanceShaders 滤镜和兼容 CoreImage 滤镜;
- 支持快速设计滤镜;
- 支持合并多种滤镜效果;
- 支持输出源的快速扩展;
- 支持相机采集特效 和 视频添加滤镜特效;
- 支持已有视频添加滤镜并导出;
大致统计下来,这边目前也已经拥有超150
多种内置滤镜提供使用,并且这边也支持兼容系统CoreImage
,这样一算那滤镜就太多了!
实现方案
该库主要分为以下几个板块,基础模块、Metal内核滤镜模块、CoreImage滤镜模块、视频滤镜模块
基础模块又主要分为以下部分:
- Core: 该模块主要处理配置Metal信息,和CoreImage兼容转换;
- Extensions: 该模块主要处理各类型资源和MTLTexture互相转换方法;
- Matrix: 该模块主要包含矩阵相关,常用矩阵卷积内核和常用颜色矩阵;
- Outputs: 该模块主要包含对外转换,
BoxxIO
快速向源添加过滤器; - Setup: 该模块主要包含配置信息,该库使用的小工具等;
如何使用
- 代码完全可以做到零侵入对图像注入滤镜功能,然后显示在UIImageView控件;
? - 原始代码:
lazy var ImageView: UIImageView = {
let imageView = UIImageView(image: originImage)
imageView.layer.borderColor = R.color("background2")?.cgColor
imageView.layer.borderWidth = 0.5
return imageView
}()
ImageView.image = originImage
图像添加滤镜
? - 注入滤镜代码:
let filter1 = C7ColorMatrix4x4(matrix: Matrix4x4.Color.sepia)
let filter2 = C7Granularity(grain: 0.8)
let filter3 = C7SoulOut(soul: 0.7)
let filters = [filter1, filter2, filter3]
简单使用`Outputable`? ? ?
ImageView.image = try? originImage.makeGroup(filters: filters)
指定图像数据源
- 可以高性能在这些数据源快速添加过滤器,例如:NSImage、UIImage、CIImage、CGImage、CMSampleBuffer, CVPixelBuffer;
- 其中CMSampleBuffer主要为相机采集回来的缓冲数据,CVPixelBuffer则可以简单用于播放视频添加滤镜;
也可数据源模式使用
let dest = BoxxIO.init(element: originImage, filters: filters)
// 同步处理
ImageView.image = try? dest.output()
异步处理
// 异步处理
dest.transmitOutput(success: { [weak self] image in
DispatchQueue.main.async {
self?.ImageView.image = image
}
})
运算符
或者运算符操作
ImageView.image = originImage -->>> filters
甚至不定参数使用
ImageView.image = originImage.filtering(filter, filter2, filter3)
总之这么多种方案,怎么使用就看你的心情了!!!?
如何设计滤镜
这边完全采用协议方式,用于自定义各种滤镜;
滤镜类型
- 主要包含以下几种类型滤镜;
public enum Modifier {
/// 基于`MTLComputeCommandEncoder`并行计算编码器,可直接生成图片
case compute(kernel: String)
/// 基于`MTLRenderCommandEncoder`渲染 3D 编码器,需配合`MTKView`方能显示
case render(vertex: String, fragment: String)
/// 基于`MTLBlitCommandEncoder`位图复制编码器,拷贝纹理同时也能生成贴图
case blit
/// 基于`CoreImage`,直接生成图片
case coreimage(CIName: String)
/// 基于`MetalPerformanceShaders`着色器
case mps(performance: MPSUnaryImageKernel)
}
主要包括下面几个协议,
- C7FilterPorotocal: 基础协议,包含滤镜类型
Modifier
、参数因子、其他输入源、以及修改尺寸大小; - ComputeFiltering: 该协议主要用于基于内核模式,传递特殊参数因子;
- CoreImageFiltering: 该协议主要用于基于CoreImage,兼容使用系统CoreImage滤镜;
- RenderFiltering: 该协议主要作用于基于片元着色器和顶点着色器;
基础 C7FilterProtocal 协议:
public protocol C7FilterProtocol {
/// 编码器类型和对应的函数名
///
/// 计算需要对应的`kernel`函数名
/// 渲染需要一个`vertex`着色器函数名和一个`fragment`着色器函数名
var modifier: Modifier { get }
/// 制作缓冲区
/// 设置修改参数因子,需要转换为`Float`。
var factors: [Float] { get }
/// 多输入源扩展
/// 包含 `MTLTexture` 的数组
var otherInputTextures: C7InputTextures { get }
/// 改变输出图像的大小
func outputSize(input size:C7Size) -> C7Size
}
设计滤镜
- 遵循协议
C7FilterProtocal
- 配置额外的所需纹理
- 配置传递参数因子,仅支持
Float
类型 - 编写基于并行计算的核函数着色器
- 简单使用,由于我这边设计的是基于并行计算管道,所以可以直接生成图片
基于内核模式的查找滤镜
kernel void C7LookupTable(texture2d<half, access::write> outputTexture [[texture(0)]],
texture2d<half, access::read> inputTexture [[texture(1)]],
texture2d<half, access::sample> lookupTexture [[texture(2)]],
constant float *intensity [[buffer(0)]],
uint2 grid [[thread_position_in_grid]]) {
const half4 inColor = inputTexture.read(grid);
const half blueColor = inColor.b * 63.0h; // 蓝色部分[0, 63] 共64种
half2 quad1;
quad1.y = floor(floor(blueColor) / 8.0h);
quad1.x = floor(blueColor) - (quad1.y * 8.0h);
half2 quad2;
quad2.y = floor(ceil(blueColor) / 8.0h);
quad2.x = ceil(blueColor) - (quad2.y * 8.0h);
const float A = 0.125;
const float B = 0.5 / 512.0;
const float C = 0.125 - 1.0 / 512.0;
float2 texPos1; // 计算颜色(r,b,g)在第一个正方形中对应位置
texPos1.x = A * quad1.x + B + C * inColor.r;
texPos1.y = A * quad1.y + B + C * inColor.g;
float2 texPos2;
texPos2.x = A * quad2.x + B + C * inColor.r;
texPos2.y = A * quad2.y + B + C * inColor.g;
constexpr sampler quadSampler(mag_filter::linear, min_filter::linear);
const half4 newColor1 = lookupTexture.sample(quadSampler, texPos1);
const half4 newColor2 = lookupTexture.sample(quadSampler, texPos2);
const half4 newColor = mix(newColor1, newColor2, fract(blueColor));
const half4 outColor = half4(mix(inColor, half4(newColor.rgb, inColor.a), half(*intensity)));
outputTexture.write(outColor, grid);
}
总结
本文对于如何使用Harbth滤镜库做了简单介绍,和如何设计滤镜等等;
欢迎大家来使用该框架,然后指正修改亦或者大家有什么需求也可提出来,后续慢慢补充完善;
也欢迎大神来帮忙使用优化此库,再次感谢!!!
对于如何使用和设计原理先简单介绍出来,关于后续功能和优化再慢慢介绍!
觉得有帮助的铁子,就给我点个星?支持一哈,谢谢铁子们~
本文滤镜框架传送门 Harbeth 地址。
有什么问题也可以直接联系我,邮箱 yangkj310@gmail.com