Commodore VIC-II Color Analysis (Preview)

The C=64 (with its VIC-II chip) features a palette of 16 colors.

#name
 0       black
 1       white
 2       red
 3       cyan
 4        purple 
 5       green
 6       blue
 7       yellow
 8       orange
 9       brown
 A light red
 B  dark grey
 C       grey
 D light green
 E light blue
 F light grey

Black, White and the three Greys are no colors and the "light"-versions of Red, Green and Blue are just brighter versions of Red, Green and Blue. That leaves us with eight unique colors.

Since the color-palette of the VIC-II has been made for the TV-system, we aren't talking a RGB-color-space, but a YUV-one (for PAL-TV that is), that we can convert to RGB later.

"Y" in the YUV-color-space is the Luma-signal which is just the "black&white-part" (the brightness) of the colors. "U" and "V" are color-difference signals, which together describe the hue and the saturation of the colors.

If you just measure the "Y" signal with an oscilloscope, you'll notice, that few of the very early VIC's just had 4 different luma-levels (excluding black) and the majority of VIC's (called the "late" ones here) have 8 different luma-levels (excluding black). They doubled them, because quite some people still had black&white TV-sets, where many colors looked exactly the same. With the "late" version, only two colors share the same brightness.

Here's a table of both of those luma-versions, ordered from darkest to brightest color (top to bottom):

 early VIC-II luma 
 0
 2, 6, 9, B
 4, 5, 8, A, C, E
 3, 7, D, F
 1

 late VIC-II luma 
       0
       6, 9
       2, B
       4, 8
       C, E
       5, A
       3, F
       7, D
       1

You can of course also verify this by minimizing color-saturation on your monitor, or just plugging "Y" in, or using a black&white monitor/tv.

However if you measure the luma-levels with an oscilloscope (Marko Mäkelä did so in 1996), you are able to see, that all of those levels are perfectly dividable by 32. I made a little Illustration here:

Commodore VIC-II Luma Illustration


As we need to stuff the luma-values into 8 bits (0 to 255) for later calculation, we just divide 255 by 32 and get 7,96875. Now we just need to multiply the values in the Illustration with 7,96875 and round the results. Of course you may just use 8 instead of 7,96875, but as I'm quite a perfectionist I'll use 7,96875 for now and maybe change it later. ;-)

  8 * 7,96875 =  63,75    =  64 
 10 * 7,96875 =  79,6875  =  80
 12 * 7,96875 =  95,625   =  96
 15 * 7,96875 = 119,53125 = 120
 16 * 7,96875 = 127,5     = 128
 20 * 7,96875 = 159,375   = 159
 24 * 7,96875 = 191,25    = 191
 32 * 7,96875 = 255,0     = 255

So... That's it for the brightness of the colors, let's come to the hue...

If you measure the chroma-signal ("u" & "v") with a vectorscope (I did so in 1999), you'll recognize, that all of the colors are on a perfect circle around the crossing-point of the "U" and "V" axis and so have exactly the same saturation (at least speaking in terms of video-language). That's why you don't need to note down the color-difference-signals by "U" and "V" coordinates, but you can use one value (degrees). All these angles are perfectly dividable by 22,5° (which is a quarter of 90°). The VIC-II creates all angles just with 4 voltage-levels being send positive and negative on "U" and "V". You can see those voltage-levels clearly in the next Illustration.

 (light) blue      0,0°  22,5° *  0 
         purple  45,0° 22,5° *  2
 (light) red 112,5° 22,5° *  5
         orange 135,0° 22,5° *  6
         brown 157,5° 22,5° *  7

The missing three angles are on the opposite side of the color-wheel from above mentioned angles. Yellow is "blue + 180°", (light) green is "purple + 180°"; and cyan is "(light) red + 180°". This is also clearly visible in the next Illustration. You can of course also take (light) red, (light) green, (light) blue, orange and brown as the base-colors and say that the missing ones are on opposite sides of them, but for now I have it written down the above mentioned way. This will be maybe changed later (no time to change it right now). Thanks again to John 'Graham' Selck's Test-Program, which helped to find the angles a bit more exact than the vectorscope was able to do.

If you want to calculate "U" and "V" coordinates from these angles in percent, you just use this formula:

U = 100 * cos(angle)
V = 100 * sin(angle)

In order to calculate the 4 voltage-levels in percent, calculate "V" from Brown, Purple and Red and "U" from Blue:

100 * sin(157,5°) =  38,26834323650897719%
100 * sin( 45,0°) =  70,71067811865475244%
100 * sin(112,5°) =  92,38795325112867561%
100 * cos(  0,0°) = 100,00000000000000000%

