``````{\$A+,B-,D+,E+,F-,G+,I+,L+,N-,O-,P-,Q-,R-,S+,T-,V-,X+,Y+}
{\$M 16384,0,32786}
Program BitMap;       { rotates/pans/scales a 256x256 bitmap }
USES CRT;                 { by Paul H. Kahler   Jan 1994 }

Var   SinTable,CosTable: Array[0..255] of integer;
Sin2Table,Cos2Table: Array[0..255] of integer;
Map:word; {used as a pointer to the bitmap}

Procedure MakeTables;                   {Creates sin/cos tables}
Var direction:integer;
angle:real;
begin
For Direction:=0 to 255 do begin   {use 256 degrees in circle}
angle:=Direction;
angle:=angle*3.14159265/128;
SinTable[Direction]:=round(Sin(angle)*256);
CosTable[Direction]:=round(Cos(angle)*256);
Sin2Table[Direction]:=round(Sin(angle+3.14159265/2)*256*1.2);
Cos2Table[Direction]:=round(Cos(angle+3.14159265/2)*256*1.2);
end;                 { the 1.2 accounts for pixel aspect ratio }
end;

Procedure DrawScreen(x,y,scale:word; rot:byte);
var Temp:Longint;            {used for intermediate large values}
ddx,ddy,d2x,d2y:integer;
i,j:word;
label hloop,vloop,nodraw;

begin
{ the following 8 lines of code calculate a 'right' and 'down' vector used
for scanning the source bitmap. I use quotes because these directions
depend on the rotation. For example, with a rotation, 'right' could mean
up and to the left while 'down' means up and to the right. Since the
destination image (screen) is scanned left-right/top-bottom, the bitmap
needs to be scanned in arbitrary directions to get a rotation. }

Temp:=(CosTable[rot]);Temp:=(Temp*Scale) div 256;
ddx:=Temp;
Temp:=(SinTable[rot]);Temp:=(Temp*Scale) div 256;
ddy:=Temp;

{ Different tables are used for the 'down' vector to account for the non-
square pixels in mode 13h (320x200). The 90 degree difference is built
into the tables. If you don't like that, then use (rot+64)and255 here
and take the pi/2 out of CreateTables. To each his own I guess. }

Temp:=(Cos2Table[rot]);Temp:=(Temp*SCALE) div 256;
d2x:=Temp;
Temp:=(Sin2Table[rot]);Temp:=(Temp*SCALE) div 256;
d2y:=Temp;

{ Since we want to rotate around the CENTER of the screen and not the upper
left corner, we need to move 160 pixels 'left' and 100 'up' in the bitmap.}

i:=x-ddx*160-d2x*100; j:=y-ddy*160-d2y*100;

{ The following chunk of assembly does the good stuff. It redraws the entire
screen by scanning left-right/top-bottom on screen while also scanning the
bitmap in the arbitrary directions determined above. }

ASM
push ds
mov  ax,[Map]      {get segment of bitmap}
mov  ds,ax
mov  ax,\$a000      {set es: to video memory}
mov  es,ax
mov  ax,0          {set ds: to upper left corner of}
mov  di,ax         {the video memory}
mov  ax,[ddx]      {this is just to speed things up later}
mov  cx,200        {Number of rows on Screen}
vloop:
push cx
mov  ax,[i]        {start scanning the source bitmap}
mov  dx,[j]        {at i,j which were calculated above.}
mov  cx,320        {Number of coulumns on screen}
hloop:
add  dx,[ddy]     {bitmap coordinates.  8.8 fixed point}
mov  bl,ah        {  bx = 256*int(y)+int(x)  }
mov  bh,dh
mov  bl,[ds:bx]   { load a pixel from source }
mov  [es:di],bl   { copy it to destination }
inc  di           { advance to next destination pixel }

{*** by repeating the above 7 instructions 5 times, and reducing
the loop count to 64, I have hit 37fps on a 486-33 with a
fast video card. ***}

loop hloop         {End of horizontal loop}

mov  ax,d2x        { get the 'down' vector }
mov  dx,d2y

{ add  si,2 }    {** uncomment this instr. for extra fun **}

add  i,ax          { i,j is the starting coords for a line }
add  j,dx          { so this moves down one line }
pop  cx            { get the row count back and loop }
loop vloop         { End of verticle loop }
pop  ds            { Restore the ds }
end;
end;

Procedure GraphMode;      {start 320x200x256 mode}
begin
Asm
Mov     AH,00
Mov     AL,13h
Int     10h
end;
end;

Procedure AllocateMem;  {returns a segment pointer for a 64K bitmap}
label noerror;
begin
asm
mov   ah,\$48
mov   bx,\$1000     { request 64K }
int   \$21
jnc   noerror
mov   ax,0000
noerror: mov   Map,ax       { The segment pointer goes in Map }
end;
If Map=0 then begin
Writeln('Could not allocate enough memory');
Writeln('Program ending...');
Halt;end;
end;

Procedure GiveBackMem; {returns the memory used for the map to the system}
begin
asm
mov  ah,\$49
mov  dx,Map
mov  es,dx
int  \$21
end;
end;

Procedure DrawImage;  {draws a test image which shows some limitations.}

{ If anyone stuffs in code to load a picture in a standard format
(ie .gif .bmp etc..) I'd like if you send me a copy. Preferably
something simple. This will have to do for now. }

Var x,y:integer;
Begin
for x:=-32768 to 32767 do mem[Map:x]:=0;
for y:=0 to 15 do          {this just frames the area}
for x:=y to 255 do begin
mem[Map:Y*256+x]:=1;
mem[Map:X*256+y]:=2;
end;
for y:=16 to 47 do         { this part show Aliasing effects }
for x:=16 to 255 do mem[Map:Y*256+x]:=2+(x and 1)+(y and 1);

for y:= -50 to 50 do       { this draw the circles }
for x:= round(-sqrt(2500 - y*y)) to round(sqrt(2500 - y*y)) do
mem[Map:(y+100)*256+x+100]:=5+(X*X+Y*Y) div 100;

for x:=0 to 100 do         { These lines also show sampling effects }
for y:=0 to 8 do
mem[Map:(Y*2560)+x+41100]:=5;
end;

Var    rot,dr:word;
x,y,dist,dd:word;

Begin
AllocateMem;
DrawImage;
MakeTables;
GraphMode;
x:=32768; y:=0;         {this corresponds to (128,0) in fixed point}
rot:=0; dr:=1;          {rotation angle and it's delta}
dist:=1200; dd:=65534;  {distance to bitmap (sort of) and its delta}
repeat
DrawScreen(x,y,dist,lo(rot));
rot:=rot+dr;
y:=y+128;      {slow panning. 1/2 pixel per frame}
dist:=dist+dd;
if (dist=2000) or (dist=2) then dd:=-dd;
if random(150)=1 then dr:=random(7)-3;
until keypressed;
GiveBackMem;
ASM {back to 80x25}
MOV AX,3
INT 10h
END;
end.
``````