2141 lines
61 KiB
Plaintext
2141 lines
61 KiB
Plaintext
|
{ GDlib Core VGA mode 13h Graphics routines
|
||
|
Gered King, 2018-2021 }
|
||
|
|
||
|
{
|
||
|
TODO list:
|
||
|
- palette loading/saving to binary .pal files
|
||
|
- custom font support, including variable-width fonts
|
||
|
- allowing usage of EMS memory to have extra gfx layers located in EMS
|
||
|
- clip-region stack, with push/pop type api
|
||
|
- blit sprite routine that allows blitting all non-transparent colours
|
||
|
using a single specified solid colour (e.g. for sprite flashing effect)
|
||
|
- translucent/blending colour table mapping and drawing routines
|
||
|
- scaled/rotated blit routines ... ?
|
||
|
}
|
||
|
|
||
|
{$A+,B-,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
|
||
|
|
||
|
unit GDGfx;
|
||
|
|
||
|
interface
|
||
|
|
||
|
const
|
||
|
MAX_LAYERS = 5;
|
||
|
MAX_EXTRA_LAYERS = 5;
|
||
|
SCREEN_LAYER = 0;
|
||
|
|
||
|
SCREEN_LEFT = 0;
|
||
|
SCREEN_TOP = 0;
|
||
|
SCREEN_RIGHT = 319;
|
||
|
SCREEN_BOTTOM = 199;
|
||
|
SCREEN_WIDTH = 320;
|
||
|
SCREEN_HEIGHT = 200;
|
||
|
|
||
|
CHAR_WIDTH = 8;
|
||
|
CHAR_HEIGHT = 8;
|
||
|
|
||
|
LAYERFLAG_ALIASEDMEM = $1;
|
||
|
|
||
|
ClipRegionLeft : integer = SCREEN_LEFT;
|
||
|
ClipRegionTop : integer = SCREEN_TOP;
|
||
|
ClipRegionRight : integer = SCREEN_RIGHT;
|
||
|
ClipRegionBottom : integer = SCREEN_BOTTOM;
|
||
|
ClipRegionWidth : integer = SCREEN_WIDTH;
|
||
|
ClipRegionHeight : integer = SCREEN_HEIGHT;
|
||
|
|
||
|
type
|
||
|
Color = byte;
|
||
|
PColor = ^Color;
|
||
|
|
||
|
PalResult = (PalNotFound, PalIOError, PalBadFile, PalBadArgs, PalOk);
|
||
|
|
||
|
Palette = array[0..255,0..2] of byte;
|
||
|
PaletteEntry = array[0..2] of byte;
|
||
|
PPalette = ^Palette;
|
||
|
PPaletteEntry = ^PaletteEntry;
|
||
|
|
||
|
Pixelbuffer = array[0..63999] of Color;
|
||
|
PPixelbuffer = ^Pixelbuffer;
|
||
|
|
||
|
BiosFontTable = array[0..255,0..7] of byte;
|
||
|
BiosFontChar = array[0..7] of byte;
|
||
|
BiosFontCharWidths = array[0..255] of byte;
|
||
|
PBiosFontChar = ^BiosFontChar;
|
||
|
|
||
|
FontResult = (FontNotFound, FontIOError, FontBadFile, FontBadArgs, FontOk);
|
||
|
|
||
|
Font = record
|
||
|
chars : BiosFontTable;
|
||
|
widths : BiosFontCharWidths;
|
||
|
height : byte;
|
||
|
end;
|
||
|
PFont = ^Font;
|
||
|
|
||
|
Bitmap = record
|
||
|
Width, Height : word;
|
||
|
Pixels : byte; { take pointer of this to get full pixels mem }
|
||
|
end;
|
||
|
PBitmap = ^Bitmap;
|
||
|
|
||
|
var
|
||
|
VgaFramebuffer : Pixelbuffer absolute $a000:$0000;
|
||
|
|
||
|
procedure InitGraphics(layers: integer);
|
||
|
procedure CloseGraphics;
|
||
|
function IsGraphicsInitialized: boolean;
|
||
|
procedure UseLayer(layer: integer);
|
||
|
procedure Cls(color: Color);
|
||
|
procedure Flip(layer: integer);
|
||
|
procedure CopyLayer(layer: integer);
|
||
|
procedure WaitForVsync;
|
||
|
function GetBoundLayerIndex : integer;
|
||
|
function GetBoundLayerSegment : word;
|
||
|
function GetBoundLayerOffset : word;
|
||
|
function GetBoundLayerOffsetAt(x, y: integer) : word;
|
||
|
function GetBoundLayerPointerAt(x, y: integer) : pointer;
|
||
|
procedure SetClipRegion(x1, y1, x2, y2: integer);
|
||
|
procedure ResetClipRegion;
|
||
|
procedure Pset(x, y: integer; color: Color);
|
||
|
procedure Psetf(x, y: integer; color: Color);
|
||
|
function Pget(x, y: integer) : Color;
|
||
|
function Pgetf(x, y: integer) : Color;
|
||
|
procedure Line(x1, y1, x2, y2: integer; color: Color);
|
||
|
procedure Linef(x1, y1, x2, y2: integer; color: Color);
|
||
|
procedure HLine(x1, x2, y: integer; color: Color);
|
||
|
procedure HLinef(x1, x2, y: integer; color: Color);
|
||
|
procedure VLine(x, y1, y2: integer; color: Color);
|
||
|
procedure VLinef(x, y1, y2: integer; color: Color);
|
||
|
procedure Box(x1, y1, x2, y2: integer; color: Color);
|
||
|
procedure Boxf(x1, y1, x2, y2: integer; color: Color);
|
||
|
procedure FilledBox(x1, y1, x2, y2: integer; color: Color);
|
||
|
procedure FilledBoxf(x1, y1, x2, y2: integer; color: Color);
|
||
|
procedure UseFont(font : PFont);
|
||
|
procedure DrawSubString(x, y: integer; color: Color; const text: string; textStart, textEnd: integer);
|
||
|
procedure DrawString(x, y: integer; color: Color; const text: string);
|
||
|
procedure DrawStringf(x, y: integer; color: Color; const text: string);
|
||
|
procedure PrintAt(x, y: integer);
|
||
|
procedure PrintShortInt(value: shortint; color: Color);
|
||
|
procedure PrintInt(value: integer; color: Color);
|
||
|
procedure PrintLongInt(value: longint; color: Color);
|
||
|
procedure PrintByte(value: byte; color: Color);
|
||
|
procedure PrintWord(value: word; color: Color);
|
||
|
procedure PrintReal(value: real; color: Color);
|
||
|
procedure PrintRealFmt(value: real; decimals: integer; color: Color);
|
||
|
procedure PrintSingle(value: single; color: Color);
|
||
|
procedure PrintSingleFmt(value: single; decimals: integer; color: Color);
|
||
|
procedure PrintDouble(value: double; color: Color);
|
||
|
procedure PrintDoubleFmt(value: double; decimals: integer; color: Color);
|
||
|
procedure PrintString(const text: string; color: Color);
|
||
|
procedure AllocBitmap(width, height: word; var bmp: PBitmap);
|
||
|
procedure FreeBitmap(bmp: PBitmap);
|
||
|
procedure GrabBitmap(x, y: integer; width, height: word; bmp: PBitmap);
|
||
|
procedure BlitSubset(srcX, srcY, srcWidth, srcHeight, destX, destY: integer; const bmp: PBitmap);
|
||
|
procedure BlitSubsetf(srcX, srcY, srcWidth, srcHeight, destX, destY: integer; const bmp: PBitmap);
|
||
|
procedure Blit(x, y: integer; const bmp: PBitmap);
|
||
|
procedure Blitf(x, y: integer; const bmp: PBitmap);
|
||
|
procedure BlitSpriteSubset(srcX, srcY, srcWidth, srcHeight, destX, destY: integer; const bmp: PBitmap);
|
||
|
procedure BlitSpriteSubsetf(srcX, srcY, srcWidth, srcHeight, destX, destY: integer; const bmp: PBitmap);
|
||
|
procedure BlitSprite(x, y: integer; const bmp: PBitmap);
|
||
|
procedure BlitSpritef(x, y: integer; const bmp: PBitmap);
|
||
|
procedure SetColor(color, r, g, b: byte);
|
||
|
procedure GetColor(color: byte; var r, g, b: byte);
|
||
|
procedure SetPalette(const pal: PPalette);
|
||
|
procedure GetPalette(pal: PPalette);
|
||
|
function FadeColor(color, r, g, b: byte; step: integer) : boolean;
|
||
|
procedure FadeRangeToColor(colorStart, colorEnd, r, g, b: byte; step: integer);
|
||
|
procedure FadeRangeToPalette(colorStart, colorEnd: byte; const pal: PPalette; step: integer);
|
||
|
function LoadFont(const filename: string; font: PFont) : FontResult;
|
||
|
function LoadPalette(const filename: string; pal: PPalette) : FontResult;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses GDClip, Toolbox;
|
||
|
|
||
|
type
|
||
|
GraphicsLayer = record
|
||
|
flags : word;
|
||
|
pixels : PPixelbuffer;
|
||
|
end;
|
||
|
|
||
|
const
|
||
|
_graphicsInitialized : boolean = false;
|
||
|
|
||
|
var
|
||
|
_layers : array[0..(MAX_LAYERS+1)] of GraphicsLayer;
|
||
|
_boundLayerIndex : integer;
|
||
|
_boundLayerSeg : word;
|
||
|
_boundLayerOfs : word;
|
||
|
_biosFont : BiosFontTable absolute $ffa6:$000e;
|
||
|
_boundFont : PFont;
|
||
|
_valueString : string[100];
|
||
|
_currentTextX : integer;
|
||
|
_currentTextY : integer;
|
||
|
_currentTextStartX : integer;
|
||
|
|
||
|
procedure InitGraphics(layers: integer);
|
||
|
{ initializes vga mode 13h graphics and allocates memory for any requested
|
||
|
layers in addition to the vga framebuffer layer }
|
||
|
var
|
||
|
i : integer;
|
||
|
begin
|
||
|
if _graphicsInitialized then
|
||
|
exit;
|
||
|
if layers > MAX_LAYERS then
|
||
|
RunError(255);
|
||
|
|
||
|
_layers[SCREEN_LAYER].flags := LAYERFLAG_ALIASEDMEM;
|
||
|
_layers[SCREEN_LAYER].pixels := Ptr($a000,$0);
|
||
|
|
||
|
for i := 1 to layers do begin
|
||
|
_layers[i].flags := 0;
|
||
|
GetMem(_layers[i].pixels, SizeOf(Pixelbuffer));
|
||
|
MemFill(_layers[i].pixels, 0, SizeOf(PixelBuffer));
|
||
|
end;
|
||
|
|
||
|
{ zero out remaining layer info (if there is any leftovers) }
|
||
|
if layers < MAX_LAYERS then begin
|
||
|
for i := (layers+1) to MAX_LAYERS do begin
|
||
|
_layers[i].flags := 0;
|
||
|
_layers[i].pixels := nil;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
ResetClipRegion;
|
||
|
UseLayer(SCREEN_LAYER);
|
||
|
Cls(0);
|
||
|
|
||
|
asm
|
||
|
mov ax, 13h
|
||
|
int 10h
|
||
|
end;
|
||
|
|
||
|
_currentTextX := 0;
|
||
|
_currentTextY := 0;
|
||
|
_currentTextStartX := 0;
|
||
|
_boundFont := nil;
|
||
|
_graphicsInitialized := true;
|
||
|
end;
|
||
|
|
||
|
procedure CloseGraphics;
|
||
|
{ resets the screen mode back to text mode, and frees memory allocated for
|
||
|
any allocated layers }
|
||
|
var
|
||
|
i : integer;
|
||
|
begin
|
||
|
if (not _graphicsInitialized) then
|
||
|
exit;
|
||
|
|
||
|
UseLayer(0);
|
||
|
|
||
|
for i := 0 to MAX_LAYERS do begin
|
||
|
if (_layers[i].flags and LAYERFLAG_ALIASEDMEM) = 0 then begin
|
||
|
if _layers[i].pixels <> nil then
|
||
|
FreeMem(_layers[i].pixels, SizeOf(Pixelbuffer));
|
||
|
end;
|
||
|
_layers[i].flags := 0;
|
||
|
_layers[i].pixels := nil;
|
||
|
end;
|
||
|
|
||
|
asm
|
||
|
mov ax, 03h
|
||
|
int 10h
|
||
|
end;
|
||
|
|
||
|
_graphicsInitialized := false;
|
||
|
end;
|
||
|
|
||
|
function IsGraphicsInitialized: boolean;
|
||
|
begin
|
||
|
IsGraphicsInitialized := _graphicsInitialized;
|
||
|
end;
|
||
|
|
||
|
procedure UseLayer(layer: integer);
|
||
|
{ switches the active graphics layer }
|
||
|
var
|
||
|
pixelsPtr : pointer;
|
||
|
begin
|
||
|
pixelsPtr := _layers[layer].pixels;
|
||
|
if pixelsPtr = nil then
|
||
|
RunError(255);
|
||
|
|
||
|
_boundLayerIndex := layer;
|
||
|
_boundLayerSeg := PtrSeg(pixelsPtr);
|
||
|
_boundLayerOfs := PtrOfs(pixelsPtr);
|
||
|
end;
|
||
|
|
||
|
procedure Cls(color: Color);
|
||
|
{ clears the current bound layer with the specified color }
|
||
|
begin
|
||
|
MemFill(Ptr(_boundLayerSeg, _boundLayerOfs), color, 64000);
|
||
|
end;
|
||
|
|
||
|
procedure Flip(layer: integer);
|
||
|
{ 'flips' the specified layer onto the vga framebuffer }
|
||
|
var
|
||
|
layerPixelsPtr : pointer;
|
||
|
begin
|
||
|
if layer = SCREEN_LAYER then
|
||
|
exit;
|
||
|
|
||
|
layerPixelsPtr := _layers[layer].pixels;
|
||
|
if layerPixelsPtr = nil then
|
||
|
RunError(255);
|
||
|
|
||
|
MemCopy(Ptr($a000, 0), layerPixelsPtr, 64000);
|
||
|
end;
|
||
|
|
||
|
procedure CopyLayer(layer: integer);
|
||
|
{ copies the entire contents of the specified layer onto the currently
|
||
|
bound layer }
|
||
|
var
|
||
|
layerPixelsPtr : pointer;
|
||
|
begin
|
||
|
if layer = _boundLayerIndex then
|
||
|
exit;
|
||
|
|
||
|
layerPixelsPtr := _layers[layer].pixels;
|
||
|
if layerPixelsPtr = nil then
|
||
|
RunError(255);
|
||
|
|
||
|
MemCopy(Ptr(_boundLayerSeg, _boundLayerOfs), layerPixelsPtr, 64000);
|
||
|
end;
|
||
|
|
||
|
procedure WaitForVsync;
|
||
|
{ blocks until the next v-sync occurs }
|
||
|
assembler;
|
||
|
asm
|
||
|
mov dx, 3dah
|
||
|
@first:
|
||
|
in al, dx
|
||
|
and al, 8
|
||
|
jnz @first
|
||
|
@second:
|
||
|
in al, dx
|
||
|
and al, 8
|
||
|
jz @second
|
||
|
end;
|
||
|
|
||
|
function GetBoundLayerIndex : integer;
|
||
|
{ returns the number/index of the currently bound layer }
|
||
|
begin
|
||
|
GetBoundLayerIndex := _boundLayerIndex;
|
||
|
end;
|
||
|
|
||
|
function GetBoundLayerSegment : word;
|
||
|
{ returns the address segment of the currently bound layer }
|
||
|
begin
|
||
|
GetBoundLayerSegment := _boundLayerSeg;
|
||
|
end;
|
||
|
|
||
|
function GetBoundLayerOffset : word;
|
||
|
{ returns the offset of the currently bound layer (this will be the offset
|
||
|
that corresponds with X=0,Y=0 on the layer). }
|
||
|
begin
|
||
|
GetBoundLayerOffset := _boundLayerOfs;
|
||
|
end;
|
||
|
|
||
|
function GetBoundLayerOffsetAt(x, y: integer) : word;
|
||
|
{ returns the memory offset that the pixel at position x,y is located at
|
||
|
within any of the screen layers }
|
||
|
assembler;
|
||
|
asm
|
||
|
mov ax, y { ax = y * 320 }
|
||
|
mov bx, ax
|
||
|
shl ax, 6
|
||
|
shl bx, 8
|
||
|
add ax, bx
|
||
|
|
||
|
add ax, x { ax = ax + x }
|
||
|
add ax, _boundLayerOfs
|
||
|
end;
|
||
|
|
||
|
function GetBoundLayerPointerAt(x, y: integer) : pointer;
|
||
|
{ returns a pointer to the pixel at position x,y on the currently bound
|
||
|
layer }
|
||
|
assembler;
|
||
|
asm
|
||
|
mov ax, y { ax = y * 320 }
|
||
|
mov bx, ax
|
||
|
shl ax, 6
|
||
|
shl bx, 8
|
||
|
add ax, bx
|
||
|
|
||
|
add ax, x { ax = ax + x }
|
||
|
|
||
|
{ return pointer in dx:ax (segment:offset) }
|
||
|
add ax, _boundLayerOfs
|
||
|
mov dx, _boundLayerSeg
|
||
|
end;
|
||
|
|
||
|
function GetBitmapOffsetAt(x, y: integer; const bmp: PBitmap) : word;
|
||
|
{ returns the memory offset that the pixel at position x,y is located at
|
||
|
within the given bitmap }
|
||
|
begin
|
||
|
GetBitmapOffsetAt := (y * bmp^.Width) + x + Ofs(bmp^.Pixels);
|
||
|
end;
|
||
|
|
||
|
procedure SetClipRegion(x1, y1, x2, y2: integer);
|
||
|
{ changes the current boundaries for all clipped drawing operations.
|
||
|
affects all layers. }
|
||
|
begin
|
||
|
if x1 < SCREEN_LEFT then
|
||
|
ClipRegionLeft := SCREEN_LEFT
|
||
|
else
|
||
|
ClipRegionLeft := x1;
|
||
|
|
||
|
if y1 < SCREEN_TOP then
|
||
|
ClipRegionTop := SCREEN_TOP
|
||
|
else
|
||
|
ClipRegionTop := y1;
|
||
|
|
||
|
if x2 > SCREEN_RIGHT then
|
||
|
ClipRegionRight := SCREEN_RIGHT
|
||
|
else
|
||
|
ClipRegionRight := x2;
|
||
|
|
||
|
if y2 > SCREEN_BOTTOM then
|
||
|
ClipRegionBottom := SCREEN_BOTTOM
|
||
|
else
|
||
|
ClipRegionBottom := y2;
|
||
|
|
||
|
ClipRegionWidth := ClipRegionRight - ClipRegionLeft + 1;
|
||
|
ClipRegionHeight := ClipRegionBottom - ClipRegionTop + 1;
|
||
|
end;
|
||
|
|
||
|
procedure ResetClipRegion;
|
||
|
{ resets the current boundaries for all clipped drawing operations to
|
||
|
match the full screen boundaries }
|
||
|
begin
|
||
|
ClipRegionLeft := SCREEN_LEFT;
|
||
|
ClipRegionTop := SCREEN_TOP;
|
||
|
ClipRegionRight := SCREEN_RIGHT;
|
||
|
ClipRegionBottom := SCREEN_BOTTOM;
|
||
|
ClipRegionWidth := SCREEN_WIDTH;
|
||
|
ClipRegionHeight := SCREEN_HEIGHT;
|
||
|
end;
|
||
|
|
||
|
procedure Pset(x, y: integer; color: Color);
|
||
|
{ draws a pixel on the current layer at x,y using the given color.
|
||
|
nothing is drawn if x,y are out of bounds of the current clipping region }
|
||
|
assembler;
|
||
|
asm
|
||
|
mov ax, x
|
||
|
cmp ax, ClipRegionLeft { off the left edge? }
|
||
|
jl @done
|
||
|
cmp ax, ClipRegionRight { off the right edge? }
|
||
|
jg @done
|
||
|
|
||
|
mov di, y
|
||
|
cmp di, ClipRegionTop { off the top edge? }
|
||
|
jl @done
|
||
|
cmp di, ClipRegionBottom { off the bottom edge? }
|
||
|
jg @done
|
||
|
|
||
|
mov bx, di { di = y * 320 + x }
|
||
|
shl di, 6
|
||
|
shl bx, 8
|
||
|
add di, bx
|
||
|
add di, ax
|
||
|
add di, _boundLayerOfs
|
||
|
|
||
|
mov al, color
|
||
|
|
||
|
mov es, _boundLayerSeg { write pixel to current layer }
|
||
|
mov es:[di], al
|
||
|
|
||
|
@done:
|
||
|
end;
|
||
|
|
||
|
procedure Psetf(x, y: integer; color: Color);
|
||
|
{ draws a pixel on the current layer at x,y using the given color.
|
||
|
does not perform any bounds checking. }
|
||
|
assembler;
|
||
|
asm
|
||
|
mov ax, x
|
||
|
mov di, y
|
||
|
|
||
|
mov bx, di { di = y * 320 + x }
|
||
|
shl di, 6
|
||
|
shl bx, 8
|
||
|
add di, bx
|
||
|
add di, ax
|
||
|
add di, _boundLayerOfs
|
||
|
|
||
|
mov al, color
|
||
|
|
||
|
mov es, _boundLayerSeg { write pixel to current layer }
|
||
|
mov es:[di], al
|
||
|
end;
|
||
|
|
||
|
function Pget(x, y: integer) : Color;
|
||
|
{ gets a pixel from the current layer at x,y, returning the color.
|
||
|
returns zero if x,y are out of bounds of the current clipping region }
|
||
|
assembler;
|
||
|
asm
|
||
|
xor ax, ax
|
||
|
|
||
|
mov cx, x
|
||
|
cmp cx, ClipRegionLeft { off the left edge? }
|
||
|
jl @done
|
||
|
cmp cx, ClipRegionRight { off the right edge? }
|
||
|
jg @done
|
||
|
|
||
|
mov si, y
|
||
|
cmp si, ClipRegionTop { off the top edge? }
|
||
|
jl @done
|
||
|
cmp si, ClipRegionBottom { off the bottom edge? }
|
||
|
jg @done
|
||
|
|
||
|
mov bx, si { si = y * 320 + x }
|
||
|
shl si, 6
|
||
|
shl bx, 8
|
||
|
add si, bx
|
||
|
add si, cx
|
||
|
add si, _boundLayerOfs
|
||
|
|
||
|
push ds
|
||
|
|
||
|
mov ds, _boundLayerSeg { read pixel from current layer }
|
||
|
mov al, ds:[si]
|
||
|
|
||
|
pop ds
|
||
|
|
||
|
@done:
|
||
|
end;
|
||
|
|
||
|
function Pgetf(x, y: integer) : Color;
|
||
|
{ gets a pixel from the current layer at x,y, returning the color.
|
||
|
does not perform any bounds checking. }
|
||
|
assembler;
|
||
|
asm
|
||
|
xor ax, ax
|
||
|
|
||
|
mov cx, x
|
||
|
mov si, y
|
||
|
|
||
|
mov bx, si { si = y * 320 + x }
|
||
|
shl si, 6
|
||
|
shl bx, 8
|
||
|
add si, bx
|
||
|
add si, cx
|
||
|
add si, _boundLayerOfs
|
||
|
|
||
|
push ds
|
||
|
|
||
|
mov ds, _boundLayerSeg { read pixel from current layer }
|
||
|
mov al, ds:[si]
|
||
|
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure Line(x1, y1, x2, y2: integer; color: Color);
|
||
|
{ draws a line from x1,y1 to x2,y2. the line is clipped at the edges of the
|
||
|
current clipping region if necessary. }
|
||
|
var
|
||
|
deltaX, deltaY : integer;
|
||
|
deltaXabs, deltaYabs : integer;
|
||
|
deltaXsign, deltaYsign : integer;
|
||
|
x, y, i, dx, dy, loops : integer;
|
||
|
offset : integer;
|
||
|
offsetXinc, offsetYinc : integer;
|
||
|
dest : PColor;
|
||
|
begin
|
||
|
dx := x1;
|
||
|
dy := y1;
|
||
|
deltaX := x2 - x1;
|
||
|
deltaY := y2 - y1;
|
||
|
deltaXabs := Abs(deltaX);
|
||
|
deltaYabs := Abs(deltaY);
|
||
|
deltaXsign := SignInt(deltaX);
|
||
|
deltaYsign := SignInt(deltaY);
|
||
|
x := deltaYabs div 2;
|
||
|
y := deltaYabs div 2;
|
||
|
offsetXinc := deltaXsign;
|
||
|
offsetYinc := deltaYsign * SCREEN_WIDTH;
|
||
|
dest := GetBoundLayerPointerAt(x1, y1);
|
||
|
|
||
|
if ((dx >= ClipRegionLeft)
|
||
|
and (dy >= ClipRegionTop)
|
||
|
and (dx <= ClipRegionRight)
|
||
|
and (dy <= ClipRegionBottom)) then
|
||
|
dest^ := color;
|
||
|
|
||
|
if deltaXabs >= deltaYabs then begin
|
||
|
loops := deltaXabs - 1;
|
||
|
for i := 0 to loops do begin
|
||
|
inc(y, deltaYabs);
|
||
|
|
||
|
if y >= deltaXabs then begin
|
||
|
dec(y, deltaXabs);
|
||
|
inc(dy, deltaYsign);
|
||
|
inc(dest, offsetYinc);
|
||
|
end;
|
||
|
|
||
|
inc(dx, deltaXsign);
|
||
|
inc(dest, offsetXinc);
|
||
|
|
||
|
if ((dx >= ClipRegionLeft)
|
||
|
and (dy >= ClipRegionTop)
|
||
|
and (dx <= ClipRegionRight)
|
||
|
and (dy <= ClipRegionBottom)) then
|
||
|
dest^ := color;
|
||
|
end;
|
||
|
|
||
|
end else begin
|
||
|
loops := deltaYabs - 1;
|
||
|
for i := 0 to loops do begin
|
||
|
inc(x, deltaXabs);
|
||
|
|
||
|
if x >= deltaYabs then begin
|
||
|
dec(x, deltaYabs);
|
||
|
inc(dx, deltaXsign);
|
||
|
inc(dest, offsetXinc);
|
||
|
end;
|
||
|
|
||
|
inc(dy, deltaYsign);
|
||
|
inc(dest, offsetYinc);
|
||
|
|
||
|
if ((dx >= ClipRegionLeft)
|
||
|
and (dy >= ClipRegionTop)
|
||
|
and (dx <= ClipRegionRight)
|
||
|
and (dy <= ClipRegionBottom)) then
|
||
|
dest^ := color;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure Linef(x1, y1, x2, y2: integer; color: Color);
|
||
|
{ draws a line from x1,y1 to x2,y2. no clipping is performed. }
|
||
|
var
|
||
|
deltaX, deltaY : integer;
|
||
|
deltaXabs, deltaYabs : integer;
|
||
|
deltaXsign, deltaYsign : integer;
|
||
|
x, y, i, loops : integer;
|
||
|
offsetXinc, offsetYinc : integer;
|
||
|
dest : PColor;
|
||
|
begin
|
||
|
deltaX := x2 - x1;
|
||
|
deltaY := y2 - y1;
|
||
|
deltaXabs := Abs(deltaX);
|
||
|
deltaYabs := Abs(deltaY);
|
||
|
deltaXsign := SignInt(deltaX);
|
||
|
deltaYsign := SignInt(deltaY);
|
||
|
x := deltaYabs div 2;
|
||
|
y := deltaYabs div 2;
|
||
|
offsetXinc := deltaXsign;
|
||
|
offsetYinc := deltaYsign * SCREEN_WIDTH;
|
||
|
dest := GetBoundLayerPointerAt(x1, y1);
|
||
|
|
||
|
dest^ := color;
|
||
|
|
||
|
if deltaXabs >= deltaYabs then begin
|
||
|
loops := deltaXabs - 1;
|
||
|
for i := 0 to loops do begin
|
||
|
inc(y, deltaYabs);
|
||
|
|
||
|
if y >= deltaXabs then begin
|
||
|
dec(y, deltaXabs);
|
||
|
inc(dest, offsetYinc);
|
||
|
end;
|
||
|
|
||
|
inc(dest, offsetXinc);
|
||
|
|
||
|
dest^ := color;
|
||
|
end;
|
||
|
|
||
|
end else begin
|
||
|
loops := deltaYabs - 1;
|
||
|
for i := 0 to loops do begin
|
||
|
inc(x, deltaXabs);
|
||
|
|
||
|
if x >= deltaYabs then begin
|
||
|
dec(x, deltaYabs);
|
||
|
inc(dest, offsetXinc);
|
||
|
end;
|
||
|
|
||
|
inc(dest, offsetYinc);
|
||
|
|
||
|
dest^ := color;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure HLine(x1, x2, y: integer; color: Color);
|
||
|
{ draws a horizontal line. automatically compensates for incorrect ordering
|
||
|
of the x coordinates and also clips the line to the current clipping
|
||
|
region. }
|
||
|
begin
|
||
|
if x2 < x1 then
|
||
|
SwapInts(x1, x2);
|
||
|
if not ClampToClipRegion(x1, y, x2, y) then
|
||
|
exit;
|
||
|
|
||
|
HLinef(x1, x2, y, color);
|
||
|
end;
|
||
|
|
||
|
procedure HLinef(x1, x2, y: integer; color: Color);
|
||
|
{ draws a horizontal line. x1 must be less than x2. no clipping is
|
||
|
performed. }
|
||
|
var
|
||
|
dest : pointer;
|
||
|
length : integer;
|
||
|
begin
|
||
|
dest := GetBoundLayerPointerAt(x1, y);
|
||
|
length := x2 - x1 + 1;
|
||
|
MemFill(dest, color, length);
|
||
|
end;
|
||
|
|
||
|
procedure VLine(x, y1, y2: integer; color: Color);
|
||
|
{ draws a vertical line. automatically compensates for incorrect ordering
|
||
|
of the y coordinates and also clips the line to the current clipping
|
||
|
region. }
|
||
|
begin
|
||
|
if y2 < y1 then
|
||
|
SwapInts(y1, y2);
|
||
|
if not ClampToClipRegion(x, y1, x, y2) then
|
||
|
exit;
|
||
|
|
||
|
VLinef(x, y1, y2, color);
|
||
|
end;
|
||
|
|
||
|
procedure VLinef(x, y1, y2: integer; color: Color);
|
||
|
{ draws a vertical line. y1 must be less than y2. no clipping is
|
||
|
performed. }
|
||
|
var
|
||
|
offs : word;
|
||
|
linesLeft : integer;
|
||
|
begin
|
||
|
offs := GetBoundLayerOffsetAt(x, y1);
|
||
|
linesLeft := y2 - y1 + 1;
|
||
|
|
||
|
asm
|
||
|
mov cx, linesLeft
|
||
|
test cx, cx { make sure there are > 0 lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov al, color
|
||
|
mov es, _boundLayerSeg
|
||
|
mov di, offs
|
||
|
|
||
|
@draw:
|
||
|
mov es:[di], al
|
||
|
add di, SCREEN_WIDTH
|
||
|
dec cx
|
||
|
jnz @draw
|
||
|
|
||
|
@done:
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure DrawTwoVerticalLines(leftOffs, rightOffs: word;
|
||
|
color: Color;
|
||
|
height: integer);
|
||
|
assembler;
|
||
|
asm
|
||
|
mov es, _boundLayerSeg
|
||
|
mov si, leftOffs
|
||
|
mov di, rightOffs
|
||
|
mov al, color
|
||
|
mov cx, height
|
||
|
|
||
|
@draw:
|
||
|
mov es:[si], al
|
||
|
mov es:[di], al
|
||
|
add si, SCREEN_WIDTH
|
||
|
add di, SCREEN_WIDTH
|
||
|
dec cx
|
||
|
jnz @draw
|
||
|
|
||
|
@done:
|
||
|
end;
|
||
|
|
||
|
procedure DrawVerticalLine(offs: word;
|
||
|
color: Color;
|
||
|
height: integer);
|
||
|
assembler;
|
||
|
asm
|
||
|
mov es, _boundLayerSeg
|
||
|
mov di, offs
|
||
|
mov al, color
|
||
|
mov cx, height
|
||
|
|
||
|
@draw:
|
||
|
mov es:[di], al
|
||
|
add di, SCREEN_WIDTH
|
||
|
dec cx
|
||
|
jnz @draw
|
||
|
|
||
|
@done:
|
||
|
end;
|
||
|
|
||
|
procedure Box(x1, y1, x2, y2: integer; color: Color);
|
||
|
{ draws an empty box. automatically compensates if coordinate pairs are
|
||
|
swapped so that x1 < x2 and y1 < y2. clips the box to the current clipping
|
||
|
region boundaries. }
|
||
|
var
|
||
|
clippedX1, clippedY1, clippedX2, clippedY2 : integer;
|
||
|
clippedWidth, clippedHeight : integer;
|
||
|
offs, offs1, offs2 : word;
|
||
|
begin
|
||
|
if x2 < x1 then
|
||
|
SwapInts(x1, x2);
|
||
|
if y2 < y1 then
|
||
|
SwapInts(y1, y2);
|
||
|
|
||
|
clippedX1 := x1;
|
||
|
clippedY1 := y1;
|
||
|
clippedWidth := x2 - x1 + 1;
|
||
|
clippedHeight := y2 - y1 + 1;
|
||
|
|
||
|
if not ClipToClipRegion(clippedX1, clippedY1,
|
||
|
clippedWidth, clippedHeight) then
|
||
|
exit;
|
||
|
|
||
|
clippedX2 := clippedX1 + clippedWidth - 1;
|
||
|
clippedY2 := clippedY1 + clippedHeight - 1;
|
||
|
|
||
|
{ top line, only if y1 was within bounds }
|
||
|
if y1 = clippedY1 then begin
|
||
|
offs := GetBoundLayerOffsetAt(clippedX1, clippedY1);
|
||
|
MemFill(Ptr(_boundLayerSeg, offs), color, clippedWidth);
|
||
|
end;
|
||
|
|
||
|
{ bottom line, only if y2 was within bounds }
|
||
|
if y2 = clippedY2 then begin
|
||
|
offs := GetBoundLayerOffsetAt(clippedX1, clippedY2);
|
||
|
MemFill(Ptr(_boundLayerSeg, offs), color, clippedWidth);
|
||
|
end;
|
||
|
|
||
|
{ draw both left and right lines if neither x1 nor x2 were clipped }
|
||
|
if (x1 = clippedX1) and (x2 = clippedX2) then begin
|
||
|
offs1 := GetBoundLayerOffsetAt(clippedX1, clippedY1);
|
||
|
offs2 := GetBoundLayerOffsetAt(clippedX2, clippedY1);
|
||
|
DrawTwoVerticalLines(offs1, offs2, color, clippedHeight);
|
||
|
|
||
|
{ draw left line if x1 was not clipped }
|
||
|
end else if x1 = clippedX1 then begin
|
||
|
offs := GetBoundLayerOffsetAt(clippedX1, clippedY1);
|
||
|
DrawVerticalLine(offs, color, clippedHeight);
|
||
|
|
||
|
{ draw right line if x2 was not clipped }
|
||
|
end else if x2 = clippedX2 then begin
|
||
|
offs := GetBoundLayerOffsetAt(clippedX2, clippedY1);
|
||
|
DrawVerticalLine(offs, color, clippedHeight);
|
||
|
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure Boxf(x1, y1, x2, y2: integer; color: Color);
|
||
|
{ draws an empty box. x1 must be less than x2, and y1 must be less than y2.
|
||
|
no clipping is performed. }
|
||
|
var
|
||
|
offs, offsLeft, offsRight : word;
|
||
|
width, linesLeft : integer;
|
||
|
begin
|
||
|
width := x2 - x1 + 1;
|
||
|
linesLeft := y2 - y1;
|
||
|
offs := GetBoundLayerOffsetAt(x1, y1);
|
||
|
offsLeft := offs;
|
||
|
offsRight := offs + width - 1;
|
||
|
|
||
|
asm
|
||
|
mov al, color { spread color byte over all of EAX so we can }
|
||
|
mov ah, al { draw 4 pixels at once horizontally. }
|
||
|
db $66; shl ax, 8 { shl eax, 8 }
|
||
|
mov al, ah
|
||
|
db $66; shl ax, 8 { shl eax, 8 }
|
||
|
mov al, ah
|
||
|
|
||
|
db $66; xor cx, cx { xor ecx, ecx }
|
||
|
mov es, _boundLayerSeg
|
||
|
mov dx, width
|
||
|
mov cx, linesLeft
|
||
|
mov di, offsLeft
|
||
|
mov si, offsRight
|
||
|
|
||
|
test cx, cx { do we have any vertical height to draw? }
|
||
|
jz @draw_horiz1 { if not, skip to horizontal drawing ... }
|
||
|
|
||
|
@draw_vert:
|
||
|
{ draw both vertical lines }
|
||
|
mov es:[di], al
|
||
|
mov es:[si], al
|
||
|
add di, SCREEN_WIDTH
|
||
|
add si, SCREEN_WIDTH
|
||
|
dec cx
|
||
|
jnz @draw_vert
|
||
|
|
||
|
@draw_horiz1:
|
||
|
{ di will be at (x1,y2) currently. this is the correct location to draw
|
||
|
the bottom horizontal line }
|
||
|
mov cx, dx { cx = number of dwords to draw }
|
||
|
mov bx, cx { bx = remaining number of pixels to draw }
|
||
|
shr cx, 2
|
||
|
and bx, 3
|
||
|
|
||
|
db $f3,$66,$ab { rep stosd - draw dwords }
|
||
|
mov cx, bx
|
||
|
rep stosb { draw remaining pixels }
|
||
|
|
||
|
@draw_horiz2:
|
||
|
{ now draw the top line. reposition to (x1,y1) }
|
||
|
mov di, offsLeft
|
||
|
mov cx, dx { cx = number of dwords to draw }
|
||
|
mov bx, cx { bx = remaining number of pixels to draw }
|
||
|
shr cx, 2
|
||
|
and bx, 3
|
||
|
|
||
|
db $f3,$66,$ab { rep stosd - draw dwords }
|
||
|
mov cx, bx
|
||
|
rep stosb { draw remaining pixels }
|
||
|
|
||
|
@done:
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure FilledBox(x1, y1, x2, y2: integer; color: Color);
|
||
|
{ draws a filled box. automatically compensates if coordinate pairs are
|
||
|
swapped so that x1 < x2 and y1 < y2. clips the box to the current clipping
|
||
|
region boundaries. }
|
||
|
begin
|
||
|
if x2 < x1 then
|
||
|
SwapInts(x1, x2);
|
||
|
if y2 < y1 then
|
||
|
SwapInts(y1, y2);
|
||
|
|
||
|
if not ClampToClipRegion(x1, y1, x2, y2) then
|
||
|
exit;
|
||
|
|
||
|
FilledBoxf(x1, y1, x2, y2, color);
|
||
|
end;
|
||
|
|
||
|
procedure FilledBoxf(x1, y1, x2, y2: integer; color: Color);
|
||
|
{ draws a filled box. x1 must be less than x2, and y1 must be less than y2.
|
||
|
no clipping is performed. }
|
||
|
var
|
||
|
offs : word;
|
||
|
width, linesLeft, remainder : integer;
|
||
|
begin
|
||
|
width := x2 - x1 + 1;
|
||
|
linesLeft := y2 - y1 + 1;
|
||
|
offs := GetBoundLayerOffsetAt(x1, y1);
|
||
|
|
||
|
asm
|
||
|
mov si, linesLeft
|
||
|
test si, si { stop if there are 0 lines to draw }
|
||
|
jz @nothing_to_draw
|
||
|
|
||
|
mov al, color { spread color byte over all of EAX so we can }
|
||
|
mov ah, al { draw 4 pixels at once horizontally. }
|
||
|
db $66; shl ax, 8 { shl eax, 8 }
|
||
|
mov al, ah
|
||
|
db $66; shl ax, 8 { shl eax, 8 }
|
||
|
mov al, ah
|
||
|
|
||
|
mov bx, width
|
||
|
mov di, offs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov dx, SCREEN_WIDTH { dx = y-inc to next line after drawing horiz }
|
||
|
sub dx, bx
|
||
|
|
||
|
{ *** WARNING: no stack/vars access after this! *** }
|
||
|
push bp
|
||
|
mov bp, bx
|
||
|
and bp, 3 { bp = remaining number of pixels to draw }
|
||
|
shr bx, 2 { bx = number of dwords to draw }
|
||
|
|
||
|
db $66; xor cx, cx { xor ecx, ecx }
|
||
|
|
||
|
test bp, bp
|
||
|
jz @draw
|
||
|
|
||
|
@draw_with_remainder:
|
||
|
mov cx, bx
|
||
|
db $f3,$66,$ab { rep stosd - draw dwords }
|
||
|
mov cx, bp
|
||
|
rep stosb { draw remainder pixel }
|
||
|
add di, dx { move to start of next line }
|
||
|
dec si { decrease line counter }
|
||
|
jnz @draw_with_remainder { keep drawing if there are more lines }
|
||
|
jmp @done
|
||
|
|
||
|
@draw:
|
||
|
mov cx, bx
|
||
|
db $f3,$66,$ab { rep stosd - draw dwords }
|
||
|
add di, dx { move to start of next line }
|
||
|
dec si { decrease line counter }
|
||
|
jnz @draw { keep drawing if there are more lines }
|
||
|
jmp @done
|
||
|
|
||
|
@done:
|
||
|
pop bp
|
||
|
@nothing_to_draw:
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure UseFont(font : PFont);
|
||
|
{ binds the specified font. all subsequent text/string drawing will be
|
||
|
done using this font until this is called again with a different font.
|
||
|
pass nil to revert back to using the standard VGA BIOS font. }
|
||
|
begin
|
||
|
if font = nil then
|
||
|
_boundFont := nil { means to use VGA BIOS font instead }
|
||
|
else
|
||
|
_boundFont := font;
|
||
|
end;
|
||
|
|
||
|
{ TODO: rewrite this function. it is poopy and sad. }
|
||
|
procedure DrawChar(x, y, drawX, drawY, width, height: integer;
|
||
|
color: Color;
|
||
|
ch: char);
|
||
|
var
|
||
|
dest, fontSrc : word;
|
||
|
cx, cy : integer;
|
||
|
offsetX, offsetY : integer;
|
||
|
destWidth, destHeight : integer;
|
||
|
fontCharMap : PBiosFontChar;
|
||
|
fontCharLine : byte;
|
||
|
pixel : byte;
|
||
|
begin
|
||
|
dest := GetBoundLayerOffsetAt(drawX, drawY);
|
||
|
|
||
|
if _boundFont <> nil then
|
||
|
fontCharMap := @_boundFont^.chars[Ord(ch), 0]
|
||
|
else
|
||
|
fontCharMap := @_biosFont[Ord(ch), 0];
|
||
|
|
||
|
{ get offset x,y to start drawing char from (will be in range 0-7) }
|
||
|
offsetX := drawX - x;
|
||
|
offsetY := drawY - y;
|
||
|
destWidth := width + offsetX - 1;
|
||
|
destHeight := height + offsetY - 1;
|
||
|
|
||
|
{ cx,cy are always in "char coordinate space" (that is, 0-7) }
|
||
|
for cy := offsetY to destHeight do begin
|
||
|
fontCharLine := fontCharMap^[cy];
|
||
|
|
||
|
for cx := offsetX to destWidth do begin
|
||
|
pixel := ((fontCharLine) and (1 shl ((CHAR_WIDTH - 1) - cx)));
|
||
|
if pixel > 0 then
|
||
|
mem[_boundLayerSeg:(dest + (cx - offsetX))] := color;
|
||
|
end;
|
||
|
|
||
|
inc(dest, SCREEN_WIDTH);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure DrawSubString(x, y: integer;
|
||
|
color: Color;
|
||
|
const text: string;
|
||
|
textStart, textEnd: integer);
|
||
|
{ draws a sub-string starting from the top-left coordinate (x,y). handles
|
||
|
multi-line strings automatically. the text is clipped to the current
|
||
|
clipping region boundaries as needed. only part of the string is drawn,
|
||
|
as specified by textStart and textEnd (non-inclusive end) }
|
||
|
var
|
||
|
ch : char;
|
||
|
i, currentX, currentY : integer;
|
||
|
width, height, length : integer;
|
||
|
spaceWidth : integer;
|
||
|
clippedX, clippedY : integer;
|
||
|
clippedWidth, clippedHeight : integer;
|
||
|
begin
|
||
|
currentX := x;
|
||
|
currentY := y;
|
||
|
width := CHAR_WIDTH;
|
||
|
|
||
|
if _boundFont <> nil then begin
|
||
|
height := _boundFont^.height;
|
||
|
spaceWidth := _boundFont^.widths[Ord(' ')];
|
||
|
end else begin
|
||
|
height := CHAR_HEIGHT;
|
||
|
spaceWidth := CHAR_WIDTH;
|
||
|
end;
|
||
|
|
||
|
for i := textStart to textEnd do begin
|
||
|
ch := text[i];
|
||
|
case ch of
|
||
|
{ move to next line }
|
||
|
#10: begin
|
||
|
currentX := x;
|
||
|
inc(currentY, height);
|
||
|
end;
|
||
|
#13: begin;
|
||
|
end;
|
||
|
{ skip a character ahead, no need to "draw" a space char (empty) }
|
||
|
' ': inc(currentX, spaceWidth);
|
||
|
else begin
|
||
|
if _boundFont <> nil then
|
||
|
width := _boundFont^.widths[Ord(ch)];
|
||
|
|
||
|
clippedX := currentX;
|
||
|
clippedY := currentY;
|
||
|
clippedWidth := width;
|
||
|
clippedHeight := height;
|
||
|
if ClipToClipRegion(clippedX, clippedY, clippedWidth, clippedHeight) then
|
||
|
DrawChar(currentX, currentY, clippedX, clippedY, clippedWidth, clippedHeight, color, ch);
|
||
|
|
||
|
inc(currentX, width);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure DrawString(x, y: integer; color: Color; const text: string);
|
||
|
{ draws a string starting from the top-left coordinate (x,y). handles
|
||
|
multi-line strings automatically. the text is clipped to the current
|
||
|
clipping region boundaries as needed. }
|
||
|
begin
|
||
|
DrawSubString(x, y, color, text, 1, length(text));
|
||
|
end;
|
||
|
|
||
|
procedure DrawStringf(x, y: integer; color: Color; const text: string);
|
||
|
{ draws a string starting from the top-left coordinate (x,y). handles
|
||
|
multi-line strings automatically. no clipping is performed. }
|
||
|
var
|
||
|
ch : char;
|
||
|
i, currentX, currentY : integer;
|
||
|
width, height, len : integer;
|
||
|
spaceWidth : integer;
|
||
|
begin
|
||
|
currentX := x;
|
||
|
currentY := y;
|
||
|
width := CHAR_WIDTH;
|
||
|
len := length(text);
|
||
|
|
||
|
if _boundFont <> nil then begin
|
||
|
height := _boundFont^.height;
|
||
|
spaceWidth := _boundFont^.widths[Ord(' ')];
|
||
|
end else begin
|
||
|
height := CHAR_HEIGHT;
|
||
|
spaceWidth := CHAR_WIDTH;
|
||
|
end;
|
||
|
|
||
|
for i := 1 to len do begin
|
||
|
ch := text[i];
|
||
|
case ch of
|
||
|
{ move to next line }
|
||
|
#10: begin
|
||
|
currentX := x;
|
||
|
inc(currentY, height);
|
||
|
end;
|
||
|
#13: begin;
|
||
|
end;
|
||
|
{ skip a character ahead, no need to "draw" a space char (empty) }
|
||
|
' ': inc(currentX, spaceWidth);
|
||
|
else begin
|
||
|
if _boundFont <> nil then
|
||
|
width := _boundFont^.widths[Ord(ch)];
|
||
|
|
||
|
DrawChar(currentX, currentY, currentX, currentY, width, height, color, ch);
|
||
|
inc(currentX, width);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure PrintAt(x, y: integer);
|
||
|
{ sets the text position for subsequent calls to any of the PrintXxx
|
||
|
functions. }
|
||
|
begin
|
||
|
if x > SCREEN_RIGHT then
|
||
|
exit;
|
||
|
if y > SCREEN_BOTTOM then
|
||
|
exit;
|
||
|
|
||
|
_currentTextX := x;
|
||
|
_currentTextY := y;
|
||
|
_currentTextStartX := x;
|
||
|
end;
|
||
|
|
||
|
procedure PrintShortInt(value: shortint; color: Color);
|
||
|
{ displays a short-integer value at the current text position. clipping is
|
||
|
performed as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintInt(value: integer; color: Color);
|
||
|
{ displays an integer value at the current text position. clipping is
|
||
|
performed as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintLongInt(value: longint; color: Color);
|
||
|
{ displays a long-integer value at the current text position. clipping is
|
||
|
performed as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintByte(value: byte; color: Color);
|
||
|
{ displays a byte value at the current text position. clipping is performed
|
||
|
as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintWord(value: word; color: Color);
|
||
|
{ displays a word value at the current text position. clipping is performed
|
||
|
as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintReal(value: real; color: Color);
|
||
|
{ displays a real value at the current text position. clipping is performed
|
||
|
as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintRealFmt(value: real; decimals: integer; color: Color);
|
||
|
{ displays a real value using a specific number of decimal points at the
|
||
|
current text position. clipping is performed as necessary. }
|
||
|
begin
|
||
|
Str(value:0:decimals, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintSingle(value: single; color: Color);
|
||
|
{ displays a single-precision value at the current text position. clipping
|
||
|
is performed as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintSingleFmt(value: single; decimals: integer; color: Color);
|
||
|
{ displays a single-precision value using a specific number of decimal points
|
||
|
at the current text position. clipping is performed as necessary. }
|
||
|
begin
|
||
|
Str(value:0:decimals, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintDouble(value: double; color: Color);
|
||
|
{ displays a double-precision value at the current text position. clipping
|
||
|
is performed as necessary. }
|
||
|
begin
|
||
|
Str(value, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintDoubleFmt(value: double; decimals: integer; color: Color);
|
||
|
{ displays a double-precision value using a specific number of decimal points
|
||
|
at the current text position. clipping is performed as necessary. }
|
||
|
begin
|
||
|
Str(value:0:decimals, _valueString);
|
||
|
DrawString(_currentTextX, _currentTextY, color, _valueString);
|
||
|
inc(_currentTextX, length(_valueString) * CHAR_WIDTH);
|
||
|
end;
|
||
|
|
||
|
procedure PrintString(const text: string; color: Color);
|
||
|
{ displays a string at the current text position. handles multi-line strings
|
||
|
automatically. clipping is performed as necessary. }
|
||
|
var
|
||
|
ch : char;
|
||
|
i, width, height, len : integer;
|
||
|
spaceWidth : integer;
|
||
|
clippedX, clippedY : integer;
|
||
|
clippedWidth, clippedHeight : integer;
|
||
|
begin
|
||
|
width := CHAR_WIDTH;
|
||
|
len := length(text);
|
||
|
|
||
|
if _boundFont <> nil then begin
|
||
|
height := _boundFont^.height;
|
||
|
spaceWidth := _boundFont^.widths[Ord(' ')];
|
||
|
end else begin
|
||
|
height := CHAR_HEIGHT;
|
||
|
spaceWidth := CHAR_WIDTH;
|
||
|
end;
|
||
|
|
||
|
for i := 1 to len do begin
|
||
|
ch := text[i];
|
||
|
case ch of
|
||
|
{ move to next line }
|
||
|
#10: begin
|
||
|
_currentTextX := _currentTextStartX;
|
||
|
inc(_currentTextY, height);
|
||
|
end;
|
||
|
#13: begin;
|
||
|
end;
|
||
|
{ skip a character ahead, no need to "draw" a space char (empty) }
|
||
|
' ': inc(_currentTextX, spaceWidth);
|
||
|
else begin
|
||
|
if _boundFont <> nil then
|
||
|
width := _boundFont^.widths[Ord(ch)];
|
||
|
|
||
|
clippedX := _currentTextX;
|
||
|
clippedY := _currentTextY;
|
||
|
clippedWidth := width;
|
||
|
clippedHeight := height;
|
||
|
if ClipToClipRegion(clippedX, clippedY, clippedWidth, clippedHeight) then
|
||
|
DrawChar(_currentTextX, _currentTextY, clippedX, clippedY, clippedWidth, clippedHeight, color, ch);
|
||
|
|
||
|
inc(_currentTextX, width);
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure AllocBitmap(width, height: word; var bmp: PBitmap);
|
||
|
{ allocates the memory necessary for a bitmap of the specified size }
|
||
|
begin
|
||
|
GetMem(bmp, SizeOf(Bitmap) + (width * height) - 1);
|
||
|
|
||
|
bmp^.Width := width;
|
||
|
bmp^.Height := height;
|
||
|
end;
|
||
|
|
||
|
procedure FreeBitmap(bmp: PBitmap);
|
||
|
{ frees previously allocated memory for the given bitmap. the bitmap should
|
||
|
not be used again after this unless re-allocated via AllocBitmap }
|
||
|
begin
|
||
|
if bmp = nil then exit;
|
||
|
FreeMem(bmp, SizeOf(Bitmap) + (bmp^.width * bmp^.height) - 1);
|
||
|
end;
|
||
|
|
||
|
procedure GrabBitmap(x, y: integer; width, height: word; bmp: PBitmap);
|
||
|
{ copies a region of pixels from the current layer onto the specified bitmap.
|
||
|
the bitmap must have already been allocated, and be large enough to hold
|
||
|
pixel data of the provided dimensions. }
|
||
|
var
|
||
|
line : word;
|
||
|
src, dest : ^byte;
|
||
|
begin
|
||
|
src := GetBoundLayerPointerAt(x, y);
|
||
|
dest := @bmp^.Pixels;
|
||
|
|
||
|
{ TODO: re-write this in inline-assembly }
|
||
|
for line := 0 to (height - 1) do begin
|
||
|
MemCopy(dest, src, width);
|
||
|
inc(src, SCREEN_WIDTH);
|
||
|
inc(dest, width);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
|
||
|
|
||
|
{ "Low-Level" Solid Blit routines. These are only really intended to be
|
||
|
called by the main "BlitXXX" functions, but possibly could be used directly
|
||
|
by applications when the utmost speed is required ...
|
||
|
These functions DO NOT perform any argument validation or clipping, etc! }
|
||
|
|
||
|
procedure LLBlit4(width, height, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination. assumes the source
|
||
|
pixels have an exact dword-sized width. }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
db $66; xor cx, cx { xor ecx, ecx }
|
||
|
mov ax, width
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
shr ax, 2 { ax = number of dwords to draw per line }
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax
|
||
|
db $f3,$66,$a5 { rep movsd - draw dwords }
|
||
|
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure LLBlit4r(width, height, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination. assumes the source
|
||
|
pixels have a width composed of some dword-sized portion PLUS a remainder
|
||
|
of 1-3 pixels at the end. }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
db $66; xor cx, cx { xor ecx, ecx }
|
||
|
mov ax, width
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
mov bx, ax
|
||
|
shr ax, 2 { ax = number of dwords to draw per line }
|
||
|
and bx, 3 { bx = remaining number of pixels }
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax
|
||
|
db $f3,$66,$a5 { rep movsd - draw dwords }
|
||
|
mov cx, bx
|
||
|
rep movsb { draw remainder pixels }
|
||
|
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure LLBlitRemainder(width, height, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination. will just use a simple
|
||
|
rep movsb for each line. intended only for blits whose width is <= 3. }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov ax, width
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax
|
||
|
rep movsb { draw pixels }
|
||
|
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
{ End of Low-Level Solid Blit functions ^^^ }
|
||
|
|
||
|
|
||
|
|
||
|
procedure BlitSubset(srcX, srcY, srcWidth, srcHeight, destX, destY: integer;
|
||
|
const bmp: PBitmap);
|
||
|
{ draws the specified subset/region of the bitmap onto the current layer. the
|
||
|
bitmap is clipped to the current clipping region boundaries. }
|
||
|
begin
|
||
|
if not ClipBlitToClipRegion(srcX, srcY, srcWidth, srcHeight, destX, destY) then
|
||
|
exit;
|
||
|
|
||
|
BlitSubsetf(srcX, srcY, srcWidth, srcHeight, destX, destY, bmp);
|
||
|
end;
|
||
|
|
||
|
procedure BlitSubsetf(srcX, srcY, srcWidth, srcHeight, destX, destY: integer;
|
||
|
const bmp: PBitmap);
|
||
|
{ draws the specified subset/region of the bitmap onto the current layer. no
|
||
|
clipping is performed. }
|
||
|
var
|
||
|
srcSeg, srcOffs, destOffs : word;
|
||
|
srcYinc, destYinc : integer;
|
||
|
width4, widthRemainder : integer;
|
||
|
begin
|
||
|
srcSeg := Seg(bmp^.Pixels);
|
||
|
srcOffs := GetBitmapOffsetAt(srcX, srcY, bmp);
|
||
|
destOffs := GetBoundLayerOffsetAt(destX, destY);
|
||
|
|
||
|
srcYinc := bmp^.Width - srcWidth;
|
||
|
destYinc := SCREEN_WIDTH - srcWidth;
|
||
|
|
||
|
width4 := srcWidth div 4;
|
||
|
widthRemainder := srcWidth and 3;
|
||
|
|
||
|
if ((width4 > 0) and (widthRemainder = 0)) then begin
|
||
|
LLBlit4(srcWidth, srcHeight, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end else if ((width4 > 0) and (widthRemainder > 0)) then begin
|
||
|
LLBlit4r(srcWidth, srcHeight, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end else begin
|
||
|
LLBlitRemainder(srcWidth, srcHeight, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end
|
||
|
end;
|
||
|
|
||
|
procedure Blit(x, y: integer; const bmp: PBitmap);
|
||
|
{ draws the given bitmap onto the current layer. the bitmap is clipped to
|
||
|
the current clipping region boundaries. }
|
||
|
var
|
||
|
srcX, srcY : integer;
|
||
|
srcWidth, srcHeight : integer;
|
||
|
begin
|
||
|
srcX := 0;
|
||
|
srcY := 0;
|
||
|
srcWidth := bmp^.Width;
|
||
|
srcHeight := bmp^.Height;
|
||
|
|
||
|
if not ClipBlitToClipRegion(srcX, srcY, srcWidth, srcHeight, x, y) then
|
||
|
exit;
|
||
|
|
||
|
BlitSubsetf(srcX, srcY, srcWidth, srcHeight, x, y, bmp);
|
||
|
end;
|
||
|
|
||
|
procedure Blitf(x, y: integer; const bmp: PBitmap);
|
||
|
{ draws the given bitmap onto the current layer. no clipping is performed. }
|
||
|
begin
|
||
|
BlitSubsetf(0, 0, bmp^.Width, bmp^.Height, x, y, bmp);
|
||
|
end;
|
||
|
|
||
|
|
||
|
|
||
|
{ "Low-Level" Transparent Blit routines. These are only really intended to be
|
||
|
called by the main "BlitXXX" functions, but possibly could be used directly
|
||
|
by applications when the utmost speed is required ...
|
||
|
These functions DO NOT perform any argument validation or clipping, etc! }
|
||
|
|
||
|
procedure LLBlitSprite8(width8, height, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination, treating colour 0 as the
|
||
|
transparent pixel which will be skipped over. assumes the source pixels
|
||
|
have an exact double-dword-sized width (that is, an exact multiple of 8). }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov ax, width8
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax { cx = number of 8-pixel runs left to draw }
|
||
|
|
||
|
@draw_px_0:
|
||
|
mov bx, ds:[si+0] { load two pixels }
|
||
|
test bl, bl
|
||
|
jz @draw_px_1 { if it is color 0, skip it }
|
||
|
mov es:[di+0], bl { otherwise, draw it }
|
||
|
@draw_px_1:
|
||
|
test bh, bh { bh has the second loaded pixel }
|
||
|
jz @draw_px_2
|
||
|
mov es:[di+1], bh
|
||
|
@draw_px_2:
|
||
|
mov bx, ds:[si+2]
|
||
|
test bl, bl
|
||
|
jz @draw_px_3
|
||
|
mov es:[di+2], bl
|
||
|
@draw_px_3:
|
||
|
test bh, bh
|
||
|
jz @draw_px_4
|
||
|
mov es:[di+3], bh
|
||
|
@draw_px_4:
|
||
|
mov bx, ds:[si+4]
|
||
|
test bl, bl
|
||
|
jz @draw_px_5
|
||
|
mov es:[di+4], bl
|
||
|
@draw_px_5:
|
||
|
test bh, bh
|
||
|
jz @draw_px_6
|
||
|
mov es:[di+5], bh
|
||
|
@draw_px_6:
|
||
|
mov bx, ds:[si+6]
|
||
|
test bl, bl
|
||
|
jz @draw_px_7
|
||
|
mov es:[di+6], bl
|
||
|
@draw_px_7:
|
||
|
test bh, bh
|
||
|
jz @end_run
|
||
|
mov es:[di+7], bh
|
||
|
|
||
|
@end_run:
|
||
|
add si, 8 { mov src and dest up 8 pixels }
|
||
|
add di, 8
|
||
|
dec cx { decrease 8-pixel run loop counter }
|
||
|
jnz @draw_px_0 { if there are still more runs, draw them }
|
||
|
|
||
|
@end_line:
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure LLBlitSprite4(width4, height, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination, treating colour 0 as the
|
||
|
transparent pixel which will be skipped over. assumes the source pixels
|
||
|
have an exact dword-sized width (that is, an exact multiple of 4). }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov ax, width4
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax { cx = number of 4-pixel runs left to draw }
|
||
|
|
||
|
@draw_px_0:
|
||
|
mov bx, ds:[si+0] { load two pixels }
|
||
|
test bl, bl
|
||
|
jz @draw_px_1 { if it is color 0, skip it }
|
||
|
mov es:[di+0], bl { otherwise, draw it }
|
||
|
@draw_px_1:
|
||
|
test bh, bh { bh has the second loaded pixel }
|
||
|
jz @draw_px_2
|
||
|
mov es:[di+1], bh
|
||
|
@draw_px_2:
|
||
|
mov bx, ds:[si+2]
|
||
|
test bl, bl
|
||
|
jz @draw_px_3
|
||
|
mov es:[di+2], bl
|
||
|
@draw_px_3:
|
||
|
test bh, bh
|
||
|
jz @end_run
|
||
|
mov es:[di+3], bh
|
||
|
|
||
|
@end_run:
|
||
|
add si, 4 { mov src and dest up 4 pixels }
|
||
|
add di, 4
|
||
|
dec cx { decrease 4-pixel run loop counter }
|
||
|
jnz @draw_px_0 { if there are still more runs, draw them }
|
||
|
|
||
|
@end_line:
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure LLBlitSprite8r(width8, height, widthRemainder, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination, treating colour 0 as the
|
||
|
transparent pixel which will be skipped over. assumes the source pixels
|
||
|
have a width that is composed of a double-dword-sized section (that is,
|
||
|
some segment that is a multiple of 8) plus some remainder bit that is
|
||
|
<= 7 pixels long. }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov ax, width8
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax { cx = number of 8-pixel runs left to draw }
|
||
|
|
||
|
@draw_px_0:
|
||
|
mov bx, ds:[si+0] { load two pixels }
|
||
|
test bl, bl
|
||
|
jz @draw_px_1 { if it is color 0, skip it }
|
||
|
mov es:[di+0], bl { otherwise, draw it }
|
||
|
@draw_px_1:
|
||
|
test bh, bh { bh has the second loaded pixel }
|
||
|
jz @draw_px_2
|
||
|
mov es:[di+1], bh
|
||
|
@draw_px_2:
|
||
|
mov bx, ds:[si+2]
|
||
|
test bl, bl
|
||
|
jz @draw_px_3
|
||
|
mov es:[di+2], bl
|
||
|
@draw_px_3:
|
||
|
test bh, bh
|
||
|
jz @draw_px_4
|
||
|
mov es:[di+3], bh
|
||
|
@draw_px_4:
|
||
|
mov bx, ds:[si+4]
|
||
|
test bl, bl
|
||
|
jz @draw_px_5
|
||
|
mov es:[di+4], bl
|
||
|
@draw_px_5:
|
||
|
test bh, bh
|
||
|
jz @draw_px_6
|
||
|
mov es:[di+5], bh
|
||
|
@draw_px_6:
|
||
|
mov bx, ds:[si+6]
|
||
|
test bl, bl
|
||
|
jz @draw_px_7
|
||
|
mov es:[di+6], bl
|
||
|
@draw_px_7:
|
||
|
test bh, bh
|
||
|
jz @end_run
|
||
|
mov es:[di+7], bh
|
||
|
|
||
|
@end_run:
|
||
|
add si, 8 { mov src and dest up 8 pixels }
|
||
|
add di, 8
|
||
|
dec cx { decrease 8-pixel run loop counter }
|
||
|
jnz @draw_px_0 { if there are still more runs, draw them }
|
||
|
|
||
|
@start_remainder_run: { now draw remaining pixels ( <= 7 pixels ) }
|
||
|
mov cx, widthRemainder
|
||
|
|
||
|
@draw_pixel:
|
||
|
mov bl, ds:[si] { load pixel }
|
||
|
inc si
|
||
|
test bl, bl { if zero, skip to next pixel }
|
||
|
jz @end_pixel
|
||
|
mov es:[di], bl { else, draw the pixel }
|
||
|
@end_pixel:
|
||
|
inc di
|
||
|
dec cx
|
||
|
jnz @draw_pixel
|
||
|
|
||
|
@end_line:
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure LLBlitSprite4r(width4, height, widthRemainder, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination, treating colour 0 as the
|
||
|
transparent pixel which will be skipped over. assumes the source pixels
|
||
|
have a width that is composed of a dword-sized section (that is, some
|
||
|
segment that is a multiple of 4) plus some remainder bit that is
|
||
|
<= 3 pixels long. }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov ax, width4
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax { cx = number of 4-pixel runs left to draw }
|
||
|
|
||
|
@draw_px_0:
|
||
|
mov bx, ds:[si+0] { load two pixels }
|
||
|
test bl, bl
|
||
|
jz @draw_px_1 { if it is color 0, skip it }
|
||
|
mov es:[di+0], bl { otherwise, draw it }
|
||
|
@draw_px_1:
|
||
|
test bh, bh { bh has the second loaded pixel }
|
||
|
jz @draw_px_2
|
||
|
mov es:[di+1], bh
|
||
|
@draw_px_2:
|
||
|
mov bx, ds:[si+2]
|
||
|
test bl, bl
|
||
|
jz @draw_px_3
|
||
|
mov es:[di+2], bl
|
||
|
@draw_px_3:
|
||
|
test bh, bh
|
||
|
jz @end_run
|
||
|
mov es:[di+3], bh
|
||
|
|
||
|
@end_run:
|
||
|
add si, 4 { mov src and dest up 4 pixels }
|
||
|
add di, 4
|
||
|
dec cx { decrease 4-pixel run loop counter }
|
||
|
jnz @draw_px_0 { if there are still more runs, draw them }
|
||
|
|
||
|
@start_remainder_run: { now draw remaining pixels ( <= 3 pixels ) }
|
||
|
mov cx, widthRemainder
|
||
|
|
||
|
@draw_pixel:
|
||
|
mov bl, ds:[si] { load pixel }
|
||
|
inc si
|
||
|
test bl, bl { if zero, skip to next pixel }
|
||
|
jz @end_pixel
|
||
|
mov es:[di], bl { else, draw the pixel }
|
||
|
@end_pixel:
|
||
|
inc di
|
||
|
dec cx
|
||
|
jnz @draw_pixel
|
||
|
|
||
|
@end_line:
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
procedure LLBlitSpriteRemainder(width, height, destYinc, srcYinc: integer;
|
||
|
destOffs, srcSeg, srcOffs: word);
|
||
|
{ blit a block of memory to the given destination, treating colour 0 as the
|
||
|
transparent pixel which will be skipped over. this is the slowest of the
|
||
|
transparent blit routines, that technically can draw at any width, but
|
||
|
is really intended for widths <= 7 only. }
|
||
|
assembler;
|
||
|
asm
|
||
|
push ds
|
||
|
|
||
|
mov dx, height { dx = line loop counter }
|
||
|
test dx, dx { stop now if there's zero lines to draw }
|
||
|
jz @done
|
||
|
|
||
|
mov ax, width
|
||
|
mov di, destOffs
|
||
|
mov si, srcOffs
|
||
|
mov es, _boundLayerSeg
|
||
|
mov ds, srcSeg
|
||
|
|
||
|
@draw_line:
|
||
|
mov cx, ax { cx = number of pixels to draw }
|
||
|
|
||
|
@draw_pixel:
|
||
|
mov bl, ds:[si] { load pixel }
|
||
|
inc si
|
||
|
test bl, bl { if zero, skip to next pixel }
|
||
|
jz @end_pixel
|
||
|
mov es:[di], bl { else, draw the pixel }
|
||
|
@end_pixel:
|
||
|
inc di
|
||
|
dec cx
|
||
|
jnz @draw_pixel { loop while there's still pixels left }
|
||
|
|
||
|
@end_line:
|
||
|
add si, srcYinc { move to next line }
|
||
|
add di, destYinc
|
||
|
dec dx { decrease line loop counter }
|
||
|
jnz @draw_line
|
||
|
|
||
|
@done:
|
||
|
pop ds
|
||
|
end;
|
||
|
|
||
|
{ End of Low-Level Transparent Blit functions ^^^ }
|
||
|
|
||
|
|
||
|
|
||
|
procedure BlitSpriteSubset(srcX, srcY, srcWidth, srcHeight, destX, destY: integer;
|
||
|
const bmp: PBitmap);
|
||
|
{ draws the specified subset/region of the given bitmap onto the current
|
||
|
layer, ignoring color 0 (treated as transparent). the bitmap is clipped to
|
||
|
the current clipping region boundaries. }
|
||
|
begin
|
||
|
if not ClipBlitToClipRegion(srcX, srcY, srcWidth, srcHeight, destX, destY) then
|
||
|
exit;
|
||
|
|
||
|
BlitSpriteSubsetf(srcX, srcY, srcWidth, srcHeight, destX, destY, bmp);
|
||
|
end;
|
||
|
|
||
|
procedure BlitSpriteSubsetf(srcX, srcY, srcWidth, srcHeight, destX, destY: integer;
|
||
|
const bmp: PBitmap);
|
||
|
{ draws the specified subset/region of the given bitmap onto the current
|
||
|
layer, ignoring color 0 (treated as transparent). no clipping is
|
||
|
performed. }
|
||
|
var
|
||
|
srcSeg, srcOffs, destOffs : word;
|
||
|
srcYinc, destYinc : integer;
|
||
|
width4, widthRemainder : integer;
|
||
|
begin
|
||
|
srcSeg := Seg(bmp^.Pixels);
|
||
|
srcOffs := GetBitmapOffsetAt(srcX, srcY, bmp);
|
||
|
destOffs := GetBoundLayerOffsetAt(destX, destY);
|
||
|
|
||
|
srcYinc := bmp^.Width - srcWidth;
|
||
|
destYinc := SCREEN_WIDTH - srcWidth;
|
||
|
|
||
|
width4 := srcWidth div 4;
|
||
|
widthRemainder := srcWidth and 3;
|
||
|
|
||
|
if (width4 > 0) and (widthRemainder = 0) then begin
|
||
|
if ((width4 and 1) = 0) then begin
|
||
|
{ width is actually an even multiple of 8! }
|
||
|
LLBlitSprite8(width4 div 2, srcHeight, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end else begin
|
||
|
{ width is a multiple of 4 (no remainder) }
|
||
|
LLBlitSprite4(width4, srcHeight, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end;
|
||
|
|
||
|
end else if (width4 > 0) and (widthRemainder > 0) then begin
|
||
|
if ((width4 and 1) = 0) then begin
|
||
|
{ width is mostly made up of an even multiple of 8. but there is also
|
||
|
a small remainder }
|
||
|
LLBlitSprite8r(width4 div 2, srcHeight, widthRemainder, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end else begin
|
||
|
{ width is >= 4 with a remainder }
|
||
|
LLBlitSprite4r(width4, srcHeight, widthRemainder, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end;
|
||
|
|
||
|
end else begin
|
||
|
{ width is <= 3 }
|
||
|
LLBlitSpriteRemainder(widthRemainder, srcHeight, destYinc, srcYinc, destOffs, srcSeg, srcOffs);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure BlitSprite(x, y: integer; const bmp: PBitmap);
|
||
|
{ draws the given bitmap onto the current layer, ignoring color 0 (treated
|
||
|
as transparent). the bitmap is clipped to the current clipping region
|
||
|
boundaries. }
|
||
|
var
|
||
|
srcX, srcY : integer;
|
||
|
srcWidth, srcHeight : integer;
|
||
|
begin
|
||
|
srcX := 0;
|
||
|
srcY := 0;
|
||
|
srcWidth := bmp^.Width;
|
||
|
srcHeight := bmp^.Height;
|
||
|
|
||
|
if not ClipBlitToClipRegion(srcX, srcY, srcWidth, srcHeight, x, y) then
|
||
|
exit;
|
||
|
|
||
|
BlitSpriteSubsetf(srcX, srcY, srcWidth, srcHeight, x, y, bmp);
|
||
|
end;
|
||
|
|
||
|
procedure BlitSpritef(x, y: integer; const bmp: PBitmap);
|
||
|
{ draws the given bitmap onto the current layer, ignoring color 0 (treated
|
||
|
as transparent). no clipping is performed. }
|
||
|
begin
|
||
|
BlitSpriteSubsetf(0, 0, bmp^.Width, bmp^.Height, x, y, bmp);
|
||
|
end;
|
||
|
|
||
|
procedure SetColor(color, r, g, b: byte);
|
||
|
{ changes the VGA color palette for the color specified }
|
||
|
begin
|
||
|
port[$3c6] := $ff;
|
||
|
port[$3c8] := color;
|
||
|
port[$3c9] := r;
|
||
|
port[$3c9] := g;
|
||
|
port[$3c9] := b;
|
||
|
end;
|
||
|
|
||
|
procedure GetColor(color: byte; var r, g, b: byte);
|
||
|
{ reads the current VGA color palette RGB values for the color specified }
|
||
|
begin
|
||
|
port[$3c6] := $ff;
|
||
|
port[$3c7] := color;
|
||
|
r := port[$3c9];
|
||
|
g := port[$3c9];
|
||
|
b := port[$3c9];
|
||
|
end;
|
||
|
|
||
|
procedure SetPalette(const pal: PPalette);
|
||
|
{ sets the current VGA color palette to the given one }
|
||
|
var
|
||
|
i : integer;
|
||
|
begin
|
||
|
for i := 0 to 255 do begin
|
||
|
SetColor(i, pal^[i, 0], pal^[i, 1], pal^[i, 2]);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure GetPalette(pal: PPalette);
|
||
|
{ reads the current VGA color palette, storing all RGB values in the
|
||
|
provided Palette structure }
|
||
|
var
|
||
|
i : integer;
|
||
|
begin
|
||
|
for i := 0 to 255 do begin
|
||
|
GetColor(i, pal^[i, 0], pal^[i, 1], pal^[i, 2]);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
function FadeColor(color, r, g, b: byte; step: integer) : boolean;
|
||
|
{ fades (in/out) the specified color towards the given RGB values by 'step'
|
||
|
amount. returns true if the color's RGB values have reached the target
|
||
|
RGB values, false otherwise. }
|
||
|
var
|
||
|
red, green, blue : byte;
|
||
|
diffR, diffG, diffB : byte;
|
||
|
modified : boolean;
|
||
|
begin
|
||
|
modified := false;
|
||
|
|
||
|
GetColor(color, red, green, blue);
|
||
|
|
||
|
if red <> r then begin
|
||
|
modified := true;
|
||
|
diffR := Abs(red - r);
|
||
|
if red > r then
|
||
|
dec(red, Min(step, diffR))
|
||
|
else
|
||
|
inc(red, Min(step, diffR));
|
||
|
end;
|
||
|
|
||
|
if green <> g then begin
|
||
|
modified := true;
|
||
|
diffG := Abs(green - g);
|
||
|
if green > g then
|
||
|
dec(green, Min(step, diffG))
|
||
|
else
|
||
|
inc(green, Min(step, diffG));
|
||
|
end;
|
||
|
|
||
|
if blue <> b then begin
|
||
|
modified := true;
|
||
|
diffB := Abs(blue - b);
|
||
|
if blue > b then
|
||
|
dec(blue, Min(step, diffB))
|
||
|
else
|
||
|
inc(blue, Min(step, diffB));
|
||
|
end;
|
||
|
|
||
|
if modified then
|
||
|
SetColor(color, red, green, blue);
|
||
|
|
||
|
FadeColor := ((red = r) and (green = g) and (blue = b));
|
||
|
end;
|
||
|
|
||
|
procedure FadeRangeToColor(colorStart, colorEnd, r, g, b: byte;
|
||
|
step: integer);
|
||
|
{ fades a range of colors in the current VGA color palette towards the
|
||
|
given RGB values. blocks, looping until the fade is complete. }
|
||
|
var
|
||
|
i : integer;
|
||
|
done : boolean;
|
||
|
begin
|
||
|
done := false;
|
||
|
step := Abs(step);
|
||
|
|
||
|
while not done do begin
|
||
|
done := true;
|
||
|
WaitForVsync;
|
||
|
for i := colorStart to colorEnd do begin
|
||
|
if not FadeColor(i, r, g, b, step) then
|
||
|
done := false;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure FadeRangeToPalette(colorStart, colorEnd: byte;
|
||
|
const pal: PPalette;
|
||
|
step: integer);
|
||
|
{ fades a range of colors in the current VGA color palette towards the
|
||
|
given color palette. blocks, looping until the fade is complete. }
|
||
|
var
|
||
|
i : integer;
|
||
|
done : boolean;
|
||
|
begin
|
||
|
done := false;
|
||
|
step := Abs(step);
|
||
|
|
||
|
while not done do begin
|
||
|
done := true;
|
||
|
WaitForVsync;
|
||
|
for i := colorStart to colorEnd do begin
|
||
|
if not FadeColor(i, pal^[i, 0], pal^[i, 1], pal^[i, 2], step) then
|
||
|
done := false;
|
||
|
end;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
function LoadFont(const filename: string; font: PFont) : FontResult;
|
||
|
{ loads a font from the given file, storing it in the Font instance
|
||
|
provided. returns FontOk on success. }
|
||
|
var
|
||
|
f : file;
|
||
|
x : integer;
|
||
|
label ioError;
|
||
|
begin
|
||
|
if font = nil then begin
|
||
|
LoadFont := FontBadArgs;
|
||
|
exit;
|
||
|
end;
|
||
|
|
||
|
Assign(f, filename);
|
||
|
|
||
|
Reset(f, 1);
|
||
|
if IOResult <> 0 then begin
|
||
|
Close(f);
|
||
|
x := IOResult; { clear i/o error flag }
|
||
|
LoadFont := FontNotFound;
|
||
|
exit;
|
||
|
end;
|
||
|
|
||
|
{ this font format is the same as the one DirectQB uses. it is a simple
|
||
|
format that i think works pretty well, so why not re-use it here ... }
|
||
|
|
||
|
with font^ do begin
|
||
|
{ standard BIOS-like format list of full ASCII 0-255 characters.
|
||
|
8-bytes per character }
|
||
|
BlockRead(f, chars, SizeOf(BiosFontTable));
|
||
|
if IOResult <> 0 then goto ioError;
|
||
|
|
||
|
{ 255 bytes, each byte representing the width of each ASCII character }
|
||
|
BlockRead(f, widths, SizeOf(BiosFontCharWidths));
|
||
|
if IOResult <> 0 then goto ioError;
|
||
|
|
||
|
{ single byte, the global character height. that is, this is the max
|
||
|
height of any of the 256 ASCII characters in this font. will be <= 8 }
|
||
|
BlockRead(f, height, 1);
|
||
|
if IOResult <> 0 then goto ioError;
|
||
|
end;
|
||
|
|
||
|
Close(f);
|
||
|
x := IOResult; { clear i/o error flag (just in case) }
|
||
|
LoadFont := FontOk;
|
||
|
exit;
|
||
|
|
||
|
ioError:
|
||
|
Close(f);
|
||
|
x := IOResult; { clear i/o error flag }
|
||
|
LoadFont := FontIOError;
|
||
|
end;
|
||
|
|
||
|
function LoadPalette(const filename: string; pal: PPalette) : FontResult;
|
||
|
begin
|
||
|
end;
|
||
|
|
||
|
begin
|
||
|
if Test8086 < 2 then begin
|
||
|
writeln('The GDGFX unit requires a 386 cpu or higher!');
|
||
|
halt;
|
||
|
end;
|
||
|
end.
|