#!/bin/bash # Test cursor row, column placement after sixel image is sent. # After a sixel image is displayed, the text cursor is moved to the # row of the last sixel cursor position, but the column stays the same # as it was before the sixel image was sent. # # This can be thought of as sixel images always ending with an # implicit Graphics Carriage Return (`$`). # ADDENDUM: It is not as simple as I thought. When a row of sixels # straddles two rows of text, the text cursor can be left on the upper row. # It seems up to three lines of pixels may be beneath any words printed. # # The rule for when this happens is not obvious to me, but can be seen # with images of height: 21, 22, 23, 24, 41, 42, 81, 82, 83, 84... # # My guess: # for a sixel image of height h, let a=(h-1)%6 and b=(h-1)%20, # then, the text will overlap the image when a>b. # # If that is the case, then the entire list of heights for which this # will happen on the VT340's 480 pixel high screen is: # # 21 22 23 24 41 42 81 82 83 84 # 101 102 141 142 143 144 161 162 # 201 202 203 204 221 222 261 262 263 264 281 282 # 321 322 323 324 341 342 381 382 383 384 # 401 402 441 442 443 444 461 462 # # Note that there are 48 entries, so that means there's a 10% chance # if heights are chosen randomly from 1 to 480. However, if one were # to always pick heights which are a multiple of the character cell # height (20px), then the chances are 0% as there are no problematic # heights divisible by 20. # Sixel images often do *not* end with a `-` (Graphics New Line = GNL) # which sends the sixel cursor down 6 pixels. Any text printed next # will potentially overlap the last row of sixels! # I am not yet positive, but I believe that, in general, applications # should send sixel images without a GNL but then send `^J`, a text # newline (NL), before displaying more text or graphics. # IMPORTANT: sometimes neither a graphics nor a text newline is wanted. # For example, if an image is full screen, either newline would cause # the top line to scroll off the screen. # | Text cursor column | Text cursor row # --------|--------------------|------------------------------------- # !GNL !NL| Unchanged | Overlapping last line of graphics # !GNL NL| Column=1 | First line immediately after graphic (usually) # GNL !NL| Unchanged | _Sometimes_ overlapping graphics # GNL NL| Column=1 | First *or* second line after graphic CSI=$'\e[' # Control Sequence Introducer DCS=$'\eP' # Device Control String ST=$'\e\\' # String Terminator set_cursor_pos() { # Home, top left is row 1, col 1. local row=$1 col=$2 echo -n ${CSI}${row}';'${col}'H' } reset_palette() { # Send DECRSTS to load colors from a Color Table Report echo -n ${DCS}'2$p' echo -n "0;2;0;0;0/" # VT color #0 is black and BG text color echo -n "1;2;20;20;79/" # VT color #1 is blue echo -n "2;2;79;13;13/" # VT color #2 is red echo -n "3;2;20;79;20/" # VT color #3 is green echo -n "4;2;79;20;79/" # VT color #4 is magenta echo -n "5;2;20;79;79/" # VT color #5 is cyan echo -n "6;2;79;79;20/" # VT color #6 is yellow echo -n "7;2;46;46;46/" # VT color #7 is gray 50% and FG text color echo -n "8;2;26;26;26/" # VT color #8 is gray 25% echo -n "9;2;33;33;59/" # VT color #9 is pastel blue echo -n "10;2;59;26;26/" # VT color #10 is pastel red echo -n "11;2;33;59;33/" # VT color #11 is pastel green echo -n "12;2;59;33;59/" # VT color #12 is pastel magenta echo -n "13;2;33;59;59/" # VT color #13 is pastel cyan echo -n "14;2;59;59;33/" # VT color #14 is pastel yellow echo -n "15;2;79;79;79" # VT color #15 is gray 75% and BOLD text color echo -n ${ST} # String Terminator } # Generate square of size w with final graphics new line removed square() { # Given a color index number and (optionally) a size, row, and column, # draw a square with top left corner at (row, column) and of size×size px. # Default size 100×100px (10cols, 5 rows) local -i color=${1:-1} # Default is color index 1 (blue) local -i size=${2:-100} # Size in pixels (defaults to 100) local -i row=$3 column=$4 # If set to 0, cursor is not moved if [[ row -ne 0 && column -ne 0 ]]; then set_cursor_pos $row $column fi # Draw a square of the right color & size squaresize $color $size } squaresize() { # Helper for square() that uses convert to return a sixel square of # the right color ($1) and size ($2). # Similar to this but with variable size squares: # echo -n ${DCS}'0;0;0q"1;1;100;100#'${color}'!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100N'${ST} local color=${1:-1} # Default color index is 1 (blue) local size=${2:-100} # Default size is 100x100 # Get a sixel string local sq=$(convert -geometry ${size}x${size} xc:black sixel:-) # Remove ImageMagick's extraneous Graphic New Line at end of image. sq=${sq%-??}$'\e\\' # VT340s always used the same color register for the first sixel # color defined no matter what number it was assigned. That means, # each time we send a new sixel image, the previous one's color # palette gets changed. We don't want squares of all the same # color, so remove the color definition and just use the defaults. sq=${sq/\#0;2;0;0;0/} # And finally, switch to the proper index for the color we want. echo -n ${sq//\#0/#${color}} } squaregnl() { # Same as square(), but sends a graphics newline at the end of the sixels. # (Sticks a `-` before the String Terminator, "Esc \") sq=$(square "${@}") echo -n ${sq%??}$'-\e\\' } main() { clear reset_palette show_labels neither_graphic_nor_text 96 4 4 31 # size, color, row, column text_newline_only 96 9 4 1 text_newline_only 84 9 4 14 graphics_new_line_only 100 1 4 51 graphics_new_line_only 96 1 4 64 set_cursor_pos 1000 1 } neither_graphic_nor_text() { # Typically sixel images should not end with a Graphics New Line (GNL) # However, if a text newline isn't sent, there will be overlap. local -i size color row column read size color row column <<<"$@" set_cursor_pos $((row++)) $column echo -n "Height $size" set_cursor_pos $((row++)) $column # Three squares sent as separate sixel images, indented +1 for i in {1..3}; do square $((color++)) $size tput cuf 1 done echo -n "overlap?" } text_newline_only() { # USING A TEXT NEWLINE (NL) after an sixel image that does NOT # have GNL is probably the best way to be on the text line # immediately below the image. However, the text will still # occasionally overlap the last four rows of pixels. # Also, if multiple images are intended to be shown, there will # usually be a gap between them when using a text newline. # Overlap happens because the height of a text cell is 20 pixels # and the height of a sixel is 6. # for (h=0; h<480; h++) if ((h-1)%6 > (h-1)%20 ) { h } # Let the pixel position of the top of the graphics cursor be 'Yg' # and let the pixel position of the top of the corresponding cell # of text which the text cursor will be placed on be 'Yt'. Note # that Yg is evenly divisible by 6 and Yt, by 20. Taking the # remainder, r, after dividing Yg by 20 tells us how many pixels # down into a row of text the last line of sixels started. When # r==0, the sixels started at the top of the text row. # When r = 14, the sixels covered the bottom six pixels on the row # of text. When 14 < r < 20, the sixel line impinged by r - 14 # pixels into the text row below and there is a chance the next # text printed will overlap. local -i size color row column read size color row column <<<"$@" local -i offset offset=$((column-1)) set_cursor_pos $row 1 if ((offset)); then tput cuf $((offset)); fi echo "Height $size" # Three squares, separated by text new lines and indented +1 for i in {1..3}; do if ((offset)); then tput cuf $offset; fi square $((color++)) $size offset=offset+1 echo done tput cuf $((offset)) echo -n "overlap?" } graphics_new_line_only() { # However, some sixel images end with a `-`, a Graphics New Line. # This can be useful for writing another image starting at the same # column without having to reposition the cursor. # # However, this runs the risk of having occasional overlap. local -i size color row column read size color row column <<<"$@" set_cursor_pos $((row++)) $column echo -n "Height $size" set_cursor_pos $((row++)) $column # Three squares, separated by graphics new lines and indented +1 for i in {1..3}; do squaregnl $((color++)) $size tput cuf 1 done echo -n "overlap?" } show_labels() { set_cursor_pos 1 10 echo -n "Should sixel images include a GNL ('-') at the end?" set_cursor_pos 3 29 echo -n "Neither NL nor GNL" set_cursor_pos 3 1 echo -n "Text New Line only" set_cursor_pos 3 51 echo -n "Graphics New Line only" set_cursor_pos 22 29 echo -n "Always overlaps" # Neither NL nor GNL set_cursor_pos 22 3 echo -n "Overlaps a little" # NL only set_cursor_pos 23 3 echo -n " Gaps a little" # NL only set_cursor_pos 22 54 echo -n "Overlaps badly" # GNL only set_cursor_pos 23 54 echo -n "Never gaps" # GNL only } main