Simulating NON-CRT Monitors with FFmpeg: Flat Panel Displays

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

Yes: if emulating CRT monitors the slow way wasn't your thing, now you can sluggishly simulate various types of old flat-panel displays, too!  All with the same FFmpeg script - which is still named FFcrt, in the grand old tradition of expanding a project's scope so that its name no longer reflects what it does!  Makes it feel all mature and stuff.

But first, a bit about some other changes that have made it into the script:

Aspect correction is now entirely unrelated to input pre-scaling.  Previously, both were taken care of in one nearest-neighbor scaling stage.  If you were going for a pixel aspect of say 5/6, your input scaling factors had to be multiples of that - X*5 and Y*6, or X*10 and Y*12, and so on.  Problem is, as the factors get larger, the "so on" part starts chewing up RAM like candy.

Now there's just a single PRESCALE_BY factor (which controls nearest-neighbor scaling of both X and Y), and a separate PX_ASPECT setting which is applied afterwards to X alone, using bilinear filtering.  A little slower, but more flexible and easier on your RAM; also, nothing's stopping you now from having pixel aspects like 117/125 (PAL Commodore 64), or some other such atrocity.  Rejoice!

Aliasing artifacts should now be history, after a couple of tweaks to the lenscorrection filter usage.  Curved bezel edges don't get the jaggies anymore, and scanline-induced moirĂ© patterns are also gone (for any sane resolution, at least).  The boring details mean that the curvature steps will have to stay separate, against my earlier hope of unifying them for the sake of efficiency, but we'll live with that.

Also, the console output isn't a huge mess anymore, and there are a couple of other minor changes that do improve efficiency.  But you can look those up in the commit log, so let's get to the point.

Flat-Panel Display Options

There are some fresh new parameters in the sample .cfg presets: additional options for MONITOR_COLOR, and a new section for "NON-CRT/flat-panel effects".  It's all commented as usual, so you can figure out what's what, but FLAT_PANEL has to be yes for the rest of them to take effect.

This lets FFcrt mimic three major types of flat panels, as common in older portable PCs - monochrome/grayscale LCDs (of various types and forergound/background colors), orange plasma panels, and electroluminescent displays:

Plasma panel, CGA (doubled w/square pixels)
Plasma panel, CGA (doubled w/square pixels)
Plasma at 640x400, e.g. Compaq/Toshiba portables
Plasma at 640x400, e.g. Compaq/Toshiba portables
Plasma panel, VGA (e.g. IBM PS/2 Model P70)
Plasma panel, VGA (e.g. IBM PS/2 Model P70)
Electroluminescent display (bit-dithered mono CGA)
Electroluminescent display (bit-dithered mono CGA)
ELD (hi-res mono EGA), like some GRiDCase models(?)
ELD (hi-res mono EGA), like some GRiDCase models(?)
ELD, simulating 16 EGA shades w/bit dithering
ELD, simulating 16 EGA shades w/bit dithering
Grayscale LCD (CGA), some motion latency visible
Grayscale LCD (CGA), some motion latency visible
Grayscale LCD (640x480 VGA), white backlight
Grayscale LCD (640x480 VGA), white backlight
Mono LCD (blue, e.g. Compaq LTE); bit-dithered CGA
Mono LCD (blue, e.g. Compaq LTE); bit-dithered CGA
Mono LCD (backlit), showing latency + PWM shading
Mono LCD (backlit), showing latency + PWM shading

As you might expect, the general type is determined by what you set as the MONITOR_COLOR, which now accepts these values in addition to the old ones:

  • PLASMA: emulates the type of orange flat panels using neon plasma cells, as seen in portables like the later Toshiba T-series (T3100, T3200, T5100...), the Compaq Portable III and 386, and the IBM PS/2 models P70/P75.  The original plasma displays dating back to PLATO terminals were 1-bit, but by the time they started cropping up in portables, they generally sported multiple shades (brightness levels) of orange.

  • ELD: electroluminescent displays (as opposed to LCD displays that simply used electroluminescent backlighting).  These uses phosphors for light emission, but unlike CRTs the phosphor resides in a thin layer between two electrodes in a matrix, and its electrons are directly manipulated by high-voltage AC power.  Most commonly yellow (AKA "golden") and strictly 1-bit; seen e.g. in some GRiD Compass and GRiDCASE models, later Data General/One laptops, and a few other portables.

  • LCD: cheaper and much more common, at the cost of contrast, viewing angle and pixel response time (latency).  This option aims to reproduce the baseline variety, a twisted-nematic (later Supertwist) monochrome LCD with a bluish-gray foreground color, a pale green background and no backlight.

  • LCD-LITE: similar to the above, but with a brighter green backround for extra range and contrast.  Can be used to simulate backlit versions of the more common monochrome LCD panels.

  • LCD-LWHITE: this one simulates later monochrome LCD panels with a grayish foreground and a white backlight, typically with better contrast; very common in later laptops before color LCD panels started becoming a thing.

  • LCD-LBLUE: less commonly, some backlit LCD panels had a vivid blue tint to their activated pixels.  As seen in the final revision of the IBM PC Convertible (5140), but also the Compaq LTE and LTE/286, the NEC Ultralite, and probably a few others.

