Skip to content

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

Open
MatanZ wants to merge 8 commits intotermux:masterfrom
MatanZ:sixel4
Open

Add graphics in terminal support: - Sixel and iTerm2 protocols#2973
MatanZ wants to merge 8 commits intotermux: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)

kyehn pushed a commit to kyehn/nix-on-droid-app that referenced this pull request Feb 17, 2026
Implement the following CSI escape sequences from
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html:

> CSI Ps ; Ps ; Ps t
> [..]
>    Ps = 1 4  ⇒  Report xterm text area size in pixels.
>    Result is CSI  4 ;  height ;  width t
> [..]
>    Ps = 1 6  ⇒  Report xterm character cell size in pixels.
>    Result is CSI  6 ;  height ;  width t

Extracted from changes in termux#2973
by @MatanZ and adopted to play well with the just merged termux#3098 (.ws_xpixel
and .ws_ypixel values in winsize).
kyehn pushed a commit to kyehn/nix-on-droid-app that referenced this pull request Feb 17, 2026
Implement the following CSI escape sequences from
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html:

> CSI Ps ; Ps ; Ps t
> [..]
>    Ps = 1 4  ⇒  Report xterm text area size in pixels.
>    Result is CSI  4 ;  height ;  width t
> [..]
>    Ps = 1 6  ⇒  Report xterm character cell size in pixels.
>    Result is CSI  6 ;  height ;  width t

