В данной статье мы продвинем раскрашивание текста в терминале на новый уровень — с помощью обычных текстовых символов, а также дизеринга в новой реализации можно сделать нечто более интересное. И возможно даже в чем-то и полезное. Мы будем просматривать изображения помощью терминала Linux.
Просмотр картинок в терминале давно не новость и существует очень много подобных утилит, однако, нам всегда было интересно, как это все устроено. И как оказалось, все построено на тех же самых escape-последовательностях, на которых работает рядовая расцветка текста в терминале, но с некоторыми модификациями. Самая интересная проблематика в создании картинки в терминале — это создание чего-то подобного пикселям: в набранном в терминале тексте всегда есть пробелы и даже, если раскрасить некоторые символы (пусть даже и пробельные), то в раскраске всегда будет промежуток между символами с цветом фона, который был задан для терминала!
Получается, что если просто подобрать некоторый символ для пикселя и просто его покрасить в цвет, который соответствует цвету пикселя исходного изображения, то картинка визуально не сойдется — пустые промежутки будут слишком заметны и изображение не будет восприниматься как нормальная картинка.
С решением этой проблемы нам помогло описание одной интересной утилиты, написанной на C++, под названием terminalvideoplayer. Эта штука позволяет проигрывать целые видео не выходя из терминала! В описании утилиты указано то, какие символы используются для формирования пикселей — и тут наш ждал сюрприз: для создания структурных элементов изображения используются символы Unicode, которые представляют собой различные вариации полного квадрата:
До этого момента нам не приходила в голову мысль, что в Unicode есть нечто подобное…
Самих этих символов оказалось не так уж и мало, но по настоящему, мы в ходе экспериментов выбрали только один из них, с которым вид был наилучшим для визуального представления. Однако, оказалось что использование символов Unicode, это только часть решения — и для полноценного формирования картинки нужно расрашивать не только сам пиксель, но и фон вокруг него. Более того, выяснилось, что цвет фона должен быть точно таким же, как и цвет у пикселя.
Это хоть и придает решению работоспособности, но те цветовые палитры, которые мы использовали в статье по раскраске текста в консоли Linux, слишком бедны для отображения полноцветных изображений. К счастью, мы узнали о том, что во многих терминалах Linux, происходят от xterm (или воплощают в себе часть его соглашений или стандарта), есть более богатая палитра. И эта палитра включает в себя 256 цветов, и формируется точно такими же кодами escape-последовательностей (то что мы ранее рассказывали про колоризацию — это частный случай расцветки из 256 цветов) !

Но, в изображении гораздо больше цветов, а также есть еще одна проблема — 256 цветов, которые предоставляются палитрой xterm, не являются равномерными и не содержат разбиения на равные диапазоны по «цветности», а это значит, что цветовое разнообразие исходного изображения надо сокращать. Простое огрубление цветов приведет в целом к ухудшению качества изображения, поэтому здесь требуется нечто соверешенное иное, здесь нужно максимальное совпадение цвета из палитры xterm и цвета из исходного изображения с точки зрения человеческого зрения.
В этой ситуации нас сможет выручить дизеринг, однако, несколько доработанный: требуется изменить алгоритм поиска ближайшего цвета. Изменение требуется небольшое: нужно изменить формулу разницы между цветами с обычного расчета евклидового расстояния (это корень квадратный из суммы разностей между компонентами цвета RGB) на формулу разности интенсивности цвета по психофизиологической модели (кстати, так обычно рассчитывается яркость по стандарту ITU).
Объединив решение указанных выше проблем, а также функций, которые будут обслуживать ввод/вывод изображений, в единый скрипт dub получим следующий код:
#!/usr/bin/env dub /+ dub.sdl: dependency "dlib" version="~>1.0.0" +/ import std.conv; import std.stdio; import std.string; import std.file; import dlib.image; // console pixels forms enum CONSOLE_PIXELS { // quadrant upper left (▘) UL = "\u2598", // quadrant upper right (▝) UR = "\u259D", // quadrant lower left (▖) LL = "\u2596", // quadrant lower right (▗) LR = "\u2597", // quadrant upper right and lower left (▞) URLL = "\u259E", // lower half block (▄) LHB = "\u2584", // right half block (▐) RHB = "\u2590", // lower quarter block (▂) LQB = "\u2582", // lower 3 quarters block (▆) L3QB = "\u2586", // left quarter block (▎) LQBL = "\u258E ", // left 3 quarters block (▊) L3Q = "\u258A" } // set foreground color auto toTerminalForeground(string msg, uint color) { import std.string : format; return format("\u001b[38;05;%dm%s\u001b[0m", color, msg); } // set background color auto toTerminalBackground(string msg, uint color) { import std.string : format; return format("\u001b[48;05;%dm%s\u001b[0m", color, msg); } // draw single terminal pixel auto drawTerminalPixel(uint color, CONSOLE_PIXELS pixelType = CONSOLE_PIXELS.LHB) { (pixelType ~ " ") .toTerminalBackground(color) .toTerminalForeground(color) .write; } // distance between colors (via human sight) auto colorDistance(Color4f a, Color4f b) { auto dR = (a.r - b.r) ^^ 2; auto dG = (a.g - b.g) ^^ 2; auto dB = (a.b - b.b) ^^ 2; return (30.0 / 255.0f) * dR + (59.0 / 255.0f) * dG + (11.0 / 255.0f) * dB; } // find nearest color from palette auto nearestColorFromPalette(Color4f a, ref Color4f[] palette) { float distance = float.max; Color4f r; foreach (b; palette) { auto d = colorDistance(a, b); if (d <= distance) { r = b; distance = d; } } return r; } // simple dithering auto dithering(SuperImage simg, ref Color4f[] palette) { foreach (x; 0..simg.width) { foreach (y; 0..simg.height) { auto oldpixel = simg[x, y]; auto newpixel = nearestColorFromPalette(oldpixel, palette); simg[x, y] = newpixel; auto quant_error = oldpixel - newpixel; simg[x + 1, y ] = simg[x + 1, y ] + quant_error * 7.0 / 16.0; simg[x - 1, y + 1] = simg[x - 1, y + 1] + quant_error * 3.0 / 16.0; simg[x , y + 1] = simg[x , y + 1] + quant_error * 5.0 / 16.0; simg[x + 1, y + 1] = simg[x + 1, y + 1] + quant_error * 1.0 / 16.0; } } return simg; } // xterm palette auto XTERM_COLORS = [ Color4f(0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(128.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 128.0f / 255.0f, 0.0f / 255.0f), Color4f(128.0f / 255.0f, 128.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 128.0f / 255.0f), Color4f(128.0f / 255.0f, 0.0f / 255.0f, 128.0f / 255.0f), Color4f(0.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f), Color4f(192.0f / 255.0f, 192.0f / 255.0f, 192.0f / 255.0f), Color4f(128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 95.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 135.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 175.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 215.0f / 255.0f), Color4f(0.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 95.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 95.0f / 255.0f, 95.0f / 255.0f), Color4f(0.0f / 255.0f, 95.0f / 255.0f, 135.0f / 255.0f), Color4f(0.0f / 255.0f, 95.0f / 255.0f, 175.0f / 255.0f), Color4f(0.0f / 255.0f, 95.0f / 255.0f, 215.0f / 255.0f), Color4f(0.0f / 255.0f, 95.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 135.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 135.0f / 255.0f, 95.0f / 255.0f), Color4f(0.0f / 255.0f, 135.0f / 255.0f, 135.0f / 255.0f), Color4f(0.0f / 255.0f, 135.0f / 255.0f, 175.0f / 255.0f), Color4f(0.0f / 255.0f, 135.0f / 255.0f, 215.0f / 255.0f), Color4f(0.0f / 255.0f, 135.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 175.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 175.0f / 255.0f, 95.0f / 255.0f), Color4f(0.0f / 255.0f, 175.0f / 255.0f, 135.0f / 255.0f), Color4f(0.0f / 255.0f, 175.0f / 255.0f, 175.0f / 255.0f), Color4f(0.0f / 255.0f, 175.0f / 255.0f, 215.0f / 255.0f), Color4f(0.0f / 255.0f, 175.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 215.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 215.0f / 255.0f, 95.0f / 255.0f), Color4f(0.0f / 255.0f, 215.0f / 255.0f, 135.0f / 255.0f), Color4f(0.0f / 255.0f, 215.0f / 255.0f, 175.0f / 255.0f), Color4f(0.0f / 255.0f, 215.0f / 255.0f, 215.0f / 255.0f), Color4f(0.0f / 255.0f, 215.0f / 255.0f, 255.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 95.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 135.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 175.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 215.0f / 255.0f), Color4f(0.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(95.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(95.0f / 255.0f, 0.0f / 255.0f, 95.0f / 255.0f), Color4f(95.0f / 255.0f, 0.0f / 255.0f, 135.0f / 255.0f), Color4f(95.0f / 255.0f, 0.0f / 255.0f, 175.0f / 255.0f), Color4f(95.0f / 255.0f, 0.0f / 255.0f, 215.0f / 255.0f), Color4f(95.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(95.0f / 255.0f, 95.0f / 255.0f, 0.0f / 255.0f), Color4f(95.0f / 255.0f, 95.0f / 255.0f, 95.0f / 255.0f), Color4f(95.0f / 255.0f, 95.0f / 255.0f, 135.0f / 255.0f), Color4f(95.0f / 255.0f, 95.0f / 255.0f, 175.0f / 255.0f), Color4f(95.0f / 255.0f, 95.0f / 255.0f, 215.0f / 255.0f), Color4f(95.0f / 255.0f, 95.0f / 255.0f, 255.0f / 255.0f), Color4f(95.0f / 255.0f, 135.0f / 255.0f, 0.0f / 255.0f), Color4f(95.0f / 255.0f, 135.0f / 255.0f, 95.0f / 255.0f), Color4f(95.0f / 255.0f, 135.0f / 255.0f, 135.0f / 255.0f), Color4f(95.0f / 255.0f, 135.0f / 255.0f, 175.0f / 255.0f), Color4f(95.0f / 255.0f, 135.0f / 255.0f, 215.0f / 255.0f), Color4f(95.0f / 255.0f, 135.0f / 255.0f, 255.0f / 255.0f), Color4f(95.0f / 255.0f, 175.0f / 255.0f, 0.0f / 255.0f), Color4f(95.0f / 255.0f, 175.0f / 255.0f, 95.0f / 255.0f), Color4f(95.0f / 255.0f, 175.0f / 255.0f, 135.0f / 255.0f), Color4f(95.0f / 255.0f, 175.0f / 255.0f, 175.0f / 255.0f), Color4f(95.0f / 255.0f, 175.0f / 255.0f, 215.0f / 255.0f), Color4f(95.0f / 255.0f, 175.0f / 255.0f, 255.0f / 255.0f), Color4f(95.0f / 255.0f, 215.0f / 255.0f, 0.0f / 255.0f), Color4f(95.0f / 255.0f, 215.0f / 255.0f, 95.0f / 255.0f), Color4f(95.0f / 255.0f, 215.0f / 255.0f, 135.0f / 255.0f), Color4f(95.0f / 255.0f, 215.0f / 255.0f, 175.0f / 255.0f), Color4f(95.0f / 255.0f, 215.0f / 255.0f, 215.0f / 255.0f), Color4f(95.0f / 255.0f, 215.0f / 255.0f, 255.0f / 255.0f), Color4f(95.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(95.0f / 255.0f, 255.0f / 255.0f, 95.0f / 255.0f), Color4f(95.0f / 255.0f, 255.0f / 255.0f, 135.0f / 255.0f), Color4f(95.0f / 255.0f, 255.0f / 255.0f, 175.0f / 255.0f), Color4f(95.0f / 255.0f, 255.0f / 255.0f, 215.0f / 255.0f), Color4f(95.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(135.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(135.0f / 255.0f, 0.0f / 255.0f, 95.0f / 255.0f), Color4f(135.0f / 255.0f, 0.0f / 255.0f, 135.0f / 255.0f), Color4f(135.0f / 255.0f, 0.0f / 255.0f, 175.0f / 255.0f), Color4f(135.0f / 255.0f, 0.0f / 255.0f, 215.0f / 255.0f), Color4f(135.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(135.0f / 255.0f, 95.0f / 255.0f, 0.0f / 255.0f), Color4f(135.0f / 255.0f, 95.0f / 255.0f, 95.0f / 255.0f), Color4f(135.0f / 255.0f, 95.0f / 255.0f, 135.0f / 255.0f), Color4f(135.0f / 255.0f, 95.0f / 255.0f, 175.0f / 255.0f), Color4f(135.0f / 255.0f, 95.0f / 255.0f, 215.0f / 255.0f), Color4f(135.0f / 255.0f, 95.0f / 255.0f, 255.0f / 255.0f), Color4f(135.0f / 255.0f, 135.0f / 255.0f, 0.0f / 255.0f), Color4f(135.0f / 255.0f, 135.0f / 255.0f, 95.0f / 255.0f), Color4f(135.0f / 255.0f, 135.0f / 255.0f, 135.0f / 255.0f), Color4f(135.0f / 255.0f, 135.0f / 255.0f, 175.0f / 255.0f), Color4f(135.0f / 255.0f, 135.0f / 255.0f, 215.0f / 255.0f), Color4f(135.0f / 255.0f, 135.0f / 255.0f, 255.0f / 255.0f), Color4f(135.0f / 255.0f, 175.0f / 255.0f, 0.0f / 255.0f), Color4f(135.0f / 255.0f, 175.0f / 255.0f, 95.0f / 255.0f), Color4f(135.0f / 255.0f, 175.0f / 255.0f, 135.0f / 255.0f), Color4f(135.0f / 255.0f, 175.0f / 255.0f, 175.0f / 255.0f), Color4f(135.0f / 255.0f, 175.0f / 255.0f, 215.0f / 255.0f), Color4f(135.0f / 255.0f, 175.0f / 255.0f, 255.0f / 255.0f), Color4f(135.0f / 255.0f, 215.0f / 255.0f, 0.0f / 255.0f), Color4f(135.0f / 255.0f, 215.0f / 255.0f, 95.0f / 255.0f), Color4f(135.0f / 255.0f, 215.0f / 255.0f, 135.0f / 255.0f), Color4f(135.0f / 255.0f, 215.0f / 255.0f, 175.0f / 255.0f), Color4f(135.0f / 255.0f, 215.0f / 255.0f, 215.0f / 255.0f), Color4f(135.0f / 255.0f, 215.0f / 255.0f, 255.0f / 255.0f), Color4f(135.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(135.0f / 255.0f, 255.0f / 255.0f, 95.0f / 255.0f), Color4f(135.0f / 255.0f, 255.0f / 255.0f, 135.0f / 255.0f), Color4f(135.0f / 255.0f, 255.0f / 255.0f, 175.0f / 255.0f), Color4f(135.0f / 255.0f, 255.0f / 255.0f, 215.0f / 255.0f), Color4f(135.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(175.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(175.0f / 255.0f, 0.0f / 255.0f, 95.0f / 255.0f), Color4f(175.0f / 255.0f, 0.0f / 255.0f, 135.0f / 255.0f), Color4f(175.0f / 255.0f, 0.0f / 255.0f, 175.0f / 255.0f), Color4f(175.0f / 255.0f, 0.0f / 255.0f, 215.0f / 255.0f), Color4f(175.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(175.0f / 255.0f, 95.0f / 255.0f, 0.0f / 255.0f), Color4f(175.0f / 255.0f, 95.0f / 255.0f, 95.0f / 255.0f), Color4f(175.0f / 255.0f, 95.0f / 255.0f, 135.0f / 255.0f), Color4f(175.0f / 255.0f, 95.0f / 255.0f, 175.0f / 255.0f), Color4f(175.0f / 255.0f, 95.0f / 255.0f, 215.0f / 255.0f), Color4f(175.0f / 255.0f, 95.0f / 255.0f, 255.0f / 255.0f), Color4f(175.0f / 255.0f, 135.0f / 255.0f, 0.0f / 255.0f), Color4f(175.0f / 255.0f, 135.0f / 255.0f, 95.0f / 255.0f), Color4f(175.0f / 255.0f, 135.0f / 255.0f, 135.0f / 255.0f), Color4f(175.0f / 255.0f, 135.0f / 255.0f, 175.0f / 255.0f), Color4f(175.0f / 255.0f, 135.0f / 255.0f, 215.0f / 255.0f), Color4f(175.0f / 255.0f, 135.0f / 255.0f, 255.0f / 255.0f), Color4f(175.0f / 255.0f, 175.0f / 255.0f, 0.0f / 255.0f), Color4f(175.0f / 255.0f, 175.0f / 255.0f, 95.0f / 255.0f), Color4f(175.0f / 255.0f, 175.0f / 255.0f, 135.0f / 255.0f), Color4f(175.0f / 255.0f, 175.0f / 255.0f, 175.0f / 255.0f), Color4f(175.0f / 255.0f, 175.0f / 255.0f, 215.0f / 255.0f), Color4f(175.0f / 255.0f, 175.0f / 255.0f, 255.0f / 255.0f), Color4f(175.0f / 255.0f, 215.0f / 255.0f, 0.0f / 255.0f), Color4f(175.0f / 255.0f, 215.0f / 255.0f, 95.0f / 255.0f), Color4f(175.0f / 255.0f, 215.0f / 255.0f, 135.0f / 255.0f), Color4f(175.0f / 255.0f, 215.0f / 255.0f, 175.0f / 255.0f), Color4f(175.0f / 255.0f, 215.0f / 255.0f, 215.0f / 255.0f), Color4f(175.0f / 255.0f, 215.0f / 255.0f, 255.0f / 255.0f), Color4f(175.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(175.0f / 255.0f, 255.0f / 255.0f, 95.0f / 255.0f), Color4f(175.0f / 255.0f, 255.0f / 255.0f, 135.0f / 255.0f), Color4f(175.0f / 255.0f, 255.0f / 255.0f, 175.0f / 255.0f), Color4f(175.0f / 255.0f, 255.0f / 255.0f, 215.0f / 255.0f), Color4f(175.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(215.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(215.0f / 255.0f, 0.0f / 255.0f, 95.0f / 255.0f), Color4f(215.0f / 255.0f, 0.0f / 255.0f, 135.0f / 255.0f), Color4f(215.0f / 255.0f, 0.0f / 255.0f, 175.0f / 255.0f), Color4f(215.0f / 255.0f, 0.0f / 255.0f, 215.0f / 255.0f), Color4f(215.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(215.0f / 255.0f, 95.0f / 255.0f, 0.0f / 255.0f), Color4f(215.0f / 255.0f, 95.0f / 255.0f, 95.0f / 255.0f), Color4f(215.0f / 255.0f, 95.0f / 255.0f, 135.0f / 255.0f), Color4f(215.0f / 255.0f, 95.0f / 255.0f, 175.0f / 255.0f), Color4f(215.0f / 255.0f, 95.0f / 255.0f, 215.0f / 255.0f), Color4f(215.0f / 255.0f, 95.0f / 255.0f, 255.0f / 255.0f), Color4f(215.0f / 255.0f, 135.0f / 255.0f, 0.0f / 255.0f), Color4f(215.0f / 255.0f, 135.0f / 255.0f, 95.0f / 255.0f), Color4f(215.0f / 255.0f, 135.0f / 255.0f, 135.0f / 255.0f), Color4f(215.0f / 255.0f, 135.0f / 255.0f, 175.0f / 255.0f), Color4f(215.0f / 255.0f, 135.0f / 255.0f, 215.0f / 255.0f), Color4f(215.0f / 255.0f, 135.0f / 255.0f, 255.0f / 255.0f), Color4f(215.0f / 255.0f, 175.0f / 255.0f, 0.0f / 255.0f), Color4f(215.0f / 255.0f, 175.0f / 255.0f, 95.0f / 255.0f), Color4f(215.0f / 255.0f, 175.0f / 255.0f, 135.0f / 255.0f), Color4f(215.0f / 255.0f, 175.0f / 255.0f, 175.0f / 255.0f), Color4f(215.0f / 255.0f, 175.0f / 255.0f, 215.0f / 255.0f), Color4f(215.0f / 255.0f, 175.0f / 255.0f, 255.0f / 255.0f), Color4f(215.0f / 255.0f, 215.0f / 255.0f, 0.0f / 255.0f), Color4f(215.0f / 255.0f, 215.0f / 255.0f, 95.0f / 255.0f), Color4f(215.0f / 255.0f, 215.0f / 255.0f, 135.0f / 255.0f), Color4f(215.0f / 255.0f, 215.0f / 255.0f, 175.0f / 255.0f), Color4f(215.0f / 255.0f, 215.0f / 255.0f, 215.0f / 255.0f), Color4f(215.0f / 255.0f, 215.0f / 255.0f, 255.0f / 255.0f), Color4f(215.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(215.0f / 255.0f, 255.0f / 255.0f, 95.0f / 255.0f), Color4f(215.0f / 255.0f, 255.0f / 255.0f, 135.0f / 255.0f), Color4f(215.0f / 255.0f, 255.0f / 255.0f, 175.0f / 255.0f), Color4f(215.0f / 255.0f, 255.0f / 255.0f, 215.0f / 255.0f), Color4f(215.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 95.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 135.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 175.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 215.0f / 255.0f), Color4f(255.0f / 255.0f, 0.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 95.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 95.0f / 255.0f, 95.0f / 255.0f), Color4f(255.0f / 255.0f, 95.0f / 255.0f, 135.0f / 255.0f), Color4f(255.0f / 255.0f, 95.0f / 255.0f, 175.0f / 255.0f), Color4f(255.0f / 255.0f, 95.0f / 255.0f, 215.0f / 255.0f), Color4f(255.0f / 255.0f, 95.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 135.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 135.0f / 255.0f, 95.0f / 255.0f), Color4f(255.0f / 255.0f, 135.0f / 255.0f, 135.0f / 255.0f), Color4f(255.0f / 255.0f, 135.0f / 255.0f, 175.0f / 255.0f), Color4f(255.0f / 255.0f, 135.0f / 255.0f, 215.0f / 255.0f), Color4f(255.0f / 255.0f, 135.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 175.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 175.0f / 255.0f, 95.0f / 255.0f), Color4f(255.0f / 255.0f, 175.0f / 255.0f, 135.0f / 255.0f), Color4f(255.0f / 255.0f, 175.0f / 255.0f, 175.0f / 255.0f), Color4f(255.0f / 255.0f, 175.0f / 255.0f, 215.0f / 255.0f), Color4f(255.0f / 255.0f, 175.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 215.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 215.0f / 255.0f, 95.0f / 255.0f), Color4f(255.0f / 255.0f, 215.0f / 255.0f, 135.0f / 255.0f), Color4f(255.0f / 255.0f, 215.0f / 255.0f, 175.0f / 255.0f), Color4f(255.0f / 255.0f, 215.0f / 255.0f, 215.0f / 255.0f), Color4f(255.0f / 255.0f, 215.0f / 255.0f, 255.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 0.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 95.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 135.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 175.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 215.0f / 255.0f), Color4f(255.0f / 255.0f, 255.0f / 255.0f, 255.0f / 255.0f), Color4f(8.0f / 255.0f, 8.0f / 255.0f, 8.0f / 255.0f), Color4f(18.0f / 255.0f, 18.0f / 255.0f, 18.0f / 255.0f), Color4f(28.0f / 255.0f, 28.0f / 255.0f, 28.0f / 255.0f), Color4f(38.0f / 255.0f, 38.0f / 255.0f, 38.0f / 255.0f), Color4f(48.0f / 255.0f, 48.0f / 255.0f, 48.0f / 255.0f), Color4f(58.0f / 255.0f, 58.0f / 255.0f, 58.0f / 255.0f), Color4f(68.0f / 255.0f, 68.0f / 255.0f, 68.0f / 255.0f), Color4f(78.0f / 255.0f, 78.0f / 255.0f, 78.0f / 255.0f), Color4f(88.0f / 255.0f, 88.0f / 255.0f, 88.0f / 255.0f), Color4f(98.0f / 255.0f, 98.0f / 255.0f, 98.0f / 255.0f), Color4f(108.0f / 255.0f, 108.0f / 255.0f, 108.0f / 255.0f), Color4f(118.0f / 255.0f, 118.0f / 255.0f, 118.0f / 255.0f), Color4f(128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f), Color4f(138.0f / 255.0f, 138.0f / 255.0f, 138.0f / 255.0f), Color4f(148.0f / 255.0f, 148.0f / 255.0f, 148.0f / 255.0f), Color4f(158.0f / 255.0f, 158.0f / 255.0f, 158.0f / 255.0f), Color4f(168.0f / 255.0f, 168.0f / 255.0f, 168.0f / 255.0f), Color4f(178.0f / 255.0f, 178.0f / 255.0f, 178.0f / 255.0f), Color4f(188.0f / 255.0f, 188.0f / 255.0f, 188.0f / 255.0f), Color4f(198.0f / 255.0f, 198.0f / 255.0f, 198.0f / 255.0f), Color4f(208.0f / 255.0f, 208.0f / 255.0f, 208.0f / 255.0f), Color4f(218.0f / 255.0f, 218.0f / 255.0f, 218.0f / 255.0f), Color4f(228.0f / 255.0f, 228.0f / 255.0f, 228.0f / 255.0f), Color4f(238.0f / 255.0f, 238.0f / 255.0f, 238.0f / 255.0f), ]; // get number from palette auto getColorNumber(Color4f a, ref Color4f[] palette) { uint number = 0; foreach (i, e; palette) { if (e == a) { number = to!uint(i); break; } } return number; } void main(string[] args) { if (args.length < 2) { writeln(`Usage: termimg <file>`); } else { string filename = args[1]; if (filename.exists) { auto img = loadImage(filename); img = img .resampleBicubic(64, 64) .dithering(XTERM_COLORS); foreach (x; 0..img.width) { foreach (y; 0..img.height) { auto color = nearestColorFromPalette(img[y, x], XTERM_COLORS); auto number = getColorNumber(color, XTERM_COLORS); drawTerminalPixel(number, CONSOLE_PIXELS.LHB); } writeln; } } else { writefln(`File %s is not exists`, filename); } } }
Данная небольшая утилитка скорее демонстрационная, чем реально необходимая и готовая к использованию программа, однако, результат действительно впечатляет:

