Skip to content

Add graphics in terminal support: - Sixel and iTerm2 protocols#2973

Open
MatanZ wants to merge 11 commits into
termux:masterfrom
MatanZ:sixel4
Open

Add graphics in terminal support: - Sixel and iTerm2 protocols#2973
MatanZ wants to merge 11 commits into
termux:masterfrom
MatanZ:sixel4

Conversation

@MatanZ
Copy link
Copy Markdown
Contributor

@MatanZ MatanZ commented Sep 4, 2022

This commit implements graphics in the terminal, including two different protocols for placing images:

  1. Sixel, and
  2. iTerm2

I ran this for a few days, and it seems to me that some people tried it from #142, and saw no crashes, so I hope it is reasonable safe.

  • In TerminalEmulator, interpret sixel sequences, and send them to
    TerminalBuffer for constructing a bitmap.
  • Sixel sequences may be longer than 8192 characters, so break them in
    natural places ($,-,#), rather than collecting all in the buffer.
  • The bitmap is sliced to character cell sized slices, and each the
    the style attribute is used to store which bitmap slice is displayed
    in place of this character.
  • In TerminalRenderer the style is interpreted, and drawn using
    drawBitmap, instead of drawText.

Support iTerm2 inline image protocol (OSC 1337):

  • Using the same bitmap display infrastructure introduced for sixels.
  • Collects the image data outside of the OSC buffer.
  • Ignoring some parameters.

Small emulator changes:

  • Also eat APC sequences, not echoing to screen.
  • Fix CSI 14 t to give actual size
  • Add CSI 16 t
  • Add 4 (sixel) to device attributes

For those who tried this branch already, I force pushed to it in order to remove changes that may not be acceptable.

The main known issue is that if graphics sequence is interrupted (in specific places), the emulator remains stuck waiting for the sequence to end, thus ignoring all input. This requires terminal reset from the menu.

@aicynide
Copy link
Copy Markdown

aicynide commented Sep 5, 2022

You are a hero my brother

@TermuxMonet
Copy link
Copy Markdown

It works! but gifs are being duplicated on the terminal when expanding and collapsing the keyboard

Regular images are being displayed fine

Screenshot_20220905-194040_Termux

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Sep 6, 2022

It works! but gifs are being duplicated on the terminal when expanding and collapsing the keyboard

This is not a bug. img2sixel displays animations by sending each frame as a different sixel with a command to send the cursor to position (1,1) between them (CSI H). This causes each frame to overwrite the previous one.

When you remove the keyboard, the screen gets taller, and termux handles this by scrolling down, so the previous frame which was at position (1,1), is now lower, so it is not covered by the following frames.

@TermuxMonet
Copy link
Copy Markdown

oh, so everything's working fine then

@TermuxMonet
Copy link
Copy Markdown

TermuxMonet commented Sep 6, 2022

Here's a small guide for anyone else who wants to test this PR

For displaying images and gifs using Sixel, do pkg install libsixel and use img2sixel image.png

For displaying images using iTerm2, download the imgcat script, and use it with the command ./imgcat image.png

@sylirre
Copy link
Copy Markdown
Member

sylirre commented Sep 6, 2022

Has issues with big images. For example when using imgcat without limiting width Termux app may get crashed due to out-of-memory exception:

09-06 19:10:37.740 10702 10702 E Termux  : java.lang.OutOfMemoryError: Failed to allocate a 521628640 byte allocation with 74375880 free bytes and 232MB until OOM, target footprint 99167840, growth limit 268435456
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalBuffer.resizeBitmap(TerminalBuffer.java:48)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalBuffer.addImage(TerminalBuffer.java:199)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.doOscSetTextParameters(TerminalEmulator.java:2304)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.doOsc(TerminalEmulator.java:2043)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:577)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.processByte(TerminalEmulator.java:553)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.append(TerminalEmulator.java:502)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalSession$MainThreadHandler.handleMessage(TerminalSession.java:345)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.os.Handler.dispatchMessage(Handler.java:106)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.os.Looper.loopOnce(Looper.java:201)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.os.Looper.loop(Looper.java:288)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.app.ActivityThread.main(ActivityThread.java:7898)
09-06 19:10:37.740 10702 10702 E Termux  :      at java.lang.reflect.Method.invoke(Native Method)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Test image (7779x4191): https://user-images.githubusercontent.com/107305601/188685258-87eb0704-12c5-4b43-a47a-a1deae99e208.jpg

