;*******************************************************************************
;* Graph12.ASM - Графический модуль для режимов 10h (EGA) и 12h (VGA)          *
;* Версия 1.00 (Январь 1998 г.)                                                *
;*                                                                             *
;* OS: DOS                                                                     *
;*                                                                             *
;* Copyright (c) 1998 rivitna                                                  *
;*******************************************************************************

LOCALS

.MODEL	SMALL


VideoBufferSeg	EQU	0A000h
BytesPerRow12	EQU	80		;логическая ширина видеобуфера
					;в режиме VGA 12h
SymbolWidth	EQU	8		;ширина матрицы символа


.CODE

PUBLIC	SetDRFSReg
PUBLIC	FillRectangle12
PUBLIC	GetBitBlock12
PUBLIC	StoreBitBlock12
PUBLIC	GetBufferSize12
PUBLIC	WriteChar12
PUBLIC	WriteChar12x
PUBLIC	WriteStr12
PUBLIC	WriteStr12x
PUBLIC	Line12
PUBLIC	Rectangle12

;*******************************************************************************
;SetDRFSReg	устанавливает содержимое регистра циклического сдвига/выбора
;		функции
;*******************************************************************************
;Использует:	нет
;
;Вызов:		AH = значение регистра циклического сдвига/выбора функции
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AL,DX
;*******************************************************************************
SetDRFSReg	PROC

		mov	dx,3CEh		;DX = порт графического контроллера
		mov	al,3		;AL = номер регистра циклического
					;сдвига/выбора функции
		out	dx,ax

		ret

SetDRFSReg	ENDP

;*******************************************************************************
;PixelAddr12	определяет адрес пиксела в видеобуфере в режиме VGA 12h
;*******************************************************************************
;Вызов:		AX = координата y (0-479)
;		BX = координата x (0-639)
;
;Возврат:	ES = сегмент видеобуфера
;		BX = смещение байта в видеобуфере
;		AH = битовая маска
;		CL = число битов, на которое осуществляется сдвиг влево
;
;Изменяемые
;  регистры:	ES,AX,BX,CL,DX,Flags
;*******************************************************************************
PixelAddr12	PROC

		mov	cl,bl

		mov	dx,BytesPerRow12
		mul	dx		;AX=y*BytesPerRow12

		shr	bx,1
		shr	bx,1
		shr	bx,1		;BX=x/8
		add	bx,ax		;BX=y*BytesPerRow12+x/8

		mov	ax,VideoBufferSeg
		mov	es,ax		;ES:BX -> видеобуфер

		and	cl,7
		xor	cl,7		;CL = число битов для сдвига влево

		mov	ah,1		;AH = несдвинутая битовая маска

		ret

PixelAddr12	ENDP

;*******************************************************************************
;FillRectangle12 формирует заполненный прямоугольник в режиме VGA 12h
;*******************************************************************************
;Использует:	PixelAddr12
;
;Вызов:		x1,y1 - координаты левого верхнего угла
;		x2,y2 - координаты правого нижнего угла
;		color - цвет прямоугольника
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,BX,CX,DX,Flags
;*******************************************************************************
FillRectangle12	PROC
		ARG	@@x1:WORD, @@y1:WORD, @@x2:WORD, @@y2:WORD, \
			@@color:BYTE = ARG_SIZE
		LOCAL	@@PixelRows:WORD, @@PixelRowLen:WORD = LOCAL_SIZE

		push	bp		;сохраняем регистры
		mov	bp,sp		;в стеке
		sub	sp,LOCAL_SIZE	;стековое пространство для локальных
					;переменных
		push	ds
		push	es
		push	si
		push	di

;вычисляем размеры блока

		mov	ax,[@@x2]
		mov	bx,[@@x1]

		mov	cl,3		;CL = количество битов, на которое
					;необходимо сдвинуть, чтобы
					;преобразовать пикселы в байты
		shr	ax,cl
		shr	bx,cl
		sub	ax,bx		;AX=(количество байтов в строке)-1
		mov	[@@PixelRowLen],ax

		mov	ax,[@@y2]
		sub	ax,[@@y1]
		inc	ax		;AX = количество строк пикселов
		mov	[@@PixelRows],ax

;устанaвливаем адресацию

		mov	ax,[@@y1]	;AX=y1
		mov	bx,[@@x1]	;BX=x1
		call	PixelAddr12	;AH = битовая маска
					;ES:BX -> видеобуфер
					;CL = количество битов для сдвига влево
		mov	di,bx		;ES:DI -> видеобуфер

		mov	bh,ah		;BH = несдвинутая битовая маска для
					;первого байта
		not	bh
		shl	bh,cl		;BH = неверная битовая маска для
					;первого байта
		not	bh		;BH = битовая маска для первого байта

		mov	cx,[@@x2]
		and	cl,7
		xor	cl,7		;CL = количество битов для сдвига влево
		mov	bl,0FFh		;BL = несдвинутая битовая маска для
					;правого байта
		shl	bl,cl		;BL = битовая маска для последнего байта

;конфигурирование графического контроллера

		mov	dx,3CEh		;DX = адрес графического контроллера

		mov	ah,[@@color]	;AH = значение пиксела
		xor	al,al		;AL = номер регистра установки/сброса
		out	dx,ax

		mov	ax,0F01h	;AH=1111b (битовая маска для регистра
					;разрешения установки/сброса)
					;AL = номер регистра разрешения
					;установки/сброса
		out	dx,ax

		mov	al,8		;AL = номер регистра битовой маски

;делаем видеобуфер адресуемым через DS:SI

		push	es
		pop	ds

;процедура установки пикселов

		cmp	[@@PixelRowLen],0
		jnz	@@01		;перейти, если в строке больше
					;одного байта

		and	bl,bh		;BL = битовая маска для строки

@@01:		mov	cx,[@@PixelRows]	;CX = количество строк пикселов

		cld

