Accurate NTSC Colour Simulation

| | August 4, 2015

I’m trying to create a method of displaying an image the way it was intended to be viewed for use with old games and emulators

The goal is to apply effects that caused blurring and such to achieve new colours and to remove visible dithering, with with the sharpness and colour clarity of RGB as opposed to composite signals so a hybrid of the two

I’ve mostly succeeded in this, i have been able to merge several pixels of luminosity so the picture stays pretty sharp but dithering is nice and smoothly blended (the main test I’ve used is a screenshot from the lion king on megadrive which looks ghastly with raw output)

I’ve not been able to reproduce the slight hue various of NTSC however (I think it’s called Colour Burst)

I take the original RGB output Gamma Decode from input colour space (I use 2.5 / 2.2, 2.5 which is the true gamma of ntsc from what i’ve read, 2.2 is the effective accounting for light levels in viewing area), then encode gamma for sRGB output (2.2)

Convert it to YIQ with the following math

final double r = col.r;
final double g = col.g;
final double b = col.b;

y = 0.299 * r + 0.587 * g + 0.144 * b;
i = 0.595879 * r - 0.274133 * g - 0.321746 * b;
q = 0.211205 * r - 0.523083 * g + 0.311878 * b;

for each colour on the x axis I multiply the Luma by 2 and add the Luma of the pixel immediately left and immediately right and divide them by 4 creating a smooth removal of dithering

I’ve attempted to do the same with the chroma with limited success, I now convert the chroma back into RGB using this formula and apply the same left right averaging but no new hues

double r, g, b;

r = 0;
g = 0;
b = 0;

r += 0.946882 * i;
g -= 0.274788 * i;
b -= 1.108545 * i;

r += 0.623557 * q;
g -= 0.635691 * q;
b += 1.709007 * q;

rgb.r = (int) (255 * r);
rgb.g = (int) (255 * g);
rgb.b = (int) (255 * b);

I then Convert that RGB back into the Chroma I Q

then convert the Y I Q into the output colour by using the following

col.r = clamp((int)(255 * (((y + (0.946882 * i) + (0.623557 * q))))));
col.g = clamp((int)(255 * (((y - (0.274788 * i) - (0.635691 * q))))));
col.b = clamp((int)(255 * (((y - (1.108545 * i) + (1.709007 * q))))));

// ntsc to srgb
final double r =  (1.5073*col.r) - (0.3725*col.g) - (0.0832*col.b);
final double g = (-0.0275*col.r) + (0.9350*col.g) + (0.0670*col.b);
final double b = (-0.0272*col.r) - (0.0401*col.g) + (1.1677*col.b);

col.r = clamp(r);
col.g = clamp(g);
col.b = clamp(b);

// Return RGB888 Value
return (col.r << 16) | (col.g << 8) | col.b

I’ve had partial success turning Composite mode CGA into the correct output colours based on a screenshot of Kings Quest in composite mode on an RGB monitor from wikipedia by

using a variation of

y  =  y + level;
i  =  i + level * cos( M_PI * (phase+p) / 6 );
q  =  q + level * sin( M_PI * (phase+p) / 6 );

From http://wiki.nesdev.com/w/index.php/NTSC_video

However that was in an older version of the code and have not been able to get it working again, part of the problem is I cant find a clear enough explanation of how colour burst works to actually understand what I’m trying to accomplish, but I do know it effects the hue to some extent.

Lion King Input:

Lion King Input

Lion King Output:

Lion King Output

As you can see it works pretty nicely so far for Images Like the Lion King test however It doesn’t quite work for this:

CGA Composite Dither, Left input image, Right Correct Output
CGA Composite Dither

My Result is:

CGA Dither Output

Does anyone have an easy to understand explanation of how the dithering is supposed to work to achieve the blues and browns as seen in the source image?

And a possible Solution?

Leave a Reply