Device config: Pixel 5, Android 13, 8 GiB RAM.

@TermuxMonet
Copy link
Copy Markdown

TermuxMonet commented Sep 6, 2022

Has issues with big images. For example when using imgcat without limiting width Termux app may get crashed due to out-of-memory exception:

09-06 19:10:37.740 10702 10702 E Termux  : java.lang.OutOfMemoryError: Failed to allocate a 521628640 byte allocation with 74375880 free bytes and 232MB until OOM, target footprint 99167840, growth limit 268435456
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalBuffer.resizeBitmap(TerminalBuffer.java:48)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalBuffer.addImage(TerminalBuffer.java:199)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.doOscSetTextParameters(TerminalEmulator.java:2304)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.doOsc(TerminalEmulator.java:2043)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:577)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.processByte(TerminalEmulator.java:553)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalEmulator.append(TerminalEmulator.java:502)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.termux.terminal.TerminalSession$MainThreadHandler.handleMessage(TerminalSession.java:345)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.os.Handler.dispatchMessage(Handler.java:106)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.os.Looper.loopOnce(Looper.java:201)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.os.Looper.loop(Looper.java:288)
09-06 19:10:37.740 10702 10702 E Termux  :      at android.app.ActivityThread.main(ActivityThread.java:7898)
09-06 19:10:37.740 10702 10702 E Termux  :      at java.lang.reflect.Method.invoke(Native Method)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
09-06 19:10:37.740 10702 10702 E Termux  :      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)

Test image (7779x4191): https://user-images.githubusercontent.com/107305601/188685258-87eb0704-12c5-4b43-a47a-a1deae99e208.jpg

Device config: Pixel 5, Android 13, 8 GiB RAM.

Image displays fine on termux-monet with android:largeHeap="true" flag. Try adding this flag to your AndroidManifest.xml, it should fix oom exception. largeHeap flag does let applications to use more RAM.
And also, don't forget to disable PhantomProcessKiller, since you're on Android 13.

Device Config: Xiaomi POCO F1, Android 12L, 6GB RAM

Screenshot_20220906-133931_Termux

@cogburnd02
Copy link
Copy Markdown

I believe if MatanZ gets this imported and issue 142 gets solved, then MatanZ gets my $100 bounty on Bountysource for that issue. It will be well worth it!! :-D

@sylirre
Copy link
Copy Markdown
Member

sylirre commented Sep 6, 2022

Thanks, but anyway there is some proper workaround needed to avoid OOM. Larger heap indeed can solve it for a particular case. Not all devices have lots of memory and part is always used by other apps including system, max heap size also vary between device models.

Unfortunately I can't propose the best way to implement that, but I think refusing to display supersized bitmaps is better than crashing all sessions. For example if dimensions are too big, user can get a toast message describing the issue.

Command line programs should not be able to crash terminal or cause resource overload by just sending some control sequence (no matter whether it is sixel or something else).

@TermuxMonet
Copy link
Copy Markdown

TermuxMonet commented Sep 6, 2022

Thanks, but anyway there is some proper workaround needed to avoid OOM. Larger heap indeed can solve it for a particular case. Not all devices have lots of memory and part is always used by other apps including system, max heap size also vary between device models.

Unfortunately I can't propose the best way to implement that, but I think refusing to display supersized bitmaps is better than crashing all sessions. For example if dimensions are too big, user can get a toast message describing the issue.

