hackerb9 / vt340test

Tests of VT340 compatibility
Creative Commons Zero v1.0 Universal
37 stars 5 forks source link

Creating a VT340 compatible palette #4

Open j4james opened 3 years ago

j4james commented 3 years ago

I had an idea for creating 256-color Sixel images in a way that is backwards compatible with a 16-color terminal like the VT340, and potentially also a 4-color terminal like the VT330. And I think this is something you could potentially use in lsix.

The way it works is you reserve a 16-color grayscale palette for the first 16 color entries, and then the rest of the color table can be assigned in whatever way works best for the given image. My understanding is that on a VT340, those additional color entries should map to the best match from the first 16, thereby giving you an reasonably good grayscale fallback.

As an example to test with, this is the well-known snake image generated with img2sixel, but with the palette remapped using the technique described above: snake-vt340.six

However, when tested on Reflection Desktop (a 16-color Sixel terminal), it didn't actually work as expected. It doesn't perform a closest-match mapping, but instead simply mods the color number (i.e. #⁠17, #⁠33, and #⁠49 will map to #⁠1, while #⁠18, #⁠34, and #⁠50 map to #⁠2, etc.).

Creating a 256-color palette that will work with those constraints is more complicated, but I've hacked together an example using the snake image to demonstrate the concept: snake-reflection.six

And finally we come to XTerm, which doesn't work with either of the above techniques (when limited to 16 color registers). The problem is that XTerm overrides earlier palette entries with later ones, even though they're using different color numbers. So to make the palette work with XTerm, you'd need to redeclare the 16 grayscale colors after everything else.

This is essentially the same as the Reflection approach, but with the first 16 colors repeated for Xterm compatibility: snake-xterm.six

On a 256-color terminal, I believe all three approaches should work, displaying the image in color. I'm also hoping that all three will work on the VT340, although obviously in grayscale. And if the first method works on the VT340, then I would also expect all three to work on a VT330 (mapping to 4 grayscale colors), although the quality obviously won't be great.

The second two examples are horribly hacky, but the first approach is quite easy to achieve, so if it actually works on a VT340, I think it might be worth raising an issue with img2sixel to see if they would be willing to add something like that as an optional output format.

j4james commented 3 years ago

Some notes on the palette choice for the first 16 colors:

Note that #⁠16 should wrap back to #⁠0 on the VT340, and #⁠4 should wrap back to #⁠0 on the VT330. In both cases you should end up with black (or at least very close to black) as the background color.

hackerb9 commented 3 years ago

That's pretty clever and is, I believe, similar to how DEC implemented 64-colour images that could be printed on 8 or 4 colour printers using their "ANSI-compatible" Printing Protocol (sixel). The difference being they have a fixed palette of colours designed such that if you ignore the upper bits(&0x03) -- which is how DEC devices with a smaller palette appear to work -- you still get a reasonable image.

I'm not sure if XTerm's implementation is a bug, but it would be nice if it was. I doubt it, though, given DEC's penchant for using a bitmask when dealing with the colour table.

I do not think I could use this technique in lsix since it always regenerates the sixels on the fly and can simply request 16 colors from a 4-bit (4096 color) depth. Also, a 256-color image would take a lot more bytes, and thus a lot more time at 9600 bps. Even two colour images are so slow, I was actually editing the sixel files by hand to see if I could cut the time in half by sending just one color. (Sixels implement transparency by simply not sending the background colour.)

But your technique does sounds useful since sixel is also used as an image file format. It would be cool to have a sixel file that could be cat'ed to any terminal without having to first detect what it supports.

How are you reserving colors in the palette? One problem I keep having is that ImageMagick doesn't know that colours 0, 6, and 15 are special and tramples them, often unnecessarily.

hackerb9 commented 3 years ago

Some notes on the palette choice for the first 16 colors:

* I started with an even spread of 16 grayscale values.

* I put (53,53,53) in slot #⁠7, (80,80,80) in slot #⁠15, and (0,0,0) in slot #⁠16, since I believe those are the default text, bold, and background colors on the VT340.

* Then for VT330 compatibility I picked the next best spread of 4 colors: (33,33,33) in slot #⁠1, (67,67,67) in #⁠2, (100,100,100) in #⁠3, and (6,6,6) in #⁠4. I think that is also reasonably close to the default VT330 settings.

* The rest of the slots I just filled with the remaining colors from the initial set of 16.

Note that #⁠16 should wrap back to #⁠0 on the VT340, and #⁠4 should wrap back to #⁠0 on the VT330. In both cases you should end up with black (or at least very close to black) as the background color.

Those values are what the REGIS section of the programmers manual said. The background of black is correct. However, my actual VT340 reported different shades for DECRQTSR: 46% grey for foreground text, and 79% grey for bold. See my resetpalette.sh script for the full table.

Here's a mediacopy of the snake-vt340.six file. (This took 30 minutes to transmit from the VT340 due to a weird bug where it sends a glitched character and then pauses for a long time.)

image

Oh, and I don't know why ImageMagick is seeing the image as being nearly twice as wide as it actually is. This sixel header pretty clearly declares the file as 800x480 with a 1-to-1 pixel aspect ratio.

hackerb9 commented 3 years ago

Note: I tried cat'ing the snake-xterm and snake-reflection.six files to the VT340 and both of those look correct, as opposed to snake-vt340 which seemed to have some of the colors mapped incorrectly.

hackerb9 commented 3 years ago

The media copy is too buggy and slow for complex images like this so I've taken photos of your snakes:

snake-vt340

snake-reflection

snake-xterm

hackerb9 commented 3 years ago

For comparison, here is what the snake looks like when I converted it (from the 256 color sixel image you sent) to 16 color:

convert snake-xterm.six -geometry 800x480 -depth 4 +dither -colors 16 > snake-convert16.six
cat snake-convert16.six

snake-convert16

j4james commented 3 years ago

Thanks for capturing all these images. The results are a bit disappointing, but it's good to know how it really works. Your VT340 output actually looks exactly the same as Reflection Desktop - I should have known they were correct! I was hoping it was a bug on their part because the VT240 does actually attempt to perform a closest-match mapping, but it looks like the VT340 didn't inherit that functionality.

Oh well. At least the second technique works, but given the difficulty in generating it, and the size compared to a custom 16-color version, it's probably not worth the effort.

I do not think I could use this technique in lsix since it always regenerates the sixels on the fly and can simply request 16 colors from a 4-bit (4096 color) depth.

The problem I was hoping to avoid was how you detect the numbers of colors. A VT340 isn't going to respond to XTerm's XTSMGRAPHICS query, and nor do quite a few of the modern sixel terminals. I suppose worst case you could assume a max of 16 if you don't get a response from XTSMGRAPHICS.

How are you reserving colors in the palette?

I just wrote a little post-processing script in Python that remapped the palette produced by img2sixel and prepended my 16 colors. It's not a general solution, because it relied on the fact that the snake image wasn't using all 256 colors, but it was just meant to be a proof of concept. If it worked, I was hoping we might be able to get the functionality built into some of the image libraries.

However, my actual VT340 reported different shades for DECRQTSR: 46% grey for foreground text, and 79% grey for bold. See my resetpalette.sh script for the full table.

That's really interesting. The few that are off-by-one I can understand, given the limited color resolution, but 46% vs 53% is quite a difference. I just checked Reflection Desktop, and again they got this right. I suspect most modern terminals will have just used the values from the REGIS manual though.

hackerb9 commented 3 years ago

I believe the XTerm and Reflection photos are supposed to be exactly the same. The garbage at the top of the XTerm one is the last line of the previous snake image. The command line covered half of it. It turns out, when a sixel image is finished, it does NOT put the text cursor on the next line empty line. That's a significant improvement that XTerm has that the VT340 lacks. I do not think it is easy on a true VT340 to know if your command line is going to be overlapping graphics after catting some sixels or not.

j4james commented 3 years ago

I believe the XTerm and Reflection photos are supposed to be exactly the same.

Yeah, that's what I would have expected. The XTerm version was just resetting the palette to workaround an XTerm bug. It shouldn't be necessary on a correct implementation, but shouldn't have any negative effect either.

It turns out, when a sixel image is finished, it does NOT put the text cursor on the next line empty line.

Yeah, that's what I was testing in the cursor_position tests. The interesting thing is that when you've got a large aspect ratio, the final cursor position can actually be several lines up from the bottom of the image.