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.
BW
48 colors
216 colors
24bit

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()
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()
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
Conclusion
Sources (there is also a display of gifs in the console): https://github.com/LedinecMing/console_images