PC Magazine Laptop Screen Comparison

That doesn't really cover everything: early flat panels (especially LCDs) came in a big and confusing bunch of colors and lighting technologies, as you can see in the adjoining PC Magazine comparison.  To vary the contrast range (and sometimes the hue, a little bit) you can play with the BRIGHTEN and BLACKPOINT configuration parameters.

Sizes and aspect ratios were similarly all over the place, and that's where you can play with PX_ASPECT (the pixel aspect ratio) and OASPECT (the output, AKA display aspect ratio) to achieve just about any of them.  The emulated storage aspect ratio (SAR) is already determined by the resolution of the input.

Given the nature of these early LCDs, "on" pixels were dark and "off" pixels allowed light through, so the image was typically the inverse of what a CRT, plasma or ELD display would show.  A lot of laptop LCDs did let you flip the signal and get light-on-dark instead, so FFcrt lets you do both, with the INVERT_INPUT parameter.  Of course, these things wouldn't be the same without pixel response times that bordered on the criminal, and that's what the LATENCY option is for.

There's just one more thing to tackle: as mentioned, some of these flat-panel displays were strictly 1-bit - pixels were either on or off, without such decadent luxuries as true greyscale shading.  That goes for ELDs (probably all of them) and a lot of early LCD panels.  But as simulated in the images/video above, many of them could still fake it well enough, and I've added an quick-n'-dirty extra tool for that.

Simulated Grayscale "Shading" on 1-Bit Monochrome Displays

Sim-rgbi1bpp (yet another batch file in the FFcrt repository) approximates the methods used by certain display controllers to show RGBI video (AKA the 16 IBM PC colors) on true monochrome displays.  It's intended to process your input before you feed it to FFcrt; use it only if you really want to simulate such a setup - for true grayscale, this is not needed.

These hardware solutions typically replaced each RGBI color with a modulated dot-pattern, either static ("bit-dithered") or dynamic (cycling across frames, a sort of refresh rate-limited PWM).  Obviously, this is very different from what you get with dither algorithms intended for image processing, so this script twists FFmpeg's arm a little bit and uses brute-force (read: slow) color replacement to implement this.

Such trickery often relied on the fact that the target display had (at least) twice the resolution of the video mode in question, so 200-scanline modes were doubled to 400.  If required, sim-rgbi1bpp will likewise upscale the input before it applies the effect.

You can see the readme for usage directions, but while we're here, it might be helpful to show some examples.  First we have the static methods, which can be used for still images as well as video:

Original RGBI image
Original RGBI image
Method 4x1a: intended for CGA graphics - 4 shades using 0%-50%-50%-100% patterns
Method 4x2a: like 4x1a, but shifts the dot patterns on alternating scanlines
Method 4x1b: intended for CGA graphics - 4 shades using 0%-25%-50%-100% patterns
Method 4x2b: like 4x1b, but doubles the vertical frequency of the dot patterns
Method 8x2: 8 shades, double scanned (no intensity), OK for CGA/text modes
Method 16x2: 16 shades, double scanned; should work for all RGBI modes

"4x1a" is the simplest method, which basically interprets CGA mode 4 as if it were mode 6 (640x200 mono); this is similar to running SIMCGA on a Hercules Graphics Card, and I believe this is what the IBM 5140 does.  The HP 100/200LX palmtops use similar methods: "4x1a" also corresponds to HP's shading mode 00b (bit-dithered), and "4x1b" to shading mode 11b.  The other methods above are basically made up, but the principle is similar.

