Конвертируем поней в символы!

+245
в блоге IT Pony!

Запилил для одного своего проекта программку, ещё весной во время кириновируса. Сейчас дошли руки приделать к ней какой-никакой интерфейс, чтобы можно было дать всем поиграться.

ГалереяLyra

Sweetie Belle

Hive

Yona

NMM & Daybreaker

Marble Pie

Adagio

Building


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

Программа состоит из следующих основных модулей:

Image осуществляет чтение/запись png-изображений, разбивку их на клетки 8х12 пикселей (стандартный шрифт в консоли семёрки) и перекрас в цвета кастомной палитры (делает это максимально в лоб, безо всякого рассеивания ошибки и прочих ухищрений).

Genetic занимается подбором палитры. Мне было очень интересно поиграть с генетическим алгоритмом на реальной задаче, поэтому взял его. Особь имеет трёхбайтную хромосому c(r,g,b) и по сути является цветом. Фитнес-функция численно равна количеству пикселей на исходной картинке, для которых квадратичная (евклидова) норма разницы цвета x с хромосомой c меньше некой величины sensitivity (этот параметр можно менять)
  • x-c2 < sensitivity.
Так как извлечение корня это довольно дорогая операция, сперва осуществляется вычисление в более грубом приближении через норму первого порядка (которая по максимальному элементу; не стукайте за термины, я весь линал уже забыл)
  • |x-c|max < sensitivity.
Графически, первому неравенству удовлетворяет шар радиуса sensitivity с центром в точке с координатами c, а второму — куб, описаный над шаром. Соответственно если пиксель пролетает мимо куба, то он уж точно пролетает мимо шара.

Кроссинговер однородный — цепочки из 24 бит у родителей смешиваются по рандомной битовой маске.
Исходная вероятность мутации 10% (выбирал от балды, так и не увидел внятной зависимости).

Критерий останова составной: либо по достижении 25 поколения, либо при становлении параметра мутации 90%. Вероятность мутации растёт в том случае, если фитнес-функция победителя совпала на двух идущих подряд итерациях. Это признак достижения сходимости алгоритма, соответственно возникает проблема определить — действительно ли получен глобальный максимум. После каждого следующего совпадения вероятность мутации растёт на 20%. Таким образом второй критерий срабатывает при совпадении фитнес-функций 5 раз подряд. При этом с каждым разом поиск новых максимумов идёт всё активнее благодаря мутантам. Если даже при вероятности мутации в 70% новых максимумов не обнаружено, а фитнес-функция стабильно держится пятое поколение подряд, имеет смысл говорить о достижении сходимости (с большой вероятностью в глобальный максимум).

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

Анализ параметра sensitivity на примере8

8+2k

14

При маленьких значениях параметра, цвета подбираются очень строго. Соответственно, например, на двухцветный градиент при sensitivity = 8 подберётся 4 разных цвета в палитру, а при sensitivity = 20 может и вообще всего один. Отсюда вытекает два следствия:

1: чем меньше sensitivity, тем точнее идёт подбор, но тем быстрее заканчиваются свободные слоты в палитре. А у нас их, напоминаю, всего 15! (Чёрный зарезервирован под фон.) Это иллюстрирует первая картинка с подписью 8. Болото закрасилось тем же цветом что и небо, ибо слишком много слотов было занято различными оттенками тёмных цветов, коих на рисунке много и они были найдены в первую очередь. На оттенки зелёных не хватило места.

2: чем больше sensitivity, тем большие области на картинке становятся однородными. Это экономит места в палитре, однако ухудшает качество изображения. Пример — скриншот с sensitivity = 8 + 2 на каждом поколении (соответственно в конце параметр выше 30). Здесь всё тёмные тона были аппроксимированы практически двумя цветами и пиксели на изображении кончились намного быстрее слотов в палитре, отчего осталось место под различные тонкие оттенки зелёного и жёлтого.

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

На Windows 10 нет подходящего консольного шрифта, поэтому будут вытянутые, не особо красивые кракозябры, соре. Под семёркой всё должно быть нормально. Багов полно, пишите в комменты. Архив с программой лежит на гуглодоках, там внутри есть readme, плюс есть краткая справка по ключу -h (или /?) в консоли. Спрашивайте ваши ответы

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

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

всё честно накостылено на плюсах (кроме libpng)
Опа, да теперь всякий бездарь сможет заиметь оригинальную аву! Спасибо папаша!
И да, за фоновую пони респект.
как что-то плохое!
Отнюдь) Как бездарь отвечаю.
Виндовая консоль ущербна. Под линуксом всё намного лушче, там и палитра норм и спец символы доступны, позволяющие рисовать квадратиками как в старые добрые времена ANSI-art ▄▀▄▀
Вот пример самого лучшего конвертера в текст из тех что видел — pixterm:
Спойлер