Command line programs should not be able to crash terminal or cause resource overload by just sending some control sequence (no matter whether it is sixel or something else).

I don't believe that making the terminal refuse to display oversized images is necessary, since most of command line programs always will be able to crash the application when overloaded or abused.

That would only limit how the user should use his terminal, creating a "fake error" that doesn't even exist for some users, like the people who has enough RAM for displaying those images.

Those people who can display the images wouldn't be able to.

But if you guys decide that's the best thing to do, i propose placing a limit only for devices with less than 4GB RAM.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Sep 6, 2022

Here's a small guide for anyone else who wants to test this PR

For displaying images and gifs using Sixel, do pkg install libsixel and use img2sixel image.png

For displaying images using iTerm2, download the imgcat script, and use it with the command ./imgcat image.png

Just so I can get more testing from the nice people who test my code:

  • There are other sixel encoders, for example ImageMagick. Simple example: convert image.jpg sixel:-
  • gnuplot can also output sixel, but I am not sure if it uses its own encoder, or one of the above. Use set terminal sixelgd.
  • imgcat has some parameters to control scaling of the image, but you don't even need it. iterm2 protocol is as simple as: echo -en '\e]1337;File=inline=1;keepAspectRatio=0;height=70%:' ; base64 -w 0 /sdcard/z1.jpg ; echo -e '\e\\' . You can have width parameter in addition to height.

@TermuxMonet
Copy link
Copy Markdown

Tested with imagemagick, and it's displaying fine on my side

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Sep 6, 2022

Thanks, but anyway there is some proper workaround needed to avoid OOM. Larger heap indeed can solve it for a particular case. Not all devices have lots of memory and part is always used by other apps including system, max heap size also vary between device models.

Unfortunately I can't propose the best way to implement that, but I think refusing to display supersized bitmaps is better than crashing all sessions. For example if dimensions are too big, user can get a toast message describing the issue.

Command line programs should not be able to crash terminal or cause resource overload by just sending some control sequence (no matter whether it is sixel or something else).

I hope this solves the problem. I put a try{}catch() block around each bitmap operation that allocates memory. This should be similar to what you suggest - ignoring large images, without hard coding a definition of large.

I believe it is better without a toast notification. Usually when a terminal cannot (or does not want to) perform a certain operation it just ignores it, without notifying the user. Maybe a line in the log.

@agnostic-apollo
Copy link
Copy Markdown
Member

@MatanZ Please use dedicated class for long sixel logic in TerminalEmulator and TerminalBuffer.

Trying to catch OutOfMemoryError and freeing resources "should" work. For normal images, one can display bitmap at lowed res, not sure if something like that can be done with sixel, instead of showing nothing at all.

Checking current memory before even trying to display large image could be done too.

https://developer.android.com/reference/android/app/ActivityManager#getMemoryInfo(android.app.ActivityManager.MemoryInfo)

https://developer.android.com/topic/performance/graphics/load-bitmap#read-bitmap

Maybe a line in the log.

This is the way. No toasts. Being done in other places too.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Sep 7, 2022

@MatanZ brother kitty image protocol when?

The way graphics display is implemented, it is not possible to implement kitty protocol. It is possible to implement very simplified ("put image here"), but image and placement management, as well as combining text and images is impossible.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Sep 7, 2022

@MatanZ Please use dedicated class for long sixel logic in TerminalEmulator and TerminalBuffer.

Refactored some code to two new classes. I don't see anything in TerminalEmulator that belongs in another class, or which may become clearer.

Trying to catch OutOfMemoryError and freeing resources "should" work. For normal images, one can display bitmap at lowed res, not sure if something like that can be done with sixel, instead of showing nothing at all.

Checking current memory before even trying to display large image could be done too.