@@RowLoop:	push	cx		;сохраняем число оставшихся строк
		push	di		;сохраняем смещение строки в видеобуфере
		mov	si,di		;DS:SI -> начало строки в видеобуфере
		mov	cx,[@@PixelRowLen]	;CX=(количество байтов в строке)-1

		or	bh,bh
		js	@@Middle	;перейти, если выровнено по байту
					;(x1-левый пиксел в байте)

		or	cx,cx
		jz	@@RightByte	;перейти, если в строке один байт

;устанавливаем значения пикселов в левом байте

		mov	ah,bh		;AH = битовая маска для первого байта
		out	dx,ax		;обновляем графический контроллер

		movsb			;обновляем битовые плоскости
		dec	cx

;устанавливаем пикселы в середине строки

@@Middle:	mov	ah,0FFh		;AH = битовая маска
		out	dx,ax		;обновляем регистр битовой маски

		rep	movsb		;изменить все пикселы в строке

;установить пикселы в правом байте

@@RightByte:	mov	ah,bl		;AH = битовая маска для последнего байта
		out	dx,ax		;обновляем графический контроллер

		movsb			;обновляем битовые плоскости

		pop	di		;ES:DI -> начало строки
		add	di,BytesPerRow12;ES:DI -> начало следующей строки
		pop	cx		;CX = число оставшихся строк
		loop	@@RowLoop	;цикл по строкам

;восстановление состояния графического контроллера по умолчанию и выход

		xor	ax,ax		;AH=0, AL=0
		out	dx,ax		;восстановление регистра установки/сброса

		inc	ax		;AH=0, AL=1
		out	dx,ax		;восстановление регистра разрешения
					;установки/сброса

		mov	ax,0FF08h	;AH=11111111b, AL=8
		out	dx,ax		;восстановление регистра битовой маски

		pop	di		;восстанавливаем регистры
		pop	si
		pop	es
		pop	ds
		mov	sp,bp
		pop	bp

		ret	ARG_SIZE

FillRectangle12	ENDP

;*******************************************************************************
;GetBitBlock12	копирует битовый блок из видеобуфера в ОП в режиме VGA 12h
;*******************************************************************************
;Использует:	PixelAddr12
;
;Вызов:		x1,y1 - координаты левого верхнего угла
;		x2,y2 - координаты правого нижнего угла
;		BuffAddr = дальний указатель на буфер в ОП
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,BX,CX,DX,Flags
;*******************************************************************************
GetBitBlock12	PROC
		ARG	@@x1:WORD, @@y1:WORD, @@x2:WORD, @@y2:WORD, \
			@@BuffAddr:DWORD = ARG_SIZE
		LOCAL	@@PixelRows:WORD, @@PixelRowLen:WORD = LOCAL_SIZE

		push	bp		;сохраняем регистры
		mov	bp,sp		;в стеке
		sub	sp,LOCAL_SIZE	;стековое пространство для локальных
					;переменных
		push	ds
		push	es
		push	si
		push	di

;вычисляем размеры битового блока

		mov	ax,[@@x2]
		sub	ax,[@@x1]
		mov	cx,0FF07h	;CH = несдвинутая битовая маска
					;CL = маска операции AND для регистра AL
		and	cl,al		;CL = число пикселов в последнем
					;байте строки
		xor	cl,7		;CL = число битов для сдвига
		shl	ch,cl		;CH = битовая маска для последнего
					;байта строки
		mov	cl,ch
		push	cx		;сохраняем в стеке

		mov	cl,3
		shr	ax,cl
		inc	ax		;AX = число байтов в строке
		push	ax		;сохраняем в стеке

		mov	ax,[@@y2]
		sub	ax,[@@y1]
		inc	ax		;AX = число строк пикселов
		push	ax		;сохраняем в стеке

;устанавливаем адресацию

		mov	ax,[@@y1]	;AX=y1
		mov	bx,[@@x1]	;BX=x1
		call	PixelAddr12	;ES:BX -> битовое смещение x1,y1

		xor	cl,7		;CL = число битов для сдвига влево

		push	es
		pop	ds
		mov	si,bx		;DS:SI -> видеобуфер

		les	di,[@@BuffAddr]	;ES:DI -> буфер в ОП

		cld

;создаем 5-байтовый заголовок битового блока

		pop	ax
		mov	[@@PixelRows],ax
		stosw			;байты 0,1 = число строк пикселов
		pop	ax
		mov	[@@PixelRowLen],ax
		stosw			;байты 2,3 = число байтов в строке
					;пикселов
		pop	ax
		mov	ch,al		;CH = битовая маска для последнего
					;байта в строке
		stosb			;байт 4 = битовая маска для последнего
					;байта

;программируем графический контроллер

		mov	dx,3CEh		;DX = адресный порт графического
					;контроллера
		mov	ax,304h		;AH=3 (первая битовая плоскость для
					;чтения)
					;AL=4 (номер регистра выбора читаемой
					;плоскости)

;копируем содержимое видеобуфера в буфер ОП

@@BitPlaneLoop:	out	dx,ax		;выбираем для чтения следующую карту
					;видеопамяти
		push	ax		;сохраняем номер карты видеопамяти
		push	[@@PixelRows]	;сохраняем число строк пикселов
		push	si		;сохраняем смещение x1,y1

@@RowLoop:	mov	bx,[@@PixelRowLen]
		push	si		;в начале строки пикселов сохраняем
					;регистр SI

@@ByteLoop:	lodsw			;AL = следующий байт в видеобуфере
					;AH = (следующий байт)+1
		dec	si		;DS:SI -> (следующий байт) + 1
		rol	ax,cl		;AL = следующие 8 пикселов в строке
		stosb			;копируем в ОП
		dec	bx
		jnz	@@ByteLoop	;цикл по строке

		and	es:[di-1],ch	;маскируем последний байт в строке
		pop	si		;DS:SI -> начало строки
		add	si,BytesPerRow12;DS:SI -> начало следующей строки

		dec	[@@PixelRows]
		jnz	@@RowLoop	;цикл по строкам пикселов

		pop	si		;DS:SI -> начало битового блока
		pop	[@@PixelRows]	;восстанавливаем число строк пикселов
		pop	ax		;AH = последняя читавшаяся карта
					;AL = номер регистра выбора читаемой
					;карты
		dec	ah
		jns	@@BitPlaneLoop	;цикл по битовым плоскостям

		pop	di		;восстанавливаем регистры
		pop	si
		pop	es
		pop	ds
		mov	sp,bp
		pop	bp

		ret	ARG_SIZE

GetBitBlock12	ENDP

;*******************************************************************************
;StoreBitBlock12 копирует битовый блок из ОП в видеобуфер в режиме VGA 12h
;*******************************************************************************
;Использует:	PixelAddr12
;
;Вызов:		x1,y1 - координаты левого верхнего угла
;		BuffAddr = дальний указатель на буфер в ОП
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,BX,CX,DX,Flags
;*******************************************************************************
StoreBitBlock12	PROC
		ARG	@@x1:WORD, @@y1:WORD, @@BuffAddr:DWORD = ARG_SIZE
		LOCAL	@@PixelRows:WORD, @@PixelRowLen:WORD, \
			@@RowCounter:WORD, @@StartMask:WORD, \
			@@EndMaskL:WORD, @@EndMaskR:WORD = LOCAL_SIZE

		push	bp		;сохраняем регистры
		mov	bp,sp		;в стеке
		sub	sp,LOCAL_SIZE	;стековое пространство для локальных
					;переменных
		push	ds
		push	es
		push	si
		push	di

;устанавливаем адресацию

		mov	ax,[@@y1]	;AX=y1
		mov	bx,[@@x1]	;BX=x1
		call	PixelAddr12	;ES:BX -> видеобуфер

		inc	cl
		and	cl,7		;CL = количество битов для сдвига влево

		mov	di,bx		;ES:DI -> видеобуфер

		lds	si,[@@BuffAddr]	;DS:SI -> буфер в ОП

		cld

;получаем размеры битового блока из заголовка

		lodsw			;AX = число строк пикселов
		mov	[@@PixelRows],ax
		lodsw			;AX = число байтов на строку пикселов
		mov	[@@PixelRowLen],ax
		lodsb			;AL = битовая маска для последнего
					;байта строки
		mov	ch,al

;программируем графический контроллер

		mov	dx,3CEh		;DX = порт ввода/вывода графического
					;контроллера

		mov	ax,805h		;AH=8 (режим чтения 1, режим записи 0)
					;AL=5 (номер регистра режима)
		out	dx,ax		;устанавливаем режим чтения 1

		mov	ax,7		;AH=0 (безразлично для всех карт,
					;чтение ЦП всегда возвращает 0FFh)
					;AL=7 (номер регистра безразличия цвета)
		out	dx,ax		;программируем регистр безразличия цвета

		mov	ax,0FF08h	;AH=11111111b (значение для регистра
					;битовой маски)
					;AL=8 (номер регистра битовой маски)
		out	dx,ax		;программируем регистр битовой маски

		mov	dl,0C4h		;DX=3C4h (порт ввода/вывода секвенсера)
		mov	ax,802h		;AH=1000b (значение для регистра маски
					;карты)
					;AL=2 (номер регистра маски карты)

		cmp	cx,0FF00h	;если маска<>0FFh или число битов
					;для сдвига <> 0
		jne	@@11		;перейти, если не выровнено по границе
					;байта

;процедура для битовых блоков, выровненных по границе байта

		mov	cx,[@@PixelRowLen]

@@01:		out	dx,ax		;разрешаем одну битовую плоскость для
					;записи
		push	ax		;сохраняем регистр маски карты
		push	di		;сохраняем внутрибуферное смещение x,y
		mov	bx,[@@PixelRows]

@@02:		push	di		;сохраняем
		push	cx		;регистры DI и CX

@@03:		lodsb			;AL = следующий байт пикселов
		and	es:[di],al	;изменяем битовую плоскость
		inc	di		;ES:DI -> следующая строка пикселов
					;в видеобуфере
		loop	@@03

		pop	cx		;восстанавливаем
		pop	di		;регистры DI и CX
		add	di,BytesPerRow12;ES:DI -> следующая строка пикселов
					;в видеобуфере
		dec	bx
		jnz	@@02		;цикл по строкам пикселов

		pop	di		;ES:DI -> смещение x,y в видеобуфере
		pop	ax		;AH = текущее значение регистра маски
					;карты
					;AL = номер регистра маски карты
		shr	ah,1		;AH = новое значение маски карты
		jnz	@@01		;цикл по всем битовым плоскостям

		jmp	@@Exit

;процедура для невыровненных битовых блоков

@@11:		push	ax		;сохраняем регистр маски карты

		mov	bx,0FFh		;BH=0 (маска для первого байта в строке)
					;BL=0FFh
		mov	al,ch		;AL = маска для последнего байта
					;в строке пикселов
		cbw			;AH=0FFh (маска для предпоследнего байта)

		cmp	[@@PixelRowLen],1
		jne	@@12		;перейти, если в строке больше
					;одного байта

		mov	bl,ch
		mov	ah,ch		;AH = маска для предпоследнего байта
		xor	al,al		;AL=0 (маска для последнего байта)

@@12:		shl	ax,cl		;сдвинуть маску в нужную позицию
		shl	bx,cl
		mov	bl,al		;сохраняем маски вместе с ...
		mov	al,8		;... номером регистра битовой маски
		mov	[@@EndMaskL],ax
		mov	ah,bl
		mov	[@@EndMaskR],ax
		mov	ah,bh
		mov	[@@StartMask],ax

		mov	bx,[@@PixelRowLen]
		pop	ax		;восстанавливаем содержимое регистра
					;маски карты

;записываем пикселы в битовые плоскости строка за строкой

@@13:		out	dx,ax		;разрешаем одну битовую плоскость для
					;записи
		push	ax		;сохраняем содержимое регистра маски
					;карты
		push	di		;сохраняем внутрибуферное смещение x,y
		mov	dl,0CEh		;DX=3CEh (порт графического контроллера)
		mov	ax,[@@PixelRows]
		mov	[@@RowCounter],ax	;инициализируем счетчик цикла

;устанавливаем пикселы в начале строки в разрешенной для записи битовой плоскости

