Skip to content

Commit

Permalink
Merge pull request #3 from Penfore/2-implement-ordered-dithering-algo…
Browse files Browse the repository at this point in the history
…rithm

Implements ordered dithering algorithm
  • Loading branch information
Penfore authored Aug 16, 2024
2 parents 954e74f + 6602510 commit fd7a5b7
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 3 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# DitherIt

DitherIt is a Dart library that implements various dithering algorithms. The library currently supports the Floyd-Steinberg dithering algorithm, which can be used to apply dithering to images, reducing the number of colors while maintaining visual quality.
DitherIt is a Dart library that implements dithering algorithms for image processing. The library currently supports Floyd-Steinberg and Ordered dithering algorithms, allowing you to apply these dithering techniques to images to reduce the number of colors used while maintaining visual quality.

## Features

- Applies the Floyd-Steinberg dithering algorithm to images.
- Floyd-Steinberg Dithering: Applies the Floyd-Steinberg dithering algorithm to an image. This well-known dithering method propagates the quantization error to neighboring pixels, enhancing the visual quality of images with reduced colors.

- Ordered Dithering: Applies the Ordered dithering (or Bayer matrix dithering) algorithm to an image. This algorithm uses a predefined threshold matrix to determine which pixels should be adjusted, providing a regular and repetitive dithering pattern.

## Installation

Expand All @@ -29,7 +31,15 @@ void main() {
// Apply Floyd-Steinberg dithering
final Image ditheredImage = DitherIt.floydSteinberg(image: image);

// Apply Ordered dithering with different Bayer matrix sizes
final Image orderedDitheredImage = DitherIt.ordered(image: image, matrixSize: 2);
final Image ordered4DitheredImage = DitherIt.ordered(image: image, matrixSize: 4);
final Image ordered8DitheredImage = DitherIt.ordered(image: image, matrixSize: 8);

// Save the dithered image
File('{path}/dithered_image.png').writeAsBytesSync(encodePng(ditheredImage));
File('{path}/floyd_steinberg_dithered_image.png').writeAsBytesSync(encodePng(floydSteinbergDitheredImage));
File('{path}/ordered_dithered_image.png').writeAsBytesSync(encodePng(orderedDitheredImage));
File('{path}/ordered4_dithered_image.png').writeAsBytesSync(encodePng(ordered4DitheredImage));
File('{path}/ordered8_dithered_image.png').writeAsBytesSync(encodePng(ordered8DitheredImage));
}
```
30 changes: 30 additions & 0 deletions lib/bayer_matrices.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
part of 'dither_it.dart';

const List<List<double>> _bayerMatrix2x2 = [
[0.0, 0.5],
[0.75, 0.25],
];

const List<List<double>> _bayerMatrix4x4 = [
[0.0, 0.5, 0.125, 0.625],
[0.75, 0.25, 0.875, 0.375],
[0.1875, 0.6875, 0.0625, 0.5625],
[0.9375, 0.4375, 0.8125, 0.3125],
];

const List<List<double>> _bayerMatrix8x8 = [
[0.0, 0.5, 0.125, 0.625, 0.03125, 0.53125, 0.15625, 0.65625],
[0.75, 0.25, 0.875, 0.375, 0.78125, 0.28125, 0.90625, 0.40625],
[0.1875, 0.6875, 0.0625, 0.5625, 0.21875, 0.71875, 0.09375, 0.59375],
[0.9375, 0.4375, 0.8125, 0.3125, 0.96875, 0.46875, 0.84375, 0.34375],
[0.015625, 0.515625, 0.140625, 0.640625, 0.046875, 0.546875, 0.171875, 0.671875],
[0.765625, 0.265625, 0.890625, 0.390625, 0.796875, 0.296875, 0.921875, 0.421875],
[0.203125, 0.703125, 0.078125, 0.578125, 0.234375, 0.734375, 0.109375, 0.609375],
[0.953125, 0.453125, 0.828125, 0.328125, 0.984375, 0.484375, 0.859375, 0.359375],
];

const Map<int, List<List<double>>> _precomputedBayerMatrices = {
2: _bayerMatrix2x2,
4: _bayerMatrix4x4,
8: _bayerMatrix8x8,
};
52 changes: 52 additions & 0 deletions lib/dither_it.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ library dither_it;

import 'package:image/image.dart';

part 'bayer_matrices.dart';

/// A library that implements various dithering algorithms.
class DitherIt {
/// Applies the Floyd-Steinberg dithering algorithm to the provided image.
Expand Down Expand Up @@ -78,4 +80,54 @@ class DitherIt {
static int _clamp(int value) {
return value.clamp(0, 255);
}

static const int _maxMatrixSize = 8;

/// Applies the Ordered Dithering algorithm to the provided image.
/// https://en.wikipedia.org/wiki/Ordered_dithering
///
/// [image]: The input image to be dithered.
///
/// [matrixSize]: The size of the Bayer matrix to use for dithering. Must be a power of 2, greater than or equal to 2, and less than or equal to 8.
///
/// Returns the dithered image.
///
/// Throws an [ArgumentError] if the matrix size exceeds the maximum allowed size of 8.
static Image ordered({required Image image, required int matrixSize}) {
if (matrixSize < 2 || (matrixSize & (matrixSize - 1)) != 0) {
throw ArgumentError('The size must be a power of 2 and greater than or equal to 2.');
}
if (matrixSize > _maxMatrixSize) {
throw ArgumentError('Matrix size exceeds the maximum allowed size of $_maxMatrixSize.');
}

final Image newImage = Image.from(image);
final List<List<double>> thresholdMap = _precomputedBayerMatrices[matrixSize]!;
final int mapSize = matrixSize;

for (int y = 0; y < image.height; y++) {
for (int x = 0; x < image.width; x++) {
final Pixel currentPixel = newImage.getPixel(x, y);
final int thresholdX = x % mapSize;
final int thresholdY = y % mapSize;
final double threshold = thresholdMap[thresholdY][thresholdX];

final int currentRed = currentPixel.r.toInt();
final int currentGreen = currentPixel.g.toInt();
final int currentBlue = currentPixel.b.toInt();

final double normalizedRed = (currentRed / 255.0) + (threshold - 0.5);
final double normalizedGreen = (currentGreen / 255.0) + (threshold - 0.5);
final double normalizedBlue = (currentBlue / 255.0) + (threshold - 0.5);

final int newRed = _findClosestColor((normalizedRed * 255).round());
final int newGreen = _findClosestColor((normalizedGreen * 255).round());
final int newBlue = _findClosestColor((normalizedBlue * 255).round());

newImage.setPixelRgb(x, y, newRed, newGreen, newBlue);
}
}

return newImage;
}
}

0 comments on commit fd7a5b7

Please sign in to comment.