The 't' methods add a temporal component, by modulating the bit-patterns on successive frames.  That means they're video-only, and won't work with still images:

Method t4x1: 4 shades at 0%-33%-67%-100% (CGA graphics), 3-frame cycle
Method t4x2: 4 shades at 0%-33%-67%-100% (CGA graphics), 3-frame cycle, double scanned
Method t8x2: 8 shades (ignores intensity), 2-frame cycle, double scanned
Method t16x2: 16 shades (all RGBI modes), 2-frame cycle, double scanned

Of course, these methods introduces massive flicker; but in the real world, this approach was mostly used on monochrome LCD panels, with enough pixel latency to make it less of a problem.  For instance, frame-based shading mode 11b on the HP 100/200LX is pretty much the same as "t4x1".  When feeding the results to FFcrt, use the LATENCY parameter to mimic that - this is what I did in the video.

As a counter-example, the GRiDCASE 1537's electroluminescent display had no such mitigating factors (video sample) - the flicker there actually looks much worse than any of the above methods, but that might just be a frame-rate mismatch in that particular video.

Anyway, none of these methods duplicate the exact implementation used by a specific type of video hardware, just the general principle.  The HP 100/200LX is the only machine for which I was able to find precise implementation details.  If you have the technical documentation on any other real-world examples, let me know.


ropersonline says:

Parts of this post are not really compatible with older browsers:
For instance, the img tag src="//" parameter uses the "//domain.tld/path" syntax that only works in newer browsers, but not in e.g. older Firefox versions, where you'd have to use either "https://domain.tld/path" or "/path".
Also, older browsers may not support .webp images, so are the advantages of that format really worth being incompatible with those? Why not just use .pngs throughout just to be on the safe side?
Perhaps these issues could be remedied?

ropersonline says:

PS - Correction: *or why not just use .gifs, even.

I had no idea that protocol-relative URLs were a new thing, but that's something I can look into.  WEBP is the only web image format I'm aware of which properly supports animation at 60 fps, so it gives the right idea of what the temporal flicker/PWM dithering methods look like.  GIF has a 1/100s timebase so 50 fps is the closest it gets.

IIRC there's a way in HTML5 to specify a fallback format for images, so maybe that could be gif, but the fallback method itself probably won't play nice with older browsers.  How old is 'older', anyway?  I'm using an HTML5 site generator for the blog, so that's one major limitation to backward compatibility.

This is very cool and exactly what I've been looking for! Looking forward to using it on whatever my next project is that requires CRT emulation, thank you so much for making it!

Have you tried this on wider gamuts such as BT2020? I'm unfamiliar with how FFMPEG manages colourspaces but right now the script only seems to output content as BT709, could this be added as a user-selectable option? Additionally, would this work with linearized EXRs?

Thanks for the feedback! I went with BT709 for two main reasons really: for one, the typical expected input is RGB material (e.g. the output from an emulator or from certain video capture devices), and sRGB/BT709 have the same primaries/gamut, so accurate color information can be easily preserved. Also, BT709 support is widespread enough and the results won't be color-mangled by Youtube and co.

Right now I have no way to test BT2020 output properly (and no monitor that supports it), but the gamut is a superset of BT709 so I expect that an accurate conversion shouldn't be a big issue for those who do know what they're doing, as opposed to me. ;)
The same thing goes for linear EXR... in fact I hadn't even heard about this format until now(!), but I'm still planning to add more output options with higher bit depths (as soon as I can get them to work right), so that should get you at least part of the way there.

As far as I can tell, >10-bit color spaces are probably overkill as the actual output for this type of material. Of course if you do any further editing, you'd want your intermediate bit depth to be as high as possible, but that would ideally be handled by the editing environment during the import stage.

Sounds good! Proper end-to-end colour management with a 32 bit working space may be out of scope for now but it's worth a look... The emulation crowd may be satisfied with display-referred color transformations but the VFX & CG industry's needs are a little different.

I've long been rather let down by the portrayal of monitors and screens when they are comped onto footage as images with no artifacts, this program is a step in the right direction for fixing some of these things and emulating these distinct looks! :D In any case I'm always amazed by what some people can make FFMPEG do.

If you know of any similar CRT / monitor emulation software I'd love to see it, mainly focused on stuff that will work on individual frame sequences and not real-time shaders.

Write a response:

* Required.
Your email address will not be published.