SkillAgentSearch skills...

DitheringEngine

iOS and MacCatalyst framework for dithering images and videos. Used in Ditherable.

Install / Use

/learn @Eskils/DitheringEngine

README

DitheringEngine

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.

A dithered image with four colors. A rose bush on a field in foreground with a pergola in the background.

This image has only four colors: black, white, cyan, and magenta.

Check out the demo application for iOS and macOS.

Table of contents

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:

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.

Threshold with default settings. CGA Mode 4 | Palette 0 | High

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).

Floyd-Steinberg dithering with default settings. CGA Text Mode palette

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.

Atkinson dithering with default settings. CGA Text Mode

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.

Jarvis-Judice-Ninke dithering with default settings. CGA Text Mode

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.

Bayer dithering with default settings. CGA Mode 5 | High palette

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
View on GitHub
GitHub Stars25
CategoryContent
Updated1mo ago
Forks2

Languages

Swift

Security Score

95/100

Audited on Feb 27, 2026

No findings