GiorgosXou / TUIFIManager

A cross-platform terminal-based termux-oriented file manager (and component), meant to be used with a Uni-Curses project or as is.
GNU General Public License v3.0
697 stars 13 forks source link

SIXEL support #86

Open GiorgosXou opened 1 year ago

GiorgosXou commented 1 year ago

So I think I found out how to display sixels properly with ueberzug

Preview

sixel_test_Peek 2023-08-15 15-02 Flexing a picture I took of a wasp, found on top of a harvesting net for olives with the 2017 Xiaomi Redmi 5A

Minimal example

import ueberzug.lib.v0 as ueberzug
import time

canvas = ueberzug.Canvas()
demo = None

# @ueberzug.Canvas()
def main(): # canvas
    global demo
    canvas.__enter__()
    paths = ['/home/xou/Desktop/xou/multimedia/images/from phone/IMG_20221201_004752.jpg', '/home/xou/Desktop/xou/multimedia/images/from phone/IMG_20221201_004752.jpg']
    demo = canvas.create_placement('add', x=0, y=0, width=10, height=10 )
    demo.path = paths[0]
    demo.visibility = ueberzug.Visibility.VISIBLE
    # time.sleep(2)

if __name__ == '__main__':
    print('Drawing Sixel')
    main()
    time.sleep(1)
    print('Changing Sixel')
    with canvas.lazy_drawing:
        demo.x = 10
        demo.y = 1
        demo.width = 40
        demo.height = 40
    time.sleep(1)
    print('Hiding Sixel')
    demo.visibility = ueberzug.Visibility.INVISIBLE
    time.sleep(1)
    print('Showing Sixel')
    demo.visibility = ueberzug.Visibility.VISIBLE
    time.sleep(2)
GiorgosXou commented 1 year ago

Proof of concept

Don't get too excited, I don't have a lot of free time to work on it (money would help) sixel_proof_of_concept_Peek 2023-09-18 16-01

GiorgosXou commented 9 months ago

Actually I should use uberzug++

GiorgosXou commented 6 months ago

You know what... I want to stay original, so... I replaced alacritty with alacritty-sixel-branch and I'll start expirimenting with real sixels instead of trying the work-around uberzug method.

GiorgosXou commented 6 months ago

👁️ Preview

Nice... we have some progress. After some intense pitfalls, research, trial and error, I finally figured out how to print actual sixels inside ncurses, without uberzug.

tuifi_original_sixel_test_Peek 2024-03-24 16-31

(Preview Might seem as a joke, but (just so you know) it is a huge progress!)

👨‍💻 Code

Here's how i managed to do so:

Click to expand ```python from io import StringIO import unicurses as uc from PIL import Image class SixelConverter: # https://github.com/lubosz/python-sixel def __init__(self, f8bit=False, ncolor=256): self._slots = [0] * 257 self._ncolor = ncolor if f8bit: # 8bit mode self.DCS = '\x90' self.ST = '\x9c' else: self.DCS = '\x1bP' self.ST = '\x1b\\' def __write_header(self): # start Device Control String (DCS) uc.putp(self.DCS) # write header aspect_ratio = 7 # means 1:1 background_option = 0 # background_option = 1 dpi = 75 # dummy value template = '%d;%d;%dq"1;1;%d;%d' args = (aspect_ratio, background_option, dpi, self.width, self.height) uc.putp(template % args) def __write_palette_section(self): palette = self.palette # write palette section for i in range(0, self._ncolor * 3, 3): no = i / 3 r = palette[i + 0] * 100 / 256 g = palette[i + 1] * 100 / 256 b = palette[i + 2] * 100 / 256 uc.putp('#%d;2;%d;%d;%d' % (no, r, g, b)) def __write_body_without_alpha_threshold_fast(self, data ): height = self.height width = self.width n = 1 for y in range(0, height): p = y * width cached_no = data[p] count = 1 c = -1 for x in range(0, width): color_no = data[p + x] if color_no == cached_no: # and count < 255: count += 1 else: if cached_no == -1 : c = 0x3f else: c = 0x3f + n if self._slots[cached_no] == 0: palette = self.palette r = palette[cached_no * 3 + 0] * 100 / 256 g = palette[cached_no * 3 + 1] * 100 / 256 b = palette[cached_no * 3 + 2] * 100 / 256 self._slots[cached_no] = 1 uc.putp('#%d;2;%d;%d;%d' % (cached_no, r, g, b)) uc.putp('#%d' % cached_no) if count < 3: uc.putp(chr(c) * count) else: uc.putp('!%d%c' % (count, c)) count = 1 cached_no = color_no if c != -1 and count > 1: if cached_no == -1 : c = 0x3f else: if self._slots[cached_no] == 0: palette = self.palette r = palette[cached_no * 3 + 0] * 100 / 256 g = palette[cached_no * 3 + 1] * 100 / 256 b = palette[cached_no * 3 + 2] * 100 / 256 self._slots[cached_no] = 1 uc.putp('#%d;2;%d;%d;%d' % (cached_no, r, g, b)) uc.putp('#%d' % cached_no) if count < 3: uc.putp(chr(c) * count) else: uc.putp('!%d%c' % (count, c)) if n == 32: n = 1 uc.putp('-') # write sixel line separator else: n <<= 1 uc.putp('$') # write line terminator def __write_body_section(self): data = self.data self.__write_body_without_alpha_threshold_fast(data) def __write_terminator(self): # write ST uc.putp(self.ST) # terminate Device Control String def getvalue(self): output = StringIO() try: self.write(output) value = output.getvalue() finally: output.close() return value def write(self, file, w=None, h=None,): self._slots = [0] * 257 image = Image.open(file) image = image.convert("RGBA" if image.format == "PNG" else "RGB").convert("P", palette=Image.Palette.ADAPTIVE, colors=self._ncolor) if w or h: width, height = image.size if not w: w = width if not h: h = height image = image.resize((w, h)) self.palette = image.getpalette() self.data = image.getdata() self.width, self.height = image.size self.__write_header() self.__write_body_section() self.__write_terminator() uc.putp('\n') stdscr = uc.initscr() # Global UniCurses Variable event = -1 uc.start_color ( ) uc.cbreak ( ) uc.noecho ( ) uc.curs_set (0) uc.mouseinterval(0) # Initializing Mouse and then Update/refresh() stdscr uc.mousemask (uc.ALL_MOUSE_EVENTS | uc.REPORT_MOUSE_POSITION) # print("\033[?1003h\n") uc.keypad (stdscr, True ) uc.nodelay (stdscr, False) uc.raw() uc.move(3,7) uc.refresh ( ) HEIGHT,WIDTH = uc.getmaxyx(stdscr) c = SixelConverter() i = 1 while event != 27 : # Main loop event = uc.get_wch() if event == uc.KEY_MOUSE: uc.clear() continue uc.lib1.mvcur(HEIGHT-1, WIDTH-1, 5,i) c.write("/home/xou/Downloads/me.png", h=96,w=96) i+=1 if event == uc.CCHAR('a'): uc.mvaddstr(i,10, 'adfabfea') # uc.lib2.puts(uc.CSTR("\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\" )) uc.refresh() if event == uc.KEY_RESIZE: uc.resize_term(0,0) uc.endwin() # # print("\x1bPq#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0#1~~@@vv@@~~@@~~$#2??}}GG}}??}}??-#1!14@\x1b\\") # c = SixelConverter() # c.write("/home/xou/Downloads/me.png", h=160,w=160) # c.write("/home/xou/Downloads/image.jpg", h=160,w=560) # c.write("/home/xou/Downloads/Afisa.jpg", h=60,w=150) ```

🌐 Research

Sixel and curses related subjects related to:

GiorgosXou commented 6 months ago

it works as expected on alacritty-sixel-branch and wezterm but not on xterm for some reason (under ncurses)

GiorgosXou commented 6 months ago

Ok it actually works just fine with xterm -ti vt340. also this video