Translucent windows
Window translucency / transparency within a terminal was (AFAIK) first implemented by notcurses, and has been "out there" (and amazing) for about 2 years I believe. vtm has also implemented it, again to nice effect. I decided it was time to learn how to do that, so this issue will track some notes.
It took a few attempts to figure out, and some critical bugs in ECMA48Terminal.flushLine() to fix. (That no one reported the fact that RGB colors are broken on the ECMA48 backend for the last 3 years tells me Jexer's userbase remains effectively zero.) I started with trying to blend the colors in Cell and CellAttributes, with some complicated logic, manual math, terrible performance, and weird behaviors with translucent cells on top of images.
But then I remembered a post/memo/something from @dankamongmen around 2 years ago to the effect that "rgb forecolor/backcolor is basically a pixel", and suddenly it was surprisingly straightforward. And thanks to my choice of Java as a language, absurdly easy. So now I have a hard time not thinking of Nick as the "Michael Abrash of the terminal", pointing us to the high-performance answers.
So here is how you do it. The code is in TApplication.drawTranslucentWindow(), LogicalScreen.blendScreen(), and LogicalScreen.copyScreen().
For the compositor part (TApplication):
- Before you draw your window, save what was underneath its area on the actual screen. (area --> oldSnapshot)
- Draw the window with opacity = 100 (a.k.a. alpha = 255).
- Copy the window area out again. (area --> newSnapshot)
- Restore what was under the window on the screen. (oldSnapshot --> area)
- Copy the window's contents back over the area, but this time honor its opacity. (blend newSnapshot over area)
- Draw the window shadow as blending a rectangle of black with a low opacity over the shadow area.
For the blending on the screen, there will be two layers. The bottom layer which is at alpha = 255 (I call it "this" screen/cell), and the top layer which is at alpha < 255 (I call is "over" screen/cell). The procedure is:
- Create a bitmap of "this" background (thisBg, and also a copy to thisOldBg), "this" foreground (thisFg), and "over" background (overBg), using RGB values for the colors. (Converting ANSI palette to RGB is easy too: ask the terminal via OSC 4 what its colors are.) Each pixel in the bitmaps represents one full cell on screen.
- Aside: Do you see why this is fast? For a text-only display, we're doing 2 blits and immediately assigning colors and are done. And these blits occur over tiny bitmaps: like 200x100 at the most. Usually you're asking for less work than putting a PNG icon somewhere.
- Blit overBg (SRC) over thisBg (DST) with an AlphaComposite (with alpha and SRC_OVER) set on the Graphics2D.
- Blit overBg (SRC) over thisFg (DST) with the same AlphaComposite.
- For text-only cells: choose which glyph will be visible, then set foreground to thisFg and background to thisBg. I say choose the glyph because blitting a space (' ') over a non-space should usually show the character underneath (but dimmed): things from "this" layer will bleed through "over" layer. But sometimes you may want ' ' to overwrite a cell, for example over the CP437 hatch characters (0x2591 - 0x2593: light/medium/dark shade).
- Image cells are a little more complicated, but not by much. If an image will be bleeding through, and it had partially- or fully-transparent pixels before, then it needs to be blitted on thisOldBg (at alpha = 255) before overBg is blitted over it (at alpha < 255). If the image is on "over", then it needs to be blitted on overBg (at alpha = 255) before the combination is blitted over thisOldBg (at alpha < 255). You just have to work through the cases and remember which layer each image is on, and conceptually treat images as behaving like the pixels of text glyphs.
- The one final thing is noting that a non-whitespace text glyph on the "over" layer ALWAYS overwrites "this" glyph AND "this" image. If you want to keep "this" glyph, or put any image under "over" glyph, then you're doing font rendering too. Which would "work", but now you're not matching the terminal's font(s) and you're also turning tons of what could be text into images. Unless you want to get really complicated and have a "rendered text" image in your Cell, and when the background image is gone you erase it and resume being a text cell with an optional image fragment on top. But it would be better for the future Good Image Protocol to support Z axis and then we don't have to render text ever.
All in all, it was that "cell fg/bg is really just a pixel" idea from Nick that made this crystal clear and easy. This was all of 2 days, and the final code (with heavy commenting) is like 300 lines.
I'm sold now. If your TUI supports RGB, then it should also support translucent windows.