Архив с программой лежит на гуглодоках
Залей на гитхаб чтоль
Квадратики это конечно хорошо, но обычный ASCII красивее и каноничнее. Не знаю что там было со шрифтами в допринтерные времена, но когда печатали на АЦПУ — такие картинки (чернобелые естественно) вовсю уже печатали «псевдографикой»
Не знаю что считать каноничностью. ASCII арт как-то язык не поворачивается назвать красивее ANSI арта, который появился еще в эпоху BBS.
Примеры: 16colo.rs/pack/fuel27/
Осторожно, очень длинная картика.
Не могу нагуглить в какие года появились бибиэски. Картинки напомнили картинки из read.me с кряками у играм)
Так вот, картинки распечатанные на АЦПУ мне доводилось видеть в середине 80х. Полагаю ANSI арт появился раньше, да?
Терминалы псевдографику поддерживали очень давно… но АЦПУ с ней я не припоминаю. Даже в память запала фраза из древней книжки по фортрану что «не вырисовывайте в каментах в программе рамочки псевдографикой, на АЦПУ будет фарш непотребный, юзайте только ——— и ==== и **** „
АЦПУ — это только лепестковые/барабанные принтеры, ушедшие в прошлое настолько давно, что даже я их не припомню (хотя помню платёжки тулгорэлектротреста (или как он там назывался), напечатанные на таком принтере, где горизонтальные линии были сделаны знаками "=", а вертикальные — "!"). Уже первые матричные умели в графику и в нормальный Alt-ASCII codeset (437).
ушедшие в прошлое настолько давно,
еще в 2002 у нас именно на АЦПУ бухи печатали расчетные листки… на огромном АЦПУ. Потом когда его списали наконец — еле разобрали, тяжеленный барабан с литерами вдвоем еле вынули)
Ну, в 2002 это можно считать уже таким реликтом, на который вряд ли стоило рассчитывать, когда делаешь ASCII-графику.
Уже на матричных принтерах можно было печатать что угодно (а на некоторых — даже грузить свои шрифты, что означало, что можно было текстом печатать хоть картинки 160х100 (это когда в знакогенератор VGA/принтера загружаешь шрифт, целиком состоящий из всевозможных комбинаций из 2х4 заполненных/незаполненных квадратиков 4х4 пикселя, правда, там был некоторый drawback — в каждом таком квадратике можно было использовать только два цвета (это похоже на спектрум), т.е. нельзя было, например, сделать вертикальную последовательность квадратиков из более чем 4 цветов подряд; но если такое печатать — то там всё равно два цвета будет, и потому это было весьма годной идеей для такого).
Ага, матричники даже к спектруму ухитрялись цеплять, помню-помню…
Ну, у спектрума вроде как комплектный матричник был даже у некоторых моделей =)
Если тот что фирменный английский, то это пародия на принтер даже по тем временам — одноигольчатый по термобумаге) А наши уже цепляли 6317 к нему и прочее…
Если тот что фирменный английский, то это пародия на принтер даже по тем временам — одноигольчатый по термобумаге)
Да, фирменный — тот, что на рекламках был =) Ну, его характеристик я не знаю, но подозреваю, что вполне так и было — Синклер же хотел брать объёмы ценой и доступностью.
А наши уже цепляли 6317 к нему и прочее…
Ну, наши, как я понимаю, моддили спектрум только так — ZX-Bus, емнип, именно придумка питерцев, так что подключить к спектруму что угодно — от не предназначенного для него принтера до зародышей систем «умный дом» — это было вполне базового уровня развлечение =)
моддили спектрум только так
И сейчас моддят… TS-Conf на ФПГА-шке уже по уровню до 386 довели где-то, оставаясь совместимым с классикой…
Лол, я думал, ванильный ZX-Evo — это типа вершина инженерной мысли на текущий момент, а там вон уже что делают =)

[email protected] when?

(да, я знаю, что нормальному линуксу нужен MMU и прочие вкусности для умения в страничную адресацию, но вроде как его, хитро попатчив, пускали на каких-то микроконтроллерах без MMU — так что чем z80 хуже? =)
ванильный ZX-Evo
TS-Conf это вершина Евы) и там не линух, там своя ось какая-то… и зайчатки MMU там есть кстати, метапорт переключения сразу групп банков страниц вроде как…
Лол, эдак уже кажется, что за спектрумом будущее =)
ага, светлое будущее, омраченное тем что все нынешние вкусные FPGA в BGA корпусах… если TQFP при помощи лома, кувалды и чьей-то матери еще запаиваются, то BGA…
Ну, что поделать, во времена спектрумов и TQFP паять было сложно, а сейчас уже без проблем =)
Плюс фирменная сборка плат подешевела сильно, можно уже и заказывать, и там всё что нужно запаяют, а не мучиться с ЛУТом и ручной пайкой (хотя, с другой стороны, романтика ночей, проведённых за лупой и паяльником в канифольном дыму, пропадает =)
романтика ночей, проведённых за лупой и паяльником в канифольном дыму, пропадает
Вот то-то и оно… так-то можно тупо малинку 4-ю взять, прошивку перепилить, и она тебе паралелльно десяток спектрумов сэмулирует)
Нее, на малинке вроде как пробовали — хардварная эмуляция тормозит, там даже процессор толком не сэмулируешь потактово, нужны оптимизации, которые снижают точность эмуляции
Да ладно… Эмулятор Шалаева в свое время на 386 под ДОС эмулировал каноничный спек-48… малинка слабей 386-го чтоли?
Я подозреваю, что это эмулятор с точностью до инструкций, а не тактов (как DOSBox), он куда проще и, соответственно, быстрее — но при этом на нём не получится делать всякие хитрости, завязанные на длительность выполнения конкретных участков, что нередко встречается в демосцене, например.
Именно что до тактов… иначе куча игрушек с защитой завязанной на регистр R тупо не взлетела бы на нем.
Хм, что-то очень странно.
Хотя, кажется, про малинку я перепутал, там пытались эмулировать не спектрум, а PC-8801, но суть всё равно одна — в одном Z80, в другом µPD780, оба с частотой в единицы мегагерц
Там работали на том эмуляторе, например, игрушки, которые юзали привязку к частоте развёртки (благодаря чему можно было получать больше двух цветов в квадрате 8х8)?
Вот насчет мультиколора не помню честно говоря, умел ли его именно шалаев…
Просто если не умел — то это как раз, похоже, эмуляция не потактовая (с чёткой фиксацией внутреннего времени на каждый такт), а по инструкциям (и с отстуствием понятия внутреннего времени, либо с его эмуляцией как раз для частных случаев типа обхода популярных защит). То есть суть в том, что потактовый эмулятор крайне медленный (так как в нём за тактовый квант делается куча вещей — от эмуляции задержек на шине и скорости нарастания фронтов, до сдвига позиции луча развёртки в графической схеме), но при этом это абсолютный аналог реального железа, на котором всё будет работать в точности так же. Поэтому, например, иногда их, чтобы отличить от «обычных» эмуляторов, называют симуляторами (у нас так, например, с симулятором «Эльбруса», хоть он, технически, симулирует только собственно процессор + кэши, а остальное эмулируется). Эмулятор инструкций, наоборот, неточный, и он ведёт себя в некоторых случаях совсем не так, как реальное железо — но при этом и падение скорости у него не в тысячи раз, а в десятки-сотни.
BBS где-то с 70х. А АЦПУ это же принтер, а мы тут про графику в терминале вроде.
В терминале и анимация была
хотеть примерно такое, только в аски и на лету. Чтоб поней без иксов смотреть на своём калькуляторе
Есть libcaca и плагин к mplayer и vlc, который позволяет делать такое
Спойлер
Но качество так себе, лучше бы конечно квадратиками.
о, пасиб, посмотрю на досуге
Вроде mplayer это через aalib (и на весь размер tty, а не 80х25) делает?
В любом случае, годно, как настоящий кулхацкер, смотреть поней прямо в текстовой консоли, не используя обращения к фреймбуферу =)
Чёт хиленько по сравнению с чем-то, что люди умели вытворять в 50Ln VGA
Не ну ты сравнил, демосцену и графику BBS, которая должна была работать даже на 2400 bit/s :)
А лол, я просто покликал и не смотрел особо, я думал, это демка какая-то шестиминутная, а это просто куча склееных интрок ббсок оказалась О.о
Рекомендую хороший фильм на тему BBS
Картинки напомнили картинки из read.me с кряками у играм)
Таки кряксцена очень плотно пересекалась с демосценой и распространением вареза по ббскам, и потому там везде были востребованы ascii-художники. Поэтому в .nfo к крякам логотипы групп как раз и были нарисованы ими.
эт понятно что у юниксов консоль стронг, в том и челлендж был отчасти — под виндой нашаманить. А касательно конвертации в цветные прямоугольники это большого ума не надо. (хотя у меня тоже, когда я отказался от стандартной палитры пропали всякие необходимости в экспериментах с псевдотонированием, разбиением на регионы, поиском градиентов и контуров и намеренным зашумлением и тоже всё стало просто, но тем не менее)

