Color emphasis effects on YIQ elements?

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
Josh
Posts: 69
Joined: Sat Mar 19, 2005 11:18 am

Color emphasis effects on YIQ elements?

Post by Josh »

When using a YIQ-based method to generate a NES palette, like this one by Blargg, what's the most accurate way to do color emphasis? Someone once posted a table of floating point constant factors for each of the 8 color emphasis values, which would be applied to the RGB values to make "emphasized" palettes. However, in the actual hardware, wouldn't the emphasis happen before the YIQ decoding? Does anyone know how to do accurate color emphasis on the YIQ components rather than the resulting RGB?
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Post by Quietust »

I've implemented this in Nintendulator, though I have no idea if it is actually accurate:

1. Given each palette entry, construct the chroma waveform (in an array of 12 floats or doubles).
2. Given the desired emphasis bits, attenuate the chroma signal.
3. Find the phase offset of your chroma signal. In my emulator, I used a function to find it within 1/5th of a degree in 3 passes (once within 30 degrees, another within 2.5 degrees, and finally within 0.208333 degrees) by comparing the chroma signal to a sine wave (with the same amplitude and offset) at 12 different phases and focusing on the one which resulted in the least absolute error.
4. Find the amplitude of your chroma signal. In my emulator, I simply subtracted the DC offset of the signal (the mean of all 12 values, also used as the luminance) and used its quadratic mean (otherwise known as RMS).
5. Feed your hue, saturation, and luminance into a YIQ->RGB converter [Y = luminance, I = saturation * sin(hue), Q = saturation * cos(hue)] and collect the results.

The results look fairly close to what my composite monitor and TV tuner display when running my Color Bars v2 test program on my CopyNES.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

The accurate way is to take the discrete Fourier transform of the 12-point signal and discard all but the DC and the first AC coefficient. The DC gives you Y, and the real and imaginary parts of AC give you U and V. YUV is the same thing as YIQ modulo tint. Because you only need three coefficients (real DC, real AC[1], imaginary AC[1]), it'll probably take only 7 multiplies and 29 adds.
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Post by Quietust »

Some of us don't know how to do a Fourier transform, though, and the Wikipedia article doesn't exactly explain it in a crystal-clear manner...
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

The discrete Fourier transform is essentially dot-producting your signal by a sine wave at each frequency that you want to test. Here's a straightforward way to do it, untested and unoptimized:

Code: Select all

static float sintable[15];

void initSintable(void) {
    int i;
    for(i = 0; i < 15; i++)
        sintable[i] = sin(i * M_PI / 6);
}

float getYUV(const float *signal, float *outY, float *outU, float *outV) {
    double y = 0, u = 0, v = 0;
    int i;

    for(i = 0; i < 12; i++)
        y += signal[i];
    for(i = 0; i < 12; i++)
        u += signal[i] * sintable[i];
    for(i = 0; i < 12; i++)
        v += signal[i] * sintable[i + 3];

    if(outY)
        *outY = y / 12.0;
    if(outU)
        *outU = u / 6.0;
    if(outV)
        *outV = v / 6.0;
}
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Post by Quietust »

How do you work Hue and Saturation adjustment settings into that?
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Saturation adjustment ("color" knob) multiplies each U and V by a scale factor. For hue adjustment ("tint" knob), shift the basis functions (sin() and cos() in this case). Given hue in radians, you can do this shift when setting up sintable[]:

Code: Select all

void initSintable(float hue) {
    int i;
    for(i = 0; i < 15; i++)
        sintable[i] = sin(i * M_PI / 6 + hue);
}
Post Reply