The VIC II was designed to generate a video signal that is compatible with analog television, that's why colors are composed of Luma (brightness) and Chroma (hue & saturation) components. During the timeframe that computers utilizing it were on sale, MOS produced several different chip revisions, that mainly differed in how much voltage is required for operation, or what TV standard is used for color encoding.
One particularly visible change was performed soon after the VIC II started gaining larger distribution. The very first revision offered only 4 uniform luma levels in addition to Black, which of course meant that it was impossible to distinguish a whole bunch of colors on monochrome monitors. In order to fix this, the amount of levels was doubled, so that only two colors had to share a single luma level at most. This improved version is considered the most commonly encountered, as it was used for all following chip revisions. Values for both versions are included in the calculation that follows.
The aim of this project, is to build a single calculation model, based on the underlying system of how the palette gets generated in general. Measurements have been quantized carefully, in order to represent all VIC II revisions in a faithful manner.
According to engineers who were involved in its creation, small lot-to-lot variations in signal output were deemed acceptable, as a primary design goal of the chip, was to keep manufacturing costs as low as possible. As a consequence, I'm not interested in documenting things like tiny variances found when comparing chip revisions.
Reimplementing the wheel
The palette offers 16 fixed colors, consisting of 8 unique hues and 8 shades or monochromes.
Black, White, Red, Cyan, Purple, Green, Blue, Yellow Orange, Brown, Lt.Red, Dk.Grey, Md.Grey, Lt.Green, Lt.Blue, Lt.Grey
A unified approach to what palette entries in video chips like the VIC, VIC II and TED consist of, can be summed up as pairs of one in 16 uniform angles on the color wheel (or none – for monochromes) and one in 32 uniform levels on the luminance scale (or none – for black level).
Except for the monochromes of course, all colors of the VIC II palette appear to use an identical amount of saturation, that's why it suffices to classify them by their angle.
Lookup Table
The values specified here, are based on my research into oscilloscope measurements by Marko Mäkelä and my own observations using a vectorscope and various frame grabbers.
Considering how popular Javascript is nowadays, I decided to use its syntax for describing the math involved.
var levels = { mc: [ 0 ], fr: [ 0 ] }; // Black (luma switched off) with ( levels ) { // most common mc[ 0x6 ] = mc[ 0x9 ] = 8; // Blue, Brown mc[ 0xb ] = mc[ 0x2 ] = 10; // Dk.Grey, Red mc[ 0x4 ] = mc[ 0x8 ] = 12; // Purple, Orange mc[ 0xc ] = mc[ 0xe ] = 15; // Md.Grey, Lt.Blue mc[ 0x5 ] = mc[ 0xa ] = 16; // Green, Lt.Red mc[ 0xf ] = mc[ 0x3 ] = 20; // Lt.Grey, Cyan mc[ 0x7 ] = mc[ 0xd ] = 24; // Yellow, Lt.Green mc[ 0x1 ] = 32; // White // first revision fr[ 0x2 ] = fr[ 0x6 ] = fr[ 0x9 ] = fr[ 0xb ] = 8 * 1; fr[ 0x4 ] = fr[ 0x5 ] = fr[ 0x8 ] = fr[ 0xa ] = fr[ 0xc ] = fr[ 0xe ] = 8 * 2; fr[ 0x3 ] = fr[ 0x7 ] = fr[ 0xd ] = fr[ 0xf ] = 8 * 3; fr[ 0x1 ] = 8 * 4; } var angles = []; angles[ 0x4 ] = 2; // Purple angles[ 0x2 ] = angles[ 0xa ] = 4; // Red angles[ 0x8 ] = 5; // Orange angles[ 0x9 ] = 6; // Brown angles[ 0x7 ] = 7; // Yellow angles[ 0x5 ] = angles[ 0xd ] = 2 + 8; // Green angles[ 0x3 ] = 4 + 8; // Cyan angles[ 0x6 ] = angles[ 0xe ] = 7 + 8; // Blue
Video components
Analog video monitors offer brightness, contrast and saturation controls, which are used intensively to adjust the overall appearance of the color palette. How these controls are set up, depends on what the user of a system prefers personally. In order to include the major influencing parameters in the calculation, display controls need to be taken into account as well and should be broken out to users, if you plan to implement this in your own application.
I observed that shifting percentage-based contrast and saturation values by a certain amount (see screen variable), helped the display-control logic to behave like Commodore's own 1084s monitor, which is a very popular choice in this field.
function compose( index, revision, brightness, contrast, saturation ) { // constants var sector = 360/16; var origin = sector/2; var radian = Math.PI/180; var screen = 1/5; // normalize brightness -= 50; contrast /= 100; saturation *= 1 - screen; // construct var components = { u: 0, v: 0 }; // monochrome (chroma switched off) if ( angles[ index ] ) { var angle = ( origin + angles[ index ] * sector ) * radian; components.u = Math.cos( angle ) * saturation; components.v = Math.sin( angle ) * saturation; } components.y = 8 * levels[ revision ][ index ] + brightness; for ( var i in components ) { components[ i ] *= contrast + screen; } return components; }
Colorspace conversion
When converting YUV components to sRGB, the result requires Gamma correction (for PAL in this case).
function convert( components ) { // matrix transformation var color = {}; color.r = components.y + 1.140 * components.v; color.g = components.y - 0.396 * components.u - 0.581 * components.v; color.b = components.y + 2.029 * components.u; // gamma correction var source = 2.8; // PAL var target = 2.2; // sRGB for ( var i in color ) { color[ i ] = Math.max( Math.min( color[ i ], 255 ), 0 ); color[ i ] = Math.pow( 255, 1 - source ) * Math.pow( color[ i ], source ); color[ i ] = Math.pow( 255, 1 - 1/target ) * Math.pow( color[ i ], 1/target ); color[ i ] = Math.round( color[ i ] ); } return color; }
Generating the palette
Attempts to imitate the VIC II palette were usually named in the past, so I decided to call this model colodore.
Running the above code for all 16 palette colors, with the most common amount of luma levels and a default set of brightness, contrast & saturation values...
for ( var i = 0; i < 16; i++ ) { console.log( convert( compose( i, "mc", 50, 100, 50 ) ) ); }
Beyond color
In order to ease the process of doing your own adjustments and better show how platform-specific graphics look, I implemented a web-app capable of palette-file generation at colodore.com. In addition to the technique described in this document, it emulates the following artefacts of the respective video signals:
• Aspect ratio (non-square pixels, as documented by Groepaz)
• Scanlines (darkened odd lines, using slightly offset, inverted Luma as alpha, for a more CRT-like look)
• Hanover Bars (22.5 degrees of hue roation for YUV's V only on odd lines)
• Delay Line (mixing Chroma of each line, with Chroma of the previous line)
• Chroma Subsampling (half horizontal resolution for Chroma)
In my opinion this achieves to look more similar to real gear, than previous attempts.
Colodore vs. Pepto
This isn't my first attempt at calculating an RGB version of the VIC II palette. In 2001, I published an exemplery calculation on my website. This basic model was implemented in quite a few tools and emulators over the years. People interested in VIC II colors started just calling it pepto, as that's my nickname.
While fairly accurate for its time and an improvement to what was available previously, I wasn't ultimately satisfied with how some colors looked, when using higher saturation. I had a hard time getting stable read-outs on the vectorscope originally, as it was very picky about signals not following specs precisely. In retrospect I think, that maybe the hanover bars might have leaked into my measurements, which could explain the slightly phase-shifted nature of the inaccuracy.
Long story short, I found back my motivation to work on this, when I learned that some guys were doing a book with mostly C64 graphics of the demoscene. I found that re-adjusting the purple/green angle and phase-shifting all colors a tiny notch, resulted in a more accurate representation of the VIC II colors. I also got to grips with calculating gamma values and was able to contribute to the book just in time.
Coming Soonish
• VIC and TED
• Improved descriptions
Cheers,
pepto