Гитхаб думаю сообразить, когда градиентный спуск запилю для палитры. Генетический алгоритм конечно хорошо, но неэффективно. Ну и в коде прибраться не мешает, куда без этого :3
Не надо изобретать велосипед.
Не так уж часто встречается арт в консоли винды. Почему бы и нет?
и спец символы доступны, позволяющие рисовать квадратиками как в старые добрые времена ANSI-art ▄▀▄▀
Но они же доступны и в досовской (и виндовой по наследству) консоли.
Под линуксом плюс только в том, что терминал поддерживает куда больше 16 цветов (но в старых досовских демках, емнип, тоже умели это делать, переключая палитру синхронизированно с развёрткой на мониторе; хотя преимущественно это делали в графическом режиме, получая на той же CGA не 4 цвета, а под 1024).
В виндовой надо шрифты менять для этого. Я не знаю, там вроде майки сейчас взялись за терминал, может уже и UTF8 завезли. Давно не чекал.
Alt+219-223, вот эти пять символов слева направо: намджун, чонгук, чингачгук 100%, 50% снизу, 50% слева, 50% справа, 50% сверху. ЧЯДНТ?
Хм, ну ок. Что бы тогда ими не рисовать?
Так ими и можно рисовать =)
Алсо, также скажу про символы 176-178: ими можно градиенты делать

Просто рисование нижней половиной ASCII-таблицы нормально работает при любой кодировке, а также позволяет делать субпиксельное (точнее, в нашем случае — субполузнакоместовое) сглаживание =)
а кстати надо попробовать использовать фулл cp866. По сути, расчёт палитры занимает около 5 минут, а подборка символов меньше секунды, так что имея палитру можно любую кодировку заюзать и любой шрифт (при наличии картинки с его символами (наверняка можно и без этого костыля, но я пока не придумал как))
спец символы доступны

Это у нас от шрифта зависит. Например:
consolas
courier new
DejaVu Sans Mono
Виндовая консоль ущербна.
А я и не знал. Ну, никто не знал, если честно.
Йона прям криповая вышла
есть такое. Мне упорно кажется, что если погонять разные параметры по нескольку раз — что-то более красивое можно получить. У меня в процессе подбора был шикарный вариант, но я его отбросил по каким-то причинам. Теперь жалею
Да, тут, кажется, для каждой картинки надо параметры вручную подбирать.
конечно. По идее есть ещё один важный элемент, который я не успел включить — возможность вручную задать несколько опорных цветов (например последнюю картинку со зданием в текущем варианте программы не получить, я там вручную задавал цвета неба)
Хочу (но не могу :( ) этому посту аж два плюса поставить, один — за концепт и его реализацию, второй — за выбор первой картинки =)
"… Меня зовут Лира Хартстрингс, и вы никогда не вспомните о том, что хотели меня представить в виде раскрашенного ASCII-текста. Вы даже не вспомните этот разговор… Погодите, что?!" =)
я когда выбирал что покрасивше на превью поставить, сперва взял Свити. Но потом провиденье направило мою руку и поставил Лиру :3
Когда-то тоже развлекался, создавая портреты… буквами и символами… ВРУЧНУЮ! НА ПЕЧАТНОЙ МАШИНКЕ!!!
Был тогда хвост 70-х годов прошлого века из минувшего тысячелетия…
Последние из тех работ — портрет Кира Булычёва (1980) и пирата-робота из мульта «Тайна Третьей Планеты» (1981) даже где-то сохранились, но искать их сейчас — неблагодарное дело… 888-)
А проги такой у меня не было, и компьютер появился первый позднее немножко… В 1983-м, сразу «Синклер» и «Агат»…
Приходилось всё ручками, ручками©Лира и на глазок… 888-)

Но потом провиденье направило мою руку и поставил Лиру :3
И за это — отдельное спасибо!

Вы даже не вспомните этот разговор… Погодите, что?!" =)
XXX-D
Вот так судьбе!
888-Р
ВРУЧНУЮ! НА ПЕЧАТНОЙ МАШИНКЕ!!!

Ух… Звучит как адская боль в суставах пальцев.
Ото ж! Хотя «Украина-2» ещё не самая тугая при нажатии на клавиши, но её не то что с компьютером — даже с электрической печатной машинкой «Ятрань» не сравнить!..
Помянутые машинки:Украина-2:


Ятрань:

>создавая портреты… буквами и символами… ВРУЧНУЮ!
моё увожение. Умей я в искусство — с радостью бы сам тоже так делал. Но приходится создавать цифрового робота, который сделает за меня
Цифровой робот — это в компьютере, а тогда в СССР компьютер занимал несколько комнат в институте, а не представлял собой коробочку на столе…
И ситуация сменилась лишь в 1982 году с появлением «Синклеров», да в 1983 году, когда краденый Лианозовскими Мастерами «Эппл-II» превратился таки в «Агат»!..
Но и тогда добыть к ним принтер было проблемой. Игольчатый «дрюкер» (кто слышал звук, с которым оные работают — поймёт, почему именно «ДРЮКер»..)
А вот руки и машинка были тут, рядышком, и умели слегка взаимодействовать друг с другом… 888-)
ВРУЧНУЮ! НА ПЕЧАТНОЙ МАШИНКЕ!!!
Делал ASCII-арт до того, как это стало мейнстримом? =)
и даже не знал, что такое «ASCII»!
Просто решил побаловаться на машинке, на которой предпочитал набирать свои рассказы (потому как почерк рукописный у меня УЖАСНЫЙ!)…
Видать, она пришла к тебе и попросила её поставить, ты это сделал, но, разумеется, этого не помнишь =)
чёрт, это настолько годно, что я на пару минут залип, переосмысляя бытие
Feel my pain =)
Я не могу себя заставить перестать во всякие моменты, когда я вроде бы помню, что чего-то не было, но при этом мне почему-то кажется, что оно должно было быть, задаю себе вопрос, не контактировал ли я с кем-то из неспетых в тот момент… =)
Вот блин, самая эпическая идея «Фоновой» — это то, что возможность существования кого-то уровня Лиры и других неспетых по определнию нефальсифицируема, а значит, мы никак не можем быть уверены, что их не существует, и не исключено, что вот десять минут назад до тебя кто-то отчаянно пытался докричаться, чтобы ты хоть что-то понял, а сейчас ты сидишь у компа и ни капельки не помнишь об этом =/
(ладно, заканчиваю философствовать, я просто слишком упоролся по «Фоновой» в своё время =)
Занимался «подобным» пару лет назад. Ну, фактически, я просто сравнивал попиксельно каждый блок размером со шрифт с каждым символом шрифта, дабы найти максимальное соответствие не только по насыщенности, но и по форме. Картинки перегонялись в монохром. Т.е. всё банально, в лоб и тупо.
Каин
Флатти

В работе это выглядит так: (только ОЧЕНЬ медленно)
Спойлер
И Тия:
Спойлер
вот у тебя вроде как раз труЪ, без расширенных символов типа смайликов и чёрно-белый. А с подбором формы я тоже не очень далеко ушёл. Сначала пытался в лоб (слева), потом чутка поменял алгоритм (справа). Линии стали резче
Спойлер
Чисто технически, у меня он просто берёт символы из такой вот матрицы

Причём берёт оттуда прямоугольник, который в настройках задаётся.
Т.е. я вообще любую таблицу могу туда засунуть, пусть хоть смайликами рисует, главное чтобы символы были 16*16 пикселей.
Т.е. у меня нифига не каноничный ASCII, он на картинках работает =)
Хотя можно легко прикрутить вывод таки в текст, если составить таблицу символ-с-картинки -> ASCII

П.С. Главная проблема у меня в том, что оно ОЧЕНЬ медленное. Эта Тия вроде больше минуты делается.
Ибо там для каждой ячейки проверяется каждый из 16*16 пикселей картинки с каждым из 16*16 пикселей каждого из 16*16 символов. Жесть.

ИМХО, конечно, но нужно делать вот какой фокус: брать мелкий шрифт (вроде есть 8*9 в консоли), и саму картинку напротив, увеличивать перед обработкой. Получится большая детализация.
Тот же шрифт, те же настройки, но сперва исходное полотно, а потом его удвоенная копия

В идеале ещё нужно сдвигать картинку на 16 пикселей по каждой оси, попиксельно, и в каждой из 256 попыток записывать точность воспроизведения, и в конце выдавать самый точный вариант. Это к тому, что при разделении картинки на символьную матрицу разбиение может быть таким, что большая часть ячеек будет максимально неудобоваримой для ASCII, и это можно побороть небольшим сдвигом. Но тут мой алгоритм замедлится ещё в 256 раз, т.е. эта Тия будет делаться часа 4…

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

Делается меньше чем за секунду, даже для относительно больших картинок (~1366х768). Основное время тратится на подбор палитры, ибо там (для картинок в посте например) 1000 особей лазает по 400 000 пикселям картинки (это уже примерно 4 секунды) на протяжении 60 поколений. Итого примерно аж 4 минуты

идея со сдвигом интересная, она у меня была но почему-то отбросил. Надо будет реализовать

насчёт переноса на видяху — подобная обработка картинок в теории идеально параллелится, так что проблема только в умении писать расчётные проги под графическое ядро. Можно ещё распараллелить в дохрена раз и на CPU, если захардкодить векторизацию через SIMD-инструкции (опять же вопрос в умении)
насчёт времени у тебя очень странные показатели. У меня по сути тоже так же идёт линейный перебор прямоугольника с картинки и прямоугольника с матрицы, попиксельно:

А с помощью чего это происходит?

Я делаю с помощью getpixel(x,y); из встроенной граф. библиотеки Паскаля, наверное поэтому так медленно.
у-у-у, тогда понятно :D
я юзаю libpng дабы получить двумерный массив байт (читай пикселей) и потом просто обращаюсь к массиву, примерно как pixels[x][y]. Всё это шустро работает без дополнительных прослоек и няшно оптимизируется, ибо C++
Ух, я слишком глюпый для таких вещей…

