Технические аспекты сотворения мира. Часть 2
Подпрограммы, обеспечивающие доступ к памяти через EMS и XMS, представлены ниже.
{Подпрограммы работы с расширенной памятью}
const sDeviceName: string[8]='EMMXXXX0';
var b
EMSStatus: Byte;
function EMSSupportPresence: Boolean; assembler;
asm
mov ax,3567h
push es
int 21h
mov si,(offset sDeviceName
)+1
mov di,10
mov al,0
mov cx,4
cld
rep cmpsw
jne @1
inc ax
@1:
pop es
end;
function AvailableExpandedMemory: Word; assembler;
asm
mov ax,4200h
int 67h
mov bEMSStatus,ah
mov ax,bx
end;
function TotalExpandedMemory: Word; assembler;
asm
mov ax,4200h
int 67h
mov bEMSStatus,ah
mov ax,dx
<
n> end;
procedure AllocateExpandedMemory (wMemory: Word; var wHandle: Word); assemble
r;
asm
mov ax,4300h
mov bx,wMemory
int 67h
mov bEMSStatus,ah
push ds
lds si,wHandle
m
ov [si],dx
pop ds
end;
procedure MoveExpandedMemory(wHandle: Wor
d; wLogicalPage: Word; bPhysical Page: Byte); assembler;
asm
mov ah,44h
<
n> mov al,bPhysicalPage
mov bx,wLogicalPage
mov dx,wHandle
int 67h
mov bEMSStatus,ah
end;
function FrameSegmen
t: Word; assembler;
asm
mov ax,4100h
int 67h
mov bEMSStatus,ah
mov ax,bx
end;
procedure FreeExpandedMemory(wH
andle: Word); assembler;
asm
mov ax,4500h
mov dx,wHandle
<
n> int 67h
mov bEMSStatus,ah
end;
{Подпрограммы р
аботы с наращиваемой памятью}
type TXMSRecord = record
lRegionLength: Long
int;
wSourceHandle: Word;
lSourceOffset: Longint;
wDest
inationHandle: Word;
lDestinationOffset: Longint
end;
var pXMSFu
nction: Pointer;
bXMSStatus: Byte;
function XMSSupportPresence: Boolean; assembler;<
r>
asm
mov ax,4300h
int 2Fh
cmp al,80h
mov al,0
jne @1
mov ax,4310h
int 2Fh
mov [offset pXMSFunction],bx
mov [(offset pXMSFunction)+2],es
mov a
l,1
@1:
end;
function AvailableExtendedMemory: Word; assembler;
asm
mov ah,8
call pXMSFunction
mov bXMSStatus,bl
end;
procedure AllocateExtendedMemory(wMemory: Word; var wHandle: Word); assembl
er;
asm
mov ah,9
mov dx,wMemory
call pXMSFunc
tion
push ds
lds si,wHandle
mov [si],dx
pop ds
mov bXMSStatus,bl
end;
procedure MoveExtendedMemory(pXMSR
ecord: Pointer); assembler;
asm
mov ah,11
push ds
lds si,pXMSRecord
call pXMSFunction
pop ds
mov bXM
SStatus,bl
end;
procedure FreeExtendedMemory(wHandle: Word); assembler;
asm
mov ah,10
mov dx,wHandle
call pXMSFunction
mov bXMSStatus,bl
end;
Теперь, когда пояснено, как можно получить доступ ко всей присутствующей в компьютере памяти, можно перейти к следующей проблеме — быстрому выводу графики.
Быстрый вывод графики
В процессе разработки программного обеспечения, осуществляющего вывод графических данных, программисты, пользующиеся компиляторами от Borland, часто прибегают к помощи Borland Graphic Interface (BGI). В Borland Pascal 7.0 использование этого интерфейса реализуется посредством подпрограмм модуля Graph, которые предоставляют широкие возможности при работе с графикой. BGI имеет множество достоинств, однако ему присущ один недостаток, который исключает возможность использования этого интерфейса в играх, нуждающихся в быстром выводе графических данных. Этот недостаток заключается в недостаточно высокой скорости работы с графикой.
В данной части статьи будет показан путь к решению проблемы быстрого вывода графики. В основу этого решения положены семь подпрограмм, на базе которых можно создать библиотеку процедур и функций, практически не уступающую модулю Graph по функциональности. Кроме базовых подпрограмм, будет дана процедура, позволяющая производить очень быструю заливку прямоугольных областей. Также будут представлены исходные тексты подпрограмм, реализующих быстрый вывод спрайтов, что пригодится разработчикам двухмерных игр. В самом конце этой части будут рассмотрены процедуры работы с палитрой, на основе которых можно реализовывать различные спецэффекты.
Прежде, чем перейти к объяснению принципов использования приведенных здесь процедур и функций, следует сказать несколько слов по поводу скорости их работы. Для сравнения быстродействия автором были проведены тесты, результаты которых отражены в таблице. В первом столбце таблицы указаны подпрограммы модуля Graph, во втором — их аналоги, представленные в этой статье, в третьем — числовые значения, показывающие, во сколько раз скорость работы аналогов выше скорости работы подпрограмм модуля Graph. Необходимо отметить, что в качестве BGI-драйвера использовался драйвер, содержащийся в файле "svga256.bgi". Этот файл не входит в список файлов, поставляемых в составе Borland Pascal 7.0, однако он довольно широко распространен в среде программистов, использующих BGI.
По данным, приведенным в таблице, видно, что при реализации графического движка, основанного на спрайтах (а для этого достаточно использования SetActivePage, SetVisualPage, DrawHorizontalLine и LoadImage), вывод графики через предложенные Вашему вниманию подпрограммы будет осуществляться практически в десять раз быстрее, чем через BGI.
| PutPixel | SetPixelColor | 3.2 |
| GetPixel | PixelColor | 2.2 |
| SetActivePage | SetActivePage | 3.6 |
| SetVisualPage | SetVisualPage | 1.0 |
| Line | DrawHorizontalLine | 11.5 |
| ImageSize | ImageSize | 4.4 |
| GetImage | SaveImage | 2.4 |
| PutImage | LoadImage | 8.8 |
Как известно, для того чтобы использовать BGI, для каждого типа видеоадаптера необходим специальный драйвер, который должен находиться либо в файле с расширением "bgi", либо в теле программы. Разработчик вынужден сам обеспечивать наличие нужного драйвера, однако, поскольку в большинстве современных персональных компьютеров установлены VGA-совместимые видеоадаптеры, то вполне достаточно обеспечения поддержки только VGA-адаптеров. Подпрограммы вывода графических данных, представленные в этой статье, используют похожий прием. Предполагается (в подавляющем большинстве случаев это предположение будет верным), что видеоадаптер пользователя поддерживает стандарт VESA (Video Electronics Standards Association), который был создан специально для видеосистем SVGA. Стандарт VESA предоставляет возможность работы с графическими режимами с очень большим разрешением и очень большим количеством цветов, однако процедуры и функции этой статьи работают только с тремя режимами (640x480, 800x600 и 1024x768), в которых возможно одновременное отображение только 256 цветов. Это ограничение связано с тем, что подпрограммы, позволяющие использовать еще и другие режимы, заняли бы гораздо больше места и выполнялись бы гораздо медленнее. В данном случае универсальность пришлось принести в жертву быстродействию. Однако это не умаляет достоинств описанных здесь подпрограмм, поскольку разрешения 1024x768 и 256 цветов вполне достаточно для того, чтобы создать хорошую игру.
Перейдем теперь к описанию процедур и функций работы с графикой. Для начала рассмотрим только базовые подпрограммы: SVGASupportPresence, InitializeGraphicMode, SetPixelColor, PixelColor, SetActivePage, SetVisualPage и UninitializeGraphicMode.
Прежде, чем будет вызвана какая-либо иная подпрограмма, необходимо проанализировать значение, возвращаемое функцией SVGASupportPresence. Эта функция определяет, присутствует ли поддержка SVGA и можно ли вызывать другие процедуры и функции. Как уже упоминалось, ответ скорей всего будет положительным, однако, если будет получено значение False, стоит вывести сообщение об ошибке.
Следующим шагом будет установка нужного графического режима. Это достигается вызовом процедуры InitializeGraphicMode, которой передается переменная (wGraphicMode), указывающая, какой режим необходим. Переменная может принимать одно из следующих значений: GraphicMode640x480, GraphicMode 800x600 или GraphicMode1024x768. Если по какой-то причине установить затребованный режим невозможно, после вызова процедуры wGraphicMode примет нулевое значение.
После установки графического режима можно вызывать другие подпрограммы. Процедура SetPixelColor устанавливает цвет (bColor) пикселя, координаты которого задаются значениями wX и wY. Функция PixelColor, наоборот, возвращает текущее значение цвета. Процедура SetActivePage задает номер активной страницы (bPage). Процедура SetVisualPage — номер видимой страницы. Следует сказать, что страницы нумеруются с нуля, а их количество зависит от размера установленной в компьютере видеопамяти. Две последние процедуры используются, как правило, таким образом: на невидимой странице сначала формируется нужное изображение, а затем невидимая страница становится видимой. Этот прием позволяет избежать мерцания, возникающего при затирании старого изображения новым.
Последним шагом, который необходимо сделать перед завершением программы, является восстановление того графического режима, который был до вызова Initialize GraphicMode, что достигается с помощью процедуры UninitializeGraphic- Mode.
{ Базовые подпрограммы работы с графикой }
{$S-}
uses Dos;
const GraphicM
ode640x480=$101; GraphicMode800x600=$103; GraphicMode1024x768=$105;
var wXResolution, wYR
esolution: Word;
wReadWindow, wWriteWindow: Word;
wReadSegment, wWrite
Segment: Word;
bChunkShift: Byte;
wReadWriteChunk, wReadChunk, wWriteC
hunk: Word;
wAdditionalChunk, wAdditionalPixel: Word;
wDeltaChunk, wDe
ltaPixel: Word;
pWindowingFunction: Pointer;
bPreviousMode: Byte;
function SVGASupportPresence: Boolean;
var ProcessorRegisters: Registers;
bBuffer: array[0..255] of Byte;
begin
with ProcessorRegisters do
begin
AX:=$4F00;
ES:=Seg(bBuf
fer);
DI:=Ofs(bBuffer);
Intr($10, ProcessorRegiste
rs);
if AX=$4F then
SVGASupportPresence:=True
else SVGASupportPresence:=False
end
end
;
procedure InitializeGraphicMode(var wGraphicMode: Word);
var ProcessorRegiste
rs: Registers;
SVGARecord: record
wModeA
ttributes: Word;
bWindowAAttributes: Byte;
bWindowBAttributes: Byte;
wWindowGran
ularity: Word;
wWindowSize: Word;
<
tab> wWindowASegment: Word;
wWindowBSegment: Word;<
r>
pWindowingFunction: Pointer;
<
tab> bBuffer: array[0..239] of Byte
end;
begin
if wGraphicMode=GraphicMode640x480 then
begin
wXResolution:=640;
wYResolution:=480
end;
<
tab>if wGraphicMode=GraphicMode800x600 then
begin
wXRes
olution:=800;
wYResolution:=600
end;
if
wGraphicMode=GraphicMode1024x768 then
begin
wXResolutio
n:=1024;
wYResolution:=768
end;
with Pro
cessorRegisters do
begin
AX:=$4F01;
CX:=wGraphicMode;
ES:=Seg(SVGARecord);
DI:=O
fs(SVGARecord);
Intr($10, ProcessorRegisters);
if
(SVGARecord.wModeAttributes and 1)=0 then
begin
wGraphicMode:=0;
Exit
end;
if (SVGARecord.bWindowAAttributes and 2)>0 then
wReadWindow:=0
else wReadWindow:=1;
if (SVGARecord.bWindowAAttributes and 4)>0 then
wWriteWindow:=0
else wWriteWindow:=1;
if wReadWindow=0 then
wReadSegment:=SVGARecord.wWindowASegment
e
lse wReadSegment:=SVGARecord.wWindowBSegment;
if wWriteWindow=0 then
<
tab> wWriteSegment:=SVGARecord.wWindowASegment
else
wWriteSegment:=SVGARecord.wWindowBSegment;
bChunkShift:=0;
while (SVGARecord.wWindowGranularity<64) do
begin
Inc(bChunkShift);
SVGARecord.wWindowG
ranularity:=SVGARecord.wWindowGranularity shl 1
end;
AX:=$4F05;
BX:=wReadWindow;
DX:=0;
Intr($10, ProcessorRegisters);
AX:=$4F05;
BX:=wWriteWindow;
DX:=0;
Intr($10, Proce
ssorRegisters);
wReadWriteChunk:=0;
wReadChunk:=0;
wWriteChunk:=0;
wAdditionalChunk:=0;
wAdditionalPixel:=0;
wDeltaChunk:=Longint(wXResolution)*Longin
t(wYResolution) div $10000;
wDeltaPixel:=Longint(wXResolution)*Longint(wYRe
solution) mod $10000;
pWindowingFunction:=SVGARecord.pWindowingFunction;
AH:=$F;
Intr($10, ProcessorRegisters);
bPreviousMode:=AL;
AX:=$4F02;
BX:=wGra
phicMode;
Intr($10, ProcessorRegisters)
end
e
nd;
procedure SetPixelColor(wX, wY: Word; bColor: Byte); assembler;
asm
mov ax,wXResolution
mul wY
add ax,wX
jnc @1
inc dx
@1:
add ax,wAdditionalPixel
jnc @2
<
tab>inc dx
@2:
mov di,ax
add dx,wAdditionalChunk
<
tab>mov cl,bChunkShift
shl dx,cl
mov bx,wWriteWindow
cm
p bx,wReadWindow
jne @3
cmp dx,wReadWriteChunk
je @4
mov wReadWriteChunk,dx
call pWindowingFunction
jmp @4
<
n> @3:
cmp dx,wWriteChunk
je @4
mov wWriteChunk,dx<
r>
call pWindowingFunction
@4:
mov ax,wWriteSegment
<
tab>push es
mov es,ax
mov al,bColor
stosb
pop es
end;
function PixelColor(wX, wY: Word): Byte; assembler;
asm
mov ax,wXResolution
mul wY
add ax,wX
jn
c @1
inc dx
@1:
add ax,wAdditionalPixel
jnc @
2
inc dx
@2:
mov si,ax
add dx,wAdditionalChun
k
mov cl,bChunkShift
shl dx,cl
mov bx,wReadWindow
cmp bx,wWriteWindow
jne @3
cmp dx,wReadWriteChunk
je @4
mov wReadWriteChunk,dx
call pWindowingFunction
jmp @4
@3:
cmp dx,wReadChunk
je @4
mov wRe
adChunk,dx
call pWindowingFunction
@4:
mov ax,wReadSegment
push ds
mov ds,ax
lodsb
pop ds
end;
procedure SetActivePage(bPage: Byte); assembler;
asm
mov ax
,wDeltaChunk
mov bl,bPage
mov bh,0
mul bx
xchg ax,bx
mul wDeltaPixel
add bx,dx
mov wAdditionalC
hunk,bx
mov wAdditionalPixel,ax
end;
procedure SetVisualPage(bPa
ge: Byte); assembler;
asm
mov ax,wYResolution
mov bl,bPage
mov bh,0
mul bx
mov dx,ax
mov ax,4F07h
xor bx,bx
xor cx,cx
int 10h
end;
pro
cedure UninitializeGraphicMode;
var ProcessorRegisters: Registers;
begin
SetVisualPage(0);
with ProcessorRegisters do
begin
<
tab> AX:=bPreviousMode;
Intr($10, ProcessorRegisters)
end
end;
Перечисленных подпрограмм достаточно для разработки процедур рисования различных геометрических фигур, заливки областей, копирования фрагментов видеопамяти и прочих вещей. Дальше, впрочем, будут даны несколько подпрограмм, которые выполняют кое-что из перечисленного, причем делают это очень быстро.
Прежде, чем перейти к их рассмотрению, следует сказать несколько слов об ошибках в данных, передаваемых вызываемым подпрограммам. В целях увеличения быстродействия подпрограмм было решено не включать в них код, ответственный за анализ входной информации. Другими словами, разработчик должен сам заботиться о том, чтобы входные данные были корректными. К примеру, переменная, передаваемая процедуре InitializeGraphicMode, должна принимать только те значения, которые были перечислены. Если это требование не будет выполняться, возможно непредсказуемое поведение программы. При вызове подпрограмм SetPixelColor и PixelColor координаты пикселей не должны превышать максимально возможные значения, хотя, в принципе, на стабильности работы это сказываться не будет. Аналогично дело обстоит с рассматриваемой далее процедурой рисования горизонтальных линий. Подпрограммы работы со спрайтами являются наиболее требовательными к корректности передаваемых данных. Это относится как к входным параметрам, так и к информации, содержащейся в буфере, указатель которого получает процедура LoadImage.
Рассмотрим теперь дополнительные процедуры и функции работы с графикой. Процедура DrawHorizontalLine, как можно догадать из названия, рисует горизонтальную линию. При вызове ей передаются координаты начального пикселя (wX и wY), длина (wLength) и цвет (bColor) линии. Процедура рисует линию слева направо. Ее основное назначение — очень быстрая заливка прямоугольных областей.
Функция ImageSize и процедуры SaveImage и LoadImage предназначены для работы со спрайтами. ImageSize определяет размер буфера, необходимый для сохранения прямоугольного фрагмента изображения (спрайта). Этой функции передаются координаты левого верхнего (wX1 и wY1) и правого нижнего (wX2 и wY2) углов спрайта. Возвращаемое значение показывает размер буфера в байтах и не должно превышать 64 килобайт. Если фрагмент слишком большой, то возвращается 0. В этом случае фрагмент следует разбить на две или более частей, которые можно сохранить по отдельности.
Процедура SaveImage сохраняет спрайт, координаты которого задаются так же, как и в предыдущей подпрограмме. Кроме координат, процедуре передается указатель буфера (pImage), в котором спрайт будет сохранен. Сохраненный спрайт может быть опять выведен на экран, причем в любую его часть. Для этого вызывается процедура LoadImage, которой передаются указатель буфера (pImage), координаты пикселя (wX и wY), с которого необходимо осуществить вывод, и параметр (bLoadMode), указывающий, в каком режиме следует производить отображение спрайта. При bLoadMode равном MoveLoadMode спрайт будет просто накладываться поверх старого изображения, затирая его. Существует возможность проводить такое наложение спрайта, при котором будет производиться логическая операция AND, OR или XOR над старым изображением, для чего параметр bLoadMode должен быть равен соответственно AndLoadMode, OrLoadMode или XorLoadMode.
{ Дополнительные подпрограммы работы с графикой }
const MoveLoadMode=0; AndLoadMode=1; Or
LoadMode=2; XorLoadMode=3;
procedure DrawHorizontalLine(wX, wY: Word; wLength: Word; bCol
or: Byte); assembler;
asm
cld
mov ax,wWriteSegment
push es
mov es,ax
mov ax,wXResolution
mul wY
add ax,wX
jnc @1
inc dx
@1:
add ax
,wAdditionalPixel
jnc @2
inc dx
@2:
mov di,ax
add dx,wAdditionalChunk
mov cl,bChunkShift
shl dx,cl
mov ax,offset @6
push ax
@3:
push dx
mov bx,wWriteWindow
cmp bx,wReadWindow
jne @4
cmp
dx,wReadWriteChunk
je @5
mov wReadWriteChunk,dx
call p
WindowingFunction
jmp @5
@4:
cmp dx,wWriteChunk
je @5
mov wWriteChunk,dx
call pWindowingFunction
@5:
pop dx
ret
@6:
mov al,bColor
mov a
h,al
push ax
mov cl,16
db 66h
shl ax,cl<
r>
pop ax
mov cx,wLength
push cx
add cx,di<
r>
jc @9
pop cx
mov bx,offset @8
push bx
@7:
push cx
shr cx,1
shr cx,1
db
66h
rep stosw
pop cx
and cx,3
rep stosb
ret
@8:
jmp @10
@9:
mov si,cx
pop cx
sub cx,si
call @7
mov bx,1
mov cl,bChunkShift
shl bx,cl
add dx,bx
push ax
<
n> call @3
pop ax
mov cx,si
call @7
@10:
pop es
end;
function ImageSize(wX1, wY1, wX2, wY2: Word): W
ord; assembler;
asm
mov ax,wX2
mov bx,wX1
mov
cx,wY2
mov dx,wY1
sub ax,bx
inc ax
sub
cx,dx
inc cx
mul cx
add ax,4
jnc @1
inc dx
@1:
or dx,dx
jz @2
xor ax,a
x
@2:
end;
procedure SaveImage(wX1, wY1, wX2, wY2: Word; pImage: Poin
ter); assembler;
var wExtraXResolution: Word;
wExtraReadWindow, wExtra
WriteWindow: Word;
wExtraReadWriteChunk, wExtraReadChunk, wExtraAdditionalChunk:
Word;
pExtraWindowingFunction: Pointer;
wLength, wExtraLeng
th: Word;
asm
mov ax,wReadWindow
mov wExtraReadWindow,ax
<
n> mov ax,wWriteWindow
mov wExtraWriteWindow,ax
mov ax,wReadW
riteChunk
mov wExtraReadWriteChunk,ax
mov ax,wReadChunk
mov wExtraReadChunk,ax
db 66h
mov ax,Word(pWindowingFunction)
db 66h
mov Word(pExtraWindowingFunction),ax
push es
les di,pImage
mov ax,wX2
mov bx,wX1
sub ax,bx
cld
stosw
inc ax
mov wLength,ax
mov ax,wY2
mov cx,wY1
sub ax,cx
stosw
inc ax
push ax
mov ax,wXResolution
mov wExtraXResoluti
on,ax
mul cx
add ax,bx
jnc @1
inc dx
<
n> @1:
add ax,wAdditionalPixel
jnc @2
inc dx
<
tab>@2:
mov si,ax
add dx,wAdditionalChunk
mov cl,bChunk
Shift
shl dx,cl
mov ax,1
shl ax,cl
mov w
ExtraAdditionalChunk,ax
mov ax,offset @6
push ax
@3:
push dx
mov bx,wExtraReadWindow
cmp bx,wExtraWriteWindow
jne @4
cmp dx,wExtraReadWriteChunk
je @5
mov
wExtraReadWriteChunk,dx
call pExtraWindowingFunction
jmp @5
@4:
cmp dx,wExtraReadChunk
je @5
mov wExtraReadChunk,dx
call pExtraWindowingFunction
@5:
pop dx
ret<
r>
@6:
pop cx
mov ax,wReadSegment
push ds
<
tab> mov ds,ax
@7:
push cx
mov cx,wLength
push cx
add cx,si
jc @9
pop cx
mov ax,o
ffset @10
push ax
@8:
push cx
shr cx,1
<
tab> shr cx,1
db 66h
rep movsw
pop cx
and cx,3
rep movsb
ret
@9:
mov wExtraLength
,cx
pop cx
sub cx,wExtraLength
call @8
a
dd dx,wExtraAdditionalChunk
call @3
mov cx,wExtraLength
call @8
@10:
mov ax,wExtraXResolution
sub ax,wLength
<
tab> add si,ax
jnc @11
add dx,wExtraAdditionalChunk
call @3
@11:
pop cx
loop @7
pop ds
mov ax,wExtraReadWriteChunk
mov wReadWriteChunk,ax
mov ax,wExtra
ReadChunk
mov wReadChunk,ax
pop es
end;
procedure
LoadImage(pImage: Pointer; wX, wY: Word; bLoadMode: Byte); assembler;
var wExtraXResoluti
on: Word;
wExtraReadWindow, wExtraWriteWindow: Word;
wExtraR
eadWriteChunk, wExtraWriteChunk, wExtraAdditionalChunk: Word;
pExtraWindowingFun
ction: Pointer;
wLength, wExtraLength: Word;
asm
mov ax
,wReadWindow
mov wExtraReadWindow,ax
mov ax,wWriteWindow
mov wExtraWriteWindow,ax
mov ax,wReadWriteChunk
mov wExtraReadWriteC
hunk,ax
mov ax,wWriteChunk
mov wExtraWriteChunk,ax
db 6
6h
mov ax,Word(pWindowingFunction)
db 66h
mov Word(pExt
raWindowingFunction),ax
mov ax,wWriteSegment
push es
mo
v es,ax
mov ax,wXResolution
mov wExtraXResolution,ax
mu
l wY
add ax,wX
jnc @1
inc dx
@1:
add ax,wAdditionalPixel
jnc @2
inc dx
@2:
mov di,ax
add dx,wAdditionalChunk
mov cl,bChunkShift
sh
l dx,cl
mov ax,1
shl ax,cl
mov wExtraAdditionalChunk,ax
mov ax,offset @6
push ax
@3:
push dx
mov bx,wExtraWriteWindow
cmp bx,wExtraReadWindow
jne @4
<
tab> cmp dx,wExtraReadWriteChunk
je @5
mov wExtraReadWriteChunk,dx
call pExtraWindowingFunction
jmp @5
@4:
cmp
dx,wExtraWriteChunk
je @5
mov wExtraWriteChunk,dx
call
pExtraWindowingFunction
@5:
pop dx
ret
@6:
push ds
lds si,pImage
cld
lodsw
inc ax
mov wLength,ax
lodsw
inc ax
mov c
x,ax
@7:
push cx
mov cx,wLength
push cx
add cx,di
jc @19
pop cx
mov ax,offset @20
<
n> push ax
@8:
push cx
shr cx,1
shr
cx,1
cmp bLoadMode,0
jne @9
db 66h
rep m
ovsw
jmp @13
@9:
or cx,cx
jz @13
cm
p bLoadMode,2
je @11
ja @12
@10:
db 66h
lodsw
db 66h
and es:[di],ax
add di,4
loop @10
jmp @13
@11:
db 66h
lodsw
<
n> db 66h
or es:[di],ax
add di,4
loop @11
jmp @13
@12:
db 66h
lodsw
db 66h
xor es:[di],ax
add di,4
loop @12
@13:
pop cx
and cx,3
cmp bLoadMode,0
jne @14
rep movsb
ret
@14:
or cx,cx
jnz @15
ret
@15:
cmp bLoadMode,2
je @17
ja @18
@16:
lodsb
and es:[di],al
inc di
loop @16
ret
@17:
lodsb
or es:[di],
al
inc di
loop @17
ret
@18:
lo
dsb
xor es:[di],al
inc di
loop @18
ret
@19:
mov wExtraLength,cx
pop cx
sub cx,wExtraL
ength
call @8
add dx,wExtraAdditionalChunk
call @3
mov cx,wExtraLength
call @8
@20:
mov ax,wExtraXRe
solution
sub ax,wLength
add di,ax
jnc @21
add dx,wExtraAdditionalChunk
call @3
@21:
pop cx
loop @22
jmp @23
@22:
jmp @7
@23:
pop ds
mov ax,wExtraReadWriteChunk
mov wReadWriteChunk,ax
mov ax,wExtraWriteChunk
mov wWriteChunk,ax
pop es
en
d;
Завершая обсуждение быстрого вывода графики, следует обратить внимание на наличие в самом начале исходных текстов директивы {$S-}, которая указывает компилятору на то, что в подпрограммы не следует вставлять код, ответственный за проверку границ стека. При достаточно большом стеке этот прием не будет сказываться на стабильности работы, зато позволит несколько увеличить быстродействие.
Перейдем теперь к рассмотрению работы с палитрой. Палитра представляет собой массив чисел, в котором хранятся красная (red), зеленая (green) и синяя (blue) составляющие (RGB-составляющие) каждого цвета. Меняя числовые значения этих составляющих, можно менять сам цвет. Пусть, к примеру, цвет с кодом 14 является желтым. Его красная и зеленая составляющие равны 255, а синяя — 0. Если, скажем, сделать так, чтобы красная и синяя составляющие стали равными 128, а зеленая 0, то в результате этого элементы изображения, имеющие желтый цвет, станут темно-пурпурными. Если изображение находится на экране, то, меняя палитру, можно сразу же наблюдать изменения, даже не перерисовывая самого изображения.
Как же можно получать и менять RGB-составляющие цветов? Тут есть два пути. Во-первых, можно работать с каждым цветом в отдельности. Процедура GetRGBComponents заносит в массив RGBComponents красную, зеленую и синюю составляющие цвета с кодом, заданным переменной bColor. Процедура SetRGBComponents, наоборот, устанавливает составляющие цвета bColor в соответствии со значениями, находящимися в RGBComponents. Во-вторых, можно работать со всеми 256 цветами сразу. Процедура GetPalette определяет, а процедура SetPalette устанавливает текущую палитру (Palette).
Работая с палитрой, можно делать различные спецэффекты. В этой статье представлены два таких спецэффекта — заход и восход солнца, — для реализации которых следует вызывать процедуры SimulateSunset и SimulateSunrise. При вызове перечисленных процедур им передаются массив (Palette), содержащий текущую палитру, и числовое значение (wPeriod), определяющее длительность спецэффекта.
Подпрограммы, позволяющие менять палитру, работают как в графических, так и в текстовых режимах. Используя их в собственных программах, необходимо помнить, что перед завершением программы следует обязательно восстановить ту палитру, которая была до запуска программы.
{ Подпрограммы работы с палитрой }
type TRGBComponents = array[1..3] of Byte;
<
tab>TPalette = array[0..255] of TRGBComponents;
procedure GetRGBComponents(bColor: Byte;
var RGBComponents: TRGBComponents);
var b1: Byte;
begin
Port[$3C7
]:=bColor;
for b1:=1 to 3 do RGBComponents[b1]:=Port[$3C9]
end;
procedure SetRGBComponents(bColor: Byte; RGBComponents: TRGBComponents);
var b1: Byte;
<
n> begin
Port[$3C8]:=bColor;
for b1:=1 to 3 do Port[$3C9]:=RGBComp
onents[b1]
end;
procedure GetPalette(var Palette: TPalette);
var b1:
Byte;
begin
for b1:=0 to 255 do GetRGBComponents(b1, Palette[b1])
end;
procedure SetPalette(Palette: TPalette);
var b1: Byte;
begin
for b1:=0 to 255 do SetRGBComponents(b1, Palette[b1])
end;
procedu
re SimulateSunset(Palette: TPalette; wPeriod: Word);
var w1: Word;
r1:
Real;
b1, b2: Byte;
RGBComponents: TRGBComponents;
begin
for w1:=wPeriod downto 1 do
begin
r1:=w1/wPeriod;
for b1:=0 to 255 do
begin
for b2:=1 to 3 do RGBComponents[b2]:=Round(Palette[b1,b2]*r1);
<
n> SetRGBComponents(b1, RGBComponents)
en
d
end
end;
procedure SimulateSunrise(Palette: TPalette; wPe
riod: Word);
var w1: Word;
r1: Real;
b1, b2: Byte;
RGBComponents: TRGBComponents;
begin
for w1:=1 to wPer
iod do
begin
r1:=w1/wPeriod;
f
or b1:=0 to 255 do
begin
for b2:=1
to 3 do RGBComponents[b2]:=Round(Palette[b1,b2]*r1);
SetRGBCompon
ents(b1, RGBComponents)
end
end
end;
Итак, с графикой закончили. Следующая тема — использование клавиатуры, мыши и джойстика.
Продолжение следует
Сергей Иванчегло

