figlet

TOIlet: цветные шрифты

Вчера я писал, что цветные шрифты — не самое важное расширение стандарта FIGlet. От своих слов я не отказываюсь, но это расширение точно одно из самых весёлых.

Напомню, что это шрифты, которые составлены из текстовых символов. Они могут использовать там, где, кроме текста, ничего нет — например, в текстовых файлах, в письмах без оформления, в консоли и так далее.

Идея цветных шрифтов простая: в консоли можно использовать цвета, которые задаются специальными управляющими последовательностями. Терминал не выводит их на экран как текст, а воспринимает как команды. Например: «с этого места рисуй всё зелёным».

Таких последовательностей довольно много, но для управления цветом обычно используют несколько основных режимов.

Немного истории. Сначала появились базовые цвета — классический набор из восьми цветов. Потом к нему добавили яркие варианты, и в результате получилась 16-цветная палитра. Позже этого стало мало, поэтому появился режим, позволяющий выбирать цвет из палитры в 256 цветов. А затем добавили и true color — возможность задавать цвет напрямую в формате RGB, то есть практически из всей 24-битной палитры.

Терминалы работают с разными палитрами, что они, как правило, умеют сообщать через переменные окружения. Например, у меня на «Маке» есть переменная COLORTERM со значением truecolor, что означает, что моему терминал можно отсылать 24-битные цвета, а на рабочих «Линуксах» такой переменной нет. Но там есть переменная TERM со значением xterm-256color, которое указывает на то, что эти терминалы умеют обрабатывать 256 цветов.

Сравнение отображения пиксельных фруктов в терминале: 16-цветный, 256-цветный и truecolor режимы с указанием переменных TERM и COLORTERM.

Когда я начал разбираться с форматом шрифтов TOIlet и библиотекой libcaca, которая их отображает (автор большой оригинал, да), оказалось, что внутри реализована идея цветных шрифтов, но ни одного примера такого шрифта нет. Судя по коду, он слегка не дописан, но тем не менее, вполне работоспособен, жаль только автор остановился на палитре из 16 цветов.

Так что, первое, что я сделал — создал из набора стандартных эмодзи цветной шрифт в формате TOIlet, где смайлики составлены из раскрашенных юникодных блочных символов. Результат мне не особо понравился. Например, местами некоторые оттенки жёлтого у меня съехали в белый (из-за ограниченной палитры) и я потом ещё улучшал алгоритм, но всё равно — в шестнадцати цветах особо не разгуляешься.

Я подумал, что было бы здорово расширить формат, но так, чтобы утилита toilet по-прежнему выводила тот же шрифт в своих знакомых шестнадцати цветах.

Если посмотреть как libcaca парсит управляющие коды, то мы заметим, что парсер достаточно вольно обращается с грамматикой, не реагируя на некоторые ошибки, например, он пропускает все управляющие числа большие 107.

for(j = 0; j < argc; j++)
{
    /* Defined in ECMA-48 8.3.117: SGR - SELECT GRAPHIC RENDITION */
    if(argv[j] >= 30 && argv[j] <= 37)
        im->fg = ansi2caca[argv[j] - 30];
    else if(argv[j] >= 40 && argv[j] <= 47)
        im->bg = ansi2caca[argv[j] - 40];
    else if(argv[j] >= 90 && argv[j] <= 97)
        im->fg = ansi2caca[argv[j] - 90] + 8;
    else if(argv[j] >= 100 && argv[j] <= 107)
        im->bg = ansi2caca[argv[j] - 100] + 8;
    else switch(argv[j])
    {
        // парсятся 1—9, 21-29, 38, 39, 48—50

        default:
            debug("ansi import: unknown sgr %i", argv[j]);
            break;
        }
    }
}

Поэтому идея возникла такая. При кодировании цвета TOIlet использует цвет фона и шрифта, чтобы точнее имитировать пиксели в текстовом режиме. Иногда используется только один из них, чтобы реализовывать условную прозрачность. Это я пишу, чтобы рассказать, что у нас есть три варианта — указан цвет фона, указан цвет шрифта или указаны оба.

