13.10.2001 :: Об одном методе красивого вывода текста. Часть 2

Опубликована в журнале
Радиомир. Ваш компьютер, 2001, №11.

В первой части статьи было рассмотрено построение статических текстов различного размера и начертания с примерами на ассемблере Z80, ориентированными на компьютер ZX-Spectrum. Здесь же я покажу Вам малую толику из всего многообразия возможностей рисования текстов указанным методом на языке Turbo Pascal для PC, а также изложу некоторые соображения по применению и развитию кода.


Для использования в собственных программах всей красоты и шарма графических текстов Вам необходим модуль NiceType.


Unit NiceType; {
+------------------------------------------------------------+
¦ Модуль предназначен для графического вывода шрифтами BIOS ¦
¦ текстов различного размера, стиля и начертания ¦
¦ Code (c) 2001 by Oleg N. Cher [aka Steve Mitchell] ¦
¦ e-mail: stef(at)anarxi.st ¦
+------------------------------------------------------------+
} (* --------------------------------------- *) Interface
uses Graph; { Прототипы BIOS-шрифтов: }
type FontAspect = (int1Fh, { текущий для INT 1Fh }
int43h, { текущий для INT 43h }
_8x14, { ROM 8x14 }
_8x8d, { ROM 8x8 двойной }
_8x8h, { -"- (старшие 128 симв) }
_9x14, { ROM alt. 9x14 (EGA, VGA)}
_8x16); { ROM 8x16 (MCGA, VGA)}
const nTypColor: word = White;
procedure nTypFont (Ft:FontAspect);
procedure mSprite (sX,sY:integer; sColor:word; sImage:string);
procedure nType (x,y,Dx,Dy,lp,hp,lcr,hcr:real;img,txt:string);
procedure nTypeC (x,y,Dx,Dy,lp,hp,lcr,hcr:real;img,txt:string;
C:word);
{ x, y - координаты левого угла первого символа текста;
Dx - расстояние между буквами по горизонтали;
Dy - расстояние между буквами по вертикали;
lp - наклон литер по горизонтали:
0 - прямо, +N влево, -N - вправо;
hp - расстояние между спрайтами букв вертикальное;
lcr - расстояние между спрайтами букв горизонтальное;
hcr - наклон литер по вертикали:
0 - прямо, +N вниз, -N - вверх;
img - образ спрайта
text - непосредственно текстовая строка
C - глобальный цвет текста }
(* ---------------------------------- *) Implementation
var nTypFt, FtHgt : byte;
procedure nTypFont (Ft : FontAspect);
begin
nTypFt := Ord (Ft); case Ft of
int1Fh: begin FtHgt := 8 end;
int43h: begin FtHgt := 16 end;
_8x14 : begin FtHgt := 14 end;
_8x8d : begin FtHgt := 8 end;
_8x8h : begin FtHgt := 8 end;
_9x14 : begin FtHgt := 16 end; {?}
_8x16 : begin FtHgt := 16 end end
end;
type byteptr = ^byte;
function GetSymbPtr (Symbol : char) : byteptr; assembler;
asm push bp
mov ax,$1130
mov bh,nTypFt
int 10h
mov dx,bp
pop bp
mov al,Symbol
xor ah,ah
mov cl,FtHgt
mul cl
add ax,dx
mov dx,es
end;
procedure mSprite (sX,sY:integer; sColor:word; sImage:string);
var i, j , bitptr, scntr, sprlen : byte; tX : integer;
begin
tX := sX; sprlen := (length (sImage) - 1) div 16 + 1;
scntr := sprlen;
for i := 1 to length (sImage) do
begin
bitptr := byte (sImage [i]);
for j:= 0 to 7 do
begin
if (bitptr and $80) = $80
then PutPixel (tX,sY,sColor);
bitptr := bitptr+bitptr; inc (tX)
end;
dec (scntr);
if scntr = 0
then begin scntr := sprlen; tX := sX; inc (sY) end
end
end;
procedure nType (x,y,Dx,Dy,lp,hp,lcr,hcr:real;img,txt:string);
var i, j, n, str8pix : byte; fontptr : byteptr;
arjx, arjy, xt, yt: real;
begin
for n := 1 to length (txt) do
begin
fontptr := GetSymbPtr (txt [n]); arjx := x; arjy := y;
for i := 1 to FtHgt do
begin
str8pix := fontptr^; inc (fontptr);
xt := x; yt := y;
for j := 0 to 7 do
begin
if (str8pix and $80) = $80
then mSprite (round(xt),
round(yt), nTypColor, img);
str8pix := str8pix+str8pix;
xt := xt+lcr; yt := yt+hcr;
end;
x := x + lp; y := y + hp
end;
x := arjx + Dx; y := arjy + Dy
end
end;
procedure nTypeC (x,y,Dx,Dy,lp,hp,lcr,hcr:real;img,txt:string;
C:word);
begin nTypColor := C;
nType (x,y,Dx,Dy,lp,hp,lcr,hcr,img,txt) end;
end.
begin nTypeFont (int43h) end.

Ох, эти строчки, строченьки, что набраны петитом...


Для воплощения своих идей в жизнь модуль предоставляет Вам на выбор фонты BIOS следующих размеров: CGA 8x8, EGA 8x14 и VGA 8x16. Кроме этого можно применить с NiceType пользовательские таблицы образов шрифтов указанных выше стандартных размеров. Впрочем, давайте по порядку: первый в прототипе фонт, на который указывает вектор прерывания 1Fh, и является такой таблицей размером 8x8. Перед использованием таблицы (в ней можно хранить национальные символы, псевдографику, текстуры) необходимо загрузить шрифт в память и отдать указатель на него вектору. Таблица требует для своего размещения 128 * 8 байтов памяти: 128 символов * 8 байтов на каждую литеру. Следующий фонт 8x16 (на него ссылается вектор 43h) имеет те же свойства. Шрифты _8x14 и _8x16 — системные BIOS-шрифты со стандартным расширением второй половины таблицы (“кракозябры” и псевдографика). Фонты _8x8d и _8x8h — соответственно основная и расширенная таблицы ASCII.

Можно предположить, что при наличии “правильного” русификатора не меняя ни единой строчки кода с помощью процедуры nType удастся вывести и русский текст (для экономии памяти скорее всего русифицирован только шрифт int43h), но согласитесь, что рассчитывать на это, как минимум, глупо, да и не является самым лучшим тоном. Советую приспособить модуль для работы с некоторыми подгружаемыми фонтами — сулит неплохие возможности в области текстостроения. Это могут быть фонты, подготовленные самостоятельно в каком-нибудь редакторе шрифтов или же готовые растровые фонты, а хоть и .chr от Windows, только разберитесь как следует в их формате. Попутно придётся изменить чуть текст программы: добавляем в тип FontAspect не-BIOS прототипы для различных пользовательских шрифтов. Изменения также коснутся процедуры GetSymbPtr, вычисляющей сегмент и смещение необходимого символа в памяти, а ещё процедуры nType, которая почему-то считает, что все растровые шрифты в мире имеют ширину 8 пикселей.

О формате монохромного спрайта для mSprite. Обратите внимание: нигде явно мы не задаём высоту и ширину спрайта. А вычисляется она по количеству байтов. Формат сей таков, что спрайты всегда квадратны, независимо от того, сколько байтов отпущено под их представление. Если байтов восемь или меньше, спрайт, получается, квадратик 8x8; больше восьми — 16x16, если больше 32 — 24x24 и т. д.

Образ спрайта задаётся в процедуре символьной строкой, каждый бит значений которой управляет светимостью пикселя так: при размере до 8x8 первый байт определяет 8 пикселей первой строки на экране, второй байт — 8 пикселей второй строки и т. д. В случае размера от 8x8 до 16x16 первые два байта определяют 16 пикселей первой строки, следующие — 16 пикселей второй строки и т. д.

“Я хочу, чтобы картинка...”
Текст в динамике

Ну, со статикой более или менее понятно. А в качестве базиса для собственных экспериментов воспользуйтесь примерами из NiceDEMO.

Эффекты, вошедшие в минидемо: гигантская надпись, выложенная из спрайтов-снежинок, “раскладывающаяся” сверху и оставляющая “снежный” след; разноцветная “тень” у символов, конструирование литер большим “рубленым” шрифтом с помощью цикла. Вращение надписи, подобно часовой стрелке и вывод её по линеечке под разными углами, “декоративные” тексты различных размеров, оттенённые, штриховые, текстурные; многоцветный вращающийся знак вопроса, скроллинг текста бегущей строкой, и, наконец, рисование текста чередующимися спрайтами. Проецирование на экран плавающих в “трёхмерном” пространстве плоскостей с лежащими на них буковками, размер которых, расстояние между ними и сторона от наблюдателя меняются динамически, впрочем, тут всё очень упрощено; и, наконец, слетающиеся отовсюду точки, которые образуют требуемую надпись.


program NiceTypeDemo;                 { This easy DEMO coded }
uses CRT, Graph, NiceType; { by Oleg N. Cher }
const
GD:Integer = VGA;
GM:Integer = VGAhi;
pi = 3.1415926535898;
z1 = 0; z2 = 340; z3 = 160; z4 = 479; {область скроллинга}
var r : real; i, c, txp, mov : byte; p : pointer;
size : word; text, spr : string; j : integer;
begin
InitGraph (GD, GM, ''); nTypFont (_8x14);
for c := 1 to 32 do for i := 0 to 1 do begin
nTypColor := i*Cyan; if c = 32 then nTypColor := LightGreen;
(* Гигантская надпись, выложенная из спрайтиков-снежинок *)
nType (110,-54,155,32,-10,c,18,4,
#0#128#4#144#32#130#18#164#72#136#4#145#18#164#1#192 +
#34#162#4#144#72#168#16#133#34#144#8#162#2#160, 'Myth')
end; spr := #0#0#0#1#0#2#1#0#2#0#1#1#1#2#2#2;
(* Многоцветное оттенение буковок *) nTypFont (int43h);
for i := 1 to 13 do nTypeC (119+i,99+i,40,16, -2,6,4,2,
#90#165#90#165#90#165#90#165, 'Corporation', i);
(* С помощью цикла конструируем "жирный" рубленый шрифт *)
for i := 0 to 28 do nTypeC (230+i,10,00,57, 0,4,16,0,
#255#255#255#255#255#255#255#255#0#0#0#0#0#0#0#0#0,
'presents', White); ClearDevice;
size := ImageSize (z1,z2,z3,z4); GetMem (p, size);
text := 'Hi folx! =) This easy DEMO was written 4 da ' +
'magazine ~Radioamateur~ especially..! ';
(* Примеры текстурных, штрихованных, оттененных текстов *)
nType (160,445,11,0,0,2,1,0,#16#17, '(C) 2001 Oleg N. Cher');
nTypeC (456,125,33,-2,2,2,4,-1,#16#17, 'DOS', Cyan);
nTypeC (441,1,18,6,0,2,2,1,#112#112, 'WINDOWS', Green);
nTypeC (431,19,18,6,-1,2,2,0,#10#10, 'OS/2', LightGray);
nTypeC (463,48,20,6,1,4,2,0,#16#16#16#16, 'LinuX', Red);
nTypeC (23,34,16,3,-1,2,2,0,#128, 'Assembler...', LightCyan);
nTypeC (62,91,8,0,0,2,1,0,#16, 'BASIC', LightRed);
nTypeC (29,125,13,0,0,4,1,0,#128#3#3#3#3, 'C++', LightBlue);
nTypeC (100,178,-13,25,-1,2,2,0,#80, 'PASCAL', Yellow);
for c := 0 to 1 do begin nTypColor := 4+c*10;
nType (419+4*c,377+3*c,16,-0.9, 0,4,2,0, #128#64#128#64,
'What to do?');
nType (419+4*c,420+3*c,18,0.9, 0,4,2,0, #128#64#128#64,
'How to be?') end;
c := 1; txp := 1; mov := 0; repeat r := 0; repeat
inc (c,2); if c = 15 then c := 1; nTypFont (int43h);
(* Сменяющими друг друга спрайтами имитируем горение букв *)
nTypeC (301,233,8,0,0,1,1,0,spr[c]+spr[c+1], 'O.K.',Yellow);
nTypFont (_8x8d); nTypColor := c;
(* Вращающийся по часовой стрелке знак вопроса *)
nType (559-45*cos(r+pi/4),279-45*sin(r+pi/4),0,0, -10*sin(r),
10*cos(r),10*cos(r),10*sin(r), #1#2#4#8#16#32#64, '?');
nTypColor := LightGreen; for i := 0 to 1 do begin
(* Вывод надписей по линеечке под различными углами *)
nType (cos(r-0.1)*28+313-i,sin(r-0.1)*28+240-i,
8*cos(r),8*sin(r), -sin(r),cos(r),cos(r),sin(r),
#1, 'Myth Corp. is the Best!'); nTypColor := Green end;
nTypeC (cos(r+pi/16-0.06)*80+313,sin(r+pi/16-0.06)*80+240,
20*cos(r+pi/16),20*sin(r+pi/16),-2*sin(r+pi/16),
2*cos(r+pi/16),2*cos(r+pi/16),2*sin(r+pi/16),
#112#112, 'Cool!', Magenta);
(* Скроллинг текста диагональной бегущей строкой *)
inc (mov); if mov = 9 then begin mov := 0; nTypFont (_8x14);
nTypeC (121,406,0,0, -2,4,4,2, #90#165#90#165#90#165#90#165,
text[txp],Cyan); if txp = length (text) then txp := 0;
inc (txp) end;
GetImage (z1+3,z2+2,z3,z4,p^); PutImage (z1,z2,p^,0);
{ nTypFont (_8x8d); } (* "Гасим" знак вопроса *)
nTypeC (559-45*cos(r+pi/4),279-45*sin(r+pi/4),10*cos(r),
10*sin(r),-10*sin(r),10*cos(r),10*cos(r),10*sin(r),
#1#2#4#8#16#32#64, '?',0); r := r + pi/8;
nTypeC (301,233,8,0,0,1,1,0,spr[c]+spr[c+1], 'O.K.',Red)
until r >= 2*pi until KeyPressed; FreeMem (p, size);
ClearDevice; SetFillStyle (8, 2); Bar (0,0,GetMaxX,GetMaxY);
(* Плавающие в "трехмерном" пространстве литеры текста *)
nTypFont (_8x8d); r := 60; for j := -200 to 200 do begin
nTypeC (200+j,97-j,51,16, 0,r,r,-1, #28#30#119#99#119#30#28,
'Myth', LightGreen); Delay(100);
nTypeC (200+j,97-j,51,16, 0,r,r,-1, #28#30#119#99#119#30#28,
'Myth', Green); r := r - 0.5 end; nTypFont (int43h);
(* Точки слетаются из пространства и образуют надпись *)
ClearDevice; for c := 30 downto 2 do begin
nTypeC (170,190,18,1,0, c,c,0,#1#2, 'Satisfaction!',
LightGreen); delay (400);
nTypeC (170,190,18,1,0, c,c,0,#1#2, 'Satisfaction!', Green)
end;
nTypeC (170,190,18,1,0, c,c,0,#1#2, 'Satisfaction!', White);
Delay (999); ReadKey; CloseGraph
end.

И ещё немножко идей по построению динамических текстовых эффектов. Можно:


И, наконец, различные комбинации этих и других эффектов.


Страницы. Если рисование литер в динамике производить только на одной странице (вывели текст, подождали чуть, стёрли, опять вывели, но немножко другого размера, или в другом месте экрана), большинство эффектов, даже несмотря на оптимизацию mSprite, будут нещадно мерцать. Самый очевидный путь преодоления этой неприятности состоит в использовании страниц видеопамяти (см. стандартные процедуры библиотеки графики SetActivePage и SetVisualPage) или виртуального экрана.

“Оптимизировать под самую крышу”

Изначально демо ориентирована на режим VGA 640x480x16. Поскольку весь вывод спрайтов для упрощения реализован через стандартную процедуру PutPixel модуля Graph (заведомо неэффективное решение), путём изменения процедуры рисования точки (или графического драйвера) изменяется и видеорежим, он может быть абсолютно любым.

Можно несколько повысить эффективность работы модуля и весь вывод осуществлять вместо mSprite стандартной процедурой PutImage, которая, ясно, быстрее, а также написать код, который будет заниматься перекодированием аргумента-спрайта nType в формат BGI. Но тогда придётся урезать универсальность NiceType каким-то одним режимом (или несколькими, но только 16 цветными, или только 256-цветными), ибо, насколько мне известно, внутренний формат спрайта BGI различен для разных графических драйверов.

Более кошерным решением здесь будет применение процедуры вывода прозрачного спрайта (с возможностью наложения на имеющееся изображение), специально оптимизированной под используемый режим дисплея. Специально писать такую процедуру, очевидно, не стоит, гораздо проще её найти.


Можно пойти ещё дальше. Мне лично очень по вкусу bitblt компиляция, идея, которую пользуют для значительного программного повышения графической производительности подавляющее большинство кодеров и демомэйкеров на платформе ZX-Spectrum. Я перерыл множество графических библиотек в поисках необходимой процедуры для собственных нужд приемлемой скорости (в 16-цветных режимах), и среди множества процедур вывода спрайтов мне ни разу не попалась реализующая этот подход, что позволяет предположить, что bitblt-компиляция остаётся почти неизвестной для большого количества программистов видеоигр на PC. А зря, ведь по самым скромным прикидкам она позволяет ускорить вывод спрайтов как минимум в три раза. Способ особенно хорош, когда приходится выводить несколько одинаковых спрайтов, как в нашем случае. И здесь ему нет равных.

Bitblt (bit-Block Transfer) — это операции объединения участков одной или нескольких битовых карт-источников и битовой карты-приёмника с помощью логических операций. Bitblt-компилирование — метод, при котором процедура компилирования преобразует спрайт в машинный код, а процедура вывода спрайта просто выполняет этот код с максимальным быстродействием.

В [2] Вы можете найти очень быструю процедуру для стандартного одностраничного режима 320x200x256, основанную на bitblt-компиляции, которую с минимальными изменениями можно использовать вместо mSprite. А как работать со страницами на низком уровне можно посмотреть в [3]. Очень информативная статья, всячески рекомендую.


А что же дальше? День и пища
Перспективы

Некоторые мысли по поводу усовершенствования. Можно попытаться сделать спрайтики многоцветными, это ещё более оживит картину. Можно выводить их не просто как normal put, а с помощью логических операций not, and, or, xor, что несомненно позволит добиться интересных и красивых эффектов, если спрайты налагаются друг на друга или на что-либо уже находящееся на экране.

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


Лучший домашний компьютер — ZX-Profi 512.

Наикомфортнейшая из оболочек — DOS Navigator.

Самая подходящая ОС для работы — OS/2.


Удачи.


Скачать архив-приложение к статье: исходные программные модули и уже собранную NiceDEMO.


Литература


  1. А. Тихонов. И надпись написал... // Монитор. — 1993. — № 3.
  2. М. Эйбраш. Быстродействующие адаптёры VGA и трёхмерная анимация // Журнал д-ра Добба. — 1993. — № 1.
  3. Б. Телеснин. Адаптёр VGA. Режим 256 цветов // Монитор. — 1993. — № 1-2.


http://colossoft.anarxi.st