Skip to content

Instantly share code, notes, and snippets.

@configurator
Created October 15, 2014 22:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save configurator/cd0cb436e43001b9cab4 to your computer and use it in GitHub Desktop.
Save configurator/cd0cb436e43001b9cab4 to your computer and use it in GitHub Desktop.
ErrorDiffusion dithering using the Sierra Matrix
/// <summary>
/// See http://en.wikipedia.org/wiki/Floyd-Steinberg_dithering for algorithm details.
/// The matrix used is Sierra [/32]:
/// x 5 3
/// 2 4 5 4 2
/// 2 3 2
/// </summary>
protected static Bitmap ErrorDiffusion(System.Drawing.Image bitmap) {
const double limit = 0.5;
var errors = new ErrorArray(bitmap.Width, bitmap.Height);
var result = new Bitmap(bitmap);
for (var y = 0; y < bitmap.Height; y++) {
for (var x = 0; x < bitmap.Width; x++) {
var originalValue = result.GetPixel(x, y).GetBrightness();
var originalError = errors[x, y];
var value = originalValue + originalError;
var white = value > limit;
result.SetPixel(x, y, white ? Color.White : Color.Black);
var residual = value - (white ? 1 : 0);
residual /= 32;
errors.AddRow(x - 2, y + 0, residual, 0, 0, 0, 5, 3);
errors.AddRow(x - 2, y + 1, residual, 2, 4, 5, 4, 2);
errors.AddRow(x - 2, y + 2, residual, 0, 2, 3, 2, 0);
}
}
return result;
}
private struct ErrorArray {
private readonly int width;
private readonly int height;
private readonly double[,] array;
public ErrorArray(int width, int height) {
this.width = width;
this.height = height;
this.array = new double[width, height];
}
public double this[int x, int y] {
get {
if (x < 0 || x >= width || y < 0 || y >= height) {
return 0;
}
return array[x, y];
}
}
public void Add(int x, int y, double value) {
if (x < 0 || x >= width || y < 0 || y >= height) {
return;
}
array[x, y] += value;
}
public void AddRow(int x, int y, double residual, params int[] multipliers) {
for (var i = 0; i < multipliers.Length; i++) {
Add(x + i, y, residual * multipliers[i]);
}
}
}
@configurator
Copy link
Author

This works great for small images, but I haven't tried it on larger once. If it's too slow or memory hungry, this code could be easily transformed so the operation is done in place with only additional O(image.Width) memory requirements, by:

  1. Removing line #11, and changing the parameter type to Bitmap (because we need GetPixel and SetPixel).
  2. making the ErrorArray a bit smarter - once row y is accessed, row y-3 will never be accessed again. It could use a rotating array of 3 rows, or always access line y % 3 (and make sure to clear the line whenever it is accessed the first time).

Changing the doubles to floats would also probably have no noticeable impact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment