В данной статье будет рассмотрен прогресс от ЧБ картинки в консоли до 24 bit изображения в такой последовательности
ЧБ
48 цветов
216 цветов
24bit


Текстовые изображения
Необходимо создать таблицу символов по возрастающей яркости
block_table: list[str] = [" ", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
Для работы с изображениями импортируйте PIL.Image
Подготовьте изображение для конвертации:
Переведите изображение в RGB
Измените размер изображения по желанию
Сделайте копию изображения в 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")
Напишем простую функцию перевода яркости пикселя в символ нашей таблицы
Через функцию
def bright_to_symbol(bright: int) -> str: return block_table[round(bright / 255 * (len(block_table) - 1))]
Через лямбду
bright_to_symbol: typing.Callable[[int], str] = \ lambda bright: block_table[ round(bright / 255 * (len(block_table) - 1))]
Проходимся по grayscale копии и конвертируем яркость в символы, выводя всё в консоль
for i in range(image.height): for j in range(image.width): print(bright_to_symbol( image_grayscale.getpixel((j, i))), end = "") print()

48 цветов
8 и 16 цветов рассматривать вообще бессмысленно
Для достижения 48 цветов из 16 имеющихся в стандартных терминалах нужно использовать стили BRIGHT и DIM, чтобы к каждому цвету прибавить 2 варианта с данными стилями
Создаём палитру такого плана
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 ], ... : [...] }
Прописываем цветовые границы
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 ""
Добавляем цвета в символьный вариант
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 цветов
Для данной расцветки ваш терминал должен поддерживать xterm-256colors
https://robotmoon.com/256-colors/
Если рассмотреть RGB значения цветов, то можно найти простую последовательность, сохраняем
pal: typing.List[int] = [0, 95, 135, 175, 215, 255]
Для приведения RGB цветов к xterm-256colors номеру цвета напишем функцию, которая определит к каким значениям ближе всего цвет
Например (100, 100, 100) -> [1, 1, 1] т.е (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
Теперь нужно перевести эти индексы в номер xterm цвета, не забываем что первые 16 цветов заняты и не относятся к последовательности
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"
В принципе и всё:D

Заключение
Исходники (там также есть показ гифок в консоли): https://github.com/LedinecMing/console_images
Цветные круги всех вариантов



