joexbayer wrote:I have been searching for a while now, through this forum and the reddit and not found a way to map 8bit RGB colors to the VGA palette.
Ive considered going through all the 255 possible colors and map them 1 to 1, but the shades of colors make this really hard.
I'll explain a simple method if you don't care about
dithering.
Let's assumine you have a palette->RGB lookup table, where you can look up "Palette Color 5" and get the RGB values for it.
Code: Select all
struct Color {
float red, green, blue;
};
Color palette[256];
I'm defining red/green/blue to be floats from 0.0 to 1.0, but they could be unsigned chars from 0 to 255.
To find the closest Palette Color from RGB color, loop through all your palette colors and return the one with the smallest error.
Code: Select all
int FindClosestPaletteColor(const Color& color) {
int closestPaletteColor = 0;
float smallest_error = INT_MAX;
for (int i = 0; i < 256; i++) {
float error = DifferenceBetweenColors(pallete[i], color);
if (error < smallest_error) {
smallest_error = error;
closestPaletteColor = i;
}
}
return closestPaletteColor;
}
For calculating how different two colors are, I'm just treating colors as a 3d vector and doing a simple squared distance, but there are
other methods too.
Code: Select all
float DifferenceBetweenColors(const Color& a, const Color& b) {
float delta_red = a.red - b.red;
float delta_green = a.green - b.green;
float delta_blue = a.blue - b.blue;
float error = delta_red * delta_red + delta_green * delta_green + delta_blue * delta_blue;
}
Now, FindClosestPaletteColor() is slow and inefficient to be called in real-time for every color. We can generate a lookup table!
We can very efficiently take a random RGB color, turn it into 5-bit (32 values) per channel RGB color, which gives us 32,768 possible colors. You could construct the lookup table like this:
Code: Select all
unsigned char palette_lookup_table[32768]; // 2^15.
void PopulateLookupTable() {
for (int red = 0; red < 32; red++) {
for (int green = 0; green < 32; green++) {
for (int blue = 0; blue < 32; blue++) {
palette_lookup_table[red + green * 32 + blue * (32 * 32)] =
FindClosestPaletteColor({static_cast<float>(red) / 31.0f,
static_cast<float>(green) / 31.0f,
static_cast<float>(blue) / 31.0f, 1.0f});
}
}
}
}
unsigned char lookupTable[32768] would use 32KB of memory. If that's too much, you may get by with your lookup table being 4-bits (16 values) per channel which would only use 4KB of memory. Smaller than that I'd worry you'd be loosing too much precision.
Now that you have your lookup table, we can efficiently lookup the closest palette index of any color:
Code: Select all
int ColorToPaletteIndex(const Color& color) const {
// Convert the color to 15-bits and look it up.
int red = static_cast<int>(color.red * 31.0f);
int green = static_cast<int>(color.green * 31.0f);
int blue = static_cast<int>(color.blue * 31.0f);
int color_index =
std::max(0, std::min(red + green * 32 + blue * 1024, 32767));
return palette_lookup_table[color_index];
}
FindClosestPaletteColor() and PopulateLookupTable() could exist in some separate program so you only have to construct the table once and you can hardcode the lookup table into your program:
Code: Select all
unsigned char palette_lookup_table[] = { ... };