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.

Write a response:

* Required.
Your email address will not be published.