Конвертируем картинку в ANSI

в 17:04, , рубрики: ansi, c++, Программирование, метки:

Не знаю, насколько это будет кому-то интересно, но на днях решил поиграться и сделать следующее:

Дано: Картинка (например, BMP) 640 на 400, шрифт 8 на 16

Надо: Перевести ее в ANSI псевдографику в стандартном режиме 80 на 25 символов, символы и фон могут иметь любой цвет (true color).

image


Здесь стоит оговориться, что «тру» EGA текстовый режим с 16 цветами нам не подойдет, так как получится слишком некрасиво из-за низкого разрешения и большого размера символов. Второй аргумент: задача не стоИт максимально приблизить картинку ее символьному представлению, так как тогда можно было бы уменьшать шрифт, увеличивать разрешение и т.д. Задача — сделать именно такой фильтр, который описан выше.

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

Для реализации нам понадобится какая-нибудь библиотека для работы с графикой, умеющая getpixel и putpixel, то есть считать цвет пикселя с экрана и нарисовать пиксель заданным цветом на экран. Я пользуюсь старенькой, но проверенной библиотекой Allegro (www.allegro.cc), которая легка в обращении, а также позволяет работать в разных ОС (даже в MSDOS!).

Но хватит слов, пора к делу.

const int size_x=640;
const int size_y=400;
const int ascii_x=80;
const int ascii_y=25;

Устанавливаем константы, отвечающие за размер картинки и размер ее текстового представления.

for(int cx=0;cx<ascii_x;cx++)
	for(int cy=0;cy<ascii_y;cy++){
		int fullr=0;
		int fullg=0;
		int fullb=0;
		int pixels=0;

//find background color
		for(int i=0;i<size_x/ascii_x;i++)
		for(int j=0;j<size_y/ascii_y;j++){
			int col=getpixel(pic,i+cx*size_x/ascii_x,j+cy*size_y/ascii_y);
			int r=getr(col);
			int g=getg(col);
			int b=getb(col);
			fullr+=r;fullg+=g;fullb+=b;
			pixels++;
		}
		fullr/=pixels;fullg/=pixels;fullb/=pixels;

Начинаем основной цикл, в котором последовательно находим каждый символ из 80 на 25 матрицы. Следующий цикл — по каждому пикселю той области картинки, которая соответствует на экране данному символу из этой матрицы. Как легко сообразить, область эта составляет 640/80=8 на 400/25=16 пикселей. Здесь полезно вспомнить, что это как раз равно размеру нашего шрифта.

В этом втором цикле наша задача — найти средний цвет, который мы установим в качестве цвета фона нашего ANSI символа. Сделать это очень просто: раскладываем цвет на R, G, B компоненты и вычисляем среднее по каждой из них.

//find ascii and its color
		int rms=100000000;

		for(int curch=1;curch<256;curch++){ //loop over ASCII table
			int charr=0;
			int charg=0;
			int charb=0;

//find char color
			pixels=0; //number of pixels for char
			for(i=0;i<size_x/ascii_x;i++)
			for(int j=0;j<size_y/ascii_y;j++){
				int col=getpixel(pic,i+cx*size_x/ascii_x,j+cy*size_y/ascii_y);
				int r=getr(col);
				int g=getg(col);
				int b=getb(col);

				int colch=getpixel(font,i+(curch%32)*8,j+(curch/32)*16);
				int rc=getr(colch);
				int gc=getg(colch);
				int bc=getb(colch);
				if(rc!=0&&gc!=0&&bc!=0){ //get actual char pixels only
					charr+=r;
					charg+=g;
					charb+=b;
					pixels++;
				}
			}
			if(pixels!=0){
				charr/=pixels;charg/=pixels;charb/=pixels;
			}

Дальше уже более интересно. Нам приходится вставить еще один цикл — по каждому символу из ASCII таблицы (всего их 256). Я беру их из черно-белой картинки с шрифтом, то есть теоретически, там могут быть любые изображения 8 на 16 в качестве символа. Стоит отметить, что в этом месте можно ограничиться только какой-то одной областью символов (например, кириллицей), что

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

Стоит также отметить, что число таких пикселей может быть равно нулю (например, символ 32 — пробел). С этим надо быть внимательнее.

//find rms
			int currms=0;
			for(i=0;i<size_x/ascii_x;i++)
			for(int j=0;j<size_y/ascii_y;j++){
				int col=getpixel(pic,i+cx*size_x/ascii_x,j+cy*size_y/ascii_y);
				int r=getr(col);
				int g=getg(col);
				int b=getb(col);
				int rr=0,gg=0,bb=0;

				int colch=getpixel(font,i+(curch%32)*8,j+(curch/32)*16);
				int rc=getr(colch);
				int gc=getg(colch);
				int bc=getb(colch);
				if(rc!=0&&gc!=0&&bc!=0){ //char pixel
					rr=charr;
					gg=charg;
					bb=charb;
				}
				else{ //back pixel
					rr=fullr;
					gg=fullg;
					bb=fullb;
				}
				currms+=sqrt((r-rr)*(r-rr)+(g-gg)*(g-gg)+(b-bb)*(b-bb));
			}

			if(currms<rms){ //find minimal rms
				findr=charr;
				findg=charg;
				findb=charb;

				findch=curch;
				rms=currms;
				if(DEBUG)printf("!!!%d %dn",currms, curch);
			}
			else
				if(DEBUG)printf("%d %dn",currms, curch);

		} //find char

Ну и, наконец, пришли к самой соли. В этом куске кода мы еще раз проходимся по пикселям заданной области картинки и считаем RMS (среднеквадратичное отклонение) всех трех компонент цвета от цвета фона или символа (в зависимости от того, находится ли данный пиксель на фоне или на символе). Суммарный RMS и будет являться критерием того, насколько данный символ хорошо соответствует данной области картинки.

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

image

Программу и исходный код можно скачать здесь: dimouse.ru/data/ansiconv.rar (130 Kb).

Как насчет нарисовать это дело в текстовом режиме? Как вы можете увидеть, программа создает файл с результатами (кроме нового bmp) — ascii. Там для каждого символа хранится его значение в ASCII таблице, его цвет и цвет его фона. Я для текстового режима пользуюсь pdcurses.sourceforge.net/ (пишите в комментариях, если знаете что-то лучше!), но эта библиотека, хоть и является враппером над SDL (во всяком случае, виндовая версия — точно), не умеет показывать цвета больше 16: тяжелое наследие древности. Как вы, наверное, помните, PDCurses берет свое начало от знаменитой Interactive Fiction игры Curses. Но один товарищ написал усовершенствование этой библиотеки и выложил здесь: www.projectpluto.com/win32a.htm

К сожалению, в ней нельзя использовать bitmap шрифт, только системные (зато можно выбрать любой), что несколько портит впечатление. Но задачу также можно считать выполненной.

Программу для чтения ascii можно скачать здесь: dimouse.ru/data/ascii.rar (70 Kb).

Автор: Dimouse

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js