Displaying images on the terminal

A project log for Handheld Linux Terminal [HaLiTerm]

A compact serial terminal with a built-in NanoPi Neo Air running DietPi.

balazsBalazs 10/18/2023 at 17:570 Comments

This is a crude and very slow solution to show images on the Terminal from within the Linux command line using a custom escape code and a simple Python program.

// Usage

path/to/ -[options] path/to/image.jpg

-w    horizontal size (default 300)
-h    vertical size (default 200)
-s    max. 100x100 pixel
-l    max. 600x400 pixel

// (requires the pillow library)


from sys import argv
from PIL import Image

screen_x = 300
screen_y = 200

# get arguments
def get_args():
    global screen_x
    global screen_y
    for i in range(1, len(argv)):
        if argv[i] == "-w":
            w = int(argv[i+1])
            if w <= 600 and w > 0:
                screen_x = w
        elif argv[i] == "-h":
            h = int(argv[i+1])
            if h <= 400 and h > 0:
                screen_y = h
        elif argv[i] == "-s":
            screen_x = 100
            screen_y = 100
        elif argv[i] == "-l":
            screen_x = 600
            screen_y = 400
    return argv[-1]

# print pixel data to stdout
def rgb_to_stdout(image):
#    img = image.convert(mode="RGBA")
    img_x, img_y = img.size
    print("{0}[{1};{2}z".format(chr(27), img_x, img_y), end="")
    for y in range(0, img_y):
        for x in range(0, img_x):
            r, g, b = img.getpixel((x, y))
            r = r // 8
            if r == 10:
                r = 11
            g = g // 4
            if g == 10:
                g = 11
            b = b // 8
            if b == 10:
                b = 11
            print("{0}{1}{2}".format(chr(r), chr(g), chr(b)), end="")

# rotate image if necessary
def check_rotation(img):
    x, y = img.size
    if ((screen_x > screen_y) and (y > x)) or ((screen_x < screen_y) and (y < x)):
        return img.rotate(90, expand=True)
        return img

# scale image
def rescale(img):
    x, y = img.size
    scale_x = x / screen_x
    scale_y = y / screen_y
    if scale_x > scale_y:
        return img.resize((screen_x, int(y / scale_x)))
        return img.resize((int(x / scale_y), screen_y))

# program entry
path = get_args()
img =, "r")
img = check_rotation(img)
img = rescale(img)

 // Details

Three weeks ago the idea struck me, that I could try to send pixel values over UART from the NanoPi to the Pico and write them directly to the DDRAM of the RA8875. This solution involves a custom escape code (ESC [ (size_x ); (size_y) z) and printing the pixel RGB565 values as characters to stdout. The terminal expects exactly three times the number of pixels as characters after the escape code and keeps writing the values to the DDRAM of the RA8875 until all pixels are received. Printing values over 127 as character to stdout results in an UTF-8 encoding. Because of this, the RGB values are converted to 565 bit format before using the chr() function. After some trial and error I figured out, that print(chr(10)) didn't work, so I simply change the channel value to 11 if it would be 10.