@@14:		push	di		;сохраняем смещение начала строки
					;пикселов
		push	si		;сохраняем смещение строки в битовом поле
		push	bx		;сохраняем количество байтов в строке

		mov	ax,[@@StartMask]
		out	dx,ax		;программируем регистр битовой маски
					;на первый байт строки

		lodsw			;AH = второй байт пикселов
					;AL = первый байт пикселов
		dec	si		;DS:SI -> второй байт пикселов
		test	cl,cl
		jnz	@@15		;перейти, если не выровнено по левой
					;границе

		dec	bx		;BX = (число байтов в строке)-1
		jnz	@@16		;перейти, если в строке минимум два байта
		jmp	@@18		;перейти, если в строке только один байт

@@15:		rol	ax,cl		;AH = левая часть первого байта и правая
					;часть второго
					;AL = правая часть первого байта и левая
					;часть второго
		and	es:[di],ah	;устанавливаем пикселы для левой части
					;первого байта
		inc	di
		dec	bx		;BX = (число байтов в строке)-2

@@16:		push	ax		;сохраняем пикселы
		mov	ax,0FF08h	;AH=11111111b, AL=8
		out	dx,ax		;устанавливаем регистр битовой маски
					;для последующих байтов
		pop	ax

		dec	bx
		jng	@@18		;перейти, если в строке пикселов
					;только один или два байта

;устанавливаем пикселы в середине строки

@@17:		and	es:[di],al	;заносим пикселы в правую часть текущего
					;байта и в левую часть следующего
		inc	di

		lodsw			;AH = (следующий+1) байт пикселов
					;AL = следующий байт пикселов
		dec	si
		rol	ax,cl		;AH = левая часть следующего байта,
					;правая часть (следующего+1) байта
					;AL = правая часть следующего байта,
					;левая часть (следующего+1) байта
		dec	bx
		jnz	@@17		;цикл по строке пикселов

;устанавливаем пикселы в конце строки

@@18:		mov	bx,ax		;BH = правая часть последнего байта,
					;левая часть предпоследнего
					;BL = левая часть последнего байта,
					;правая часть предпоследнего
		mov	ax,[@@EndMaskL]	;AH = маска для предпоследнего байта
					;AL = номер регистра битовой маски
		out	dx,ax		;программируем регистр битовой маски
		and	es:[di],bl	;заносим пикселы в предпоследний байт

		mov	ax,[@@EndMaskR]	;AH = маска для последнего байта пикселов
					;AL = номер регистра битовой маски
		out	dx,ax		;программируем регистр битовой маски
		and	es:[di+1],bh	;заносим пикселы в последний байт

		pop	bx		;BX = количество байтов в строке пикселов
		pop	si
		add	si,bx		;DS:SI -> следующая строка в битовом
					;блоке
		pop	di
		add	di,BytesPerRow12;ES:DI -> следующая строка пикселов
					;в видеобуфере
		dec	[@@RowCounter]
		jnz	@@14		;цикл по строкам пикселов

		pop	di		;ES:DI -> смещение x,y в видеобуфере
		pop	ax		;AX = текущее значение регистра маски
					;карты
		mov	dl,0C4h		;DX=3C4h (порт ввода/вывода секвенсера)
		shr	ah,1		;AH = следующее значение регистра маски
					;карты
		jnz	@@13		;цикл по битовым плоскостям

;восстанавливаем содержимое регистров графического контроллера
;и секвенсера по умолчанию

@@Exit:		mov	ax,0F02h	;AH=1111b, AL=2
		out	dx,ax		;восстанавливаем содержимое регистра
					;маски карты

		mov	dl,0CEh		;DX=3CEh (порт графического контроллера)
		mov	ax,5		;AH=0, AL=5
		out	dx,ax		;восстанавливаем содержимое регистра
					;сравнения цвета

		mov	ax,0F07h	;AH=1111b, AL=7
		out	dx,ax		;восстанавливаем содержимое регистра
					;сравнения цветов

		mov	ax,0FF08h	;AH=11111111b, AL=8
		out	dx,ax		;восстанавливаем содержимое регистра
					;битовой маски

		pop	di		;восстанавливаем регистры
		pop	si
		pop	es
		pop	ds
		mov	sp,bp
		pop	bp

		ret	ARG_SIZE

StoreBitBlock12	ENDP

;*******************************************************************************
;GetBufferSize12 определяет размер буфера (в байтах) для сохранения битового
;		 блока в режиме VGA 12h
;*******************************************************************************
;Использует:	нет
;
;Вызов:		AX=x1
;		BX=y1
;		CX=x2
;		DX=y2
;
;Возврат:	AX = объем битового блока в байтах
;
;Изменяемые
;  регистры:	AX,CX,DX,Flags
;*******************************************************************************
GetBufferSize12	PROC

		sub	cx,ax		;CX=x2-x1
		mov	ax,cx
		mov	cl,3
		shr	ax,cl		;AX=(x2-x1)/8
		inc	ax		;AX=(x2-x1)/8+1

		sub	dx,bx		;DX=y2-y1
		inc	dx		;DX=y2-y1+1
		mul	dx

		mov	cl,2
		shl	ax,cl		;AX=4*(y2-y1+1)*((x2-x1)/8+1)
		add	ax,5		;AX=5+4*(y2-y1+1)*((x2-x1)/8+1)

		ret

GetBufferSize12	ENDP

;*******************************************************************************
;WriteChar12	выводит символ в режиме VGA 12h
;*******************************************************************************
;Используется:	PixelAddr12
;
;Вызов:		AL = знак ASCII, записываемый на экран
;		BL = значение пиксела символа
;		BH = значение пиксела фона
;		CX = координата x
;		DX = координата y
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,CX,DX,Flags
;*******************************************************************************
WriteChar12	PROC
		LOCAL	@@Shift:WORD = LOCAL_SIZE

		push	bp		;сохраняем регистры
		mov	bp,sp		;в стеке
		sub	sp,LOCAL_SIZE	;стековое пространство для локальных
					;переменных
		push	ds
		push	es
		push	si
		push	di

		push	ax		;сохраняем код ASCII в стеке
		push	bx		;сохраняем атрибуты символа в стеке