Если сразу за цветом в палитре из 16 цветов писать значение цвета, к которому я прибавлю такое число, чтобы оно гарантировано пропускалось парсером libcaca, то смогу спрятать от него там более точные значения цвета.

Например: \e[31;287;41;297m. Тут \e[ — начало специальной последовательности управляющих кодов (\e в реальности — символ с кодом 27, он никак не отображается, а только говорит, что дальше идёт команда).

Потом 31 — установка фона в красный, 287 — код цвета из 256-цветной палитры с прибавленным числом 256, чтобы оно пропускалось парсером. Так как оно идёт сразу после установки фона, то значит и тут мы ставим фон.

Следующим идёт, как возможно вы уже догадались, установка цвета шрифта, 41 — это тоже красный цвет, но для шрифта, коды разные, надо же отличать что именно мы устаналиваем. За ним — 297, принцип тот же, но так как тут значение идёт сразу после кода установки цвета шрифта, то мы понимаем, что и тут ставится цвет шрифта.

Символ m закрывает управляющую последовательность.

Таким образом утилита toilet и её библиотека libcaca увидят последовательность \e[31;41m, а мой парсер выкинет числа для 16-цветной палитры и оставит себе только 287 и 297, вычтет 256 и поставит нужный цвет.

При этом, если мой парсер таких числе не видит, то он читает цвет 16-цветной палитры. Что, во-первых, даёт обратную совместимость, во-вторых, в редких случаях, когда цвет из 256-цветной палитры совпадает в точности с 16-цветной, то можно указать «короткий» цвет и немного сэкономить.

С цветом RGB работает всё так же, только прибавляем 512. Правда цвет у меня кодируется в обратном порядке — BGR, потому что синяя компонента у́же всего используется в эмоджи, а красная — шире, поэтому если развернуть значения, числа получаются меньше и шрифт становится компактнее. Я думаю это оттого, что синий цвет хуже распознаётся глазом, недаром все градиенты во времена эпохи 256 цветов на мониторах старались делать синими.

Окно установки SimCity 3000: копируется файл 00000006_vehicles.dat, прогресс 36%, кнопка «Abbrechen».
Синий градиент в инсталляторе игры «SimCity 3000» в палитре 256 цветов

Шрифты, конечно, получаются просто огромными — по несколько мегабайт, но здесь помогает сжатие: и FIGlet, и TOIlet поддерживают сжатые шрифты.

Для конвертации эмодзи я написал конвертер на «Пайтоне», который преобразует их в шрифт отдельно в 256 цветах и в полноцветной палитре: хранить все варианты цветов было бы слишком накладно. А библиотека при отображении уже сама приближает цвет, если это требуется для конкретного терминала.

Вот, кстати, где ИИ сильно у меня начал буксовать — на задаче как компактнее спрятать новые цвета в старом формате. Сначала пришлось подумать самому, а потом уже пошло веселее — «Опус 4.6», который я не устаю нахваливать, подхватил идею и мы с ним быстро проверили различные гипотезы.

Добавлено позднее: реализацию всего вышесказанного можно увидеть в моём репозитории на «Гитхабе».

4 комментария

Figlet: шрифты из текста

Думаю, когда человек впервые увидел слова, которые вывел первый компьютер на первый дисплей, сразу же захотел большего — вывести туда ещё и картину. Но первые дисплеи были алфавитно-цифровыми и графику не умели выводить ещё очень долгое время. В ответ на это желание человечество придумало так называемую ASCII-графику. Не знаю, как произносит это слово читатель, но в той среде, где я общался, говорили «а-с-цы», реже — «а-с-ки».

Скриншот шахматной игры «ШАХМАТЫ RIGA 1987»: уровень 0,1 : 0, белые-1, чёрные-0 : 1, ваш ход.

С этим типом «графики» (не уверен, нужны ли кавычки) я впервые столкнулся за первым компьютером, на котором начал программировать, — «Радио-86РК». Очень хорошо помню программу «шахматы», которая выводила фигуры знаками псевдографики. К шахматам у меня особого интереса никогда не было, но этот факт я для себя отметил.

Позже, когда у меня появился ПК, такого рода графика мне время от времени встречалась в различных текстовых файлах. Особенно часто её использовали как одну из форм самовыражения ребята, пиратившие софт. В их сопроводительных файлах её было навалом. А когда я попал в интернет в 90-е, обнаружил её там в изобилии.

                          ________________
                         /      MAKE      \
          ___________   /   AMERICA GREAT  \
       (_(  ___ ___  )  \      AGAIN!      /
       /_/-( c) (c )< \  \   _____________/
       | |    <_      |   | /
       |_| '  ()  '   | ___/
      __\_  ( __ )  _/__
  / /|   \        /    |\ \
 (    )    ~      ~    (    )
(    |\  ( .     . )   /|    )
 (   ) \  --     --   / (   )
 |  ,| |     ___     | ,|   |
 \__/ \|  / - | - \  |/ \__/
  \oooO\_____________/Oooo/
        \   _____   /
         \. p | m ./
         | \_____/ |
        /    / \    \
       (____)  (____)
      /_____|  |_____\
    ___|   |    |   |___
   (_______|    |_______)


Не скажу, что я специально искал где-то текстовую графику, собирал её или любил любоваться ею вечерами, но, когда она мне попадалась, её, как правило, было интересно рассматривать.

Среди такой графики попадались и надписи, сделанные подобным образом, — странная, казалось бы, идея: писать буквы другими буквами. Но таким способом можно было имитировать различные шрифты или, например, рукописное начертание.


 ,ggggggggggg,     _,gggggg,_      ,ggggggggggg,     _,gggggg,_      ,ggg,        gg
dP"""88""""""Y8, ,d8P""d8P"Y8b,   dP"""88""""""Y8, ,d8P""d8P"Y8b,   dP""Y8b       88
Yb,  88      `8b,d8’   Y8   "8b,dPYb,  88      `8b,d8’   Y8   "8b,dPYb, `88       88
 `"  88      ,8Pd8’    `Ybaaad88P’ `"  88      ,8Pd8’    `Ybaaad88P’ `"  88       88
     88aaaad8P" 8P       `""""Y8       88aaaad8P" 8P       `""""Y8       88aaaaaaa88
     88""""Y8ba 8b            d8       88"""""    8b            d8       88"""""""88
     88      `8bY8,          ,8P       88         Y8,          ,8P       88       88
     88      ,8P`Y8,        ,8P’       88         `Y8,        ,8P’       88       88
     88_____,d8’ `Y8b,,__,,d8P’        88          `Y8b,,__,,d8P’        88       Y8,
    88888888P"     `"Y8888P"’          88            `"Y8888P"’          88       `Y8


В 1991 году кому-то пришло в голову, что процесс отрисовки таких надписей можно было бы автоматизировать, как это сделано со шрифтами для графических режимов. Так появилась небольшая утилита newban, которая потом выросла в «FIGlet» (название было составлено из имён автора и его друзей) и получила бешеную популярность.

Когда я ей заинтересовался в двухтысячных, по сети уже гуляло несколько сотен разных шрифтов. Восемьсот или около того. Мне очень хотелось разобраться как это всё работает.

Я тогда писал много пакетов для ПХП, мне очень нравился этот язык и очень хотелось что-нибудь сделать для сообщества. Поэтому пакет для работы со шрифтами FIGlet я написал именно на этом языке. Несколькими годами раньше это был бы Перл.

В 2008-м году появилось логическое продолжение — утилита toilet и формат шрифтов TOIlet («The Other Implementation’s letters»).

Скриншот терминала с командами программы toilet: «Hello! こんにちは» в радужном шрифте smmono9, «wow, I can read Braille» точками, и «UNICODE ART IS W00T» в стиле metal.

Основное отличие — шрифты стали юникодными: для поддержки символов за пределами одного байта больше не приходится делать отдельный файл с перекодировкой. Кроме того, появились цветные шрифты, но это, на мой взгляд, всё же менее важно.

Когда я начал переписывать свою библиотеку с помощью ИИ, я даже не подозревал, насколько глубока кроличья нора. В очередной раз хочу сказать, насколько сильно ИИ уже изменил и ещё изменит расклад в разработке. Если бы я переписывал эту библиотеку исключительно вручную, мне пришлось бы пойти на ряд компромиссов ради экономии времени. ИИ же позволяет удовлетворить перфекционизм почти любого уровня — только плати деньги.

Оставить комментарий

Возвращение Text_Figlet

Нейросеть «Опус 4.6», конечно, жрёт невероятное количество денег.

Я сделал небольшую паузу в парном программировании с ней, но недавно не выдержал, потому что решил потихоньку заняться тем, до чего у меня много лет не доходили руки, — закончить или переделать некоторые проекты. Наверное, какое-то время буду транжирить деньги именно в таком режиме.

У меня уже много лет чешутся руки переделать мою старенькую библиотеку Text_Figlet. Это, наверное, самое популярное моё детище — она время от времени встречается мне в каких-то продуктах и даже входит в виде пакета в некоторые дистрибутивы «Линукса».

Библиотека написана на ПХП и умеет выводить текст специальными фиглет-шрифтами, составленными из символов. Та моя версия, помимо этого, умеет делать так называемый smushing — объединение соседних букв по специальным правилам. Этот эффект виден в примере ниже.

«Смашинг» был сделан только по горизонтали; вертикальный алгоритм я в своё время не дописал, и этот факт — одна из причин, почему мне захотелось вернуться к библиотеке.

 _  _     _ _                        _    _ _ 
| || |___| | |___    __ __ _____ _ _| |__| | |
| __ / -_) | / _ \_  \ V  V / _ \ '_| / _` |_|
|_||_\___|_|_\___( )  \_/\_/\___/_| |_\__,_(_)
                 |/                           

В итоге я так разогнался, что полностью переписал библиотеку при помощи нейросетей, реализовав всё задуманное и даже больше — например, добавил поддержку шрифтов «TOIlet» и цветных фильтров для них, а также покрыл всё тестами и подготовил библиотеку к установке через «композер».

Какое-то офигительное чувство — когда задуманное так быстро превращается в код. Да, его до сих пор приходится просматривать, чтобы подсказывать нейросети, что переделать, но, в общем-то, меня это не коробит. Зато скорость программирования вырастает многократно. Я даже не могу предположить, сколько ещё откладывал бы переделку этой библиотеки, если бы не было настолько продвинутых нейросетей.

10 комментариев

Figlet

Вчера написал Thomas Goirand из Debian; с какого-то перепугу мой модуль для PHP шестилетний давности — Text::Figlet решили включить в Дебиан, собирают пакет и в этой связи спрашивают — все ли шрифты, идущие в комплекте обладают совместимой лицензией.

Как изменились времена, раньше никого это не интересовало. Я понятия не имею какая лицензия у этих шрифтов, честно говоря, поэтому просто выкинул их пакета из все и положил один сконверченный собственным конвертором из TTF в FIGlet бесплатный шрифт «Makisupa».

Заодно поправил пару багов и сделал поддержку зазипованных шрифтов.

Удивительно, но этот бородатый формат не стоит на месте — за то время, пока я на него не смотрел, у него отрос костыльный контрольный файл. Нет чтобы расширить существующий формат за счёт комментариев, сделали ещё рядом файл.

Оставить комментарий

Будущее CAPTCHA

Кажется, перед разработчиками начинает маячить проблема роботов, оставляющих спам в форумах или выполняющих автоматическую регистрацию (часто — для того чтобы спамить в тех форумах, где анонимно это сделать не получается).

Традиционно, разработчики либо мирятся с неизбежным злом, либо используют так называемый CAPTCHA-метод — пользователю выводится (часто зашумлённая) картинка с кодом или кодовым словом, пользователь должен ввести то, что он видит на картинке в специальное поле.

CAPTCHA-метод плох вот чем. Относительно простые изображения легко распознаются. Я, в качестве эксперимента, писал простой OCR для Chat.Ru и, посложнее, для SMS-сервиса Beeline.Ru (вероятность правильного распознования — 90%).

Если сильно исказить картинку, пользователь может не понять (со мной такое бывало), что на ней, собственно, изображено и после первой же неудачно попытки ввода уйти — робота же это не смутит. Если, скажем, вероятность распознавания составить всего лишь 20%, ничего не мешает роботу сделать несколько попыток до успеха.

Второй недостаток… Недавнее изобретение спамеров — метод «леммингов». Спамер открывает порносайт и… предлагает всем желающим войти, в качестве «платы», ввести код, написанный на картинке. Картинка берётся с сайта «жертвы». Как только «лемминг» вводит код, спам-робот использует его для регистрации или спама в нужном ему форуме.

Разновидность этого CAPTCHA-метода — просто писать слово с опечаткой или пропущенной буквой с просьбой ввести правильный вариант. Решение простое, но, когда я подумал над проблемой обхода такой защиты, решение этой проблемы пришло мне в голову менее чем за 15 минут. Многие сервисы поисковика «Яндекс» (например, Lingvo) предлагают варианты замены неверно написанного слова. Дело техники — «скормить» Яндексу слова с ошибкой и получить верное написание.

Защиту от техники леммингов, в общем случае, я предложить не берусь — разве что отлавливать подозрительную активность с какого-то IP адреса. А вот плохораспознаваемые роботом CAPTCHA-методы, но хорошо воспринимаемые человеком, на мой взгляд (приглашаю к здоровой дискуссии), существуют. Методы расположено от более вероятных (опять же, на мой взгляд), к более сомнительным.

Образы.

Иллюстрация (3.45КиБ)

Незаслуженно редко используют в качестве CAPTCHA-токенов изображения. Подобрать сто случайных изображений и сменить их, в случае активности спамеров — не проблема. Защиты такого рода существуют, но у них, на мой взгляд, существует недостатки:

  • Они предлагают пользователю ввести слово
    целиком. Пользователю неудобно. Я предлагаю использовать radio-кнопки, вероятность
    угадывания роботом увеличивается, но смотри пункт 2.
  • Они ничего не делают в случае неправильного ответа. Единственное, что сразу приходит
    в голову — сразу блокировать IP нарушителя. Делать так можно, но существует
    опасность заблокировать весь dialup-пул какого-нибудь провайдера, всех пользователей
    какого-нибудь NAT или прокси-серверов.

    Пользователь (см. иллюстрацию)
    может не попасть по «зайцу» (кто-то отвлёк, мышка заела) и выбрать «дрезину», но
    он не за что не кликнет в «рукомойник». Человек, в здравом уме, не спутает зайца с рукомойником.
    Ошибки позиционирования для близлежащих элементов можно и нужно прощать, за остальные —
    наказывать: для этого IP переходить к другому, более неприятному CAPTHA-методу,
    заставляя вводить слово целиком.
  • Они не пытаются запутать робота. Базу картинок, можно слить, если спам на
    данный сервер очень важен или у спамера много свободного времени,
    составить hash каждой иллюстрации и занести в базу.

    Несложно увеличить стоимость взлома — достаточно выводить не одну картинку, а, например,
    вывести в одну картинку сразу три изображения и туда же (в картинку) писать вопросы вида:
    «что изображено на картинке в центре».

    Количество вариантов изображения увеличивается
    до N*M*S, где N — количество изображений в базе, M — количество изображений, печатаемых
    в одну иллюстрацию, S — количество вариантов фраз. Важно, кстати, не делать изображения
    фиксированными по ширине — иначе их легко будет разделить. Проявите фантазию — изображения
    можно чуть масштабировать, инвертировать или перечеркнуть, фразу — писать в случайном месте
    и так далее. Число вариантов увеличивается настолько, что базу можно и не менять. А методы
    разделения и распознавания изображения по его участку не сработают.

Figlet.

История FIGlet началась весной 1991 года. Frank Sheeran написал другу автора первой версии Ian Chai письмо, где в качестве подписи использовалась строка, написанная ASCII-графикой. После чего Glenn Chappell, автор первой версии FIGlet, написал на C небольшуй программу, позволяющую писать придуманным им шрифтом любые строчки. FIGlet так и расшифровывается «Frank, Ian and Glen’s letters» («письма Френка, Иана и Глена).

С тех пор появилось несколько версий подобных программ, библиотеки на различных языках, я сам, относительно недавно, написал библиотеку Text_Figlet для PHP (скоро войдёт в PEAR). В данный момент доступно примерно 420 шрифтов для написания.

Шрифт в изображении можно уменьшить, например, до 4 пикселей и фраза на экране будет занимать вполне приемлемое пространство. Жизнь спамера осложняется не только тем, что количество шрифтов довольно велико, но и эффектом „smushing“ (горизонтальная склейка), который используется в некоторых шрифтах. Посмотрите внимательно на написанную фразу — соседние буквы в ней „склеены“.

В начале было слово…

В начале было слово, но записать его, думаю, было нечем — Богу вряд ли нужна авторучка с бумагой, а людей тогда ещё не было. Первые люди писать тоже не могли, но могли говорить. Так что голос — довольно древнее средство общение, но с его распознованием у роботов всё ещё большие проблемы.

Некоторые сервисы (LiveJournal), например, используют альтернативный CAPTCHA-метод — проговаривание фразы синтезированным голосом. В синтезе голоса давно нет ничего удивительного, но есть свои недостатки — синтез голоса более ресурсоёмкая операция, фраза с трудом распознаётся не носителями языка, да и звуковые платы есть не у всех. Пока метод можно рекомендовать к использованию разве что в качестве альтернативы.

Активный контент.

Надеюсь, этот метод распространения не получит распространения — я такой контен недолюбливаю, но спам не люблю ещё больше. В браузерах есть такая вещь как плагины — наиболее распространнёными, наверное, можно считать Flash и Java — два языка программирования, на которых вполне можно написать приложение выводящее текст или цифры.

Разобраться в том, как это приложение работает, чтобы понять что оно выводит роботу не по силам. В принципе, можно было бы давать Java-машине или Flash-плагину отобразить изображение, снять с него скриншот и распознать его, но стоимость такого взлома, как мне кажется, непомерно высока.

Animated GIF.

Несколько более простая разновидность предыдущего метода не требующая отдельного плагина — Animated GIF. Если бы вы смогли разыскать где-нибудь 13-й параграф из „Ководства“ Лебедева (на сайте его нет), то вы бы смогли прочитать следующее:

Хитрость заключается в том, что GIF позволяет создавать анимации, где каждый кадр может иметь свою локальную палитру. Предел палитры в GIFе, как мы знаем — 256 цветов. Но никто не говорил, что нельзя записать 10 кадров, в каждом из которых было бы по максимуму цветов и которые были бы все одновременно видны. В результате мы получим 2560 цветов. Неплохо для начала.

Градиентный фон, переходящий от насыщенного красного слева к оранжевому справа.

Подобная идея приходила в голову не одному только Лебедеву, конечно. Картинка — одна из иллюстраций из статьи True Color GIF, где в формате GIF используются 32796 цветов. Заливка, которую вы видите, нужна лишь для демонстрации — в „Ководстве“ Лебедева GIF, содержащий 1792 цвета отображался без заливки, сразу целиком.

Пора, наверное, уже сказать к чему я клоню, если вы ещё сами не догадались. Разбор GIF’a, скомпонованного подобным образом — задача довольно сложная. Писать можно полноцветными буквами, части которых находятся в разных кадрах, играя с их перекрытием (например, заслонять символы или их части).

Безусловно, задача решаемая — нужно только последовательно разобрать все кадры и наложить их один на другой, но для этого нужно уметь работать с animated GIF, да и правильно наложить один кадр на другой — в общем, тут есть где споткнуться спамеру.

Оставить комментарий