Сергей Андрианов
17.12.2001
В статье «Палитра VGA: управление цветом» было рассказано о том, как правильно вывести спрайт на экран. Теперь настала очередь разобраться с текстом. Нетрудно догадаться, что единственный способ его вывода на экран — прорисовка каждой буквы по точкам, т. е. здесь применяется та же самая технология, которая уже была рассмотрена на примере отображения спрайтов. Самый универсальный вариант — выводить каждую букву как спрайт, тем более что при этом можно применить красивые многоцветные буквы различного размера. Однако, во-первых, на экране с разрешением 320x200 точек не очень-то развернешься с крупными шрифтами, а при использовании мелких трудно получить что-нибудь более удачное, чем стандартный шрифт. Во-вторых, даже при размере символа 8x8 точек для хранения одного шрифта придется отвести 16 Кбайт оперативной памяти. И в-третьих, такие шрифты нужно самостоятельно рисовать, что при 256 символах нелегкий труд, да и довольно бессмысленный для небольшой игрушки. Поэтому в качестве альтернативы пойдем по самому простому и наименее ресурсоемкому пути и выберем стандартный шрифт, который ничто не мешает сочетать со спрайтовыми.
В VideoBIOS компьютера помещена таблица со шрифтами разных размеров: 8x8, 8x14 и 8x16 точек. В России они, как правило, хранятся в теле резидентной программы, проводящей русификацию. Будем работать с самым «экономичным» из них — 8x8 точек, но для этого нужно знать адрес той ячейки памяти, с которой начинается его размещение. Этот адрес можно получить с помощью функции VideoBIOS (см. листинг 1). Для его хранения предусмотрена переменная FontTable. Ее значение ставится в блоке инициализации модуля, и тогда не требуется вызывать никаких дополнительных процедур, а шрифты станут доступны, начиная с первой строки основной программы. Надо сказать, что память при этом не расходуется (не считая 4 байт на указатель), поскольку используются шрифты, загруженные в ОЗУ.
Итак, приступим к выбору цветов для символов и фона. Вопрос не такой уж простой, каким кажется на первый взгляд. Дело в том, что после установки палитры в первых 16 регистрах уже не будет тех цветов, к которым все привыкли: 0 — черный, 1 — синий, 2 — зеленый и т. д. Конечно, можно смириться с этим и просмотреть большую простыню1 с описанием всех 256 цветов, чтобы отобрать самый подходящий. Правда, для работы все равно придется ограничиться таблицей с номерами все тех же хорошо знакомых 16 цветов, а раз так, не проще ли поручить компьютеру конвертировать привычные для нас номера в номера рабочей палитры. Однако тогда мы уже не сможем применять для отображения текста остальные 240 цветов, но так ли уж велика эта потеря? Мне, например, более симпатичен именно такой вариант.
У редактора Paint, с помощью которого рисовались спрайты для нашей программы, есть своя любимая палитра. Если создавать картинку с нуля или преобразовать полноцветное (24-разрядное с 16 млн. цветов) изображение в палитровое (8-разрядное 256-цветное), то этот редактор будет работать с одной и той же палитрой. Хотя ее цвета могут оказаться не самыми оптимальными для решения нашей задачи, есть уверенность, что все выполненные изображения будут воспроизводиться на экране в правильных цветах. Номера цветов палитры Paint, наиболее близкие к стандартным 16, хранятся в массиве Colors нашей программы. Если 16 цветов все-таки будет не хватать, длину этого массива можно увеличить. На нулевом месте записан номер черного цвета, на первом — синего и т. д. Если вы выберете какую-либо другую палитру, то цифры придется подкорректировать. Можно, конечно, подбор цветов поручить и компьютеру, но тогда такую процедуру придется вызывать только после установки палитры. Впрочем, этот блок позволяется разместить и в модуле программы, устанавливающем палитру, однако мне хотелось сделать так, чтобы все модули были независимы друг от друга.
Модуль вывода изображения на экран в режиме 256 цветов содержит следующее:
процедуру задания параметров вывода текста SetTextParm, с помощью которой можно установить цвет символов, цвет фона под символами (если он будет отображаться), а также способ вывода текста (с прозрачным или с непрозрачным фоном);
дополнительную процедуру запроса текущих параметров текста GetTextParm. В обеих приведенных выше процедурах используются номера цветов, соответствующие стандартной палитре;
процедуру вывода текстовой строки в определенное место экрана PutText. Даются координаты верхнего левого угла прямоугольника, в котором будет выведена первая буква, т. е. так же, как и для спрайтов. Процедура не проверяет вероятность выхода за пределы экрана и не производит перенос на следующую строку, — забота об этом лежит на программисте;
процедуру вывода отдельного символа PutChar, которая используется процедурой PutText, но имеет и отдельный вход в интерфейсной части модуля;
процедуру вывода точки на экран PutPixel и функцию GetPixel, возвращающую цвет запрошенной точки. Первая используется, в свою очередь, процедурой PutChar, а вторая введена лишь для большей полноты картины. Если эти процедуры необходимы, то следует скопировать их заголовки в интерфейсную часть модуля (изначально они отсутствуют). Но вряд ли это целесообразно, поскольку поточечный вывод изображения происходит очень медленно, а в модуле вывода текста используется лишь потому, что сам текст занимает обычно лишь очень небольшую долю экрана и, кроме того, не перерисовывается на каждом кадре.
Как уже было сказано, буквы выводятся по одной точке, каждой из которых на экране отводится 1 байт, а вот в шрифтах они лежат гораздо компактнее: на одну точку приходится один бит. Весь горизонтальный ряд точек помещается в одном байте, а вся буква — в восьми. При отрисовке буквы перед записью байта в видеопамять проверяется, что находится в нужном бите таблицы шрифтов: 0 или 1. В нашем примере будем выводить надписи в каждом кадре, что более наглядно.
Чтобы посмотреть, как работает описанный модуль, следует включить его имя в директиву uses, описать дополнительную типизированную константу TextColor : byte = 0 и вставить фрагмент, приведенный в листинге 2, между вызовами процедуры отрисовки спрайта PutSprite и процедуры ожидания луча обратного хода WaitVerticalRetrace. На экране должна появиться переливающаяся разными цветами надпись, одна половина которой отображается на непрозрачном фоне, а другая — на прозрачном.
Однако за простоту и нетребовательность к ресурсам приходится платить: предлагаемый способ вывода текста не обладает свойствами спрайта и портит под ним фон. Так что если текст требуется часто перерисовывать, то следует либо самому заботиться о сохранении фона, либо задать его непрозрачным.
листинг 1
interface
{ установка параметров вывода текста }
procedure SetTextParm(color,bkcolor,typetext:byte);
{ color - цвет текста }
{ bkcolor - цвет фона }
{ typetext = 0 - прозрачный фон }
{ typetext = 1 - непрозрачный фон }
{ запрос текущих параметров }
procedure GetTextParm(var color,bkcolor,typetext:byte);
{вывод текста по координатам x,y (верхний левый угол)}
procedure PutText(x,y:word;text:string);
{вывод символа по координатам x,y (верхний левый угол)}
procedure PutChar(x,y:word;chr:char);
implementation
uses dos;
type
FTType = array[0..255,0..7]of byte; {для шрифта}
const
Colors : array[0..15]of byte =
( 0, 2, 20, 22,160,162,172,182,
109,111,125,127,237,239,253,255);
{цвета, соответствующие номерам 0-15}
var
FontTable : ^FTType; {таблица шрифта}
Color1,bkColor1 : byte;
{номера <стандартных> цветов текста и фона}
Color2,bkColor2 : byte;
{номера цветов текста и фона в выбранной палитре}
TextType : byte; {способ вывода (прозрачно или нет)}
procedure SetTextParm(color,bkcolor,typetext:byte);
begin
Color1 := color;
bkColor1 := bkcolor;
TextType := typetext;
Color2 := Colors[Color1];
bkColor2 := Colors[bkColor1];
end;
procedure GetTextParm(var color,bkcolor,typetext:byte);
begin
color := Color1;
bkcolor := bkColor1;
typetext := TextType;
end;
procedure PutText(x,y:word;text:string);
var
i:word;
begin
if(byte(text[0])>0)then
for i := 1 to byte(text[0]) do
putchar(x+8*(i-1),y,text[i])
end;
procedure PutPixel(x,y:word;c:byte); {вывод точки}
begin
mem[SegA000:x+y*320] := c;
end;
function getpixel(x,y:word):byte; {запрос цвета точки}
begin
getpixel := mem[SegA000:x+y*320];
end;
procedure putchar(x,y:word;chr:char);
var
i,j,k,l : word;
cc,bb : byte;
begin
l := byte(chr);
case TextType of
0: for i := 0 to 7 do { прозрачный фон }
for j := 0 to 7 do
if (FontTable^[l,i] and
(1 shl (7-j)) <> 0) then
putpixel(x+j,y+i,Color2);
1: for i := 0 to 7 do { непрозрачный фон }
for j := 0 to 7 do
if (FontTable^[l,i] and
(1 shl (7-j)) <> 0) then
putpixel(x+j,y+i,Color2)
else putpixel(x+j,y+i,bkColor2);
end;
end;
var r : registers;
begin {инициализация - получаем адрес таблицы шрифтов}
r.ax := $1130;
r.bh := 3;
intr($10,r);
FontTable := ptr(r.es,r.bp);
Color1 := 15; {заносим величины по умолчанию}
bkColor1 := 0;
TextType := 1;
Color2 := Colors[Color1];
bkColor2 := Colors[bkColor1];
end.
листинг 2
inc(TextColor);
SetTextParm(TextColor div 16, (TextColor + 48) div 16,1);
PutText(56,16,'Демонстрационная');
SetTextParm(TextColor and $F,0,0);
PutText(192,16,'программа'); |