https://developer.android.com/reference/android/app/ActivityManager#getMemoryInfo(android.app.ActivityManager.MemoryInfo)
https://developer.android.com/topic/performance/graphics/load-bitmap#read-bitmap

I am not sure this is a good idea. I would have to add assumptions about how much memory is used by the android for various bimap manipulations (decode, scale, resize). I'll settle for trying anything the user asks for, and catching the errors. I did not crash termux with this, with all the above, and various other large images.

@agnostic-apollo
Copy link
Copy Markdown
Member

Thanks. Will take another look later myself during merging.

You can also let OutOfMemoryError happen and then try to display lower res once and see if OutOfMemoryError triggers or not and abort if it did. This way would increase chances for user to get to see something than nothing at all. Of course that would add code, so just a suggestion.

@sylirre
Copy link
Copy Markdown
Member

sylirre commented Sep 7, 2022

Seems like there is a problem with some GIFs. Notice the artifacts at the left part. Same as #142 (comment)?

Gif file doesn't seem to be corrupted.

screen-20220908-005128.2.mp4

This doesn't happen with another gif:

screen-20220908-005944.2.mp4

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Sep 8, 2022

I cannot see those videos. And can you please include the actual files used, and the commands that cause the problem?

@sylirre
Copy link
Copy Markdown
Member

sylirre commented Sep 8, 2022

Perhaps the issue with Termux img2sixel utility, not with application.

I can reproduce it when connected over SSH to Termux from Konsole on laptop. But I can't reproduce the issue when animation is played via img2sixel directly in Konsole.

Screenshot with the problem (notice the block with lines on the left side):
Screenshot_20220908-115530

You can get the file from https://upload.wikimedia.org/wikipedia/commons/thumb/2/2c/Rotating_earth_%28large%29.gif/274px-Rotating_earth_%28large%29.gif


libsixel package version differences:

  • Termux
    img2sixel 1.10.3
    
    configured with:
      libcurl: no
      libpng: no
      libjpeg: no
      gdk-pixbuf2: no
      GD: no
    
  • Laptop (OpenSUSE Tumbleweed)
    img2sixel 1.10.3
    
    configured with:
      libcurl: yes
      libpng: no
      libjpeg: yes
      gdk-pixbuf2: yes
      GD: yes
    

The difference in a build time configuration. Though maybe OpenSUSE also applies some patches to the package.

sylirre added a commit to termux/termux-packages that referenced this pull request Sep 8, 2022
Fixes issue with some GIFs.

See my comment at termux/termux-app#2973 (comment)

Also enable libcurl support.
@sylirre
Copy link
Copy Markdown
Member

sylirre commented Sep 8, 2022

Commit termux/termux-packages@b72be3c resolves img2sixel issue with animation.

termux-pacman-bot added a commit to termux-pacman/termux-packages that referenced this pull request Sep 8, 2022
Fixes issue with some GIFs.

See my comment at termux/termux-app#2973 (comment)

Also enable libcurl support.
@sylirre

This comment was marked as off-topic.

@sylirre

This comment was marked as off-topic.

@sylirre
Copy link
Copy Markdown
Member

sylirre commented Sep 9, 2022

If some package doesn't work - open a new issue in https://github.com/termux/termux-packages/issues.

@leap0x7b
Copy link
Copy Markdown

  • ✅ Works
  • ℹ️ Has problems/implementation issues
  • ❌ Doesn't work

Sixel coverage:

  • img2sixel
    Screenshot_20220917-173331.png
  • ✅ ImageMagick convert
    Screenshot_20220917-173412.png
  • ✅ Chafa
    Screenshot_20220917-173612.png
  • ✅ lsix
    Screenshot_20220917-173430.png
  • ✅ neofetch
    Screenshot_20220917-173458.png

Conclusion: All applications that uses and/or supports Sixel works properly

