适用于 iOS 和 Mac Catalyst 的框架,用于对图像和视频进行抖动处理。
抖动是一种向图像添加噪声的过程,目的是使我们感知到图像具有更丰富的色彩。
此图像仅包含四种颜色:黑色、白色、青色和品红色。
查看适用于 iOS 和 macOS 的演示应用程序。
要在 SwiftPM 项目中使用此包,您需要将其设置为包依赖项
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
.package(
url: "https://github.com/Eskils/DitheringEngine",
.upToNextMinor(from: "1.8.2") // or `.upToNextMajor
)
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "DitheringEngine", package: "DitheringEngine")
]
)
]
)
该引擎适用于 CGImages 和视频 URLs/AVAsset。
支持的抖动方法有
注意: 默认情况下,有序抖动方法是在 GPU 上使用 Metal 计算的。您可以根据需要指定在 CPU 上运行它们。
开箱即用支持的调色板有
使用示例
// Create an instance of DitheringEngine
let ditheringEngine = DitheringEngine()
// Set input image
try ditheringEngine.set(image: inputCGImage)
// Dither to quantized color with 5 bits using Floyd-Steinberg.
let cgImage = try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .quantizedColor,
withDitherMethodSettings: FloydSteinbergSettingsConfiguration(direction: .leftToRight),
withPaletteSettings: QuantizedColorSettingsConfiguration(bits: 5)
)
使用示例
// Create an instance of VideoDitheringEngine
let videoDitheringEngine = VideoDitheringEngine()
// Create a video description
let videoDescription = VideoDescription(url: inputVideoURL)
// Set preferred output size.
videoDescription.renderSize = CGSize(width: 320, height: 568)
// Dither to quantized color with 5 bits using Floyd-Steinberg.
videoDitheringEngine.dither(
videoDescription: videoDescription,
usingMethod: .floydSteinberg,
andPalette: .quantizedColor,
withDitherMethodSettings: FloydSteinbergSettingsConfiguration(direction: .leftToRight),
andPaletteSettings: QuantizedColorSettingsConfiguration(bits: 5),
outputURL: outputURL,
progressHandler: progressHandler, // Optional block to receive progress.
completionHandler: completionHandler
)
以下是可用抖动方法的概述。
阈值给出图像中颜色与调色板中颜色最接近的匹配,而无需添加任何噪声或改进。
Token: .threshold
Settings: EmptyPaletteSettingsConfiguration
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .threshold,
andPalette: .cga,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CGASettingsConfiguration(mode: .palette0High)
)
Floyd-Steinberg 抖动将减少像素颜色的误差扩散到相邻像素,从而使图像在细节丰富的区域(例如草地和树木)看起来接近原始图像,并在细节少的区域(例如天空)中产生有趣的伪影。
Token: .floydSteinberg
Settings: FloydSteinbergSettingsConfiguration
FloydSteinbergDitheringDirection
:
.leftToRight
.rightToLeft
.topToBottom
.bottomToTop
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .cga,
withDitherMethodSettings: FloydSteinbergSettingsConfiguration(direction: .leftToRight),
withPaletteSettings: CGASettingsConfiguration(mode: .textMode)
)
Atkinson 抖动是 Floyd-Steinberg 抖动的一种变体,其工作原理是将减少像素颜色的误差扩散到相邻像素。 Atkinson 扩散的面积更大,但不分配全部误差,从而使与调色板匹配的颜色具有较少的噪声。
Token: .atkinson
Settings: EmptyPaletteSettingsConfiguration
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .atkinson,
andPalette: .cga,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CGASettingsConfiguration(mode: .textMode)
)
Jarvis-Judice-Ninke 抖动是 Floyd-Steinberg 抖动的一种变体,其工作原理是将减少像素颜色的误差扩散到相邻像素。 此方法将误差分散到更大的区域,因此使图像看起来更平滑。
Token: .jarvisJudiceNinke
Settings: EmptyPaletteSettingsConfiguration
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .jarvisJudiceNinke,
andPalette: .cga,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CGASettingsConfiguration(mode: .textMode)
)
Bayer 抖动是一种有序抖动,它将预先计算的阈值添加到每个像素,从而嵌入一个特殊的图案。
Token: .bayer
Settings: BayerSettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
thresholdMapSize | Int | 4 |
指定正方形阈值矩阵的大小。 默认值为 4x4。 |
intensity | Float | 1 |
指定噪声图案的强度。 强度从 thresholdMapSize 计算得出,此属性指定要应用的计算强度的一部分。 |
performOnCPU | Bool | false |
确定是否在 CPU 上执行计算。 如果为 false,则使用 GPU 可以获得更快的性能。 |
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .bayer,
andPalette: .cga,
withDitherMethodSettings: BayerSettingsConfiguration(),
withPaletteSettings: CGASettingsConfiguration(mode: .mode5High)
)
白噪声抖动在转换为所选调色板时,会向图像添加随机噪声,从而使图像看起来有颗粒感且杂乱。
Token: .whiteNoise
Settings: WhiteNoiseSettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
thresholdMapSize | Int | 7 |
指定正方形阈值矩阵的大小。 默认值为 128x128。 |
intensity | Float | 0.5 |
指定噪声图案的强度。 |
performOnCPU | Bool | false |
确定是否在 CPU 上执行计算。 如果为 false,则使用 GPU 可以获得更快的性能。 |
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .whiteNoise,
andPalette: .apple2,
withDitherMethodSettings: WhiteNoiseSettingsConfiguration(),
withPaletteSettings: Apple2SettingsConfiguration(mode: .hiRes)
)
您可以提供自己的噪声纹理,以便在执行有序抖动时进行采样。
此图像使用蓝色噪声图案进行抖动处理,从而呈现出颗粒状、有机的外观。
Token: .noise
Settings: NoiseDitheringSettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
noisePattern | CGImage? | nil |
指定用于有序抖动的噪声图案。 |
intensity | Float | 0.5 |
指定噪声图案的强度。 |
performOnCPU | Bool | false |
确定是否在 CPU 上执行计算。 如果为 false,则使用 GPU 可以获得更快的性能。 |
示例
let noisePatternImage: CGImage = ...
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .noise,
andPalette: .gameBoy,
withDitherMethodSettings: NoiseDitheringSettingsConfiguration(noisePattern: noisePatternImage),
withPaletteSettings: EmptySettingsConfiguration()
)
以下是内置调色板的概述
一个包含两种颜色的调色板:黑色和白色。
Token: .bw
Settings: EmptyPaletteSettingsConfiguration
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .bw,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: EmptyPaletteSettingsConfiguration()
)
一个包含所有灰度阴影的调色板。
Token: .grayscale
Settings: QuantizedColorSettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
bits | Int | 0 | 指定要量化的位数。 位数可以在 0 到 8 之间。灰度的阴影数由 2^n 给出,其中 n 是位数。 |
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .grayscale,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: EmptyPaletteSettingsConfiguration()
)
具有颜色通道量化位的调色板。 指定用于颜色的位数 - 从 0 到 8。 颜色数由 2^n 给出,其中 n 是位数。
Token: .quantizedColor
Settings: QuantizedColorSettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
bits | Int | 0 | 指定要量化的位数。 位数可以在 0 到 8 之间。颜色数由 2^n 给出,其中 n 是位数。 |
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .quantizedColor,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: QuantizedColorSettingsConfiguration(bits: 2)
)
具有老式 CGA 调色板的调色板。 CGA 是一种于 1981 年推出的显卡,能够在 IBM PC 上显示颜色。 它使用 4 位接口(红色、绿色、蓝色、强度),总共可以显示 16 种颜色。 然而,由于视频内存有限,320x200 的最常见分辨率只能在屏幕上同时显示四种颜色。 在此模式下,d 开发人员可以从四个调色板中进行选择,这些调色板具有漂亮的颜色组合,例如黑色、青色、品红色和白色,或者黑色、绿色、红色和黄色。
Token: .cga
Settings: CGASettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
mode | CGAMode | .palette1High |
指定要使用的图形模式。 每种图形模式都有一组独特的颜色。 颜色最多的是 .textMode 。 |
CGAMode
:
名称 | 颜色 | 图像 |
---|---|---|
.palette0Low |
黑色、绿色、红色、棕色 | ![]() |
.palette0High |
黑色、浅绿色、浅红色、黄色 | ![]() |
.palette1Low |
黑色、青色、品红色、浅灰色 | ![]() |
.palette1High |
黑色、浅青色、浅品红色、白色 | ![]() |
.mode5Low |
黑色、青色、红色、浅灰色 | ![]() |
.mode5High |
黑色、浅青色、浅红色、白色 | ![]() |
.textMode |
全部 16 种颜色 | ![]() |
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .quantizedColor,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CGASettingsConfiguration(mode: .palette1High)
)
Apple II 是最早具有颜色的个人电脑之一。 与降低成本相关的技术挑战为图形启用了两种模式 - 一种是具有六种颜色的高分辨率模式,另一种是具有 16 种颜色的低分辨率模式。
Token: .apple2
Settings: Apple2SettingsConfiguration
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
mode | Apple2Mode | .hiRes |
指定要使用的图形模式。 每种图形模式都有一组独特的颜色。 |
Apple2Mode
:
名称 | 颜色数量 | 图像 |
---|---|---|
.hiRes |
6 种颜色 | ![]() |
.loRes |
16 种颜色 | ![]() |
注意: Apple2 Lo-Res 调色板的 16 种颜色与 CGA 的文本模式调色板不同。
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .atkinson,
andPalette: .apple2,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: Apple2SettingsConfiguration(mode: .hiRes)
)
老式四色绿色阴影单色显示器。
Token: .gameBoy
Settings: EmptyPaletteSettingsConfiguration
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .atkinson,
andPalette: .gameBoy,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: EmptyPaletteSettingsConfiguration()
)
Intellivision 是 70 年代后期的游戏机。 它的图形由标准电视接口芯片提供支持,该芯片带有 16 色调色板。
Token: .intellivision
Settings: EmptyPaletteSettingsConfiguration
示例
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .atkinson,
andPalette: .jarvisJudiceNinke,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: EmptyPaletteSettingsConfiguration()
)
您可以使用适当的 API 创建自己的调色板。
调色板由 BytePalette
结构表示,该结构可以从查找表 (LUT) 和颜色集合 (LUTCollection) 构造。 最有用的可能是 LUTCollection。
如果您的调色板中包含 UIColors 数组,则首先需要将颜色值提取到 SIMD3<UInt8>
列表中。 这可以按如下方式完成
let entries = colors.map { color in
var redNormalized: CGFloat = 0
var greenNormalized: CGFloat = 0
var blueNormalized: CGFloat = 0
color.getRed(&redNormalized, green: &greenNormalized, blue: &blueNormalized, alpha: nil)
let red = UInt8(clamp(redDouble * 255, min: 0, max: 255))
let green = UInt8(clamp(greenDouble * 255, min: 0, max: 255))
let blue = UInt8(clamp(blueDouble * 255, min: 0, max: 255))
return SIMD3(x: red, y: green, z: blue)
}
之后,您可以创建一个 LUTCollection
并从中创建一个调色板
let collection = LUTCollection<UInt8>(entries: entries)
let palette = BytePalette.from(lutCollection: collection)
抖动图像时,请选择 .custom
调色板,并在 CustomPaletteSettingsConfiguration
中提供您的调色板
try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .custom,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CustomPaletteSettingsConfiguration(palette: palette)
)
完整示例
let entries = colors.map { color in
var redNormalized: CGFloat = 0
var greenNormalized: CGFloat = 0
var blueNormalized: CGFloat = 0
color.getRed(&redNormalized, green: &greenNormalized, blue: &blueNormalized, alpha: nil)
let red = UInt8(clamp(redDouble * 255, min: 0, max: 255))
let green = UInt8(clamp(greenDouble * 255, min: 0, max: 255))
let blue = UInt8(clamp(blueDouble * 255, min: 0, max: 255))
return SIMD3(x: red, y: green, z: blue)
}
let collection = LUTCollection<UInt8>(entries: entries)
let palette = BytePalette.from(lutCollection: collection)
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
try ditheringEngine.dither(
usingMethod: .floydSteinberg,
andPalette: .custom,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CustomPaletteSettingsConfiguration(palette: palette)
)
除了 DitheringEngine
抖动图像之外,还存在 VideoDitheringEngine
来抖动视频。 VideoDitheringEngine 的工作原理是将调色板和抖动方法应用于视频中的每一帧。 您还可以选择调整视频的大小作为此过程的一部分。
使用示例
// Create an instance of VideoDitheringEngine
let videoDitheringEngine = VideoDitheringEngine()
// Create a video description
let videoDescription = VideoDescription(url: inputVideoURL)
// Set preferred output size.
videoDescription.renderSize = CGSize(width: 320, height: 568)
// Dither to quantized color with 5 bits using Floyd-Steinberg.
videoDitheringEngine.dither(
videoDescription: videoDescription,
usingMethod: .floydSteinberg,
andPalette: .quantizedColor,
withDitherMethodSettings: FloydSteinbergSettingsConfiguration(direction: .leftToRight),
andPaletteSettings: QuantizedColorSettingsConfiguration(bits: 5),
outputURL: outputURL,
progressHandler: progressHandler,
completionHandler: completionHandler
)
使用有序抖动方法更快,并且会给出最佳结果,因为图案不会“移动”(像静态噪声)。
默认情况下,最终视频的帧率为 30。您可以在初始化 VideoDitheringEngine 时提供帧率来调整最终帧率。 最终帧率小于或等于指定值。
VideoDitheringEngine(frameRate: Int)
默认情况下,视频帧是并发渲染的。 您可以禁用此行为,或使用 numberOfConcurrentFrames
属性更改同时处理的帧数。
将其设置为 1 将有效地禁用并发帧处理。 如果 CPU 有足够的内核来处理负载,则更高的数字会更快,但也会使用更多的内存。
抖动视频时,您可以提供视频应如何处理的选项。 以下选项可用
precalculateDitheredColorForAllColors
: 创建一个索引映射,将所有颜色映射到抖动颜色。这会在开始时增加等待时间。对于大型 LUTCollections(例如 CGA)和较长的视频,可能速度更快。在使用 LUT(例如,量化颜色)时将被忽略,因为 LUT 已经是基于索引的。
removeAudio
: 不从原始视频传输音频。
您可以使用 VideoDescription
类型设置要用作输入的视频。这是 AVAsset
的一个方便的包装器,允许您设置首选的输出大小。
属性
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
renderSize | CGSize? { get set } | nil | 指定渲染最终抖动视频的大小。 |
framerate | Float? { get } | nominalFrameRate | 返回每秒帧数。如果资源不包含视频,则为 Nil。 |
transform | CGAffineTransform? { get } | preferredTransform | 视频的变换(方向,缩放)。 |
duration | TimeInterval { get } | duration.seconds | 返回视频的持续时间。 |
sampleRate | Int? { get } | naturalTimeScale | 返回每秒音频采样数。如果资源不包含音频,则为 Nil。 |
size | CGSize? { get } | naturalSize | 返回视频的大小。如果资源不包含视频,则为 Nil。 |
方法
/// Reads the first frame in the video as an image.
func getPreviewImage() async throws -> CGImage