Commodore VIC-II Chroma Illustration


Now "Y", "U" and "V" are known and in order to convert to the RGB-color-space, you just have to find out how much saturation is possible without getting "out of gamut"/"invalid" rgb-values. Brown is the peak-color of the VIC-II color-palette. It is the first color to produce a negative blue-value when converting from YUV to RGB with too much saturation.

I found out the desired value with the brute force method by calculating around with different values in order to more and more close up to the needed value which produces Brown's Blue-Channel to be very little above 0 when converting from YUV to RGB. The value I came up with is 34,0081334493.

Please note however, that monitors/tv's also do clipping (out of gamut colors), when saturation, brightness or contrast is set too high. Really every C64-User has set-up his monitor/tv in a different way. Most people I personally know have set up their equipment to show a normalized palette though, because I think that this looks the best to the eye. This is a matter of taste. If you want a more saturated palette or higher brightness/contrast, you'll have to calculate with greater values (before gamma-correction — more on this later). The palette I'm calculating here is normalized in every aspect (brightness, contrast and saturation) and this is also the way I have set up my original C64 with its 1084-monitor.

What is maybe interesting for you to know is, that John 'Graham' Selck is currently coding a routine for the new CCS64-Beta (it will probably be included in other emulators later) which lets you modify contrast, brightness saturation and gamma in realtime while emulating, based on the color-logic described on this page. This is the most cool way to emulate your equipment, as you'll be able to modify the settings on the fly, as you do with real equipment. Additionally Graham emulates the short-comings of the PAL-TV-system (for example by using only half the resolution of the Y-layer for the U and V-layer — like in a real PAL-system) which will also come closer to the look of a real C64 connected to a real 1084 monitor. Make sure to watch out for the new CCS64-Beta.

But let's continue with the calculation... ;-)

#nameangley ("late" luma)uv
 0       blackn/a  0 * ( 255 : 32 )00
 1       whiten/a 32 * ( 255 : 32 )00
 2       red 5 * ( 360° : 16 ) 10 * ( 255 : 32 ) peak * cos(angle)  peak * sin(angle)
 3        cyan    5 * ( 360° : 16 ) + 180°  20 * ( 255 : 32 )  peak * cos(angle)  peak * sin(angle) 
 4       purple 2 * ( 360° : 16 ) 12 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 5       green 2 * ( 360° : 16 ) + 180° 16 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 6       blue 0 * ( 360° : 16 )  8 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 7       yellow 0 * ( 360° : 16 ) + 180° 24 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 8       orange 6 * ( 360° : 16 ) 12 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 9       brown 7 * ( 360° : 16 )  8 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 A light red 5 * ( 360° : 16 ) 16 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 B  dark greyn/a 10 * ( 255 : 32 )00
 C       greyn/a 15 * ( 255 : 32 )00
 D light green 2 * ( 360° : 16 ) + 180° 24 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 E light blue 0 * ( 360° : 16 ) 15 * ( 255 : 32 ) peak * cos(angle) peak * sin(angle)
 F light greyn/a 20 * ( 255 : 32 )00

...and the result of this calculation is:

#nameangley (late)uv
 0       blackn/a   0,0 0 0
 1       whiten/a 255,0 0 0
 2       red 112,5°  79,6875 -13,01434923670814368  +31,41941843272073796
 3        cyan    292,5°  159,375  +13,01434923670814368  -31,41941843272073796 
 4       purple  45,0°  95,625 +24,04738177749708281 +24,04738177749708281
 5       green 225,0° 127,5 -24,04738177749708281 -24,04738177749708281
 6       blue   0,0°  63,75 +34,0081334493 0
 7       yellow 180,0° 191,25 -34,0081334493 0
 8       orange 135,0°  95,625 -24,04738177749708281 +24,04738177749708281
 9       brown 157,5°  63,75 -31,41941843272073796 +13,01434923670814368
 A light red 112,5° 127,5 -13,01434923670814368 +31,41941843272073796
 B  dark greyn/a  79,687500
 C       greyn/a 119,5312500
 D light green 225,0° 191,25 -24,04738177749708281 -24,04738177749708281
 E light blue   0,0° 119,53125  +34,00813344930
 F light greyn/a 159,37500

Now we need to convert YUV to RGB. Here's the formula:

R = Y             + 1,140 * V
G = Y - 0,396 * U - 0,581 * V
B = Y + 2,029 * U

...and here's the result:

#namergb
 0       black   0,0                 (  0)   0,0                 (  0)   0,0                 (  0)
 1       white 255,0                 (255) 255,0                 (255) 255,0                 (255)
 2       red 115,5056370133016413  (116)  66,58650018832567614 ( 67)  53,28138539871917648 ( 53)
 3       cyan 123,5568629866983587  (124) 172,4759998116743239  (172) 185,7811146012808235  (186)
 4        purple  123,0390152263466744  (123)   72,13070800338535009 ( 72)  144,417137626541581   (144) 
 5       green 100,0859847736533256  (100) 150,9942919966146499  (151)  78,70786237345841898 ( 79)
 6       blue  63,75                ( 64)  50,2827791540772     ( 50) 132,7525027686297     (133)
 7       yellow 191,25                (191) 204,7172208459228     (205) 122,2474972313703     (122)
 8       orange 123,0390152263466744  (123)  91,17623437116303968 ( 91)  46,83286237345841898 ( 47)
 9       brown  78,5863581298472838  ( 79)  68,63075279282998076 ( 69)   0,00000000000962268 (  0)
 A light red 163,3181370133016413  (163) 114,3990001883256761  (114) 101,0938853987191765  (101)
 B  dark grey  79,6875              ( 80)  79,6875              ( 80)  79,6875              ( 80)
 C       grey 119,53125             (120) 119,53125             (120) 119,53125             (120)
 D light green 163,8359847736533256  (164) 214,7442919966146499  (215) 142,457862373458419   (142)
 E light blue 119,53125             (120) 106,0640291540772     (106) 188,5337527686297     (189)
 F light grey 159,375               (159) 159,375               (159) 159,375               (159)

Now you need to gamma-correct the rgb-colors. Please note, that the gamma-correction is always the last thing you do to the palette. If you want to change brightness, contrast, saturation, mix-colors for interlace-emu, rotate hue or anything else, you have to do this before the gamma-correction.

I'm not 100% sure about the used gamma-values in the following calculation, but I'm working on it and it's probably right. This page will be updated in the future with the right values in case the one I'm using now are wrong. If you feel like a gamma-expert, please get in touch with me at pepto@pepto.de, while I keep on investigating the matter. ;-)

In order to correct the PAL-Gamma for VGA-Output you need to calculate each r, g and b value with an exponent of "2,5 : 2,2" (which is 1,136) and stretch the results back proportionaly to 8 bit valid values (between 0 and 255). There's a lot of false and irritating information about gamma around, but I'll fix the right value in the future. Maybe 1,136 is even right and because it should theoretically and it does look correct, I'll use it for the moment (Thanks to John 'Graham' Selck for a little help with the gamma-values by just deciding which ones to use, while I was still thinking about which one to choose from the ones talked about in the gamma-faq's.). Theoretically Gamma is 2,5, but most people use 2,2 and PAL is officially noted with a gamma of 2,8. PeeCee's use a gamma of 2,2. Macintosh-computers have a gamma of 1,8 by default, but you're able to change it system-wide with the control-pannel "adobe-gamma" or "monitors" to the more common 2,2. Programs like Adobe Photoshop are also able to emulate the gamma for you in the "color-preferences". More Information about this will be added here later. I also don't know much about the gamma of Amiga-computers (ECS and AGA), even if I would love to as they are my computers of choice. Please get in touch with me if you know more about this.

Here are the results, after the gamma-correction:

#namergb
 0        black   0,0         (  0)    0,0         (  0)    0,0         (  0) 
 1        white 254,999999878 (255) 254,999999878 (255) 254,999999878 (255)
 2        red 103,681836072 (104)  55,445357742 ( 55)  43,038096345 ( 43)
 3        cyan 111,932673473 (112) 163,520631667 (164) 177,928819803 (178)
 4        purple  111,399725075 (111)  60,720543693 ( 61) 133,643433983 (134)
 5        green  88,102223525 ( 88) 140,581101312 (141)  67,050415368 ( 67)
 6        blue  52,769271594 ( 53)  40,296416104 ( 40) 121,446211753 (121)
 7        yellow 183,892638117 (184) 198,676829993 (199) 110,585717385 (111)
 8        orange 111,399725075 (111)  79,245328562 ( 79)  37,169652483 ( 37)
 9        brown  66,932804788 ( 67)  57,383702891 ( 57)    0,0         (  0)
 A  light red 153,690586380 (154) 102,553762644 (103)  89,111118307 ( 89)
 B   dark grey  67,999561813 ( 68)  67,999561813 ( 68)  67,999561813 ( 68)
 C        grey 107,797780127 (108) 107,797780127 (108) 107,797780127 (108)
 D  light green 154,244479632 (154) 209,771445903 (210) 131,584994128 (132)
 E  light blue 107,797780127 (108)  94,106015515 ( 94) 180,927622164 (181)
 F  light grey 149,480882981 (149) 149,480882981 (149) 149,480882981 (149)