iTerm2 coverage:

  • ✅ Basic echo method
    Screenshot_20220917-173833.png
  • ✅ iTerm2 imgcat
    Screenshot_20220917-173740.png
  • ❌ Chafa
    When using chafa --format iterm2, Termux just straight up crashes
  • ℹ️ neofetch
    The image for some reason is in the bottom which shouldn't be
    Screenshot_20220917-174017.png
    After all of the information is fully displayed, the image for some reason suddenly vanished
    Screenshot_20220917-174031.png

Conclusion: Not all applications that uses and/or supports iTerm2 works properly

@TermuxMonet
Copy link
Copy Markdown

  • Chafa iTerm2 (chafa --format iterm2) crash report:

Report Info

User Action: crash report
Sender: TermuxActivity
Report Timestamp: 2022-09-17 11:30:33.505 UTC

Crash Details

Crash Thread: Thread[main,5,main]
Crash Timestamp: 2022-09-17 11:30:29.822 UTC

Crash Message:

Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference

Stacktrace

java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference
	at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:798)
	at com.termux.terminal.TerminalBitmap.<init>(TerminalBitmap.java:93)
	at com.termux.terminal.TerminalBuffer.addImage(TerminalBuffer.java:593)
	at com.termux.terminal.TerminalEmulator.doOscSetTextParameters(TerminalEmulator.java:2307)
	at com.termux.terminal.TerminalEmulator.doOsc(TerminalEmulator.java:2046)
	at com.termux.terminal.TerminalEmulator.processCodePoint(TerminalEmulator.java:577)
	at com.termux.terminal.TerminalEmulator.processByte(TerminalEmulator.java:553)
	at com.termux.terminal.TerminalEmulator.append(TerminalEmulator.java:502)
	at com.termux.terminal.TerminalSession$MainThreadHandler.handleMessage(TerminalSession.java:345)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:201)
	at android.os.Looper.loop(Looper.java:288)
	at android.app.ActivityThread.main(ActivityThread.java:7875)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

…age (`OSC 1337`)

Support for displaying bitmaps inside the terminal has been added via `TerminalBitmap` which can be created from image `byte[]` or sixel bitmap. The bitmaps are sliced to character cell sized slices. The `TerminalBuffer` stores a map for bitmap number to the `TerminalBitmap` loaded in the terminal. The bitmap number and coordinates are encoded in the `long` `TerminalRow.mStyle` for the `TerminalRow` character of a column by `TerminalBitmap#buildOrThrow()` by getting encoded value from `TextStyle.encodeTerminalBitmap()`. The `TerminalRenderer.render()` then checks during rendering terminal output whether a character at a row/coloumn index is a bitmap instead of text by calling `TextStyle.isTerminalBitmap()`, then draws it using `Canvas.drawBitmap()` instead of `Canvas.drawText()`.

Sixel images can be created with Sixel Device Control String command sent via `DCS q s..s ST` or `DCS P1; P2; P3; q s..s ST`.

- The `TerminalEmulator` interprets sixel sequences, and sends them to `TerminalBuffer` for constructing a `TerminalSixel`. Once the sixel command has been completely processed, a `TerminalBitmap` is created from the `TerminalSixel`. If an error occurred during processing (like OOM), then remaining sixel command is completely read, but is ignored and no sixel is drawn (done by setting `mTerminalSixel` to `null` so that `TerminalBuffer.sixelReadData()` ignores further commands).
- Since a sixel sequence can be very long to render a full image and can have length greater than `TERMINAL_CONTROL_ARGS__MAX_LENGTH` (`16384`), the entire sequence is not stored in the `mTerminalControlArgs` buffer before being processed as it will result in an overflow error, instead as soon as length crosses `TERMINAL_CONTROL_ARGS__MAX_LENGTH / 2` and a complete sixel sub command (`#`, `!`, or `"`) has been received, it is immediately processed, and then further commands are read after emptying buffer.
- If "rough" horizontal and vertical size of image is received at start of sixel data string with a `Raster Attributes` command, like done by `img2sixel` command, then sixel commands args buffer capacity (`mTerminalControlArgs`) is increased and sixel bitmap in `TerminalSixel` is resized at start, instead of having to keep resizing buffer/bitmap as more sixel data is received, which has a performance hit due to memory reallocations and copying.
- The `4` (sixel) value has been added to `CSI` `Primary Device Attributes` terminal response.