Единственное, что я смог сделать — это повысить скорость за счёт памяти. Запихал переменные, которые мешали распараллеливанию, в мегамассивы массивов интеджеров, или типа того.
Время обработки тестовой картинки уменьшилось с 19,04 с. до 15,48 с. И это с выключенным оптимизатором, который ускорял нахождение полностью чёрных/белых участков с 256 проходов до 1.
Заодно заметил странность:
Этот код
if (ut.GetPixel(x * 16 + xPixel, y * 16 + yPixel) = ascii[0, 2].GetPixel(xPixel, yPixel))
на треть быстрее, чем этот:
if (ut.GetPixel(x * 16 + xPixel, y * 16 + yPixel) = clWhite)//clWhite — константа
Хотя мне всегда казалось, что должно быть наоборот…

Провёл более подробный тест на увеличенной вдвое тестовой картинке из первого теста. Результаты шокировали:
С оптимизатором: 49,5 с.
Без оптимизатора: 61,5 с.
Версия, которую я три часа оптимизировал: 66 с.

Выводы:
1. Оптимизатор существенно ускоряет на картинках с большими однородными пространствами, но сильно замедляет на картинках со сложным рельефом.
2. Из меня вышел не просто фиговый кодер, но и очень фиговый оптимизатор.
про константу не подскажу, по моим представлениям второй вариант должен быть слегка быстрее, но тут надо весь код видеть, чтоб сказать наверняка

оптимизации это вообще лютый хаос, как только масштаб проекта выходит за рамки парочки функций. Я вот пытался у себя ген. алгоритм по кэшу оптимизировать: ручками разложил в памяти массивы на свои места, классы заменил структурами, методы — процедурами. В результате стало медленнее :D
Опыт показывает, что в подавляющем большинстве случаев современные компиляторы оптимизируют лучше человека =) (если человек при написании кода не пишет ну уж совсем глупостей)
Хотя мне всегда казалось, что должно быть наоборот…
Могу предположить две вещи:
1) clWhite скрыто разыменовывается не в иммедиат, а в константу, лежащую в памяти «дальше», чем массив ascii (например, в другом сегменте в реальном режиме или в странице, в которую всё время идёт промах при страничной адресации)
2) первый код компилятор, раскручивая цикл, оптимизирует в склейку пересылок (т.е. копирует 4/8/16 байт за раз), а второй заполняет по одному байту — не догадывается, что можно сформировать (uint32_t)(char[4] {clWhite, clWhite, clWhite, clWhite}) и присвоить его сразу.
Я делаю с помощью getpixel(x,y); из встроенной граф. библиотеки Паскаля, наверное поэтому так медленно.
А лол, конечно.
Перегоняй всё изображение в битмап в памяти и потом обращайся к каждому биту напрямую. Так у тебя быстродействие возрастёт не то, что в разы, а на порядки.
Перегоняй всё изображение в битмап в памяти и потом обращайся к каждому биту напрямую.

Как?
Там есть System.Drawing.Bitmap, но он такой же по скорости, как и обычная картинка. Более того, я видел на форуме мельком обсуждение это проблемы, и там пришли к выводу, что что-то куда-то так офигенно глубоко запрятано, что достать это оттуда невозможно, и вместо быстрой графики — страдай. Сейчас эту тему найти не могу, хотя там могли быть подсказки…
есть вариант просто создать в памяти массив байт, и один раз в самом начале проги через getpixel его заполнить. Потом можно вместо вызовов этой функции обращаться к массиву в памяти.
помню с какой-то конференции доклад был, где ребята до того дошли, что создали массив вида double x[] = {0, 1, 0.5, 0.33, 0.25, 0.2, 0.17 ...} и потом вместо 4/13 писали 4*x[13]. Сэкономили на делении
Вариант неплохой, но я никогда не работал с массивом байт, и даже не представляю себе, как это происходит =)
Эм, ну… как с обычным массивом? Индексы там, все дела…
Попробовал, скорость не изменилась.
Но выяснил много нового для себя:
1. array of array работает быстрее, чем одномерный массив того же размера. Не знаю, почему. Работает в 5 раз быстрее.
2. Большая часть затрат времени в одномерном массиве — это вычисление индекса [y*size+x];

(a: array of byte; a[y*size+x],00:00:01.1568314)
(b: array of array of byte; b[x,y],00:00:00.2420561)
(c: array [0..size,0..size] of byte; c[x,y],00:00:00.4520275)
(e: array [,] of byte := new byte[size, size]; e[x,y],00:00:00.2990774)

Почему array of array почти вдвое быстрее статического массива, при этом динамический прямоугольный массив быстрее статического, но медленнее динамического лесенкой? А чёрт его знает. Придётся сейчас всё обратно переписывать в лестничные массивы.

Заменил picture на array of array of byte.
Время выполнения png -> ASCII-png преобразования снизилось с 69 секунд до 19. + 10 секунд преобразования картинки в массив байт. Разумеется, этот код не будет поддерживать цвета, но это мелочи, всё же программа пилилась изначально монохромная.

П.С. Слегка оптимизировал говнокод picture -> array of array of byte, и сэкономил 5 секунд (из 10) этой части программы. В итоге теперь вся картинка со старта до готовности делается за 22 секунды, против 69 секунд одного только финального прогона в старой версии.

П.П.С. Всё таки у меня не работает {$omp parallel for} в циклах, выдаёт ошибку. Не знаю, почему, видимо не до конца убрал критические штуковины.
П.П.П.С. Всё-таки поставил где надо {$omp critical}. Теперь главный цикл выполняется за 4-5 секунд вместо 19 (и это с дорогущим рисованием! Без него за 2-4 секунды.). Ну и 5 секунд на все прошлые штуки.

Единственная проблема: где-то раз в 5-6 запусков всё равно в параллельном цикле возникает «какая-то ошибка». Никак не могу понять, с чем она связана.

P._P.P.P.s: Попробовал переводить в текст, и это не так просто, ибо 1. кодировки 2. она печатается перевёрнутой в непонятную сторону, и я сходу не могу понять, как именно это решить.

