Floyd-Steinberg is actually easy to understand - It will be useful if you try to display a higher-quality picture on a lower-quality (in terms of colour depth) display.
It starts by comparing
what it wants to display with
what it's capable to display. In the code, it scales down R3G3B3 Amiga (00000RRR0GGG0BBB) pictures to R1G1B1 (number == bit depth/colour) QL pictures - The original has 7 shades of each base colour, the QL only one (on or off). Let's assume the original pixel has "not quite full" red (0000010000000000 == 100R). The QL can only display "full red" - So, when we display a "full red" (111b) pixel for this one on the QL, we have accumulated 011b "too much red". This is compensated by deducting this "too much red" from neighboring pixels. (That also works the other way round, when we display "not enough" of a colour because of rounding down at 011b). The "too much" or "not enough" of a base colour is distributed in 1/16 of the difference to neighbours: 7/16 of the difference goes to the pixel to the right, 1/16 to the pixel one down and one right, 5/16 goes to the pixel below us, and 3/16to the one diagonally left and down (in the code, the division by 16 is delayed to long after the final sum of the applied differences is calculated to keep rounding errors small - we're doing integer arithmetic only). This is done for all 3 base colour components (red, green, blue) separately - Basically a "wave of colour differences" runs from the top left to bottom right and evens out the picture.
The following code converts one Amiga pixel into one QL pixel and sets the correction arrays (oldErrors and newErrors). Note these arrays (even if they "look" local) are static and survive until the next (and all following) function call. So, while we run horizontally through the line, we collect an array of colour correction values for the next line in the nextLinCorr array - which will be applied when we run through the next line.
Code: Select all
unsigned short RGB8ToQLiFS(unsigned int RGB8, int x, int y) {
char r, b, g, newR, newG, newB;
short rCorr, bCorr, gCorr;
int newCol;
static colourCorr nextLinCorr [259], thisLinCorr [259];
//static colourCorr nextPixCorr;
static colourCorr *oldErrors;
static colourCorr *newErrors;
if ((x > 250) || (x < 0)) return 0;
if ((y > 250) || (y < 0)) return 0;
// Initialize if we start a new picture
if ((x == 0) && (y == 0)) {
memset (nextLinCorr, 0, sizeof (nextLinCorr));
memset (thisLinCorr, 0, sizeof (thisLinCorr));
oldErrors = &(nextLinCorr [1]); // Catches out of bounds at start and end of a line
newErrors = &(thisLinCorr [1]);
} else if (x == 0) // Initialize if we start a new line (alternate the two arrays)
{
colourCorr *tmp = oldErrors;
oldErrors = newErrors;
newErrors = tmp;
memset(newErrors - sizeof (colourCorr), 0, sizeof(nextLinCorr)); // clear new
//memset(&nextPixCorr, 0, sizeof (colourCorr));
}
// Convert color parts to 0-7
r = (((RGB8 & red_mask)) >> red_shift);
g = (((RGB8 & green_mask)) >> green_shift);
b = (RGB8 & blue_mask);
// Add the correction values
r += (oldErrors [x + 1].RCorr / 16);
g += (oldErrors [x + 1].GCorr / 16);
b += (oldErrors [x + 1].BCorr / 16);
// Now find the colour errors (in original colour space) that occur when we map the colour values
newR = (r > 3) ? 7 : 0;
newG = (g > 3) ? 7 : 0;
newB = (b > 3) ? 7 : 0;
rCorr = r - newR;
gCorr = g - newG;
bCorr = b - newB;
//printf ()
// And apply the correction Values to the correction array
// (note correction array is shifted one to the right!)
oldErrors [x + 2].RCorr += 7 * rCorr;
newErrors [x + 2].RCorr += 1 * rCorr;
newErrors [x + 1].RCorr += 5 * rCorr;
newErrors [x + 0].RCorr += 3 * rCorr;
oldErrors [x + 2].GCorr += 7 * gCorr;
newErrors [x + 2].GCorr += 1 * gCorr;
newErrors [x + 1].GCorr += 5 * gCorr;
newErrors [x + 0].GCorr += 3 * gCorr;
oldErrors [x + 2].BCorr += 7 * bCorr;
newErrors [x + 2].BCorr += 1 * bCorr;
newErrors [x + 1].BCorr += 5 * bCorr;
newErrors [x + 0].BCorr += 3 * bCorr;
// simply fumble the three bits into one number 0-7
return (((newB != 0)) | ((newR != 0) << 1) | ((newG != 0) << 2));
}
Normally, you would simply apply the correction values for the "next right" and "next row down" pixels back to the original picture - The code above uses a two separate arrays that are alternated between rows for the correction values, because the original picture is read-only, which makes the code a bit harder to understand.