;вычисляем адрес первого пиксела

		mov	ax,dx		;AX=y
		mov	bx,cx		;BX=x
		call	PixelAddr12	;ES:BX -> видеобуфер
					;CL = количество битов для сдвига влево
		inc	cx
		and	cl,7		;CL = число битов сдвига при
					;маскировании символа
		mov	ch,0FFh
		shl	ch,cl		;CH = битовая маска для правого края
					;символа
		mov	[@@Shift],cx

		mov	si,bx		;SI = смещение видеобуфера

		pop	bx		;BL = значение пиксела символа
					;BH = значение пиксела фона
		pop	ax		;AL = код символа

		push	es		;сохраняем сегмент видеобуфера

;устанавливаем адресацию таблицы определения символов

		mov	di,40h
		mov	ds,di		;DS = сегмент области данных видео-BIOS
		mov	cx,ds:[85h]	;CX = высота матрицы символа
		mul	cl		;AX = смещение таблицы определения
					;символов

		xor	di,di
		mov	ds,di		;DS = абсолютный нуль

		les	di,ds:[43h*4]	;ES:DI -> начало таблицы символов
		add	di,ax		;DI = адрес определения символов

		pop	ds		;DS:SI -> видеобуфер

;устанавливаем регистры графического контроллера

		mov	dx,3CEh		;DX = порт графического контроллера

		mov	ax,0A05h	;AL = номер регистра режима
					;AH = режим записи 2 (биты 0,1)
					;     режим чтения 1 (бит 4)
		out	dx,ax

		mov	ax,7		;AH= биты безразличия цвета
					;AL = номер регистра безразличия цвета
		out	dx,ax		;"безразлично" для всех битовых
					;плоскостей

;выбор подпрограммы вывода зависит от того выровнен ли символ по границе байта

		cmp	BYTE PTR [@@Shift],0	;проверяем число битов для сдвига
		jne	@@02		;перейти, если символ не выровнен
					;по границе байта

;подпрограмма для символов, выровненных по границе байта

		mov	al,8		;AL = номер регистра битовой маски

@@01:		mov	ah,es:[di]	;AH = набор для следующего ряда пикселов
		out	dx,ax		;изменяем регистр битовой маски
		and	[si],bl		;изменяем пикселы символа
		not	ah
		out	dx,ax
		and	[si],bh		;изменяем пикселы фона

		inc	di		;ES:DI -> следующий байт в таблице
					;определения символов
		add	si,BytesPerRow12;переходим к следующей строке
					;в видеобуфере
		loop	@@01
		jmp	@@Exit

;подпрограмма для символов, не выровненных по границе байта

@@02:		push	cx		;сохраняем счетчик цикла
		mov	cx,[@@Shift]	;CH = маска для правой стороны символа
					;CL = число байтов для сдвига влево

;левая половина символа

		mov	al,es:[di]	;AL = биты для следующего ряда пикселов
		xor	ah,ah
		shl	ax,cl		;AH = биты для левой стороны символа
					;AL = биты для правой стороны символа
		push	ax		;сохраняем биты для правой стороны
					;символа в стеке
		mov	al,8		;AL = номер регистра битовой маски
		out	dx,ax		;устанавливаем битовую маску для
					;пикселов символа

		and	[si],bl		;изменяем пикселы символа

		not	ch		;CH = битовая маска для левой стороны
					;символа
		xor	ah,ch		;AH = биты для пикселов фона
		out	dx,ax		;устанавливаем битовую маску

		and	[si],bh		;изменяем пикселы фона

;правая сторона символа

		pop	ax
		mov	ah,al		;AH = биты для правой стороны символа
		mov	al,8
		out	dx,ax		;устанавливаем битовую маску

		and	[si+1],bl	;изменяем пикселы символа

		not	ch		;CH = маска для правой стороны символа
		xor	ah,ch		;AH = биты для пикселов фона
		out	dx,ax

		and	[si+1],bh	;изменяем пикселы фона

;инкремент к следующему ряду пикселов в символе

		inc	di		;ES:DI -> следующий байт в таблице
					;определения символов
		add	si,BytesPerRow12;DS:SI -> следующая строка в видеобуфере

		pop	cx
		loop	@@02

;восстанавливаем состояние регистров графического контроллера по умолчанию

@@Exit:		mov	ax,0FF08h	;AH=11111111b, AL=8
		out	dx,ax		;восстанавливаем содержимое регистра
					;битовой маски

		mov	ax,5		;AH=0, AL=5
		out	dx,ax		;восстанавливаем содержимое регистра
					;режима

		mov	ax,0F07h	;AH=1111b, AL=7
		out	dx,ax		;восстанавливаем содержимое регистра
					;безразличия цвета

		pop	di		;восстанавливаем регистры
		pop	si
		pop	es
		pop	ds
		mov	sp,bp
		pop	bp

		ret

WriteChar12	ENDP

;*******************************************************************************
;WriteChar12x	выводит символ в режиме VGA 12h
;*******************************************************************************
;Используется:	PixelAddr12
;
;Вызов:		AL = знак ASCII, записываемый на экран
;		BL = значение пиксела символа
;		CX = координата x
;		DX = координата y
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,CX,DX,Flags
;*******************************************************************************
WriteChar12x	PROC
		LOCAL	@@Shift:BYTE = LOCAL_SIZE

		push	bp		;сохраняем регистры
		mov	bp,sp		;в стеке
		sub	sp,LOCAL_SIZE	;стековое пространство для локальных
					;переменных
		push	ds
		push	es
		push	si
		push	di

		push	ax		;сохраняем код ASCII в стеке
		push	bx		;сохраняем атрибуты символа в стеке

;вычисляем адрес первого пиксела

		mov	ax,dx		;AX=y
		mov	bx,cx		;BX=x
		call	PixelAddr12	;ES:BX -> видеобуфер
					;CL = количество битов для сдвига влево
		inc	cx
		and	cl,7		;CL = число битов сдвига при
					;маскировании символа
		mov	[@@Shift],cl

		mov	si,bx		;SI = смещение видеобуфера

		pop	bx		;BL = значение пиксела символа
		pop	ax		;AL = код символа

		push	es		;сохраняем сегмент видеобуфера

;устанавливаем адресацию таблицы определения символов

		mov	di,40h
		mov	ds,di		;DS = сегмент области данных видео-BIOS
		mov	cx,ds:[85h]	;CX = высота матрицы символа
		mul	cl		;AX = смещение таблицы определения
					;символов

		xor	di,di
		mov	ds,di		;DS = абсолютный нуль

		les	di,ds:[43h*4]	;ES:DI -> начало таблицы символов
		add	di,ax		;DI = адрес определения символов

		pop	ds		;DS:SI -> видеобуфер

;устанавливаем регистры графического контроллера

		mov	dx,3CEh		;DX = порт графического контроллера

		mov	ax,0A05h	;AL = номер регистра режима
					;AH = режим записи 2 (биты 0,1)
					;     режим чтения 1 (бит 4)
		out	dx,ax

		mov	ax,7		;AH= биты безразличия цвета
					;AL = номер регистра безразличия цвета
		out	dx,ax		;"безразлично" для всех битовых
					;плоскостей

;выбор подпрограммы вывода зависит от того выровнен ли символ по границе байта

		cmp	[@@Shift],0	;проверяем число битов для сдвига
		jne	@@02		;перейти, если символ не выровнен
					;по границе байта

;подпрограмма для символов, выровненных по границе байта

		mov	al,8		;AL = номер регистра битовой маски

@@01:		mov	ah,es:[di]	;AH = набор для следующего ряда пикселов
		out	dx,ax		;изменяем регистр битовой маски
		and	[si],bl		;изменяем пикселы символа

		inc	di		;ES:DI -> следующий байт в таблице
					;определения символов
		add	si,BytesPerRow12;переходим к следующей строке
					;в видеобуфере
		loop	@@01
		jmp	@@Exit

;подпрограмма для символов, не выровненных по границе байта

@@02:		push	cx		;сохраняем счетчик цикла
		mov	cl,[@@Shift]	;CL = число байтов для сдвига влево

;левая половина символа

		mov	al,es:[di]	;AL = биты для следующего ряда пикселов
		xor	ah,ah
		shl	ax,cl		;AH = биты для левой стороны символа
					;AL = биты для правой стороны символа
		push	ax		;сохраняем биты для правой стороны
					;символа в стеке
		mov	al,8		;AL = номер регистра битовой маски
		out	dx,ax		;устанавливаем битовую маску для
					;пикселов символа

		and	[si],bl		;изменяем пикселы символа

;правая сторона символа

		pop	ax
		mov	ah,al		;AH = биты для правой стороны символа
		mov	al,8
		out	dx,ax		;устанавливаем битовую маску

		and	[si+1],bl	;изменяем пикселы символа

;инкремент к следующему ряду пикселов в символе

		inc	di		;ES:DI -> следующий байт в таблице
					;определения символов
		add	si,BytesPerRow12;DS:SI -> следующая строка в видеобуфере

		pop	cx
		loop	@@02

;восстанавливаем состояние регистров графического контроллера по умолчанию

@@Exit:		mov	ax,0FF08h	;AH=11111111b, AL=8
		out	dx,ax		;восстанавливаем содержимое регистра
					;битовой маски

		mov	ax,5		;AH=0, AL=5
		out	dx,ax		;восстанавливаем содержимое регистра
					;режима

		mov	ax,0F07h	;AH=1111b, AL=7
		out	dx,ax		;восстанавливаем содержимое регистра
					;безразличия цвета

		pop	di		;восстанавливаем регистры
		pop	si
		pop	es
		pop	ds
		mov	sp,bp
		pop	bp

		ret

WriteChar12x	ENDP

;*******************************************************************************
;WriteStr12	выводит строку символов с атрибутами цвета в режиме VGA 12h
;*******************************************************************************
;Использует:	WriteChar12
;
;Вызов:		DS:SI -> ASCIIZ-строка
;		BL = значение пиксела символа
;		BH = значение пиксела фона
;		CX = координата x
;		DX = координата y
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,CX,Flags
;*******************************************************************************
WriteStr12	PROC

		push	si

		cld

@@WriteLoop:	lodsb			;AL = текущий символ строки
		or	al,al		;конец строки?
		jz	@@Exit

		push	cx		;сохраняем регистры CX и DX
		push	dx		;в стеке
		call	WriteChar12	;выводим символ на экран
		pop	dx		;восстанавливаем
		pop	cx		;регистры CX и DX

		add	cx,SymbolWidth	;CX = новая позиция символа
		jmp	@@WriteLoop

@@Exit:		pop	si

		ret

WriteStr12	ENDP

;*******************************************************************************
;WriteStr12x	выводит строку символов с атрибутами цвета в режиме VGA 12h
;*******************************************************************************
;Использует:	WriteChar12x
;
;Вызов:		DS:SI -> ASCIIZ-строка
;		BL = значение пиксела символа
;		CX = координата x
;		DX = координата y
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,CX,Flags
;*******************************************************************************
WriteStr12x	PROC

		push	si

		cld

@@WriteLoop:	lodsb			;AL = текущий символ строки
		or	al,al		;конец строки?
		jz	@@Exit

		push	cx		;сохраняем регистры CX и DX
		push	dx		;в стеке
		call	WriteChar12x	;выводим символ на экран
		pop	dx		;восстанавливаем
		pop	cx		;регистры CX и DX

		add	cx,SymbolWidth	;CX = новая позиция символа
		jmp	@@WriteLoop

@@Exit:		pop	si

		ret

WriteStr12x	ENDP

;*******************************************************************************
;Line12		формирует прямую в графическом режиме VGA 12h
;		по алгоритму Бризенхэма
;*******************************************************************************
;Использует:	PixelAddr12
;
;Вызов:		x1,y1 - координаты одного конца прямой
;		x2,y2 - координаты другог конца прямой
;		color - цвет прямой
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,BX,CX,DX,Flags
;*******************************************************************************
Line12		PROC
		ARG	@@x1:WORD, @@y1:WORD, @@x2:WORD, @@y2:WORD, \
			@@color:BYTE = ARG_SIZE
		LOCAL	@@VertIncr:WORD, @@Incr1:WORD, @@Incr2:WORD, \
			@@Routine:WORD = LOCAL_SIZE

		push	bp		;сохраняем регистры
		mov	bp,sp		;в стеке
		sub	sp,LOCAL_SIZE	;стековое пространство для локальных
					;переменных
		push	es
		push	si
		push	di

