Технические аспекты сотворения мира. Часть 2

on ноября 30, 2000 - 00:00

Подпрограммы, обеспечивающие доступ к памяти через 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;

Итак, с графикой закончили. Следующая тема — использование клавиатуры, мыши и джойстика.

Продолжение следует

Сергей Иванчегло

№ 11

Яндекс.Метрика