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

SIXEL support #86

Open
GiorgosXou opened this issue Aug 15, 2023 · 6 comments
Open

SIXEL support #86

GiorgosXou opened this issue Aug 15, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@GiorgosXou
Copy link
Owner

GiorgosXou commented Aug 15, 2023

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 GiorgosXou added the enhancement New feature or request label Aug 15, 2023
@GiorgosXou
Copy link
Owner Author

GiorgosXou commented Sep 18, 2023

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
Copy link
Owner Author

GiorgosXou commented Dec 21, 2023

Actually I should use uberzug++

@GiorgosXou
Copy link
Owner Author

GiorgosXou commented Mar 23, 2024

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
Copy link
Owner Author

GiorgosXou commented Mar 24, 2024

👁️ 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
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
Copy link
Owner Author

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

@GiorgosXou
Copy link
Owner Author

GiorgosXou commented Mar 25, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant