Simulating CRT Monitors with FFmpeg (Pt. 2: Monochrome CRTs)

Sample video (watch @ 1080p)

Part of an update series on FFcrt.  See also:

  1. CRTs Part 1: Color
  2. CRTs Part 2: Monochrome
  3. Flat-Panel Displays

Been hacking away some more at the CRT transform script for FFmpeg: as before, you can grab the latest from the Github repository.  Nope, generally speaking it isn't any faster, but there's all kinds of fun new options to tinker with.

As a few of you may begin to suspect from the title, the obvious addition is monochrome display mimicry, but more about that in a bit.  Other changes worth mentioning are:

  • You'll now need a recent build of FFmpeg (2021-01-27 or later), on account of they done fixed a couple of bugs and added some features.

  • The intermediate conversion steps now use 16 bits per component whenever a gamma transform is needed; this extra precision mitigates those ugly banding artifacts that could crop up in darker areas of the picture.  (You may still spot a little bit of banding with the halation option in certain situations, but the reason there is different, and it's much less noticeable so I'll be looking into that one later.)

  • Instead of a single setting for curvature, you can control CRT_CURVATURE and BEZEL_CURVATURE separately.  With displays meant to have an overscan area, the bezel's curvature tends to be bit higher so more of the overscan is obscured towards the corners.

  • Speaking of curvature, FFmpeg's 'lenscorrection' filter now supports bilinear interpolation, so I switched to that instead of the 4x oversampling kludge mentioned in part 1.  This should also let us clean things up by just invoking 'lenscorrection' once, after the layers have already been blended; but I still haven't verified that there's a real quality improvement, so I haven't done that yet.  In the meantime, this does seem to save some RAM.

  • There's a BLACKPOINT adjustment which lightens the blacks if set higher than 0.  Monochrome CRTs often aren't intrinsically 'black' due to the phosphor coating, and when combined with the BRIGHTEN setting, this also gives you more control over contrast.

  • Beside the phosphor persistence options, you now have LATENCY to control the pixel on/off response time.  This is an entirely separate effect, and in fact should be left at zero for most CRTs, but see below.

For the how-to about those new settings, see the comments in the sample config files.  These changes (and others) mean that the older config files won't work properly anymore, but I've included a few new presets to compensate (and to give myself an incentive against making any more breaking changes).

What about the monochrome thing, then? - Easy, just change MONITOR_COLOR to any of the listed values other than 'rgb'.  That will (1) disable the shadowmask overlay, (2) perform a gamma-correct grayscale summation, and (3) apply a 'curves' filter to get a sort of gradient map.  The new video above shows off a few examples, but here are some hi-res stills:

B&W NTSC TV (CGA) w/overscan, blurry
B&W NTSC TV (CGA) w/overscan, blurry
Green composite (Apple ][), sharp
Green composite (Apple ][), sharp
Low-res amber TTL (CGA, custom palette w/overscan)
Low-res amber TTL (CGA, custom palette w/overscan)
Low-res green TTL (CGA), bright + halation/glow
Low-res green TTL (CGA), bright + halation/glow
Hi-res amber TTL (MDA text) w/overscan, bright
Hi-res amber TTL (MDA text) w/overscan, bright
Hi-res green TTL (IBM 5150, HGC), phosphor trail visible
Hi-res green TTL (IBM 5150, HGC), phosphor trail visible
White mono VGA, custom resolution, sharp
White mono VGA (custom resolution), sharp
Paper White VGA (640x480 monochrome mode)
"Paper White" VGA (640x480 monochrome mode)
Amber VGA (320x200 double-scanned), very sharp
Amber VGA (320x200 double-scanned), very sharp
P7 phosphor, latency & peristence visible
P7 phosphor, latency & peristence visible