The `img2sixel` command can be used to display sixel images after installing with `libsixel` package with `pkg install libsixel`, like with `img2sixel --width=1000px image.jpg`.

To manually send an escape sequence, check the `digiater.nl` link below, but it is too cumbersome to create images large enough to be easy viewable in the terminal.

See Also:
- https://vt100.net/docs/vt3xx-gp/chapter14.html
- https://en.wikipedia.org/wiki/Sixel
- https://www.digiater.nl/openvms/decus/vax90b1/krypton-nasa/all-about-sixels.text

iTerm images can be created with `1337` Operating System Control command.

- Both `File=` and `MultipartFile=` (chunk based) protocols are supported. The `inline` parameter should be `1` to display inline images in the terminal. Downloading images to Downloads folder with the value `0` will be ignored as that is not supported.
- The escape sequences/image data cannot be greater than `TERMINAL_CONTROL_ARGS__MAX_LENGTH` (`16384`) bytes if sent via (`File=`) protocol, otherwise it will be ignored with an overflow error. For larger images, send images via `MultipartFile=` protocol in chunks with `FilePart=`, the `imgcat` utility uses that with 200-byte chunks if `--legacy` flag is not passed.
- The `TerminalEmulator` interprets iTerm images sequences and creates an `ITermImage` to process parameters and store the base64 encoded image sent. Once all the data has been received, which can be over multiple `OSC` commands for `MultipartFile=` protocol, the encoded image is decoded to a `byte[]`, which is then passed to `TerminalBuffer`, which creates a `TerminalBitmap` for the image.

The `imgcat` utility can be used for sending images, like with `imgcat --width 1000px image.jpg` (`MultipartFile=`) or `imgcat --width 1000px --legacy image.jpg` (`File=`).

To manually send an escape sequence, run `echo -en '\e]1337;File=inline=1;keepAspectRatio=0;width=1000px;:' ; base64 -w 0 ./image.jpg ; echo -e '\e\\'` (`File=`).

See Also:
- https://iterm2.com/documentation-images.html
- https://iterm2.com/utilities/imgcat
- https://github.com/gnachman/iTerm2-shell-integration/blob/d1d4012068c3c6761d5676c28ed73e0e2df2b715/utilities/imgcat
The `TerminalBitmap.MAX_BITMAP_SIZE` defines the max size of a Terminal bitmap for its pixels. Each pixel is stored on 4 bytes for a `Bitmap.Config.ARGB_8888` bitmap color config. The value should normally be between `100-200MB` depending on device and Android version. Check the variable docs for more info.

The sixel image size cannot be greater than `TerminalBitmap.MAX_BITMAP_SIZE`. The repeat value for sixel Graphics Repeat Introducer command cannot be greater than `TerminalSixel.SIXEL__MAX_REPEAT` (`8192`).

The iTerm image data sent for `File=` and `MultipartFile=` protocols cannot be greater than `TerminalBitmap.MAX_BITMAP_SIZE` bytes.
@agnostic-apollo
Copy link
Copy Markdown
Member

Thanks for the report @Kabouik.

For chafa -f sixels: The sixel DCS command P3 parameter must end with a ; and then be followed by q as per spec. Chafa does not send the semicolon, so it wasn't recognized as a valid sixel command. Have fixed it with 506183f since other clients might not be sending it either.

https://vt100.net/docs/vt3xx-gp/chapter14.html#S1S14.2.1

