Console Images (B&W to 24bit)

This article will review the progress from BW images in the console to 24 bit images in the following sequence.

  1. BW

  2. 48 colors

  3. 216 colors

  4. 24bit

my plush toy on the background of the monitor, the image in the console
my plush toy on the background of the monitor, the image in the console

Text Images

It is necessary to create a symbol table in increasing brightness

block_table: list[str] = [" ", "▂", "▃", "▄",
                          "▅", "▆", "▇", "█"]

To work with images, import PIL.Image

Prepare your image for conversion:

  • Convert the image to RGB

  • Resize the image as desired

  • Make a copy of the image in Grayscale

from PIL.Image import Image

size: tuple[int, int] = (..., ...)
image: Image = Image.open("filepath.extension").resize(size)
image = image.convert("RGB")
image_grayscale: Image = image.convert("L")

Let’s write a simple function for converting the brightness of a pixel into a symbol of our table

Through the function
def bright_to_symbol(bright: int) -> str:
  return block_table[round(bright / 255 * (len(block_table) - 1))]
Through lambda
bright_to_symbol: typing.Callable[[int], str] = 
	lambda bright: block_table[
  	round(bright / 255 * (len(block_tableee) - 1))]

We go through the grayscale of the copy and convert the brightness to symbols, outputting everything to the console

for i in range(image.height):
  for j in range(image.width):
    print(bright_to_symbol(
      	 image_grayscale.getpixel((j, i))),
         end = "")
	print()
  
Minecraft screenshot with shaders
Minecraft screenshot with shaders

48 colors

8 and 16 colors are generally meaningless to consider

To achieve 48 colors out of 16 available in standard terminals, you need to use the BRIGHT and DIM styles, in order to add 2 variants with these styles to each color

We create a palette of such a plan

colors: typing.Dict[str, typing.List[str]] = 
    {"GREEN": [colorama.Style.DIM + colorama.Fore.GREEN,
               colorama.Fore.GREEN,
               colorama.Style.BRIGHT + colorama.Fore.GREEN,
               colorama.Style.DIM + colorama.Fore.LIGHTGREEN_EX,
               colorama.Fore.LIGHTGREEN_EX, colorama.Style.BRIGHT +
               colorama.Fore.LIGHTGREEN_EX
               ],
		... : [...]
    }

Writing color borders

def color_it48(color: typing.Tuple[int, int, int]) -> str:
    """48 colors"""
    if all([col > 240 for col in color]) 
            and color[0] * 3 - 10 < sum(color) < color[0] * 3 + 10:
        return colors["WHITE"][
            round((len(colors["WHITE"]) - 1)
                  / 255 * (color[1] + color[2]) / 2)]
    if all([col < 30 for col in color]) 
            and color[0] * 3 - 10 < sum(color) < color[0] * 3 + 10:
        return colors["BLACK"][
            round((len(colors["BLACK"]) - 1)
                  / 255 * (color[1] + color[2]) / 2)]
    if max(color) == color[1] and color[1] > color[0] + color[2] - 20:
        return colors["GREEN"][
            round((len(colors["GREEN"]) - 1)
                  / 255 * color[1])]
    if max(color) == color[0] and color[0] > sum(color[1:3]) - 20:
        return colors["RED"][
            round((len(colors["RED"]) - 1)
                  / 255 * color[0])]
    if max(color) == color[2] and color[2] > sum(color[0:2]) - 20:
        return colors["BLUE"][
            round((len(colors["BLUE"]) - 1)
                  / 255 * color[0])]
    if color[1] + color[2] > color[0] * 2 + 40:
        return colors["CYAN"][
            round((len(colors["CYAN"]) - 1)
                  / 255 * (color[1] + color[2]) / 2)]
    if color[0] + color[2] > color[1] * 2 + 40:
        return colors["MAGENTA"][
            round((len(colors["MAGENTA"]) - 1)
                  / 255 * (color[2] + color[1]) / 2)]
    if sum(color[0:2]) > color[2] * 2 + 40:
        return colors["YELLOW"][
            round((len(colors["YELLOW"]) - 1)
                  / 255 * (color[1] + color[0]) / 2)]
    return ""
  

Adding colors to the character variant

for i in range(0, image.height):
  for j in range(0, image.width):
    print(color_it48(image.getpixel((j, i))) +
          bright_to_symbol(image_grayscale.getpixel((j, i))),
          end='')
  print()
48 colors, looks scary
48 colors, looks scary

216 colors

For this color, your terminal must support xterm-256colors

https://robotmoon.com/256-colors/
If we consider the RGB values ​​of colors, then you can find a simple sequence, save

pal: typing.List[int] = [0, 95, 135, 175, 215, 255]

To convert RGB colors to xterm-256colors color number, we will write a function that will determine which values ​​are closest to the color
For example (100, 100, 100) -> [1, 1, 1] i.e. (95, 95, 95)

def get_pal(color: typing.Tuple[int, int, int]) -> typing.List[int]:
    """Get nearest value of pal to color's rgb"""
    col_data: typing.List[int] = []
    for col in color:
        added: bool = False
        for i in enumerate(pal[1:]):
            added = False
            if (col - pal[i[0]]) / (i[1] - pal[i[0]]) < 0.5:
                col_data.append(i[0])
                added = True
                break
        if not added:
            col_data.append(len(pal) - 1)
    return col_data
  

Now you need to translate these indices into the xterm color number, do not forget that the first 16 colors are occupied and do not belong to the sequence

def color_it216 (color: typing.Tuple[int, int, int]) -> str: "" "216 colors" "" color_data: typing.List[int] = get_pal (color) color_num: int = sum ([6 ** (len(color_data) - index - 1) * data
                          for index, data in enumerate(color_data)]) return f " 033[38;05;" 
           f"{16 + color_num }m"
Выглядит куда приятнее
Выглядит куда приятнее

24bit

На удивление самая простая часть, поддерживается огромное кол-во терминалов
https://gist.github.com/XVilka/8346728

def color_it_full(color: typing.Tuple[int, int, int]) -> str: "" "Full rgb" "" return f " 033[38;02;{color[0]}; {color[1]}; {color[2]} m "

Basically that’s all: D

This is beauty
This is beauty

Conclusion

Sources (there is also a display of gifs in the console): https://github.com/LedinecMing/console_images

Colored circles of all variants

Similar Posts

Leave a Reply