Calculating the color palette of the VIC II
v1  /  Feb 2017

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