The valid choices for 'MONITOR_COLOR' go like this:

  • RGB: color monitor with a shadow mask, exactly the same as before.  "RGB" refers to the phosphor colors, not to the monitor interface in common terminology: composite, digital TTL and analog component or VGA are all 'rgb' for this purpose, as long as they're in color.

  • BW-TV: simulates the kinda-bluish P4 phosphor used in black and white TVs, as well as the CRTs in early TRS-80 and Commodore PET models; has the shortest persistence among the common monochrome tube types.

  • GREEN1: gives a very saturated green like the P39 phosphor, as used in the IBM 5151 (and the Apple III monitor, and many others).  Generally that's the one you see having slow decay, i.e. long persistence.

  • GREEN2: this one is a less retina-burning green, like the P31 or similar, which is what most 'fast-phosphor'/short-persitence green monitors have - from Amdek, DEC, Sanyo/Apple, Tektronix and loads of others.

  • AMBER: the warm orange-yellow tone also seen in shorter-persistence CRTs.  AFAIK there was no "standard" amber phosphor, and varying mixtures were used, but this should look close to the version seen in IBM's 5155 display (manufactured by Zenith).

  • WHITE: many variations of white phosphors exist, used mostly by hi-res monitors (TTL/MDA, mono EGA/VGA and upwards).  This option produces a simple grayscale transformation of the input, with no additional tint.

  • PAPERWHITE: popular in late 1980s hi-res displays, especially for desktop publishing and the likes.  The idea was to have a "warmer"-looking white similar to paper, and this generally means a mixture of bluish and yellowish phosphors, sometimes uneven:

    "Paper-white monitors use a solid coating of mixed colors. This is usually a blend of yellow, light blue, and small amounts of pink to make the white look warmer." 1
    "If you look closely, you might see fine specs of colors, such as a bright yellow dappled into so-called "white" phosphors." 2
    Each manufacturer had its own concoction, so the results could vary a lot.  This option is a special case: to mimic the above effect, it generates a uniform-noise texture designed to give a warmer, "cream" sort of paper tone, and blends it with the screen contents.

  • P7: confusingly, monitors using the medium-persistence P7 phosphor were also sometimes referred to as "paper-white", and were also mostly high-resolution and intended for similar purposes: some examples include early Lisa and Macintosh CRTs, and a few of IBM's monochrome VGA monitors from the PS/2 era.  P7 has a blue-white spot color (steady state), but the persistence (decay) color is a greenish yellow, so this one's also handled as a special case in the script.

    The blue and yellow tinges weren't supposed to be too noticeable in normal use; but especially with aging CRTs, the blue/white tends to "wear out" more, and jacking up the brightness (to compensate) emphasizes the yellow-green persistence.  You can also notice a bit of latency, where pixels briefly look very blue while they light up.  Youtube has some video examples where you can see this effect in slow motion: here's an IBM one and an Apple Lisa one.  The ffcrt script will produce a similar result if you use a large P_DECAY_FACTOR along with a nonzero LATENCY.

That wraps it up for this update.  Overall I'm happy with the way it looks so far, but there are still a bunch of things to fine-tune, for both performance and appearance.  So there will probably be a Part Three... you've been warned.


Dmitri says:

Thanks for writing this up!

These monochrome CRT simulations look pretty convincing, but I've got this question which I can't resolve myself as I don't have any CRTs around myself:

What is the reason for visible scanlines in the black but glowing areas? Do monochrome CRTs really have them?

They do, but only if the brightness is turned up high.  If it's properly adjusted the blacks are black, but AFAIK typical brightness controls add a DC bias to the video signal that's fed to the cathode, and this affects the luminance of both dark and light pixels.  So if you increase the brightness above a certain level, the intensity of the scanning electron beam will be enough to make the phosphors glow visibly, even in areas that are supposed to be black.

In this script you can adjust that from the config file, with the 'BLACKPOINT' parameter.  The values I used in the sample images are kinda high (maybe too high), mostly to emphasize the bezel edges... but if you set it to 0 you'll get true blacks.

Write a response:

* Required.
Your email address will not be published.