Do the colors of the SNES palette have definite RGB values or is the palette subjective like the one on the NES?
But only 15 bits, right? So, how does a specific color calculate to the 24 bit RGB value?
I assume the SNES color value
R = 00000b
G = 00000b
B = 00000b
would be the value 0 (black) in 24 bit RGB as well.
And
R = 11111b
G = 11111b
B = 11111b
in 15 bit RGB would probably be 0xFFFFFF in 24 bit RGB since it's the color white.
(If it was literally 0x1F1F1F in 24 bit RGB, it wouldn't be white anymore, but dark gray.)
But what about all the colors in between? How does the SNES palette calculate to actual 24 bit colors?
Doesn't matter.
You can zero fill (equivalent to exactly multiplying by 8) or scale by (255/31), but the difference is small. (It's about 3%)
In practice, NTSC "black" is brighter than computer monitor black, so you may want to scale by less and add something. And NTSC gamma (2.2) may not be the same as the user's monitor's gamma.
To map a lower bit-depth RGB value to 24 bits, and have the brightest value = #FFFFFF, it's common practice to repeat the old bits however many times across the newly-available lowest bits (24-bit).
Eg: 6-bit (SMS palette, etc.)
Old: 00,01,10,11b
to 24 bits: 00000000,01010101,10101010,11111111b
So, with a 15-bit palette, taking any arbitrary value:
Old: 11001b
to 24 bits 11001110
ccovell wrote:
To map a lower bit-depth RGB value to 24 bits, and have the brightest value = #FFFFFF, it's common practice to repeat the old bits however many times across the newly-available lowest bits (24-bit).
Why don't they use the calculation of A / B = C / D?
For each separate color red, green, blue, do:
PC_Color / Max_PC_Color = SNES_Color / Max_SNES_Color
PC_Color / 255 = SNES_Color / 31
PC_Color = SNES_Color * 255 / 31
Because division is expensive, and bit shifts are cheap. Also, once rounded to 8-bit integers, you can't tell the difference between 255/31 and (R<<3+R>>2).
lidnariq wrote:
Because division is expensive
Sure, but who says that the division has to be done every time? There are only 32 values per color, so why not create an array that holds all the possible PC RGB values (for each separate sub color), so they only have to be created once in the beginning?
You can even create three arrays and already shift the values to the correct position beforehand, so that you can OR-connect all three values for the output:
Code:
pcColor = shiftedPCColorRed[snesColorRed] | shiftedPCColorGreen[snesColorGreen] | shiftedPCColorBlue[snesColorBlue];
To me, this looks much cleaner than "repeating the bits". For example, ZSNES is not able to create a perfect white.
Also:
lidnariq wrote:
In practice, NTSC "black" is brighter than computer monitor black, so you may want to scale by less and add something. And NTSC gamma (2.2) may not be the same as the user's monitor's gamma.
The relation between a computer monitor and an actual TV should play no role here. This is what brightness options should be used for.
But for the pixel perfect output, the fact that a Super Nintendo white transforms to the value 240 instead of 255 shouldn't be abused in any way just because "the NTSC TV looks different anyway".
These are two unrelated topics: The topic "Calculation algorithm for 15 to 24 bit colors" has no relation to "physical differences between CRT TVs and LCD computer monitors".
So, am I right or am I wrong?
If I'm wrong, what's wrong in my assumptions?
If I'm right, why don't they calculate the correct values instead of using such a dirty algorithm of "repeating the bits"?
lidnariq wrote:
Because division is expensive, and bit shifts are cheap. Also, once rounded to 8-bit integers, you can't tell the difference between 255/31 and (R<<3+R>>2).
The output values are more evenly distributed when you use bit shifts instead of division.
I recall reading a very convincing argument in favor of using bit shifts for bit depth expansion. (It had actual math, not just "even distributions look nice".) Unfortunately, I don't remember
where I read it...
Probably the fact that it happens to round to nearest, not towards zero.
Even if the division is adjusted to round towards the nearest, it's still off by a very slight amount.
I'm sure it's the same mathematical property of expanding fractions when the denominator is (10^x)-1
eg: 5/9 = 0.555555..., 34/99 = 0.343434343434..., 678/999 = 0.678678678678...
Only in this case (being a binary value), the denominator is (2^x)-1:
$C / $F = $0.CCCCCC..., $13 / $1F = $0.9CE739CE739CE73..., $78 / $FF = $0.78787878...
Thus, if you convert from any fixed-point hex number to fractional (numerator / dominator is (2^x)-1) hex (as with an RGB value being a percentage of a maximum value), and vice-versa, the digits will repeat, giving you those repeated digits when going to a larger bit depth.
What's up with all of these repeating numbers and stuff? Just do 256/32 and that equals 8. moving 8 places using 8 bits should be the exact same as moving 1 in 5 bits, for 24 bit to 15 bit color depth. Anyway, are you trying to convert 24 bit palettes to 15 palettes at runtime? That's wasting a byte and more CPU time for every palette. Why not just have the 15 bit color depth in mind when you make your graphics. I just really don't see the point.
What's up with all of these repeating numbers and stuff? Just do 256/32 and that equals 8. moving 8 places using 8 bits should be the exact same as moving 1 in 5 bits, for 24 bit to 15 bit color depth. Anyway, are you trying to convert 24 bit palettes to 15 palettes at runtime? That's wasting a byte and more CPU time for every palette. Why not just have the 15 bit color depth in mind when you make your graphics. I just really don't see the point.
Ah, the irony of... nevermind.
The original post was asking about going from a low bit depth to its 24-bit representation. Your suggestion of truncating bits (256/32) is for the reverse operation (much simpler, as you've demonstrated, thank you).
Just as how quantization and interpolation are "reverse" operations but the latter needs more finesse (creating meaningful bits out of "nothing".)