For chafa -f iterm: Android throws a Failed to create image decoder with message 'invalid input' error in logcat when trying to create an image from the decoded base64 image data sent by chafa with BitmapFactory.decodeByteArray(). That's likely because android does not support TIFF format used by chafa. To add support for that would require some external library to decode it, which is out of scope at this time.

You can grab APK build for current push from https://github.com/termux/termux-app/actions/runs/25403833943?pr=2973

@j4james
Copy link
Copy Markdown

j4james commented May 5, 2026

The sixel DCS command P3 parameter must end with a ; and then be followed by q as per spec.

@agnostic-apollo The example in the VT330/VT340 manual, while technically valid, is not an indication of the required format for Sixel sequences. And I suspect it's probably a typo, because the VT340 itself doesn't produce Sixel output like that when generating a hardcopy.

According to the DEC STD 070 reference (§ 3.5.4.1 and 3.5.3.1), DCS parameters should be parsed in the same way as CSI parameters. A semicolon is a parameter separator, not a parameter terminator; zero-length parameter value represents a default value; and default parameter values can be omitted from the end of the parameter list.

So something like \eP1;2;3;q is equivalent to \eP1;2;3;0q, which is also the same as \eP1;2;3q. And note that \ePq is also a perfectly valid sequence introducer, and would be the equivalent of \eP0q, \eP0;0q, or \eP0;0;0q.

@robertkirkman
Copy link
Copy Markdown
Member

…olon and allow not passing all optional parameters
@agnostic-apollo
Copy link
Copy Markdown
Member

@j4james Well, not sure if its a typo, even wikipedia says "Sixel mode is entered by sending the sequence ESC Pp1;p2;p3;q". Hard to know if real world applications were actually following it. I do know that the last parameter normally doesn't end with a semicolon, even for sixel control function commands, but I am sure we can agree that parsing is easier if every parameter were to end with a semicolon. The sixel DCS command start sequence itself is also a stupid design, the optional parameters should have come after the q instead of them coming before and parsers having to look ahead and then going back, the other DCS commands start with a $q or +q, then follow other data, could be some legacy design port issue.

https://en.wikipedia.org/wiki/Sixel

would be the equivalent of \eP0q, \eP0;0q, or \eP0;0;0q.

I wasn't allowing missing parameters before, but have fixed it with 2151d08

You can grab APK build for current push from https://github.com/termux/termux-app/actions/runs/25445369130?pr=2973

@Kabouik
Copy link
Copy Markdown

Kabouik commented May 6, 2026