Последняя попытка выглядит вообще так:
Лиру распегасило
'���������������������������������������������������������������
''��������������������������������������������������������������
'''�������������������������������������������������������������
'''?������������������������������������������������������������
'''?�����������������������������������������������������������
''?§����������������������������������������������������������
''?'???���������������������������������������������������������
'?'?§��������������������������������������������������������
'?'?'???�������������������������������������������������������
'?'?'?§?������������������������������������������������������
'?'?''?§�����������������������������������������������������
'?''§§?'����������������������������������������������������
'?'?§??''���������������������������������������������������
'?§?''§''��������������������������������������������������
'?'??''??'''�������������������������������������������������
''?§?'''?§'''������������������������������������������������
'''?§§§?§§§'''�����������������������������������������������
''??§§§§§§§???''����������������������������������������������
''?§§§§§§?'§§§§���������������������������������������������
'''?§§§§§§?'§???§��������������������������������������������
'''§§§§?§''?'''§§�������������������������������������������
''?''????§a'''''§Ao������������������������������������������
''?''??'''?''''???�����������������������������������������
''?''?''''''''''??'??����������������������������������������
'''?'''?'''''''''''???§���������������������������������������
'''''§''''''''''§'?��������������������������������������
'''?'§§'''''?'?�������������������������������������
'''c''??§'?§'''??'������������������������������������
'''?c'?'§§§§§§§'???''�����������������������������������
'''?'?§§§§§§§§§§§§§?'''����������������������������������
'''?'u'§§§§§§§§§§§§§§§'?'���������������������������������
'''?'§§§§§§§§?§§§§§§?''§§§��������������������������������
''?'§§§§§§?''?§§§§§§§§§§§§§§�������������������������������
B??'§§§§?'''§§§§§§§§§§§§§§§?'������������������������������
§??'?'§§??'''§§§§§§§§§§§§§§§'''�����������������������������
'''''§'§§'''?§§§§§§§§§§§§§§§?'''''����������������������������
'''''?'§§?§§§§§§§§§§§§§§§§§'''''''���������������������������
''''''?§§§§§§§§§§§§§§§§§§§§§§?''''?'''��������������������������
'''''''§§§§§§§§§§§§§§§§§§§§?''?'''?�������������������������
'''''''''§§§§§§§§§§§§§§§§§§§''??''?������������������������
'''''''''§§§§§§§§§§§§§§§§§§§''r?''???�����������������������
'''''''''§§§§§§§§§§§§§§§§§§§§''r?4''?§'����������������������
'''''''''§§§§§§§§§§§§§§§§§§§§'d'??''?���������������������
'''''''''?§§§§§§§§§§§§§§§§§§§''?'§'''?'��������������������
''''''''?§§§§§§§§§§§§§§§§§§'§'§''?''�������������������
''''''''?'§§§§§§§§§§§§§§§§§§?''??'§§''?'������������������
''''''''?'§§§§§§§§§§§§§§§§§§''''''?§§§?'''�����������������
''''''''?''?§§§§§§§§§§§§§§?§'''''?§§§§§'''����������������
''''''''§'''§§§§§§§§§§§§?''?§''''?§§§§§§''f''���������������
'''''''??''§§§§§§§§§§??''''§§''''?§§§§?''''''��������������
''''''§''§§§§§§§§§§§''''''?§?'''§§§§?'?''''''�������������
'''''''?''§§§§§§§§§§§''''''§§''''§§§?''''''§������������
'''''''''''?§§§§§§§§§§§''''''§§?''''?§§§'?'''''?'�����������
''''''''''§§§§§§§§§§§§?'''''''?§§''''?§§§'''''?''����������
'''''''''§§§§§§§§§§§§§§''''''?§§''''?§§§§'?''''§''���������
'''''''''§§§§§§§§§§§§§§'''''§§?'''§§§§§?'?'''§''?��������
'''''''''?§§§§§§§§§§§§§§?'''''§§''''§?§§§?'''?'''???'�������
''''''''§§§§§§§§§§§§§§§?'''''§?''''§?§§§§''c''§???''������
'''''''''§§§§§§§§§§§§§§§§'''§§''''§''?§§§?'''???'''�����
''''''''''§§§§§§§§§§§§§§§'''?§?''''?§'§§§?''''???'����
''''''''''§§§§§§§§§§§§§§§?'§§'''''?§§§§§§''''??"???W??'���
''''''''''§§§§??????§§§§§§?'?§?'''''??§§§§§'''?'''''''''?��
''''''''''§§§§''''§§§§§§§?§§''''''?''?§§§?'''?'''''W?''�
''''''''''§§§?'''?§§§§§§§?§§?''''''??'''?§§§?'''§§§?§?"''''
Кажется, я нашёл ошибку.
Итак, по порядку.
except
    on E: Exception do
      Print(E.InnerException.ToString + ' поднята ошибка, с сообщением : ' + E.Message);

Нашёл этот код в интернете для Дельфи, но работает на Паскале. Не очень понимаю, как оно работает, что это за «on», но благодаря этому я получил более подробные сведения об ошибке. Ошибка возникала из-за одновременно вызова ut.GetHeight.
Создал переменную
var utHeight:= ut.Height

Сделал замену по всему коду ut.Height на utHeight.
и ошибки вроде пропали.
Ошибка возникала из-за одновременно вызова ut.GetHeight.
Ох лол, не thread-safe ридеры… Одна из самых злых ошибок в криво написанном коде, и тут, кажется, это код стандартной библиотеки, да? Вот уж чего не ожидалось-то…
Попробовал переводить в текст, и это не так просто, ибо 1. кодировки 2. она печатается перевёрнутой в непонятную сторону, и я сходу не могу понять, как именно это решить.
1. Юзай UTF-8 и будет тебе счастье =)
2. Возможно, у тебя битмап читается не в том направлении? Например, некоторые форматы любят хранить картинку снизу вверх, а не нормально.
Разумеется, этот код не будет поддерживать цвета
Ну, у тебя ж там по байту на пиксель, ты можешь и цвет хранить (а получать его, аппроксимируя все цвета в квадрате 8х8). Но это да, уже вторичная задача.
По 256 цветов на картинку, что соответствует цветовой палитре консоли windows с 16 и 16 цветами фона и символа.
Кстати, именно поэтому хранить средний цвет не подходит: нужно хранить цвет каждого пикселя. И потом сравнивать каждую ячейку с 256 символами, для каждого из которых делать 256 проверок на цветовые комбинации. Скажем так, сейчас обработка фотографий занимает до 6 секунд. В цвете это будет занимать до 30 минут. И код переписывать. Не. Не хочу.

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

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

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