Оригинал картинки здесь
Пара слов о том, как это работает. Сначала мы объявляем необходимые импорты из стандартной библиотеки, а также делаем импорт модуля image из dlib. Помимо этого, создаем перечисление с возможными вариантами пикселей, буквенные наименования которых являются сокращением от их реальных наименований из таблички, которая была показана ранее. Также используется только один из этих видов пикселей, хотя приведен целый список вариантов в виде перечисления, что выглядит избыточно, но мы посчитали, что так будет удобнее для экспериментов и тюнинга со стороны наших читателей. Далее, у нас есть две интересные функции — toTerminalForeground (перекрасить в цвет тона) и toTerminalBackground (перекрасить в цвет фона), которые работают примерно одинаково и «оборачивают» некоторый текст в escape-последовательность (вставляют нужные последовательности в начало и конец строки), на основании поданного номера цвета в палитре xterm (номер от 0 до 255). Эти функции могут быть применены и за пределами данной утилиты, и через них выражается функция drawTerminalPixel, которая позволяет отобразить пиксель нак основании указанного цвета и типа пикселя, координаты рисуемого пикселя здесь не задаются, поскольку для формирования изображения используется обход самого изображения в цикле.
Как говорилось ранее, мы изменили подход к дизерингу в плане расчета нового цвета — теперь функция colorDistance использует оценку близости цвета к некоторому другому на основании психофизической модели зрения, что учитывается введлением соответствующих коэффициентов (30, 59 и 11 — для красного, синего и зеленого), которые устанавливают интенсивность цвета на основании чувствительности человеческого глаза к каждому из компонентов цвета. Остальные функции ответственные за дизеринг почти не изменились, но к ним добавилась функция getColorNumber, которая позволяет найти код для сооответствующего цвета из палитры xterm. Данный код будет использоваться для указания цвета в вызовах функции drawTerminalPixel, которая выводит в терминал пиксель с уже подготовленным цветом палитры xterm.
Остальной код несложен и это обычная для консольных утилит обработка входных аргументов из командной строки. В ходе работы самой утилиты извлекается наименование (с полным путем) файла изображения, после ряда проверок которого происходит уменьшение размера изображения до размера 64 на 64 пикселя (можно поменять в коде самой утилиты) при помощи бикубической интерполяции (resampleBicubic из dlib.image), обработка изображения дизерингом и попиксельный вывод в терминал.
Вот собственно и все, теперь и вы можете похвастаться выводом в терминал не только текста :)