I’ve spent these past 2 months focusing only on my university coursework. Even over the fall break, I was just spending most of the day working on course projects or studying. I just feel so relieved now, now that my most stressful, overwhelming university semester ever is finally over. No more of that servers-within-servers-within-servers nonsense.
Just this past week, I was able to get a cheap used colorimeter on eBay. My latest update here is a hasty attempt to use this colorimeter to approximate my 1989 RCA ColorTrak’s colors in a shader, with all of the imperfections that a consumer CRT has. The NES colors turned out nice, but the Genesis didn’t turn out quite right. This colorimeter likely has drifted over time.
This update has many other random changes too, which I made during what little free time I had.
This is also my last planned update to patchy-ntsc. I’m planning to ditch patchy-ntsc entirely and make another shader from scratch, which I hope will have much more bearable performance, cleaner code, better accuracy, and better familiarity and ease-of-use for other LibRetro shader users. This shader still can do much, much better with accuracy, since it currently is just doing rough guesses at the signal’s filtering/effects using windowed sinc functions, while it could benefit well from hardcoded LUTs or arrays instead, as seen in Maister NTSC and NTSC Adaptive. Plus, there are several improvements that just can’t be added into patchy-ntsc, such as Chthon’s full gamutthingy LUTs. By this update, patchy-ntsc has just gotten too patchy, with such high performance requirements and such messy, unmaintainable, unoptimized code.
In this release, I’ve decided not to include any CRT presets. Anytime I would append a “sanitized” CRT preset, the result would change too much. I recommend appending an interpolation shader, such as bicubic or b-spline.
Download at https://www.mediafire.com/file/idunzdx47lur8xb/colortrak_2024_12_16.zip/file and place all these folders directly inside your “shaders” folder, which contains another folder called “shaders_slang”.
I’ve also sampled an NES palette from this 1989 RCA ColorTrak, albeit with noise throwing off the results slightly. In comparison to the palette that I eyeballed from my 1995 Toshiba CE20D10 (which I no longer have), this is drastically different. Were all late-80s/early-90s CRTs like this? Since I couldn’t sample the D-column of the palette in the 240p test suite, I reused the grays from the 00 column, whereas in the shader preset pack above, I used simple bilinear interpolation to approximate guess the gray axis and de-emphasized colors. https://www.mediafire.com/file/98t1kvwl2x30s2c/colorimeter_palette.pal/file Note: This palette already is based on the final output colors. If you use it with a CRT shader, make sure you set both input gamma and output gamma to 2.2, and make sure any gamut-affecting settings just pass the color through. For instance, in crt-hyllian, set LUT to off.
Edit (2024-12-20): I have now done this same procedure on my 2000 Panasonic CT-36D30B. I will post the palettes and data in a bit. It allows you to set the white point to “warm”, “normal”, or “cool”, but funny enough, “warm” is actually the standard white (D65), whereas “normal” is 9300K, and “cool” is ~14000-15000K. All 3 had the same demodulation settings. Seeing as my 1989 RCA was about 9300K too, I think this is a pattern.
My methodology for reversing this CRT’s NTSC gamut correction
Spoiler (click to show/hide)
To try to replicate the colors for consoles other than NES, I measured three things. I am making several assumptions about how this CRT works which might throw off the result.
First, I sampled my CRT’s phosphors. I used the bottom row of the NES palette with high saturation and low brightness to isolate each individual phosphor. The results averaged to the following:
Red: x: 0.598576833333333, y: 0.35618925
Green: x: 0.303930178571429, y: 0.593650071428571
Blue: x: 0.153156090909091, y: 0.0663752121212122
(Edit: I’ve since used this same colorimeter to measure the phosphors and white points on my other CRT (which let me pick “warm”, “normal”, and “cool”), and the results were close to standard. The colorimeter is probably right. Regardless, I’m going to re-measure this 1989 CRT, just to be safe.)
Notice how this gamut mostly fits nicely into sRGB, with little need for gamut compression. This is great for my shader project, but I’m still doubting this colorimeter’s accuracy.
With this information, I’m able to represent any color from the CRT as a linear combination of these 3 phosphors, in linear space.
Second, I sampled a grayscale. I connected a cheap HDMI-to-composite converter to my laptop, and used HCFR to be able to easily sample the full grayscale from 0 to 255, but with only one sample per gray level, to save time.
Both the HCFR software and the HDMI-to-composite converter may have been altering the colors. I found that the input colors from 0-15 and 236-255 were getting clamped, which meant that something was interpreting my RGB as being in limited space from 16 to 235. With further trial-and-error, I found out that the grayscale approximated a gamma of about 2.1, which is about 2.4*2.2/2.5, meaning that something was correcting from sRGB gamma to NTSC gamma, while my CRT was outputting directly with approximately 2.4 gamma with no further corrections. The grayscale from the CRT did not have a steady white point (as expected from a consumer unit), so in Excel, I converted each xyY point into XYZ, and then into a linear combination of the 3 phosphors.
Once I had the full grayscale, I was able to take any color from the CRT, represent it as the 3 phosphors in linear-light space, and invert the CRT’s EOTF (a.k.a. gamma) to get the internal RGB values.
Graph of the grayscale: https://www.desmos.com/calculator/uoiwfketdn (Edit: This isn’t the right graph. If you want to see the grayscale, represented as linear Y luminance values for each phosphor, you can find it in my shader code, in patchy-ntsc-eotf.slang.)
Third, I sampled a full chroma cycle (500 colors) in YIQ space, keeping Y and sqrt(I^2 + Q^2) constant. I did this before realizing that the 16-235 clamp and the 2.2/2.5 gamma correction were happening, but when I graphed the data without correcting for those things, the result still formed very clean sine waves. The graph showed very clearly that this CRT is doing an ordinary R-Y/B-Y demodulation to correct the colors, as I saw in a number of chips’ documentation. I found that R-Y, G-Y, and B-Y were at these offsets and gains:
R-Y: offset 54.5168223778 degrees, gain 47.170890387
G-Y: offset 214.599290038 degrees, gain 15.5833234335
B-Y: offset 317.451910996 degrees, gain 52.5768429019
(I = 0 degrees, Q = 90 degrees, chroma radius: 0.1 (0 to 1) or 25.5 (0 to 255))
Gains are in RGB space. I had forgotten to take into account the 16-235 clamp and the 2.2/2.5 gamma correction, so the numbers are a bit off. Simply multiplying the gains by (235.0 - 16.0) / 255.0 makes the result more accurate.
Graph of R, G, and B in the full chroma cycle: https://www.desmos.com/calculator/kkazpj4z1a
The result had R-Y about 97 degrees off from B-Y, at about 0.9 relative amplitude, while G-Y was about 237 degrees off from B-Y at about 0.3 relative amplitude. This is similar to Sony’s JP axes in the CXA2025AS and CXA1644AS. This makes sense with the CRT’s white (mostly in the brighter half of the grayscale) being steadily near (x=0.28, y=0.295), which is close to 9300K, the reference white used in the Japanese NTSC standard at the time.