Skip to content

Instantly share code, notes, and snippets.

@tahb
Created July 19, 2013 13:53
Show Gist options
  • Save tahb/6039254 to your computer and use it in GitHub Desktop.
Save tahb/6039254 to your computer and use it in GitHub Desktop.
Colour Dither
public ColorImage apply(ColorImage image)
{
// Store the image objects width and height in local variables.
int image_width = image.getWidth();
int image_height = image.getHeight();
// Store all given fixed colours into array.
Color[] palette = new Color[8];
palette[0] = new Color (0,0,0);
palette[1] = new Color (255,255,255);
palette[2] = new Color (255,0,0);
palette[3] = new Color (0,255,0);
palette[4] = new Color (0,0,255);
palette[5] = new Color (0,255,255);
palette[6] = new Color (255,0,255);
palette[7] = new Color (255,255,0);
// Declare and create a 2D array for all pixels (x,y) in image to provide quicker lookups.
int[][] pixels = new int[image_width][image_height];
// Iterate over each pixel and get the RGB values from the original image object and store them in this array.
for ( int y = 0; y < image_height; y++ ) {
for ( int x = 0; x < image_width; x++ ) {
pixels[x][y] = image.getRGB( x, y );
}
}
// Iterate over each pixel starting on the Y axis and then the X axis for full coverage.
for ( int y = 0; y < image_height; y++ ) {
for ( int x = 0; x < image_width; x++ ) {
// Determine if there are pixels to the bottom, left and right of this pixel for later dithering.
boolean left, right, bottom;
bottom = ( y + 1 ) < ( image_height - 1 );
right = ( x + 1 ) < ( image_width - 1 );
left = ( x - 1 ) >= 1;
// Create new temp array to store the RGB values for *this* pixel from the pre-defined pixels array
int[] old_rgb = new int[3];
// Create counter used later in Euclid's theorem to detect the nearest colour accurately.
double counter = 99999;
// Create variable that will hold the best colour we can find from the palette array
Color best_color = null;
// Create error array that will hold the error values, each index will correspond to RGB values
int[] error = new int[3];
// Find the RGB (0-255) values from *this* pixel in the array by bit shifting (no extra method calls to getRed())
old_rgb[0] = ( pixels[x][y] >> 16 ) &255;
old_rgb[1] = ( pixels[x][y] >> 8 ) &255;
old_rgb[2] = ( pixels[x][y] ) &255;
// For each Palette colour defined, loop over so we cover each one
for ( int i = 0; i < palette.length; i++ ) {
// Using Euclid's theorem, use the RGB values for the old pixel with the corresponding RGB values of
// each colour in our palette. In order to establish the most likely colour
double color_vector = Math.sqrt(
Math.pow((old_rgb[0] - palette[i].getRed()), 2) +
Math.pow((old_rgb[1] - palette[i].getGreen()), 2) +
Math.pow((old_rgb[2] - palette[i].getBlue()), 2)
);
// If the resulting color_vector is lower than any previous tested palette colours, set it as the new best match
if ( color_vector < counter ) {
counter = color_vector;
best_color = palette[i];
}
}
// From the best matched colour found, calculate the difference for each RGB value by taking it away from the original pixel
error[0] = old_rgb[0] - best_color.getRed();
error[1] = old_rgb[1] - best_color.getGreen();
error[2] = old_rgb[2] - best_color.getBlue();
// Update this pixel on the image object with its new colour.
image.setPixel( x, y, best_color );
// Depending on neighbouring pixels, call the diffuse method on each position in the pixels array and set the new value back
if ( right ) {
// Call the dither method giving it the current position 7, the RGB values and the error array
// Assign this new RGB integer that dithering returns to the correct position in the pixels array so it accumalates
pixels[x+1][y] = dither (7, ( pixels[x+1][y] >> 16 ) &255, ( pixels[x+1][y] >> 8 ) &255, pixels[x+1][y] &255, error);
}
if ( bottom ) {
pixels[x][y+1] = dither (5, ( pixels[x][y+1] >> 16 ) &255, ( pixels[x][y+1] >> 8 ) &255, pixels[x][y+1] &255, error);
}
if ( bottom && right ) {
pixels[x+1][y+1] = dither (1, ( pixels[x+1][y+1] >> 16 ) &255, ( pixels[x+1][y+1] >> 8 ) &255, pixels[x+1][y+1] &255, error);
}
if ( bottom && left ) {
pixels[x-1][y+1] = dither (3, ( pixels[x-1][y+1] >> 16 ) &255, ( pixels[x-1][y+1] >> 8 ) &255, pixels[x-1][y+1] &255, error);
}
}
}
// Return the image after applying Ffloyd Steinberg algorithm using Euclid's theorem for improved quality
return image;
}
/**
* Apply dithering to a particular pixel
* @param multiply The value to multiply by when creating the quant_error
* @param red The RGB value for Red
* @param green The RGB value for Green
* @param blue The RGB value for Blue
* @param error[] Is an array of three error values pre determined from the difference of the old and new pixel
* @return The new RGB value after dithering in integer form
*/
private int dither (int multiply, int red, int green, int blue, int[] error) {
// Store RGB values into an array to iterate over and compare against it's error
int[] rgb_values = new int [3];
rgb_values[0] = red;
rgb_values[1] = green;
rgb_values[2] = blue;
// For each colour (will always be three)
for ( int i = 0; i < rgb_values.length; i++ ) {
// Calculate the associative error to add onto the pixel for each RGB value
int quant_error = ( ( multiply * error[i] ) / 16 );
// If combining the current RGB and the error results in going over 255, thus an invalid value, cap it at max (255)
if ( rgb_values[i] + quant_error > 255 ) {
rgb_values[i] = 255;
// If the same addition results in a negative number and therefore will cause a broken value after bit shifting cap at 0
} else if ( rgb_values[i] + quant_error < 0 ) {
rgb_values[i] = 0;
} else {
// Else adding the error on does not put the value out of bounds
rgb_values[i] += quant_error;
}
}
// Create a new RGB integer value via bit shifting and return it
int new_pixel = ( ( rgb_values[0] &255 ) << 16 ) | ( ( rgb_values[1] &255 ) << 8 ) | rgb_values[2] &255;
return new_pixel;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment