Page 1 of 1

640x480x8 Display, 8bit RGB to VGA palette.

Posted: Sun Mar 12, 2023 1:53 am
by joexbayer
I have a 640x480x8 display meaning 1 byte per pixel. This is mostly because I want to restrain myself
and use as little memory as possible. Now I have found out that because of the 8bit, I use the VGA palette:
Image
This was "fine" until I started to add icons / pictures that are exported to 8bit RGB.

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.

Is this just an impossible task or have I not been searching hard enough?

I really hope there isn't a obvious answer that I have missed all this time :D

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Mon Apr 03, 2023 6:01 pm
by Octocontrabass
This question was also posted elsewhere.

Since 640x480x8 is a VBE mode, you can use VBE functions to set the palette. Using your own palette should make things much easier, but you might still need to research quantization algorithms to map the colors in your image to the colors available on the screen.

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Mon Apr 03, 2023 6:50 pm
by alexfru
Change the palette to all combinations of RGBs that fit into 256 colors, e.g. 6 reds * 6 greens * 6 blues = 216 colors.
Then use e.g. Ordered dithering.

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Mon Apr 03, 2023 8:41 pm
by Gigasoft
Personally I use a 676 palette, but if you want to map colors to the standard VGA palette, you could find the saturation and value, use a gray if the saturation is below the limit (depending on value) where the color is best approximated by a gray, otherwise use the appropriate row based on which grid square the value and saturation fall into. Might also check if the color is better approximated by one of the 12 non-gray colors in the first row, again by classifying according to HSV coordinates. For dithering, it is not so easy, haven't done it myself but you'll probably need to precompute a map from chroma and value to the closest set of three points in SV space, and then use the hue as well to get six colors to use for the mix.

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Tue Apr 04, 2023 1:23 am
by joexbayer
Octocontrabass wrote:This question was also posted elsewhere.

Since 640x480x8 is a VBE mode, you can use VBE functions to set the palette. Using your own palette should make things much easier, but you might still need to research quantization algorithms to map the colors in your image to the colors available on the screen.
Yes, posted it here and on reddit at the same time but it wasn't approved here until now! :D
Thanks again for your feedback, managed to solve it at last.

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Tue Apr 04, 2023 1:23 am
by joexbayer
alexfru wrote:Change the palette to all combinations of RGBs that fit into 256 colors, e.g. 6 reds * 6 greens * 6 blues = 216 colors.
Then use e.g. Ordered dithering.
Thanks! I will have a look at Ordered dithering! :)

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Fri May 05, 2023 8:47 am
by AndrewAPrice
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[] = { ... };

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Tue May 16, 2023 7:08 am
by joexbayer
AndrewAPrice wrote:
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[] = { ... };
Thanks for the detailed response!
I will have a look at it, currently I got it working by remapping the palette to 8bit RGB.
Although the limitation of no real gray colors is pushing me back to VGA.

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Sat May 20, 2023 8:41 am
by eekee
Plan 9 has an interesting color map. color(6) goes into what it is and why, and has code to compute the palette. (Note: The code may come under the Lucent LPL which has an odd legal clause. It might be best to use the numbers it outputs but not include the code itself in your OS.)

I might post again with a screenshot of the colors program; I've been meaning to install a Plan 9 VM.

Re: 640x480x8 Display, 8bit RGB to VGA palette.

Posted: Sun May 21, 2023 12:38 am
by eekee
Here's the Plan 9 color chart as stated. I'm not sure why it's laid out like this, but the greys are on the diagonal.