fruit-popper/GDLIB/GDGFX.PAS

2141 lines
61 KiB
Plaintext
Raw Normal View History

{ 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.