Update (11/3/2018):
Now available on GitHub.
------
Since I recently had to update my html5 nes palette generator to make newer versions of Firefox accept decimal values in the number fields, I kinda pondered over this problem again.
Right now, I've hit a wall. I cannot go any further, and googling turns up absolutely zero help, and after a year and a half, I've forgotten about any suggestions anyone told me, so here's what I know so far in hopes that maybe some day I might be able to solve this problem either directly or indirectly:
Televisions take a YIQ color signal. Y represents luminance (the black and white portion of the color), and IQ represent the hue and chroma (I'm not keen on how "chroma" is different from "saturation" but that's aside the point), which is then added on top of the black and white.
I made the assumption that the CRT television takes the YIQ information and converts it to RGB. This is not sRGB (what your computer display uses) however, but rather "how much energy to hit the red phosphor, the green phosphor, and the blue phosphor with". I also made the assumption that the television clamps negative R, G, and B values to 0, but doesn't clamp positive values to anything specific, probably just analog voltage limits.
This means that a CRT television taking in an analog signal has the capability of displaying colors that are not displayable on a computer monitor. For the NES specifically, these are many of the bright blue colors. You've probably seen the sky in SMB represented by a myriad of different bluish and purplish colors. The truth is, none of those colors are correct because that blue is actually one of the out-of-range colors I just mentioned.
I tried a bunch of different methods to generate a palette. The one I came up with that almost works is to partially convert the YIQ to RGB. Basically you take the YIQ->RGB matrix and remove Y from it. In my code, I call this tR, tG, and tB for some reason. Then, using CIELuv, I take the NTSC CIE definitions of what color the red, green, and blue phosphors are supposed to be, and treat them as vectors pointing away from the white point (D65 is the white point I'm using). I then multiply these vectors by tR, tG, and tB, and then I add them together, which gives me a point that represents the final color, which I then convert from CIELuv to CIEXYZ (this is where I use the Y that I didn't add to tR, tG, and tB), and from CIEXYZ to sRGB. If select the CIELuv option in my palette generator, you can see the result of this.
It's pretty close, but here's the wall I keep running into. I need to apply gamma correction, and it's not as simple as you'd think. Darker colors mix differently from brighter colors: the colors shift towards primary colors, so they barely mix at all. In my palette generator, the top row is supposed to look like three blues (just straight blue, the only difference is lightness, not hue), then magenta, then three reds (same deal as the blues), then super-dark brown (darkest non-black color in the palette actually), then three greens (again, straight greens, only difference is lightness), and then blue again, and the reason is because that's how it looks on my CRT TV. What you actually see in my palette generator is the full rainbow, which is not what I just described.
I absolutely cannot get this to work, so I figured I should try out a new method: I was going to convert YIQ completely to RGB, then mix R, G, and B in CIELuv space, using NTSC's definitions of red, green, blue, and white, and then convert that to CIEXYZ and then sRGB. When I tried this, the palette was skewed badly, so I probably didn't mix the colors right.
CIELuv is attractive because it's a linearized CIEXYZ. That means, if you pick two colors on the CIELuv map, and draw a straight line between them, the line represents all the colors you can create if you have two colored lights of those two colors. If you have three colored lights, I'm not quite sure what to do, because that makes a triangle, but the center point of the triangle is not necessarily white, which is why you have to use a white point, and a white point just means "what color should be created if all three lights have equal power?"
Though, in this case, my lights are actually phosphors being hit with an electron gun. I don't know if phosphors change color when they get brighter, which would be something to consider. However, this seems to be the way to go because I can gamma correct the R, G, and B signal, which simulates the gamma curve the phosphors have, and by mixing them in CIELuv space, I can simulate how our eyes percieve these colors when they mix on the TV screen. However, I don't know how to properly mix three colors together in CIELuv space, and even if I got it, I don't know if adding the gamma correction would solve the problem with the darker colors not being correct.
And that's why I gave up way back then; I simply do not have enough information to continue, and obtaining said information seems to be more of a chore than I have the will for, since I seem to be the only person on Earth trying to do this. :\
Now available on GitHub.
------
Since I recently had to update my html5 nes palette generator to make newer versions of Firefox accept decimal values in the number fields, I kinda pondered over this problem again.
Right now, I've hit a wall. I cannot go any further, and googling turns up absolutely zero help, and after a year and a half, I've forgotten about any suggestions anyone told me, so here's what I know so far in hopes that maybe some day I might be able to solve this problem either directly or indirectly:
Televisions take a YIQ color signal. Y represents luminance (the black and white portion of the color), and IQ represent the hue and chroma (I'm not keen on how "chroma" is different from "saturation" but that's aside the point), which is then added on top of the black and white.
I made the assumption that the CRT television takes the YIQ information and converts it to RGB. This is not sRGB (what your computer display uses) however, but rather "how much energy to hit the red phosphor, the green phosphor, and the blue phosphor with". I also made the assumption that the television clamps negative R, G, and B values to 0, but doesn't clamp positive values to anything specific, probably just analog voltage limits.
This means that a CRT television taking in an analog signal has the capability of displaying colors that are not displayable on a computer monitor. For the NES specifically, these are many of the bright blue colors. You've probably seen the sky in SMB represented by a myriad of different bluish and purplish colors. The truth is, none of those colors are correct because that blue is actually one of the out-of-range colors I just mentioned.
I tried a bunch of different methods to generate a palette. The one I came up with that almost works is to partially convert the YIQ to RGB. Basically you take the YIQ->RGB matrix and remove Y from it. In my code, I call this tR, tG, and tB for some reason. Then, using CIELuv, I take the NTSC CIE definitions of what color the red, green, and blue phosphors are supposed to be, and treat them as vectors pointing away from the white point (D65 is the white point I'm using). I then multiply these vectors by tR, tG, and tB, and then I add them together, which gives me a point that represents the final color, which I then convert from CIELuv to CIEXYZ (this is where I use the Y that I didn't add to tR, tG, and tB), and from CIEXYZ to sRGB. If select the CIELuv option in my palette generator, you can see the result of this.
It's pretty close, but here's the wall I keep running into. I need to apply gamma correction, and it's not as simple as you'd think. Darker colors mix differently from brighter colors: the colors shift towards primary colors, so they barely mix at all. In my palette generator, the top row is supposed to look like three blues (just straight blue, the only difference is lightness, not hue), then magenta, then three reds (same deal as the blues), then super-dark brown (darkest non-black color in the palette actually), then three greens (again, straight greens, only difference is lightness), and then blue again, and the reason is because that's how it looks on my CRT TV. What you actually see in my palette generator is the full rainbow, which is not what I just described.
I absolutely cannot get this to work, so I figured I should try out a new method: I was going to convert YIQ completely to RGB, then mix R, G, and B in CIELuv space, using NTSC's definitions of red, green, blue, and white, and then convert that to CIEXYZ and then sRGB. When I tried this, the palette was skewed badly, so I probably didn't mix the colors right.
CIELuv is attractive because it's a linearized CIEXYZ. That means, if you pick two colors on the CIELuv map, and draw a straight line between them, the line represents all the colors you can create if you have two colored lights of those two colors. If you have three colored lights, I'm not quite sure what to do, because that makes a triangle, but the center point of the triangle is not necessarily white, which is why you have to use a white point, and a white point just means "what color should be created if all three lights have equal power?"
Though, in this case, my lights are actually phosphors being hit with an electron gun. I don't know if phosphors change color when they get brighter, which would be something to consider. However, this seems to be the way to go because I can gamma correct the R, G, and B signal, which simulates the gamma curve the phosphors have, and by mixing them in CIELuv space, I can simulate how our eyes percieve these colors when they mix on the TV screen. However, I don't know how to properly mix three colors together in CIELuv space, and even if I got it, I don't know if adding the gamma correction would solve the problem with the darker colors not being correct.
And that's why I gave up way back then; I simply do not have enough information to continue, and obtaining said information seems to be more of a chore than I have the will for, since I seem to be the only person on Earth trying to do this. :\