Мир ПК, #11/2001
Постоянный адрес статьи: http://www.osp.ru/pcworld/2001/11/100.htm
Палитра VGA: управление цветом
Сергей Андрианов
21.11.2001
В прошлый раз (см. статью «Формат Bmp-файла») нам так и не удалось добиться
для спрайта правильных цветов. Восполним этот пробел, а заодно познакомимся
с новыми возможностями, предоставляемыми палитрой. Чтобы в дальнейшем
не путаться в терминах, давайте договоримся, что цветом будем называть
зрительное ощущение от какого-либо оттенка, а цветовыми составляющими
— компоненты (в виде чисел) трех основных цветов, на которые разлагается
цвет: красный, зеленый и синий. Номером цвета назовем число, хранящее
в видеопамяти информацию о цвете точки (или индекс в массиве палитры,
что то же самое).
Что же такое палитра?
Во времена монохромных дисплеев понятия «палитра» не существовало,
и цветов было только два: черный (цвет пустого экрана) и зеленый,
желтый или белый, как повезет. Имелись даже дисплеи с оранжевым свечением.
Кодировался цвет очень просто — одним разрядом: 0 — темная точка,
1 — светлая. Но скоро выяснилось, что этого явно недостаточно, и в
монохромном режиме добавилось управление яркостью, т. е. второй разряд.
И эти два разряда обеспечивали три или четыре уровня яркости (зачастую
разряд яркости не влиял на черный цвет).
С созданием цветных дисплеев картина радикально изменилась, стало
отображаться гораздо больше цветов. В первом массовом цветном видеоадаптере
CGA для персональных компьютеров можно было использовать до 16 цветов
в текстовом режиме и до четырех в графическом, что обусловлено ограничением
объема видеопамяти (всего 16 Кбайт). При 4 тыс. знакомест текстового
режима этого объема хватало с избытком, а вот при 64 тыс. точек графического
(разрешение 320x200 точек) для кодирования цвета каждой точки нельзя
было отвести больше двух разрядов.
Достаточно просто формировались 16 цветов CGA: по одному разряду
на красную, зеленую и синюю составляющую и еще один разряд для повышенной
яркости. Сразу возникает вопрос: если точка может быть только одного
из четырех цветов, то какие цвета следует выбрать? Для этого в CGA
предусмотрено два набора взаимно контрастных цветов: в один набор
входят черный, голубой, малиновый и белый, в другой — черный, зеленый,
желтый и красный. Значит, появились две фиксированные палитры. Иными
словами, палитра позволяет одновременно использовать только несколько
цветов из гораздо большего числа доступных. Таким образом, благодаря
палитре снижаются до разумных пределов требования к объему видеопамяти.
Она устанавливается для всего экрана, следовательно, нельзя сделать
одну его часть бело-малиновой, а другую — красно-желтой.
В видеоадаптере EGA, сменившем CGA, яркость каждой цветовой составляющей
можно было устанавливать независимо, т. е. на цветовую составляющую
приходилось по два разряда (четыре градации яркости). Всего было доступно
64 цвета, но на экране одновременно появлялось не больше 16 в графическом
режиме. Кроме того, увеличилось число палитр CGA до 6416 (в десятичной
записи — 79228162514264337593543950336): каждый из 16 номеров цвета
можно было выбирать из 64 доступных. Поэтому от номеров палитры отказались,
а для ее определения стали использовать массив, где указывались составляющие
трех основных цветов для каждого из номеров цвета. В текстовом режиме
также появилась возможность изменить цвет, соответствующий определенному
номеру.
Последний стандарт видеоадаптера VGA существенно расширил диапазон
допустимых цветов. Теперь на любой номер цвета выделяется по три шестиразрядных
регистра, по одному на цветовую составляющую. Каждый компонент мог
выводиться в 64 градациях яркости, а общее число отображаемых цветов
достигло 262 144. Конечно, глупо было бы ограничиться лишь 16 цветами,
и потому применили режим с глубиной цвета 8 разрядов на точку, вследствие
чего число цветов возросло до 256. Нетрудно подсчитать, что это потребовало
разместить внутри видеоадаптера 768 регистров для хранения компонентов
цвета.
Формат массива, пригодного для занесения составляющих цвета в регистры
палитры VGA, существенно отличается от формата палитры Bmp-файла.
Во-первых, тем, что порядок цветов в этом массиве иной: красный, зеленый,
синий. Во-вторых, у него нет резервного байта, так что длина массива
составляет всего 768 байт, в то время как в файле Bmp было 1024 байт
(см. «Формат Bmp-файла», листинг 1).
В-третьих, в регистрах используются только младшие шесть разрядов,
которые соответствуют старшим шести разрядам в байте палитры BMP.
Поэтому байты, считанные из Bmp-файла перед тем, как они будут помещены
в регистры палитры, надо сдвинуть на два разряда вправо. Это соответствует
делению на четыре, что и нужно для того, чтобы ввести диапазон значений
0—255 в диапазон 0—63.
Появление 240 дополнительных цветов наряду с 16 прежними вывело компьютерную
графику и игровую индустрию на качественно новый уровень. Если раньше
мало кто задумывался о целесообразности переопределения стандартной
палитры, потому что «лишних» цветов, которые можно было бы заменить
другими, не было, то теперь для этого открылись широкие возможности.
Как работает палитра?
В видеопамяти находятся номера цветов — на точку приходится по одному
байту. Видеоадаптер читает байт из видеопамяти и выбирает соответствующую
его номеру тройку регистров палитры, а затем содержимое этих регистров
через цифроаналоговый преобразователь управляет интенсивностью красного,
зеленого и синего лучей кинескопа.
При работе в 256-цветном режиме нужно помнить, что все рисунки, выводимые
на экран одновременно, должны иметь одинаковую палитру. Но здесь встречаются
исключения, которые можно использовать, например, в игре. Для персонажей
и основных элементов интерфейса выбирается одна часть палитры, для
фона или ландшафта — другая. Тогда на протяжении всей игры палитра
может изменяться. В любом случае нужны процедуры, позволяющие варьировать
как целиком палитру, так и отдельные ее части. Модуль, реализующий
эти возможности, приведен в листинге 1.
Функции SetPal и GetPal помогают задать новую палитру и прочитать
установленную, применяя стандартное прерывание BIOS. Им нужны такие
параметры, как первый байт массива с данными о палитре, номер первого
из заданных цветов и число тех цветов, которые требуется изменить.
Первые три байта массива соответствуют красной, зеленой и синей цветовым
составляющим первого из изменяемых цветов, следующие три — второго
и т.д. Номера цветов начинаются с 0, так что для изменения палитры
целиком в качестве первого цвета следует задать 0, а число цветов
указать равным 256.
Функции FadeIn и FadeOut обеспечивают плавное гашение экрана и столь
же плавное «проявление» изображения из темноты.
Кроме того, модуль содержит процедуры ожидания обратного хода вертикальной
развертки WaitVerticalRetrace и мгновенного гашения экрана палитрой
BlackPal. Последнюю процедуру целесообразно вызывать сразу после установки
видеорежима и до заполнения видеопамяти, если затем предполагается
применить FadeIn. Все процедуры модуля работают как в текстовом, так
и в графическом режиме, но следует помнить, что при смене видеорежима
устанавливается палитра по умолчанию.
Теперь наконец можно вывести наш спрайт в «правильных» цветах. Для
этого нужно подключить директивой uses новый модуль и внести в основную
программу изменения, показанные в листинге 2.
Новая программа начинает работу с плавного гашения экрана. Сразу
после установки графического режима палитру надо снова «погасить».
Если этого не сделать, то в процессе заполнения видеопамяти на экране
появится помеха из-за установки в новом режиме палитры по умолчанию.
А после формирования изображения яркость плавно увеличивается до нормальной.
В основном цикле, где и формируется картинка вместе со спрайтами,
процедура WaitVerticalRetrace ожидает обратного хода развертки. Ведь
коль скоро она включена в модуль Pal, некрасиво было бы, если бы происходило
иначе.
Заканчивается программа плавным гашением экрана. Конечно, можно было
бы при возвращении в текстовый режим снова плавно изменить яркость,
но вряд ли это будет замечено — после смены видеорежима экран пуст.
Итак, основа графического спрайтового движка у нас есть: мы умеем
рисовать точки (напрямую в видеопамять), выводить на экран спрайты,
а если необходимо, то даже вертикальные и горизонтальные линии (в
цикле по точкам). По сути дела, у нас остался еще один важный элемент
— текст, но об этом в следующий раз.
Листинг 1
unit pal; {работа с 256-цветной палитрой}
interface
procedure SetPal(var pal:byte;nbegpal,lenpal:integer);
{установка 256-цветной палитры}
procedure GetPal(var pal:byte;nbegpal,lenpal:integer);
{чтение 256-цветной палитры}
procedure WaitVerticalRetrace;
{ожидание вертикально обратного хода луча}
procedure BlackPal; {установка <черной> палитры}
procedure FadeOut(p:array of byte);
{плавное гашение палитры}
procedure FadeIn(p:array of byte);
{плавная установка палитры}
implementation
uses dos;
{установка 256-цветной палитры}
procedure SetPal(var pal:byte;nbegpal,lenpal:integer);
var r:registers;
begin
r.ax := $1012;
r.bx := nbegpal;
r.cx := lenpal;
r.dx := ofs(pal);
r.es := seg(pal);
intr($10,r);
end;
{чтение 256-цветной палитры}
procedure GetPal(var pal:byte;nbegpal,lenpal:integer);
var r:registers;
begin
r.ax := $1017;
r.bx := nbegpal;
r.cx := lenpal;
r.dx := ofs(pal);
r.es := seg(pal);
intr($10,r);
end;
{ожидание вертикально обратного хода луча}
procedure WaitVerticalRetrace;
begin
while (port[$3da] and 8) = 0 do;
end;
{установка <черной> палитры}
procedure BlackPal;
var p : array[0..767]of byte;
begin
fillchar(p,sizeof(p),0);
SetPal(p[0],0,256);
end;
{плавная установка палитры}
procedure FadeIn(p:array of byte);
var
p1 : array[0..767]of byte;
i,j : integer;
begin
BlackPal;
for i := 0 to 63 do begin
for j := 0 to 767 do {<поднимаем> цвета до }
p1[j] := round(p[j]/63*i);{ нужной палитры }
WaitVerticalRetrace;
SetPal(p1[0],0,256);
end;
end;
{плавное гашение палитры}
procedure FadeOut(p:array of byte);
var
p1 : array[0..767]of byte;
i,j : integer;
begin
for i := 0 to 767 do
p1[i] := p[i];
for i := 63 downto 0 do begin
for j := 0 to 767 do {<опускаем> цвета}
p1[j] := round(p[j]/63*i); { до 0 }
WaitVerticalRetrace;
SetPal(p1[0],0,256);
end;
end;
end.
Листинг 2
begin
GetPal(p[0],0,256);
FadeOut(p);
CreateSprite('sprt01.bmp',0,0,1,1);
r.ax := $13; { устанавливаем режим }
intr($10,r); { 320х200х256 цветов }
scr := ptr(SegA000,0);
BlackPal;
PutBackGround; {рисуем фон}
FadeIn(p);
GetBuffer; {сохраняем фон под спрайтом}
PutSprite; {и рисуем на его месте спрайт}
repeat {теперь спрайт будет двигаться по экрану}
{до тех пор, пока мы не нажмем на клавишу}
PutBuffer; {восстанавливаем фон}
CalcSpritePosition;
GetBuffer; {сохраняем фон}
PutSprite; {рисуем спрайт}
WaitVerticalRetrace;
{ожидаем обратный ход луча кадровой развертки}
until keypressed;
readkey; {чистим буфер клавиатуры}
FadeOut(p);
r.ax := $3;
intr($10,r); {возвращаемся в текстовый режим}
DestroySprite;
end.
Мир ПК, #11/2001
Постоянный адрес статьи: http://www.osp.ru/pcworld/2001/11/100.htm