Finished... Now here's a comparison of how some c64-pics look with the palette which is included in ccs64 v2.0 beta (this is just an example) and with the palette we just calculated...

ccs64 v2.0bmy palette

ccs64 v2.0bmy palette

ccs64 v2.0bmy palette

And here's the palette in hex-format for easier usage in certain emulators:

00 00 00
FF FF FF
68 37 2B
70 A4 B2
6F 3D 86
58 8D 43
35 28 79
B8 C7 6F
6F 4F 25
43 39 00
9A 67 59
44 44 44
6C 6C 6C
9A D2 84
6C 5E B5
95 95 95

Have fun using it... ;-) (watch out — work in progress — more to come...)
Sorry again for poor documentation and maybe typos, but remember that this is a preview...



Philip "Pepto" Timmermann, <pepto@pepto.de>


P.S.: Here's an interesting eMail which Bob Yannes (co-developer of the VIC-II) sent to me. Please note that I can't disclose Bob's eMail address to you, as he's very concerned about his privacy and the only way to get in touch with him myself, was through a third person, who forwarded our mails to eachother. In other words, I don't have his eMail-address myself, so don't ask. Additionally please note that Bob talks about the NTSC color wheel. NTSC uses YIQ instead of PAL's YUV, but in order to convert from YIQ to YUV you just have to rotate everything by 33° and exchange the color-difference axis. This is what they probably did in hardware in order to make the VIC-II work with PAL and so NTSC and PAL-VIC's probably have the same colors.





Subject: "Re: VIC-II colors"
   From: Robert 'Bob' Yannes
     To: Philip 'Pepto' Timmermann
   Date: 27.09.1999

I was involved with the development of the VIC-II, however the actual implementation of the design, including the Color Palette, was done by someone else. I have forwarded your message to him, but it is up to him if he wants to respond.

I can tell you that the design was based on the principle that adding a sine wave of a particular frequency and amplitude to an inverted version of the same sine wave at a different amplitude produces a phase-shifted sine wave of the same frequency. The amount of phase shift is directly proportional to the amplitudes of the two sine waves.

The VIC-II used the 14.31818 MHz master clock input (4 times the NTSC color burst frequency of 3.579545 MHz) to produce quadrature square-wave clocks. These clock signals were then integrated into triangle waves using analog integrators. The triangle waves were then integrated again into sine waves (actually rounded triangle waves, but good enough for this application). This produced a 3.579545 MHz sine wave, inverse sine wave, cosine wave and inverse cosine wave.

An analog summer was used to create the phase-shifts in the Chroma signal by adding together the appropiate two waveforms at the appropiate amplitudes. The Color Palette data went to a look-up table that specified the amplitude of the waves by selecting different resistors in the gain path of the summer. The end result was that we could create any hue we wanted by looking at the NTSC color wheel to determine the phase-shift and then picking the appropiate resistor values to produce that phase-shift.

Color Saturation was controlled by scaling the gain of the summer. When we picked the resistor values to determine the output phase-shift, we also scaled them to produce the desired output amplitude. Luminance was controlled using a simple voltage divider which switched different pull-down resistors into the open-drain output. We could create any Luminance we wanted by choosing the desired resistor value.

I'm afraid that not nearly as much effort went into the color selection as you think. Since we had total control over hue, saturation and luminance, we picked colors that we liked. In order to save space on the chip, though, many of the colors were simply the opposite side of the color wheel from ones that we picked. This allowed us to reuse the existing resistor values, rather than having a completely unique set for each color.

I believe that Commodore actually got a patent on this technique. It was certainly superior to the Apple or Atari approach at the time, as they ended up with whatever colors that came out — ours allowed the designer to freely select Hue, Saturation and Luminance.

Since all of this was based on selecting different resistor values and resistance varied from chip lot to chip lot, there was variation from one Commodore 64 to another. It wasn't as bad as it could have been though, since all of the Chrominance selection was based on resistor ratios, which could be kept constant even if the actual resistor values varied. Luminance was more of a problem. A trimmer resistor should really have been used to pull up the output. This would have allowed the Luminance to be adjusted for consistency from unit to unit, however Commodore didn't care enough about consistency to bother with adjusting each unit.


Robert 'Bob' Yannes