а подобные ошибки
она печатается перевёрнутой в непонятную сторону, и я сходу не могу понять, как именно это решить

это нормальный процесс дебага. У меня, например, первый запуск проги был таким:
кек
это нормальный процесс дебага. У меня, например, первый запуск проги был таким:

Я бы даже сказал, что перевёрнутая Лира это не процесс дебага, а обычная путанность строк и строчек между собой. Постоянно такая проблема, что пишешь этот двумерный цикл, и путаешь, в каком порядке его читать-писать надо. Но я сейчас так плыву, что делать текстовый вывод уже не хочу. Надо для учёбы задания делать, накопил долгов немного.
динамический массив медленне статического емнип в основном при дополнительных аллокациях памяти и перемещениях его содержимого в новые места. Если он создан единожды и не меняется — то они плюс-минус равны.
Это ещё могло бы иметь значение в реальном (или крайне редко используемом защищённом с сегментной адресацией) режиме, если heap близкий, а bss далёкий, и наоборот — но обычно во всех моделях памяти и heap, и bss были одинаковой «дальности». При страничной адресации обычно вся память в одном сегменте, т.е. там этого фактора нет (есть промахи в страницы, но обычно, если не жрать память гигабайтами, то проблем с ними не возникает).
array of array работает быстрее, чем одномерный массив того же размера. Не знаю, почему. Работает в 5 раз быстрее.
Да уж, тут даже у меня догадок нет О.о
Казалось бы, ещё в 8086 появилась индексная арифметика для лоадов (вот это вот mov ax, [bp + si]), а сейчас с FMA и прочими вкусностями вообще с этим никаких проблем не должно возникать, однако ж.
В DOOM-е так синусы и косинусы оптимизировали — просто создавали при запуске предрассчитанную таблицу, и потом вместо sin(x) использовали sin_table[x] =)
Кстати нынче такая оптимизация может и во вред пойти… При теперешней разнице скоростей проца и памяти может оказаться выгодней пересчитать синус на FPU чем тащить из памяти, особенно если обращения не частые, и из кэшей его выкинуло.
Да, сейчас FPU, вроде как, все такие операции считает очень быстро (не исключено, что у него внутри тоже есть кэш результатов на SRAM), и такая оптимизация уже не нужна.
думаю там не кэш результатов как таковой, а благодаря жирному банку GPR, коих нам снаружи видно только 8/16 простых, 8 FPU и 8/16 SIMD, но внутри же их под сотню с переименованием…
Ну это технически уже и есть кэш на SRAM, лол =)
ну да, по сути SRAM, только извратная многопортовая, иначе смысл потеряется если паралелльно в несколько мест не залезть в нее.
Ну, кэш и так должен быть многопортовый, потому что в него сразу несколько конвейеров могут лезть =)
Ну кэш даже L1 в бытовых процах вроде 8-way максимум… а в регистровом банке вообще каждый в каждый должен уметь лазать же…
Ну, 8 портов должно хватать даже для суперскаляров и для гипертрединга (сложно представить ситуацию, когда одновременно 8 исполнительным устройствам понадобилось делать лоад или стор).
>нынче такая оптимизация может и во вред пойти
собсна это был пример устаревшай оптимизации с доклада про кэш от разработчика архитектуры микропроцев
Там есть System.Drawing.Bitmap
Хм. Из него вот прямо никак-никак нельзя получить массив байт, который memcpy-нуть куда-то к себе и потом с ним поработать?
Может, есть какая-нибудь библиотека, типа порт libpng для паскаля, которую можно подцепить и заюзать для такого?
Ну, в самом крайнем случае, как советуют в этой ветке, просто разок пробежаться getpixel-ом по картинке, преобразовав её в массив, а потом из массива putpixel-ом закинуть обратно.
Кстати, тебе, наверное, будет удобнее при этом хранить картинку не в виде сетки пикселей, а в виде сетки блоков 8х8, т.е. не в таком виде:
rows[N]
{
    columns[M]
    {
        pixel_t pixel;
    } 
}

а в таком:
rows[N/8]
{
    columns[M/8]
    {
        pixel_t pixel[8*8];
    } 
}

Так тебе будет проще работать с 64-байтными блоками, сравнивая их с нужным глифом, а не вырезать по куску из каждой из 8 занятых глифом строк.
если захардкодить векторизацию через SIMD-инструкции (опять же вопрос в умении)
Современные компиляторы сами умеют в SIMD (включая FMA) всё пихать, в принципе. Особенно на -O4 и -mtune=native. Скорее тут можно задуматься над раскидкой по ядрам (OpenMP, например), и перекидыванием на видюху с помощью OpenCL (вроде как, clang+LLVM умеют даже из С делать шейдеры, но это может быть не так эффективно, как если их сразу писать на соответствующих языках).
мне знакомый мозговитый прогер сказал, что копилляторы начиная с -O3 умеют, конечно, худо-бедно в векторизацию, но им надо специальным образом код писать, чтобы они поняли. Так что хз, пока себя этим не гружу и сижу на -O2
им надо специальным образом код писать, чтобы они поняли.
Ага, критичные участки весьма пользительно дизассемблером глядеть, и тюнинговать исходный код ручками… не только в части SIMD но и в целом. В компиляторе нынешнем удивительно сочетаются гений и идиот) Он может автоматом векторизировать 6-этажную формулу так что уххх-ты… и рядом тупо влепить три трамплина на один эксцепшен, убив все что только выгадал…
В идеале там ещё perf пригодится, чтобы ловить места, где промахов в кэш много (это уж компилятор никак не отловит, только если тупо с -fprofile-generate и потом -fprofile-use, но ручками всегда видно лучше, где правда надо фиксить, а где и так сойдёт).
Сейчас, вроде как, уже не худо-бедно (у меня при -mtune=native на -O3 куча операций с XMM-регистрами в дизасме всплывали, емнип, и это безо всяких #pragma ivdep) — то есть это стандартная фича на -O3.
надо еще имхо -msse4 ставить…
Возможно, но кажется, -msse4 автоматом включается при -mtune=native на тех процах, которые его поддерживают.
Тут спорить не буду… мне то для работы сборки с -mtune=native делать не с руки, ибо зоопарк железа тот еще… поэтому -O3 -msse4 ставлю
главное чтобы символы были 16*16 пикселей.
Но это же VGA 8x8 шрифт. Или ты апсэмплишь его вдвое?
хз, можно как-то сравнения цветов перенести на видеокарту?
Переписать код на OpenCL =)
Но это же VGA 8x8 шрифт. Или ты апсэмплишь его вдвое?