Awesome, thanks @agnostic-apollo, I can confirm chafa and my TUI streaming app now show covers correctly (though with some flicker and slowness that I don't observe on desktop Linux):

Screenshot_20260506-201543

@agnostic-apollo
Copy link
Copy Markdown
Member

agnostic-apollo commented May 6, 2026

Welcome. An android device or at least termux is likely going to be slower than desktop, but check if resolution of video being passed is not higher than display size, otherwise share the commands and video you are trying to view and I can try to test. Is video streaming app using sixel or iterm?

@Kabouik
Copy link
Copy Markdown

Kabouik commented May 6, 2026

Welcome. An android device or at least termux is likely going to be slower than desktop, but check if resolution of video being passed is not higher than display size, otherwise share the commands and video you are trying to view and I can try to test. Is video streaming app using sixel or iterm?

It's just music album covers which are all 640×640. However, I don't have time to dive into it right now, but I believe there are many redraws occurring when the TUI is navigated, and that may exacerbate any slowness compared to desktop. It should be using chafa -f sixels, but I need to check if that logic is actually respected because the flickering I'm observing should not be there if that was really the case, since I don't observe it on PC with the same dependencies.

@agnostic-apollo
Copy link
Copy Markdown
Member

Okay, let me know if you figure it out. iterm is 20x faster than sixel though.

I'll wait a couple of days more for any more tests by users, before I merge and make a release.

@robertkirkman
Copy link
Copy Markdown
Member

@Kabouik
Copy link
Copy Markdown

Kabouik commented May 7, 2026

Okay, let me know if you figure it out. iterm is 20x faster than sixel though.

I'll wait a couple of days more for any more tests by users, before I merge and make a release.

An official Termux release with sixel support? Awesome.

However I just tested chafa -f iterm /path/to/jpg and it shows nothing at all. You suggested using iterm because it's faster, so does it work on your end?

@robertkirkman
Copy link
Copy Markdown
Member

I can confirm that,

for me, chafa -f sixel (chafa sixel), img2sixel (1st-party-ish sixel tool), and imgcat (1st-party-ish iterm2 tool) all work, but chafa -f iterm does not seem to work.

imgcat comes from:

curl https://iterm2.com/utilities/imgcat > $PREFIX/bin/imgcat
chmod +x $PREFIX/bin/imgcat
Screenshot_20260507-113857_Termux

@agnostic-apollo
Copy link
Copy Markdown
Member

As I already said chafa will not work with iterm as it sends the image in tiff format regardless of source image format, and tiff is not supported by android platform libraries for decoding. To support tiff in termux end would require adding libtiff or some other external native/java library in the app, adding native libraries is complex for an Android app and I avoid adding dependencies unless necessary. And it may not be needed anyways as chafa maintainer says he plans to shift to PNG images, which will automatically work in termux when chafa shifts, you can track that in hpjansson/chafa#333.

@agnostic-apollo
Copy link
Copy Markdown
Member

Instructions are also available at end of #2973 (comment) for how to use iterm protocol with just simple echo/base64 commands without any other external tool, like imgcat/chafa.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented May 7, 2026

chafa uses uncompressed tiff. It would be quite easy to decode without any external library.

@agnostic-apollo
Copy link
Copy Markdown
Member

I haven't looked at how tiff format works, and how chafa is using it. And not sure whether a simpler decoding will work for other clients.

I also don't have time to work or even research into it currently, I'm busy with termux rewrite, if you want work on it yourself right now, let me know and I'll hold off merging the pull. Otherwise can wait for chafa to switch to png or add basic tiff support in future and merge for now.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented May 7, 2026

I believe it is better to merge (and release) as soon as possible. chafa works in sixel mode, and other tools work also in iterm2 mode.

It is always possible to add this later if desired.

@robertkirkman
Copy link
Copy Markdown
Member

robertkirkman commented May 7, 2026

The link and explanation above suggests that a future version of chafa plans to switch to png for iterm2, which would solve the problem without termux intervention, I guess? in my opinion that makes it not a high priority unless many other iterm2 apps also require tiff support.

@agnostic-apollo
Copy link
Copy Markdown
Member

Ok, merge it is then.

@WALElionel
Copy link
Copy Markdown

Wifizone je suis très intéressé

@robertkirkman
Copy link
Copy Markdown
Member

On April 17th the status was that approval of the current version was required from MatanZ, and on May 7th MatanZ gave approval of this version.

In between those dates, on May 6th, an ETA of 2 days was given, and if the merging preparation work is still ongoing and more time is needed then that is fine, I just thought that I should mention that time passed since an ETA was given.

@agnostic-apollo
Copy link
Copy Markdown
Member

Sorry, pull is ready to be merged, other than squashing commits and updating commit messages. I have been busy with finishing some of the termux-app rewrite stuff and couldn't shift attention from it to do a release as I have a limited context window size. Will get to it soon.

@robertkirkman
Copy link
Copy Markdown
Member

Thanks for the update, based on the context I had just assumed that you were prepared to and wanted to merge this PR before finishing the major rewrite project, but if it would be easier and less stressful for you to postpone it until afterward I wouldn't mind that.

@agnostic-apollo
Copy link
Copy Markdown
Member

I didn't mean the entire rewrite, I meant a part of it, have had to think on some complex issues and couldn't be distracted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support for CSI 14 Feature Request: sixel graphics mode