Extracted from changes in termux#2973
by @MatanZ and adopted to play well with the just merged termux#3098 (.ws_xpixel
and .ws_ypixel values in winsize).
MatanZ and others added 8 commits April 9, 2026 03:24
…n` only on Android 5 as later function is not available on it and will cause a runtime crash

The change was done in 6c00f1f via termux#2997
… text received for setting to clipboard from terminal and fix error format typo
… for OSC/DCS/APC in `TerminalEmulator`, add support for fast path for OSC/DCS terminal commands, and properly clear buffer after commands

Rename `ESC_P` to `ESC_DCS`, `ESC_APC_ESCAPE` to `ESC_APC__ESC`, `doDeviceControl()` to `doDcs()`, `doOsc()` to `receiveOsc()`, `doOscEsc()` to `receiveOscEsc()`, `doApc()` to `receiveApc()`, `doApcEscape()` to `receiveApcEsc()`, `doOscSetTextParameters()` to `doOsc()`.

`mOSCOrDeviceControlArgs` `StringBuilder` buffer has been renamed to `mTerminalControlArgs` as it stores args for all 3 OSC/DCS/APC commands.

Additionally, for `mTerminalControlArgs`, only `setLength(0)` was called at end of commands to clear it, which will only update internal length marker and not reduce internal array capacity, and to deallocate extra memory `trimToSize()` needs to be called, which creates another smaller array. So if an OSC/APC command was ever received with `MAX_OSC_STRING_LENGTH`, then the buffer size would never be brought back down to a lower capacity (default 16bytes), which would have slightly affected performance/memory for later smaller commands. That is fixed now by `clearTerminalControlArgs()` with a newer logic for freeing memory, which is required to free memory for long commands like sixels or iterm image to be added later.

`ensureTerminalControlArgsCapacity()` has also been added to allow control commands to increase buffer capacity to reduce/prevent reallocation and copying of memory as more data is received.

`collectTerminalControlArgs()` has been updated with better error message, catches OOM in case caused when buffer capacity is increased when appending, and returns `false` if an error occurred like an overflow/OOM so that callers can terminate processing. This was required because `receiveApcEsc()`/`receiveOscEsc()` functions appended `ESC`/`27` and then appended `b` without checking if `collectOSCArgs()` had already failed when collecting `ESC`, triggering a second error. `collectTerminalControlArgs()` is now also used for `DCS` commands properly handle errors/exceptions.

`MAX_OSC_STRING_LENGTH` has been renamed to `TERMINAL_CONTROL_ARGS__MAX_LENGTH` and the length has been increased from `8192` to `16384` to receive larger sixel data and iTerm image data. With `16384`, around 12KB images can be received with legacy `File=` command, otherwise with `8192`, any image over 6KB would have triggered an overflow. Sixel commands strings can also be split into larger parts in future with the new limit.

The `TerminalEmulator.processCodePoint()` processes bytes received by the terminal, however there are around ~30 case comparisons done before `ESC_OSC` or `ESC_DCS` is processed for each byte received. While it may be necessary for general commands, some commands like sixel or iterm image data parse their own input byte, which may be random data, so a fast path has been added at start of `processCodePoint()` to call `doOsc` or `doDcs()` if enabled by the currently running command. Fast path can be enabled for OSC commands via `setOscTypeVariables()` and for `DCS` commands via `doDcs()`.

OSC commands can also prevent CR/LF characters being printed by enabling `mIgnoreCrLfForOsc` via `setOscTypeVariables()`.

Escape states for Terminal APC commands fast path added in f35063d to `TerminalEmulator.processCodePoint()` has also been fixed.
- 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 iTerm 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
…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
@agnostic-apollo
Copy link
Copy Markdown
Member

agnostic-apollo commented Apr 9, 2026

Hi @MatanZ, thanks for implementing sixels and iTerm images. Sorry, it took this long to look over this, didn't get time (not that I had time right now).

I have added my changes in 948ffa4 and 430ae28 primarily, but have kept your old ones in a7f2872 for now after rebasing against master and fixing conflicts. I will squash both sixel commits before merging pull, leaving it now to track old and new changes. While I have reviewed/fixed/improved changes for bitmaps/sixel/iterm, I have not fully looked at the rendering/scaling bitmap parts, but they seem to be working fine, will look into them in future when I have more time and issues arise.

Users should thoroughly test the terminal for both sixel and iterm images, and also other text editors, etc in case something got broken. I have done by own testing for the entire protocols, but not a variety of images (primarily tested attached interstellar image). Once testing has been done and @MatanZ reviews the changes, I will merge the pull and make a new Termux app version release for F-Droid users.

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

Commit message for 430ae28 is below.


Added: Add terminal support for Bitmaps, Sixel (DCS q) and iTerm Image (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:

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:


284x177

interstellar

bitmap-tests

1920x1200

interstellar1

bitmap-large-tests

@Kreijstal
Copy link
Copy Markdown

finally I can get termux again

@robertkirkman
Copy link
Copy Markdown
Member

robertkirkman commented Apr 9, 2026

If someone who already has Termux installed wants to test this without necessarily having to back up, erase and then later reinstall the regular Termux, they can use this two.termux with this PR applied that I built.

I also built a com.termux one with similar settings, but have not linked it because agnostic-apollo already linked one above so if you don't already have a com.termux installed you should use their link.

In order to avoid speculation from anyone who notices that the build I linked also currently contains this other PR,

I would like to acknowledge that and also emphasize that the termux-generator links I send are not for replacing or competing with the official Termux as a fork with more PRs merged, so that detail should not be interpreted that way. I send them because I want PRs that "request testers" to be accessible to anyone who only has 1 device and for whom uninstalling and reinstalling the regular Termux constantly while testing PRs is frustrating or inconvenient.

@robertkirkman
Copy link
Copy Markdown
Member

robertkirkman commented Apr 10, 2026

I have tested this in comparison to the konsole package running in Termux:X11, and for the most part it looks great,

Screenshot_20260410-021158_Termux_X11

However, I do seem to have found a sixel image test case that konsole currently seems to be able to handle better than this PR.

When I use img2sixel arrow.jpg on this large-resolution arrow.jpg I found, this PR doesn't seem to be able to display any portion of it, while konsole displays the portion that it's able to fit into its window.

Screenshot_20260410-021502_Termux_X11

arrow

however, if I reduce the resolution of arrow.jpg to something more reasonable using an image editor, I'm able to display it in both konsole and this PR:

Screenshot_20260410-022526_two termux

arrow_small

That seems somewhat reasonable; there has to be a size limit at some point, and maybe the size limit of konsole is just greater than that of this PR.

@agnostic-apollo
Copy link
Copy Markdown
Member

Likely this is engaging.

https://github.com/termux/termux-app/pull/2973/changes#diff-f729a9bbec25dbca63392265f8852d3ec8d4f0afa848fccf3b2db837c457ab98R29

@robertkirkman
Copy link
Copy Markdown
Member

robertkirkman commented Apr 10, 2026

I see, I think 2048 is a reasonable limit for sixel.

I've tested imgcat (iterm2) now as well, and interestingly, I've noticed that imgcat is able to display the same image at full resolution with no problems,

Screenshot_20260410-035115_two termux

however, imgcat is also faster. It takes much less time to display the large image than img2sixel, even when img2sixel is limited to a working size with --width=2000.

If iterm2 is just more efficient than sixel, it's probably best for the size limit of sixel to be smaller than the size limit of iterm2 (as it currently is)

@agnostic-apollo
Copy link
Copy Markdown
Member

agnostic-apollo commented Apr 10, 2026

There are some discussions at following links about size limits, I choose 2048x2048 (which uses 16MB memory) as max sixel bitmap dimensions as android devices are generally more stricter with RAM allocation for apps than linux distros and will trigger an OOM, especially with all the other data and processes running in termux. Only issue seems to be that sixel command should still have been read completely without being printed to screen, and no image displayed, just like iterm.

contour-terminal/contour#680
contour-terminal/contour#656 (comment)

@agnostic-apollo
Copy link
Copy Markdown
Member

iterm (or possibly imgcat) is around 20 times faster than sixel, I did calculations and rendering same image with iterm was around 7-10ms on average, compared to around 200ms or more for sixel.

@robertkirkman
Copy link
Copy Markdown
Member

Only issue seems to be that sixel command should still have been read completely without being printed to screen, and no image displayed, just like iterm.

It displays no output for a small range of sizes around 2049-2100, then after that it starts displaying this large text artifact in the terminal, but shows similar messages in logcat both cases:

image

@agnostic-apollo
Copy link
Copy Markdown
Member

Can you post logcat errors for that?

@agnostic-apollo
Copy link
Copy Markdown
Member

agnostic-apollo commented Apr 10, 2026

The sixel repeat command Pn value 4500 is greater than max repeat value 2048 at index 3410 of sixel input.

Ah, a repeat value of 4500 is considered as an invalid sixel sequence and further input is not read, and that gets printed to screen. Will have to think on whether to continue reading or keep as is, probably former as 4500 is a valid integer sent by client, we just dont accept it. Other sixel command limits would need to be looked too.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Apr 10, 2026

Hi @MatanZ, thanks for implementing sixels and iTerm images. Sorry, it took this long to look over this, didn't get time (not that I had time right now).

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.

I understand supporting MultipartFile=. It was not in iterm2 when I wrote this patch. But intentionaly crippling File= seems to me pointless. There are a lot of older tools that only support 'File=' method. They work with my patch, but not with yours. Furthermore, limiting chunks to 16K, when iterm2 uses 1MB, with no way to query the terminal for this value, mean that programs need to test terminal type and have a database of terminal capabilities, which is not a reasonable approach.

I suggest removing the arbitrary 16K limit for both File= and MultipartFile= methods.

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Apr 10, 2026

There are some discussions at following links about size limits, I choose 2048x2048 (which uses 16MB memory) as max sixel bitmap dimensions as android devices are generally more stricter with RAM allocation for apps than linux distros and will trigger an OOM, especially with all the other data and processes running in termux. Only issue seems to be that sixel command should still have been read completely without being printed to screen, and no image displayed, just like iterm.

contour-terminal/contour#680 contour-terminal/contour#656 (comment)

Agan, I prefer to avoid unnecessary arbitrary limits. Termux does run on screens wider than 2048 pixels, and programs might want to use all that. Images need not be square, so one might want to draw a 2300x50 header for the page.

If you want to reduce the chance of OOM, maybe limit actual area, instead of each dimension separately.

Not that there are no problems with supporting large sixels. lsix for example, uses maximum sixel size rather than actual screen width to decide its display width.

@robertkirkman
Copy link
Copy Markdown
Member

I prefer to avoid unnecessary arbitrary limits. Termux does run on screens wider than 2048 pixels, and programs might want to use all that.

Do you happen to know whether Konsole and other terminal emulators are really fully unlimited, or do they just have a size limit that's really big?

@MatanZ
Copy link
Copy Markdown
Contributor Author

MatanZ commented Apr 10, 2026

There actually need to be limits, for reporting in XTSMGRAPHICS escape sequence.

In foot it is 10000 pixels. In konsole, 16384.

@robertkirkman
Copy link
Copy Markdown
Member

robertkirkman commented Apr 10, 2026

Maybe if the limit were set to something around 8192 in Termux, that would be an acceptable compromise? That is somewhat in between the limit agnostic-apollo wanted, and the limits of other terminals. Estimating based on Agnostic-apollo's previous calculation, I think that would put the maximum memory use somewhere in between 100 and 200 MB, which isn't actually much even on Android. (assuming only attempting to display one large image at a time)

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