Нет, я просто настолько тупой, что до сих пор даже не подозревал об этом. А у меня 16 захардкожено повсюду. Щито поделать, пойду переделывать, и заодно введу переменную symbol_size.
Так, ну я привёл в соответствие шрифт и код, и на радостях нарисовал Селестию, но только из букв имени Её (и пробелов).
Чтобы увидеть буковки, откройте в новом окне и приближайте.
Ня, правда, я щас подумал, что чтобы её всю увидеть на железе, для этого надо иметь 4К-монитор и установить в консоли шрифт 8х8, и то, может, не хватит =)
комментарий скрыт
комментарий скрыт
Ты вообще пост читал?
комментарий скрыт
комментарий скрыт
>не оправдывает переработку пони в крякозябры.
правильно! Переработка в радугу куда полезней
комментарий скрыт
комментарий скрыт
комментарий скрыт
Это ж сколько нужно сидеть и писать кода для такой красоты. И я как программер скажу -это очень сложно
если прикинуть в сухом остатке — понадобился примерно месяц работы часов по 5-6 в день (но здесь львиная доля времени у меня ушла на продумывание алгоритмов и курение мануалов). А ещё где-то треть времени это неудачные попытки справиться в рамках стандартной палитры винды
например используя зашумление | псевдотонирование
Когда-то у меня была идея аналогичной программы, только в теории она должна была генерить HTML с выдачей цвета через style=«color: #xxxxxx;» каждому отдельному символу, и тогда бы не было ограничений на цвета, как в случае с консолью. Но чет руки так и не дошли.
Мы так с народом, когда были ламерами в веб-дизайне, делали динамически генерящуюся капчу: table 160x80 из ячеек 1х1, каждая со своим bgcolor =) Ох и большой HTML-ник тогда у нас получался…
(да, даже про CSS мы тогда не знали =)
Помню было такое tabun.everypony.ru/blog/computers/114109.html
Да, про это тоже помню. И ещё про одну чью-то реализацию 2016 года. Но сразу найти не удалось, а дальше лень
Поэтому, когда кто-то приходит опять с тем же самым, это вызывает небольшое раздражение…
Там все ссылки дохлые. «Покажите мне код» © Linus Torvalds
Я понимаю, конечно, что, во-первых, про это уже успели сказать выше.
А во-вторых, никто не ищёт в удалённых постах, обычно.
НО блин, ещё в 2013 году, совсем недолго до моего появления, тут сообщалось о возможности смотреть видео в консоли. И да, на винде тоже.
Поэтому я считаю, что вам не удастся сказать что-то новое в этой теме, и не следует уделять этому проекту так много времени.
Но, если полностью осознавая, что это лишь учебный проект, вы хотите хорошо чему-то научиться, то… впрочем, да хозяин-барин, вы сами господа своего времени.
«Не удастся сказать что-то новое» — это ты про уже давно наскучивший всем mplayer + libcaca? Тут человек даже код выложил, а не просто скачал и запустил.
Ан нет. Прога таки без кода, ну всё равно писал сам, что уже похвально.
ладно, ты меня уболтал. Скоро всё залью и обновлю постецкий
я с самого начала понимал, что изобретаю велосипед, но задача была и остаётся слишком уж интересной. Проект не столько учебный, сколько развлекательный — хобби, короче говоря. Потраченного времени не жаль; всяко полезнее того, чем я обычно занимаюсь
залил на гитхаб. Ругать меня за хреновый код предлагаю тут, в отдельной ветке
В ридми бы билд-инструкции добавить (не, я понимаю, что там простое `c++ *.cpp -o ascii-conv`, но об этом бы сказать).
И коммит-мессаги малоинформативные немножко…

А ещё я такой думаю, о, щас как напилю себе текстовых картинок, и такой иду и…
sakuya ~ # git clone https://github.com/RinoNeiber/ascii-conv
Клонирование в «ascii-conv»…
remote: Enumerating objects: 27, done.
remote: Counting objects: 100% (27/27), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 27 (delta 8), reused 0 (delta 0), pack-reused 0
Распаковка объектов: 100% (27/27), готово.
sakuya ~ # cd ascii-conv
/root/ascii-conv
sakuya ~/ascii-conv # c++ *.cpp -o ascii-conv && cp ascii-conv /usr/local/bin
lcc: "Drawing.cpp", строка 5: фатальная ошибка: не могу открыть исходник файл
          "windows.h"
  #include <windows.h>
                      ^

1 катастрофическая ошибка обнаружено при компиляции "Drawing.cpp".
Compilation terminated.
sakuya ~/ascii-conv #
Ну ок, думаю я, привык я слишком к кроссплатформенности =(
коммиты пока просто с малюсенькими поправками, особо не парился. Билд-инструкции добавлю. Насчёт кроссплатформенности — вроде на каждом углу писал «for Windows» D:

порт на линукс у меня первый в списке дел по проекту, так что ожидайте
Окей, будем ждать =)
Просто я, как уже говорил, привык к кроссплатформенности слишком, видимо, и забываю подумать о том, что «это может не пойти на линуксе» перед тем, как сделать clone / make / make install =)
Я с гуманитарием головного мозга:
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.