;конфигурирование графического контроллера

		mov	dx,3CEh		;DX = адрес порта графического
					;контроллера

		mov	ah,[@@color]	;AH = значение пиксела
		xor	al,al		;AL = номер регистра установки/сброса
		out	dx,ax

		mov	ax,0F01h	;AH=1111b (битовая маска для регистра
					;разрешения установки/сброса)
					;AL = номер регистра разрешения
					;установки/сброса
		out	dx,ax

;проверяем, вертикальная ли линия

		mov	si,BytesPerRow12	;инкремент для видеобуфера

		mov	cx,[@@x2]
		sub	cx,[@@x1]	;CX=x2-x1
		jz	VertLine12	;перейти, если вертикальная линия

;приводим к x1<x2

		jns	@@01		;перейти, если x2>x1

		neg	cx		;CX=x1-x2

		mov	bx,[@@x2]	;обменять значения x1 и x2
		xchg	bx,[@@x1]
		mov	[@@x2],bx

		mov	bx,[@@y2]	;обменять значения y1 и y2
		xchg	bx,[@@y1]
		mov	[@@y2],bx

;вычисляем dy=ABS(y2-y1)

@@01:		mov	bx,[@@y2]
		sub	bx,[@@y1]	;BX=y2-y1
		jz	HorizLine12	;перейти, если горизонтальная прямая

		jns	@@02		;перейти, если наклон положительный

		neg	bx		;BX=y1-y2
		neg	si		;изменить знак инкремента для видеобуфера

;выбрать подпрограмму для соответствующего наклона прямой

@@02:		mov	[@@VertIncr],si	;сохранить значение инкремента
					;видеобуфера
		mov	[@@Routine],OFFSET LoSlopeLine12
		cmp	bx,cx
		jle	@@03		;перейти, если dy<=dx (наклон <= 1)
		mov	[@@Routine],OFFSET HiSlopeLine12
		xchg	bx,cx		;обменять значения dy и dx

;вычисляем начальные значения переменной решения и инкрементов

@@03:		shl	bx,1		;BX=2*dy
		mov	[@@Incr1],bx	;Incr1=2*dy
		sub	bx,cx
		mov	si,bx		;SI=d=2*dy-dx
		sub	bx,cx
		mov	[@@Incr2],bx	;Incr2=2*(dy-dx)

;вычисляем адрес первого пиксела

		push	cx		;сохраняем CX

		mov	ax,[@@y1]	;AX=y1
		mov	bx,[@@x1]	;BX=x1
		call	PixelAddr12	;AH = несдвинутая битовая маска
					;ES:BX -> видеобуфер
					;CL = количество битов для сдвига влево

		mov	di,bx		;ES:DI -> видеобуфер
		shl	ah,cl		;AH = битовая маска в нужной позиции
		mov	bl,ah
		mov	dx,3CEh		;DX = адрес порта графического
					;контроллера
		mov	al,8		;AL = номер регистра битовой маски

		pop	cx		;восстанавливаем CX
		inc	cx		;CX = количество выводимых пикселов

		jmp	[@@Routine]	;перейти к подпрограмме для
					;соответствующего наклона прямой

;подпрограмма для вертикальных прямых

VertLine12:	mov	ax,[@@y1]	;AX=y1
		mov	bx,[@@y2]	;BX=y2
		mov	cx,bx
		sub	cx,ax		;CX=dy
		jge	@@11		;перейти, если dy>=0

		neg	cx		;приводим к dy>=0
		mov	ax,bx		;AX=y2

@@11:		inc	cx		;CX = количество выводимых пикселов
		mov	bx,[@@x1]	;BX=x1
		push	cx		;сохраняем CX
		call	PixelAddr12	;AH = несдвинутая битовая маска
					;ES:BX -> видеобуфер
					;CL = количество битов для сдвига влево

;программируем графический контроллер

		mov	dx,3CEh		;DX = адрес порта графического
					;контроллера
		mov	al,8		;номер регистра битовой маски
		shl	ah,cl		;AH = битовая маска в нужной позиции
		out	dx,ax

		pop	cx		;восстанавливаем CX

;формируем линию

@@12:		or	es:[bx],al	;устанавливаем пиксел
		add	bx,si		;переходим к следующей строке
		loop	@@12

		jmp	@@Exit

;подпрограмма для горизонтальных прямых (наклон = 0)

HorizLine12:	push	ds		;сохраняем DS

		mov	ax,[@@y1]	;AX=y1
		mov	bx,[@@x1]	;BX=x1
		call	PixelAddr12	;AH = несдвинутая битовая маска
					;ES:BX -> видеобуфер
					;CL = количество битов для сдвига влево
		mov	di,bx		;ES:DI -> видеобуфер

		mov	dh,ah		;DH = несдвинутая маска для левого байта
		not	dh
		shl	dh,cl		;DH = неверная битовая маска для
					;первого байта
		not	dh		;DH = битовая маска для первого байта

		mov	cx,[@@x2]
		and	cl,7
		xor	cl,7		;CL = количество битов для сдвига влево
		mov	dl,0FFh		;DL = несдвинутая маска для правого байта
		shl	dl,cl		;DL = битовая маска для последнего байта

;определяем байтовое смещение для первого и последнего пикселов строки

		mov	ax,[@@x2]	;AX=x2
		mov	bx,[@@x1]	;BX=x1

		mov	cl,3
		shr	ax,cl
		shr	bx,cl
		mov	cx,ax
		sub	cx,bx		;CX=(количество байтов в прямой - 1)

		mov	bx,dx		;BH = битовая маска для первого байта
					;BL = битовая маска для последнего байта

		mov	dx,3CEh		;DX = порт графического контроллера
		mov	al,8		;AL = номер регистра битовой маски

