Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

MatanZ
Copy link
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

aicynide commented Sep 5, 2022

You are a hero my brother

@TermuxMonet
Copy link

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
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

oh, so everything's working fine then

@TermuxMonet
Copy link

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
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

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

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
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

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
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

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

@MatanZ
Copy link
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
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
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
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
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
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
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
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
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
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

  • ✅ 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

  • 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)

@Kreijstal
Copy link

idc if this get merged, where can I get an apk ??? o.o
me want

@AnonymouX47
Copy link

@Kreijstal
Copy link

@Kreijstal: https://github.com/KitsunedFox/termux-monet

I uninstalled termux so fast I forgot to backup my configuration.
Doesn't matter sixel, was worth it.

@AnonymouX47
Copy link

For what it's worth, I think the APK signatures are compatible. Over here, I can install one over the other (and vice versa) without loosing any data... though, I'm not sure how good an idea it is.

@ikcikoR
Copy link

ikcikoR commented Apr 2, 2024

For what it's worth, I think the APK signatures are compatible. Over here, I can install one over the other (and vice versa) without loosing any data... though, I'm not sure how good an idea it is.

Not if you got your Termux from f-droid sadly

@aicynide
Copy link

Waiting for kitty support

@TomJo2000
Copy link
Member

Out of scope for this PR, please do not reply to dormant threads unnecessarily.
It notifies everybody involved in the thread.

SastroXXX

This comment was marked as spam.

fornwall added a commit that referenced this pull request Sep 26, 2024
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 #2973
by @MatanZ and adopted to play well with the just merged #3098 (.ws_xpixel
and .ws_ypixel values in winsize).
fornwall added a commit that referenced this pull request Sep 26, 2024
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 #2973
by @MatanZ and adopted to play well with the just merged #3098 (.ws_xpixel
and .ws_ypixel values in winsize).
Sodastream11 pushed a commit to Sodastream11/termux-app that referenced this pull request Sep 27, 2024
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).
- 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
Add missing {} that change the logic.
- For iterm2 images - catch the error, and cancel the image.
- For sixels - if it happens when resizing the bitmap, than ignore drawing
  outside of the current image.
- Move working bitmap code (drawing current bitmap) to
  WorkingTerminalBitmap class.
- Move bitmap handling code to TerminalBitmap class.
To avoid removing elements from the map while iterating over it.
This should solve the Concurrent Modification Exception in the bitmap
garbage collection.
Avoid crash when BitmapFactory cannot decode image
…last column.

This creates a zero length text run, so skip it.
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