DitheringEngine
iOS and MacCatalyst framework for dithering images and videos. Used in Ditherable.
Install / Use
/learn @Eskils/DitheringEngineREADME

Dithering Engine
Framework for iOS and macOS to dither images and videos. Used in Ditherable.
Dithering is the process of adding noise to an image in order for us to perceive the image more colorful.

This image has only four colors: black, white, cyan, and magenta.
Check out the demo application for iOS and macOS.
Table of contents
- Installation
- Usage
- Dithering methods
- Built-in palettes
- Creating your own palette
- Video Dithering Engine
- Contributing
Installation
To use this package in a SwiftPM project, you need to set it up as a package dependency:
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyPackage",
dependencies: [
.package(
url: "https://github.com/Eskils/DitheringEngine",
.upToNextMinor(from: "1.10.0") // or `.upToNextMajor
)
],
targets: [
.target(
name: "MyTarget",
dependencies: [
.product(name: "DitheringEngine", package: "DitheringEngine")
]
)
]
)
Usage
The engine works on CGImages and video URLs/AVAsset.
Supported dithering methods are:
- Threshold
- Floyd-Steinberg
- Atkinson
- Jarvis-Judice-Ninke
- Bayer (Ordered dithering)
- White noise (Ordered dithering)
- Noise (Ordered dithering)
NOTE: The ordered dither methods are computed on the GPU using Metal by default. You can specify to run them on the CPU if desired.
Supported out of the box palettes are:
Dithering images
Example usage:
// 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)
)
Preserving transparency
Dithering Engine supports preserving the alpha channel of the input image. The alpha channel is copied from the input to the dithered image without any modifications. You may disable transparency preservation by setting preserveTransparency to false.
Example:
let ditheringEngine = DitheringEngine()
ditheringEngine.preserveTransparency = false
// ...
Dithering videos
Example usage:
// 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
)
Dithering methods
Here is an overview over the available dithering methods.
Threshold
Threshold gives the nearest match of the color in the image to the color in the palette without adding any noise or improvements.

Token: .threshold
Settings: EmptyPaletteSettingsConfiguration
Example:
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
Floyd-Steinberg dithering spreads the error from reducing the color of a pixel to the neighbouring pixels—yielding an image looking close to the original in areas of fine detail (e.g. grass and trees) and with interesting artifacts in areas of little detail (e.g. the sky).

Token: .floydSteinberg
Settings: FloydSteinbergSettingsConfiguration
| Name | Type | Default | Description |
|------|------|---------|-------------|
| direction | FloydSteinbergDitheringDirection | .leftToRight | Specifies in what order to go through the pixels of the image. This has an effect on where the error is distributed. |
| matrix | [Int] | [7, 3, 5, 1] | A matrix (array of four numbers) which specifies what weighting of the error to give the neighbouring pixels. The weighing is a fraction of the number and the sum of all numbers in the matrix. For instance: in the default matrix, the first is given the weight 7/16. The image explains how the weights in the matrix are distributed.
.
FloydSteinbergDitheringDirection:
.leftToRight.rightToLeft.topToBottom.bottomToTop
Example:
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
Atkinson dithering is a variant of Floyd-Steinberg dithering, and works by spreading error from reducing the color of a pixel to the neighbouring pixels. Atkinson spreads over a larger area, but does not distribute the full error—making colors matching the palette have less noise.

Token: .atkinson
Settings: EmptyPaletteSettingsConfiguration
Example:
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
Jarvis-Judice-Ninke dithering is a variant of Floyd-Steinberg dithering, and works by spreading error from reducing the color of a pixel to the neighbouring pixels. This method spreads distributes the error over a larger area and therefore leaves a smoother look to your image.

Token: .jarvisJudiceNinke
Settings: EmptyPaletteSettingsConfiguration
Example:
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
let cgImage = try ditheringEngine.dither(
usingMethod: .jarvisJudiceNinke,
andPalette: .cga,
withDitherMethodSettings: EmptyPaletteSettingsConfiguration(),
withPaletteSettings: CGASettingsConfiguration(mode: .textMode)
)
Bayer
Bayer dithering is a type of ordered dithering which adds a precalculated threshold to every pixel, baking in a special pattern.

Token: .bayer
Settings: BayerSettingsConfiguration
| Name | Type | Default | Description |
|------|------|---------|-------------|
| thresholdMapSize | Int | 4 | Specifies the size of the square threshold matrix. Default is 4x4. |
| intensity | Float | 1 | Specifies the intensity of the noise pattern. Intensity is calculated from the thresholdMapSize, and this property specifies the fraction of the calculated intensity to apply. |
| performOnCPU | Bool | false | Determines wether to perform the computation on the CPU. If false, the GPU is used for quicker performance. |
Example:
let ditheringEngine = DitheringEngine()
try ditheringEngine.set(image: inputCGImage)
l