;делаем видеобуфер адресуемым через DS:SI

		push	es
		pop	ds
		mov	si,di		;DS:SI -> видеобуфер

;устанавливаем пикселы для левого байта строки

		or	bh,bh
		js	@@22		;перейти, если выровнено по байту
					;(x1-левый пиксел в байте)

		or	cx,cx
		jnz	@@21		;перейти, если в прямой больше одного
					;байта

		and	bl,bh		;BL = битовая маска для прямой
		jmp	@@23

@@21:		mov	ah,bh		;AH = битовая маска для первого байта
		out	dx,ax		;обновляем графический контроллер

		cld

		movsb			;обновляем битовые плоскости
		dec	cx

;устанавливаем пикселы в середине строки

@@22:		mov	ah,0FFh		;AH = битовая маска
		out	dx,ax		;обновляем регистр битовой маски

		rep	movsb		;изменить все пикселы в строке

;установить пикселы в правом байте

@@23:		mov	ah,bl		;AH = битовая маска для последнего байта
		out	dx,ax		;обновляем графический контроллер

		movsb			;обновляем битовые плоскости

		pop	ds		;восстанавливаем DS

		jmp	@@Exit

;подпрограмма для dy<=dx (наклон <= 1)
;			ES:DI -> видеобуфер
;			AL = номер регистра маски
;			BL = битовая маска для первого пиксела
;			CX = число отображаемых пикселов
;			DX = адрес порта графического контроллера
;			SI = переменная решения

LoSlopeLine12:

@@31:		mov	ah,bl		;AH = битовая маска для следующего
					;пиксела

@@32:		or	ah,bl		;маскируем текущую позицию пиксела
		ror	bl,1		;сдвигаем битовую маску пиксела
		jc	@@34		;перейти, если битовая маска сдвинута
					;в левую для пиксела позицию

;битовая маска еще не сдвинута

		or	si,si		;проверяем знак d
		jns	@@33		;перейти, если d>=0

		add	si,[@@Incr1]	;d=d+Incr1
		loop	@@32

		out	dx,ax		;обновляем регистр битовой маски
		or	es:[di],al	;устанавливаем остальные пикселы

		jmp	@@Exit

@@33:		add	si,[@@Incr2]	;d=d+Incr2
		out	dx,ax		;обновляем регистр битовой маски

		or	es:[di],al	;обновляем битовые плоскости

		add	di,[@@VertIncr]	;инкремент y
		loop	@@31

		jmp	@@Exit

;битовая маска сдвинута

@@34:		out	dx,ax		;обновляем регистр битовой маски

		or	es:[di],al	;обновляем битовые плоскости
		inc	di		;инкремент x

		or	si,si		;проверяем знак d
		jns	@@35		;перейти, если неотрицательно

		add	si,[@@Incr1]	;d=d+Incr1
		loop	@@31
		jmp	@@Exit

@@35:		add	si,[@@Incr2]	;d=d+Incr2
		add	di,[@@VertIncr]	;вертикальный инкремент
		loop	@@31

		jmp	@@Exit

;подпрограмма для dy>dx (наклон > 1)
;			ES:DI -> видеобуфер
;			AH = битовая маска для первого пиксела
;			AL = номер регистра маски
;			CX = число выводимых пикселов
;			DX = адрес порта графического контроллера
;			SI = переменная решения

HiSlopeLine12:
		mov	bx,[@@VertIncr]	;BX=инкремент y

@@41:		out	dx,ax		;обновляем регистр битовой маски
		or	es:[di],al	;обновляем битовые плоскости

		add	di,bx		;инкремент x

		or	si,si		;проверяем знак d
		jns	@@42		;перейти, если d>=0

		add	si,[@@Incr1]	;d=d+Incr1
		loop	@@41
		jmp	@@Exit

@@42:		add	si,[@@Incr2]	;d=d+Incr2

		ror	ah,1		;сдвигаем битовую маску
		adc	di,0		;инкремент DI, если маска сдвинута
					;до левой позиции пиксела

		loop	@@41

;восстановливаем состояния графического контроллера по умолчанию

@@Exit:		xor	ax,ax		;AH=0, AL=0
		out	dx,ax		;восстановление регистра установки/сброса

		inc	ax		;AH=0, AL=1
		out	dx,ax		;восстановление регистра разрешения
					;установки/сброса

		mov	ax,0FF08h	;AH=11111111b, AL=8
		out	dx,ax		;восстановление регистра битовой маски

		pop	di		;восстанавливаем регистры
		pop	si
		pop	es
		mov	sp,bp
		pop	bp

		ret	ARG_SIZE

Line12		ENDP

;*******************************************************************************
;Rectangle12	формирует прямoугольник в графическом режиме VGA 12h
;*******************************************************************************
;Использует:	Line12
;
;Вызов:		x1,y1 - координаты левого верхнего угла
;		x2,y2 - координаты правого нижнего угла
;		color - цвет прямоугольника
;
;Возврат:	нет
;
;Изменяемые
;  регистры:	AX,BX,CX,DX,Flags
;*******************************************************************************
Rectangle12	PROC
		ARG	@@x1:WORD, @@y1:WORD, @@x2:WORD, @@y2:WORD, \
			@@color:BYTE = ARG_SIZE

		push	bp
		mov	bp,sp

;Строим верхнюю составляющую

		push	WORD PTR [@@color]
		push	[@@y1]
		push	[@@x2]
		push	[@@y1]
		push	[@@x1]
		call	Line12

;Строим левую составляющую

		push	WORD PTR [@@color]
		push	[@@y2]
		push	[@@x1]
		push	[@@y1]
		push	[@@x1]
		call	Line12

;Строим правую составляющую

		push	WORD PTR [@@color]
		push	[@@y2]
		push	[@@x2]
		push	[@@y1]
		push	[@@x2]
		call	Line12

;Строим нижнюю составляющую

		push	WORD PTR [@@color]
		push	[@@y2]
		push	[@@x2]
		push	[@@y2]
		push	[@@x1]
		call	Line12

		pop	bp

		ret	ARG_SIZE

Rectangle12	ENDP


END
