I am interested in dithering, ordered dithering to be more precise. I spent many hours on researching and experimenting. And hope that after all the effort my code works as it should.
I am going to provide my code that does the dithering, and some examples.
My questions
- How does it work?
- Is my code correct?
- Could there be any simplifications?
Here is the pseudo code for the dithering algorithm from this Wikipedia article, on which my code is based on.
And here are the variable explanations:
M(i, j) is the threshold map on the i-th row and j-th column, c′ is the transformed color, and r is the amount of spread in color space. Assuming an RGB palette with N³ evenly chosen colors where each color coordinate is represented by an octet, one would typically choose:
Now, here is my implementation (in pseudo code):
ditherMatrix = 4x4; // By 4x4 I mean the normal 4x4 dither matrix from the wikipedia article
ditherMatrixSize = 4;
offset = (ditherMatrixSize * (ditherMatrixSize / 2) - 0.5);
/* In the wiki article it uses 0.5 as an offset,
but I have to do this instead.
Is this correct? Atleast it works. */
allowedChanelValues = 1; // example: 2 = (0 or 128 or 255 red, 0 or 128 or 255 green, 0 or 128 or 255 blue)
r = 255 / allowedChanelValues / (ditherMatrixSize * ditherMatrixSize);
// Applying the dither
transfromedColor.r =
currentPixel.r + r * ((ditherMatrix[x % ditherMatrixSize, y % ditherMatrixSize]) - offset);
transfromedColor.g =
currentPixel.g + r * ((ditherMatrix[x % ditherMatrixSize, y % ditherMatrixSize]) - offset);
transfromedColor.g =
currentPixel.g + r * ((ditherMatrix[x % ditherMatrixSize, y % ditherMatrixSize]) - offset);
// Quantizing, see https://youtu.be/0L2n8Tg2FwI 6:40 and 9:58 for explanation
transfromedColor.r = Round(allowedChanelValues * oldR / 255) * (255 / allowedChanelValues);
transfromedColor.g = Round(allowedChanelValues * oldG / 255) * (255 / allowedChanelValues);
transfromedColor.b = Round(allowedChanelValues * oldB / 255) * (255 / allowedChanelValues);
IMPORTANT: Now, this code could be optimized in many ways, but that's not my intention (for now) I just want it to work correctly, and then I'll look at what could be optimized.
SIDE NOTE: When I was just starting out I was hardcoding the values, for example, instead of ditherMatrixSize * (ditherMatrixSize / 2) - 0.5, I was hardcoding 7.5. But after trying other dither matrices I just used this code to get the value instead of hardcoding it.
Here are some examples
(Top - Dithered, Bottom - Original. The matrix is 4x4 and allowedChanelValues is 1)
(Left - Dithered, Right- Original. The matrix is 4x4 and allowedChanelValues is 1)
I talked earlier about if it was possible to simplify the code.
I found this pseudo code here
Threshold = COLOR(256/4, 256/4, 256/4); /* Estimated precision of the palette */
For each pixel, Input, in the original picture:
Factor = ThresholdMatrix[xcoordinate % X][ycoordinate % Y];
Attempt = Input + Factor * Threshold
Color = FindClosestColorFrom(Palette, Attempt)
Draw pixel using Color
In order to implement this, I have to normalize the matrix so it ranges from 0 to 1. And this is how I implement it:
factor = 1;
offset = (ditherMatrixSize * (ditherMatrixSize / 2) - 0.5d) / (ditherMatrixSize * ditherMatrixSize);
// This time the offset is a bit different because I normalized the matrix
r = 256 / factor; // r is the same as the Threshold
transformedPixelColor.R =
currentPixel.R + r * (ditherMatrix[(x) % ditherMatrixSize, (y) % ditherMatrixSize] - offset)
transformedPixelColor.G =
currentPixel.G + r * (ditherMatrix[(x) % ditherMatrixSize, (y) % ditherMatrixSize] - offset)
transformedPixelColor.B =
currentPixel.B + r * (ditherMatrix[(x) % ditherMatrixSize, (y) % ditherMatrixSize] - offset)
This works the same as the other one. I even found a third way to dither, but it's more complicated and dithers the same way as these two, so I won't cover it.
So what do you think? Is the algorithm working corectly?
Thank you in advance!