[Back to GRAPHICS SWAG index]  [Back to Main SWAG index]  [Original]

{
PART 2 OF NEWGRAPH.PAS

You need NEWGRPH1.PAS first. Insert this into the bottom of the
NEWGRPH1.PAS file, compile and use this TPU till yer blue in the
face!!!



****************
Palette routines
****************

}




{
Get the red, green and blue components of a colour.

Expects :   ColourNumber is the number of the colour of which you
            want to read the Palette values (0-255).
            RedValue, GreenValue, BlueValue need not be initialised.

Returns :   The Red, Green, Blue Values of the colour specified
            by ColourNumber.

}

Procedure GetPalette(ColourNumber : Byte;
          VAR RedValue, GreenValue, BlueValue : Byte); Assembler;
Asm
   MOV DX,$3C7          { $3C7 is colour ** READ ** select port. }
   MOV AL,ColourNumber   { Select colour to read }
   OUT DX,AL
   ADD DL,2             { DX now = $3C9, which must be read 3 times
                          in order to obtain the Red, Green and
                          Blue values of a colour }

   IN AL,DX             { Read red amount. Don't use IN AX,DX as
                          for some strange reason it doesn't work ! }
   LES DI,RedValue
   MOV [ES:DI],AL       { Techie saddos note : STOSB is approx 4 cycles
                          slower and requires double cache multiplex,
                          which basically means "who gives a shit ?". :-)
                        }

   IN AL,DX
   LES DI,GreenValue
   MOV [ES:DI],AL

   IN AL,DX             { Read blue }
   LES DI,BlueValue
   MOV [ES:DI],AL
End;





{
This will change the red green and blue components of a colour,
thereby affecting it's shade. How's that for picturesque speech ?

Note : You don't need a PaletteType record to use this command,
it affects the screen directly.

Expects : ColourNumber is the number of the colour from 0 to 255.
          RedValue is the red component of the colour (0-63).
          GreenValue is the green component of the colour (0-63).
          BlueValue is the blue component of the colour (0-63).

Returns : Nothing

Corrupts : AL,DX
}


Procedure SetPalette(ColourNumber, RedValue, GreenValue, BlueValue : Byte); Assembler;
Asm
   MOV AL,ColourNumber
   MOV DX,$3c8          { Write to Port $3C8 with number of Colour to alter }
   OUT DX,AL
   INC DL               { $3C9 again ! }
   MOV AL,RedValue      { Store Red }
   OUT DX,AL
   MOV AL,GreenValue    { Store Green }
   OUT DX,AL
   MOV AL,BlueValue     { Store Blue }
   OUT DX,AL
End;






(*
This Procedure takes a snapshot of all of the colours on screen.

Expects : Palette is a variable of type PaletteType which will
          hold the 256 colour palette.

Returns : Nothing

Notes   : Use this command just before a mode change so that
          you can restore the palette to it's original state
          (via SetAllPalette) at the end of the program.
          (Unless of course you want to corrupt everything)

*)


Procedure GetAllPalette(Var Palette : PaletteType);
Var ColourCount:byte;
Begin
     For ColourCount:=0 to (MaxColours - 1) do
     GetPalette(ColourCount,Palette.RedLevel[ColourCount],
     Palette.GreenLevel[ColourCount],Palette.BlueLevel[ColourCount]);
End;







{
Do I need to explain what this does? It loads in a Palette
from file FileName and stores it in the variable Palette.
Easy enough to use.

Expects : FileName is standard MS-DOS filename which refers to the
          palette file.
          Palette is variable of type PaletteType used to hold
          the palette data.

Corrupts : Don't know.
}

Procedure LoadPalette(FileName: String; Var Palette : PaletteType);
Var PaletteFile: File;
Begin
     Assign(PaletteFile,FileName);
     Reset(PaletteFile,1);
     BlockRead(PaletteFile,Palette,SizeOf(Palette));
     Close(PaletteFile);
End;





{
Guess what this does then !.

Expects : FileName is the MS-DOS file spec of the palette to be saved.
          Palette is the palette to be saved.

Returns : Nothing

Corrupts : As it's not in assembler it's hard to say, but I guess
           Pascal preserves all registers on entry to a routine ..
           (Don't quote me on that !)
}

Procedure SavePalette(FileName: String; Palette : PaletteType);
Var PaletteFile: File;
Begin
     Assign(PaletteFile,FileName);
     Rewrite(PaletteFile,1);
     BlockWrite(PaletteFile,Palette,SizeOf(Palette));
     Close(PaletteFile);
End;







{
This sets the DACs to the Colours specified in your
Palette array. Do NOT alter the Palette data structure
or else this won't work.

Expects : Palette is an initialised palette of PaletteType.

Returns : Nothing

Corrupts : AL, BX, CL, DX, SI, DI, ES
}


Procedure SetAllPalette(Palette : PaletteType); Assembler;
Asm
   PUSH DS
   LDS BX, Palette      { DS:BX points to Palette record }
   XOR AL,AL
   MOV DX,$3c8          { $3c8 selects the first colour to alter.
                          After 3 writes to $3c9, the VGA automatically
                          moves to the next Colour so there is no
                          need to write to $3c8 again. }
   OUT DX,AL
   INC DL               { Make DX = $3c9, which is used to set the
                          Red / Green and Blue values of a Colour }

   MOV CL,(MaxColours-1) { 256 colours }

   MOV SI,BX
   ADD SI,MaxColours     { Make SI point to green levels }
   MOV DI,BX
   ADD DI,MaxColours     { Make DI point to blue levels }
   ADD DI,MaxColours

{
Note: I read somewhere that some VGA adapters don't like
      being hit with continuous data too quickly..

      If not then you should use the BIOS load palette
      function (which will be 20 times slower than this
      hack trick)
}

@WritePaletteInfo:
   MOV AL, [BX]         { Read red level from Palette struct }
   OUT DX,AL            { Write to port $3c9 }
   MOV AL, [SI]         { Read green level from Palette struct }
   OUT DX,AL            { Write to port $3c9 }
   MOV AL, [DI]         { Read blue level from Palette struct }
   OUT DX,AL            { Write to port $3c9 }

   INC DI               { Next Red part of record }
   INC BX               { Next Green }
   INC SI               { Next Blue }

   DEC CL
   CMP CL,$FF               { Dunno if a JNZ works when register is 0
                            or $ff. }
   JNZ @WritePaletteInfo
   POP DS
End;






{
Set the new graphics colour. Also affects text routines as well.

Expects : NewColour is the new graphics Colour.

Returns : Nothing.

Corrupts : AL.
}


Procedure SetColour(NewColour:byte); Assembler;
Asm
    MOV AL,NewColour
    MOV CurrentColour,AL
End;






{
Get the current graphics colour.
}

Function GetColour: byte; Assembler;
Asm
     MOV AL, CurrentColour;
End;
























{
****************
Sprite Functions
****************
}


{
Get the width of a shape

Expects : DataPtr^ points to a shape in memory

Returns : Width of shape (1-255)

Corrupts : ES, DI
}

Function ShapeWidth(Var DataPtr): byte; assembler;
Asm
   LES DI,DataPtr
   MOV AL,[ES:DI]
End;




{
Get the height (in pixels) of an Shape.

Expects : DataPtr^ points to a shape held in memory

Returns : Height of shape (1-255)

Corrupts : ES,DI
}

Function ShapeHeight(Var DataPtr): byte; assembler;
Asm
   LES DI,DataPtr
   MOV AL,[ES:DI+1]
End;







{
This Function returns the number of bytes required to store
a shape object of a given width and height.

Expects : ShapeWidth is the width of the Shape (1-255). You can
          obtain the width of a shape by using the ShapeWidth
          Function above.

          Shapeheight is the height of the Shape (1-255). You can
          obtain the height of a shape by using the ShapeHeight
          Function above.

Returns : ExtShapeSize = No of bytes shape uses.


Corrupts : AL,BL.

}


Function ExtShapeSize(ShapeWidth, ShapeHeight : byte): word; Assembler;
Asm
   MOV AL, ShapeWidth
   MOV BL, ShapeHeight
   MUL BL
   INC AX
   INC AX
End;




{
Calculate the number of bytes required to hold a shape in memory,
if grabbed from the screen.

Expects :     X1, Y1, X2, and Y2 define a rectangular region that
              lies on an imaginary screen (No reading of source/
              dest Bitmap is done!). X1 and X2 must be in the range of
              0-319; Y1 and Y2 must be in the range of 0-199.

              You are restricted to images up to 255 x 200 pixels
              in size. (Why 200? Well, you can't grab past the
              vertical limits of the VGA screen can you ?)

Returns :     Number of bytes used to hold image. If 0, then this
              means the image is too large to load into a 64K
              portion of RAM.

Corrupts :    BX,DX.
}

Function ShapeSize(x1,y1,x2,y2:word):word; Assembler;
Asm
     MOV AX,x2          { Width = (X2 - X1) + 1 }
     SUB AX,x1
     INC AX             { Add one extra width byte }
     AND AH,$7F
     OR AH,AH
     JNZ @TooBig

     MOV BX,y2          { Height = (Y2 - Y1) + 1 }
     SUB BX,y1
     INC BX
     AND BH,$7F         { And again }
     CMP BX,201
     JB @ShapeFine      { No, shape is OK in width and height }

@TooBig:
     XOR AX,AX          { Set AX to return 0, meaning error }
     JMP @Finished

@ShapeFine:
     MUL BL             { SpriteDataSize = Width * Height }
     ADD AX,2           { Take into account 2 bytes for Shape "header" }

@Finished:
End;











{
Display a shape at a given position on screen, over the current
background (Most games with sprites use this technique). And
if this isn't the fastest sprite routine in the SWAG then I'll
eat my C64.

Expects : X and Y specify a horizontal and vertical position for
          the TOP LEFT of an Shape. (Regardless whether or not the
          shape's edge is transparent)

          X and Y are presumed ALWAYS valid : i.e. Within bounds of
          screen; Also, it is presumed that the sprite is not placed
          in a position on screen that over runs the screen borders:
          unexpected effects would occur. Sorry! Use ClipBlit if you
          must place sprites in the screen border.

          DataPtr, the untyped variable, must point to data for a
          sprite which is up to 254 pixels wide and 200 pixels
          tall.

Returns : Nothing

Corrupts : AX,BX,CX,DX,SI,DI,ES, & Direction Flag

}

Procedure Blit(x,y:word; Var DataPtr); Assembler; { A - Ha ! }
Asm
   MOV AX,x
   MOV BX,y
   CALL CalculateOffset         { Calculate where to blit to }

   MOV ES,SourceBitmapSegment   { Point ES to source Bitmap }

   MOV CX,DS                    { Faster than stack }
   LDS SI,DataPtr               { resides in memory. }

   MOV DX,[SI]                  { Get Width into DL and height to DH }
   INC SI                       { Faster than ADD SI,2 - I think }
   INC SI
   CLD                          { Make sure writes are descending }

   MOV AH,DL                    { Save width in CL }

@Outer:
   MOV DL,AH                    { Reload DL }
   MOV DI,BX                    { DI = Where to write to }

{ You could use a LODSD, but to be honest it's more trouble than
  it's worth writing the tons of extra code just to save an extra
  clock cycle or two. That is, if it does..
}


@Main:
   LODSB                { Read byte from DS:SI }
   OR AL,AL             { Is it value 0, meaning transparent ? }
   JZ @NoBlit           { Yes, so ignore byte }
   MOV [ES:DI],AL       { Otherwise write it to the screen. Don't
                          use STOSB ! }

@NoBlit:
   INC DI
   DEC DL               { Reduce horizontal counter }
   JNZ @Main            { If not zero then do next byte of the
                          sprite column }

@NextScanLine:
   ADD BX,320           { Move down 1 scan line }
   DEC DH               { Reduce vertical count }
   JNZ @Outer           { If not all lines of sprite done back to @Outer }
   MOV DS,CX            { Restore Data Segment }
End;








{
This routine writes a shape to the source Bitmap with no Colour 0
transparency, totally overwriting everything "beneath" it.
Also, there is no clipping of Shape. (Use ClipBlock for this
purpose)

Expects  : X and Y specify the horizontal and vertical coordinate
           of the Shape pointed to by DataPtr.

Returns  : Nothing

Corrupts : AX,BX,CL,DX,SI,DI,ES

Notes    : Block is especially useful for "tile" based maps.
}

Procedure Block(x,y:word; Var DataPtr); Assembler;
Asm
   MOV AX,x
   MOV BX,y
   CALL CalculateOffset
   CMP BX,-1                    { Off screen ? }
   JZ @StupidUser

   PUSH DS                      { Save DS on stack }
   MOV ES,SourceBitmapSegment   { ES: BX -> Where sprite written to }

   CLD                          { Make sure writes are descending }
   LDS SI,DataPtr               { This has to be last access of memory
                                  variable as DS is now altered }
   MOV DX,[SI]                  { Get width into DL, height into DH }
   ADD SI,2                     { SI now points to sprite data }


@Outer:
   MOV DI,BX                    { DI = Offset into VGA screen }
   MOV CL,DL                    { CL = Width of sprite }

   CMP CL,4                     { Bytes left < 4 ? }
   JB @CantDoLongWordBlit       { Yeah, so can't do the 4 byte blit }

   SHR CL,2                     { Divide Bytes left by 4 }

@CopyLong:
   DB $66                       { Otherwise, store longword to [ES:DI] ! }
   MOVSW
   DEC CL                       { CL is long word count }
   JNZ @CopyLong                { If CL <> 0 go back to CopyLong }

   MOV CL,DL                    { Restore CL }
   AND CL,3
   OR CL,CL
   JZ @NoBytesLeft

@CantDoLongWordBlit:
   CMP CL,2                     { Byte count < 2 ? }
   JB @DoByteBlit               { Yes, can't do a word blit (Shit !)
                                  so that means that there's only
                                  1 byte left. }

@CopyWord:
   MOVSW                        { Otherwise, write word }

@DoByteBlit:
   TEST CL,1                    { Is there a byte left ? }
   JZ @NoBytesLeft              { No, so no more blits this line }

   MOVSB                        { Store the last byte }

@NoBytesLeft:
   ADD BX,320                   { Advance BX to next scan line }
   DEC DH                       { Reduce Y count }
   JNZ @Outer                   { if <>0 then go to Outer }
   POP DS                       { Otherwise, restore Data Segment }

@StupidUser:
End;







{
Perform clipping calculations on an object.

Expects : AX to be an X coordinate for a sprite
          BX to be a Y coordinate
          ES:DI to point to the sprite data

Returns : If no draw can be done, carry is set TRUE.
          Else carry is FALSE and :

          SI will point to first byte to blit
          DI will be the VGA screen offset for first blit
          (ES still is at sprite segment however so must
          be changed afterwards)
          CL is the number of bytes to blit ACROSS
          CH is the number of bytes to blit DOWN
          DX is the MODULO for the image (i.e. how many bytes SI should
          skip (after reload) to get to the start of next row of
          sprite data)

Notes :   Unless you are planning to write extra routines which may
          clip images up to 256 x 256 it is wise to leave this Procedure
          as private to the unit as it is quite complex.
}


Procedure ClipCalculations; Near; Assembler;
Asm
   CMP BX,199              { Y > 199 ? }
   JG @NoDraw              { JG is for SIGNED integers. If Y pos is
                             > 199 then no blit }

   CMP AX,319              { X > 319 ? }
   JG @NoDraw              { Yes, Do not do any blits at all }

   MOV SI,DI
   INC SI
   INC SI                  { Make SI point to actual sprite data }

   XOR CH,CH
   MOV CL,[ES:DI]          { CL holds Clipwidth }

   CMP AH,$80              { Quick test if X position is negative }
   JB @XNotNegative        { If not then check if image is off right hand
                             of screen }
   NEG AX                  { Make X position positive }

   CMP AX,CX               { If Abs(X) >= Image Width Then Don't Draw }
   JA @NoDraw

   SUB CX,AX               { Dec(ClipWidth, Abs(X)) }
   ADD SI,AX               { Inc(DataStart, Abs(X)) }
   XOR AX,AX               { Set X to 0 }
   JMP @NowDoY             { Do Y portion of data now. }


@XNotNegative:
   MOV DX,CX               { Set DX to clipwidth }
   ADD DX,AX               { If X + ClipWidth < 320 Then }
   CMP DX,320
   JB @NowDoY              { Do Y part (No need to clip width) }
   MOV CX,320
   SUB CX,AX               { ClipWidth = 320 - X }


{
At this point:

AX is the X position of the Shape
BX is the Y position of the Shape
CL is the clipped width of the Shape.

Now it is time to do the height part and set the result in
CH.
}

@NowDoY:
   XOR DH,DH               { Make DX the height of image }
   MOV DL,[ES:DI+1]
   MOV CH,DL               { Set CH also to height for main blit routine }

   CMP BH,$80              { Quick test if Y position is negative }
   JB @YNotNegative

   NEG BX                  { Make Y a positive number }

   CMP BX,DX               { If Y > ClipHeight }
   JA @NoDraw
   SUB DX,BX               { Dec(ClipHeight, Abs(Y) ) }
   MOV CH,DL               { As an image can only be 255 bytes high
                             this works fine.. }
   PUSH AX                 { Save X Coord on stack }
   XOR AH,AH
   MOV AL,[ES:DI]          { AX = Width }
   MUL BX                  { Calculate Y * Width }
   ADD SI,AX               { Inc(DataStart, Abs(Y) * Width ) }
   POP AX
   XOR BX,BX               { Set Y to 0 }
   JMP @NowDoBlit          { NOW do the blit work. Whew! }



@YNotNegative:
   ADD DX,BX               { If Y + ClipHeight > 199 Then }
   CMP DX,200
   JB @NowDoBlit
   MOV DX,200
   SUB DX,BX               { ClipHeight = 200 - Y }
   MOV CH,DL


{
At this point AX is the X position
              BX is the Y position
              CL is the ClipWidth and
              CH is the ClipHeight.

As the width/height of an Shape can only be an 8 bit
quantity (i.e. < 256) I can discard the H portions of
the registers. Whew!

Now follows some weird code.. I'm going to make :

DX = Modulo for datastart (which is the width in bytes of Shape.
And yes, I do know that Width could be held in DL but adding extra
code just to satisfy you optimisation junkies is v. boring.)

DS:SI already points to data
ES:DI points to active (source) Bitmap

}

@NowDoBlit:
   PUSH CX                     { Save ClipWidth & ClipHeight on stack }
   CALL CalculateOffset        { Use AX and BX to calculate screen
                                 offset. On exit BX is offset }
   POP CX                      { Restore ClipWidth and ClipHeight }

   XOR DH,DH
   MOV DL,[ES:DI]              { DX = Modulo }
   MOV DI,BX                   { Ahhh. Now DI points to the screen offset }
   CLC
   JMP @End

@NoDraw:
   STC                         { Indicate no blit possible }

@End:
End;









{
This routine does the same as Blit but takes into account
the fact that the sprite may be off the edges of the
screen.

Its quite a bit slower than the normal Blit, but that's only
to be expected as there's more computations to be
done.

Expects  : X, Y specify the horizontal and vertical position of the Shape,
          DataPtr points to the data to blit.

Returns  : Nothing

Corrupts : BX,CX,DX,SI,DI,ES.
}

Procedure ClipBlit(x,y:integer; Var DataPtr); Assembler;
Asm
   MOV AX,X
   MOV BX,Y
   LES DI,DataPtr
   CALL ClipCalculations
   JC @NoDraw

   PUSH DS
   PUSH BP

   MOV AX,SourceBitmapSegment
   MOV BX,ES
   MOV DS,BX                   { Now DS: SI points to correct space }
   MOV ES,AX

   MOV BX,SI                   { BX to be used to reload SI }
   MOV BP,DI                   { And the screen modulo }

   MOV AH,CL                   { AH = Width }
   CLD                         { Make sure LODSB works OK }

@Outer:
   MOV CL,AH                   { Re-load CL }
   MOV SI,BX                   { And SI with address of next sprite row }
   MOV DI,BP                   { And DI with address of next scan line }


@WriteByte:
   LODSB                       { Read byte from DS:SI }
   OR AL,AL                    { Is byte 0 (transparent) ? }
   JZ @NoBlit                  { yes, so don't blit }
   MOV [ES:DI],AL              { Otherwise store byte }

@NoBlit:
   INC DI                       { Move DI to next pos. on screen }
   DEC CL                       { Reduce shape width count }
   JNZ @WriteByte               { If not zero, end of shape not reached }

   ADD BX,DX                    { BX = BX + Modulo, so BX now points
                                  to first byte of next sprite line
                                  to blit }
   ADD BP,320                   { Make BP point to next line. Note :
                                  If you are going to add some extra
                                  stuff here make sure you're not
                                  accessing local variables! }


   DEC CH
   JNZ @Outer

   POP BP
   POP DS
@NoDraw:
End;





{
This routine does the same as Block except that it takes into account
that the shape object may be off screen.

Expects  : Same as Block.

Returns  : Nothing

Corrupts : AX,BX,CX,DX,SI,DI,ES are corrupt on exit.
}



Procedure ClipBlock(x,y:integer; Var DataPtr); Assembler;
Asm
   MOV AX,X
   MOV BX,Y
   LES DI,DataPtr          { ES:DI points to data }
   CALL ClipCalculations
   JC @NoDraw


{
Prepare for blit !
}

   PUSH DS
   PUSH BP

   MOV AX,SourceBitmapSegment
   MOV BX,ES
   MOV DS,BX                   { Now DS: SI points to correct space }
   MOV ES,AX

   MOV BX,SI                   { BX to be used to reload SI (+Image Width) }
   MOV BP,DI                   { And BP to reload DI (+Screen Width) }

   CLD                         { Make sure LODSB works OK }

@Outer:
   PUSH CX
   MOV CH,CL                   { CH is set to ClipWidth }

   MOV SI,BX
   MOV DI,BP

   CMP CH,4                     { Bytes left < 4 ? }
   JB @CantDoLongWordBlit       { Yeah, so can't do the 4 byte blit }

   SHR CH,2                     { Divide Bytes left by 4 }

@CopyLong:
   DB $66                       { Otherwise, store longword to [ES:DI] ! }
   MOVSW
   DEC CH                       { Reduce long word count }
   JNZ @CopyLong

   MOV CH,CL                    { Restore CL }
   AND CH,3
   OR CH,CH
   JZ @NoBytesLeft



@CantDoLongWordBlit:
   CMP CH,2                     { Byte count < 2 ? }
   JB @CheckDoByteBlit          { Yes, can't do a word blit (Shit !)
                                  so that means that there's only
                                  1 byte left. }
@CopyWord:
   MOVSW                        { Otherwise, write word }


@CheckDoByteBlit:
   TEST CH,1                    { Is there a byte left ? }
   JZ @NoBytesLeft              { No, so no more blits this line }

@DoByteBlit:
   MOVSB                        { Store the last byte }

@NoBytesLeft:
   ADD BX,DX                    { BP to next byte of image to read }
   ADD BP,320                   { Advance BX to next scan line }

   POP CX
   DEC CH                       { Reduce Y count }
   JNZ @Outer                   { if <>0 then go to Outer }

   POP BP                       { Restore base pointer and }
   POP DS                       { Data Segment }

@NoDraw:
End;







{
Grab a rectangular area of bytes from the screen for use
as a shape object.

Expects     : X1,Y1 define the TOP LEFT of the area to grab.
              X2,Y2 define the BOTTOM RIGHT of the area.

              X1 MUST be less than X2;
              Similarly, Y1 MUST be less than Y2.

              Also, it is NOT possible to grab an image that
              is more than 255 pixels wide and 200 pixels
              high.

Returns     : Nothing

Notes       : Use the ShapeSize Function to calculate
              bytes needed to hold shape object in memory .

Corrupts    : AX,BX,CX,DX,SI,DI,ES

}

Procedure GetAShape(x1,y1,x2,y2:word;Var DataPtr); Assembler;
Asm
   MOV AX,x1
   MOV BX,y1
   CALL CalculateOffset
   CMP BX,-1
   JZ @StupidUser

   MOV AX,x2                    { Width = (X2 - X1) +1 }
   SUB AX,x1
   INC AX                       { Take into account extra pixel }
   MOV DL,AL

   MOV AX,y2                    { Height = (Y2 - Y1) +1 }
   SUB AX,y1
   INC AX
   MOV DH,AL

   LES DI,DataPtr
   MOV [ES:DI],DX               { Store Width & Height }
   ADD DI,2

   PUSH DS
   MOV DS,SourceBitmapSegment
   CLD                          { Make sure writes are descending }


@Outer:
   MOV SI,BX                    { SI = Offset into VGA screen }
   MOV CL,DL                    { CL = Width of sprite held in DL }

   CMP CL,4                     { Bytes left < 4 ? }
   JB @CantDoLongWordBlit       { Yeah, so can't do the 4 byte blit }

   SHR CL,2                     { Divide Count by 4 }

@CopyLong:
   DB $66                       { Otherwise, store longword to [ES:EDI] ! }
   MOVSW
   DEC CL                       { CL is long word count }
   JNZ @CopyLong                { If CL <> 0 go back to CopyLong }

   MOV CL,DL                    { Restore CL to width of Shape }

@CantDoLongWordBlit:
   AND CL,3
   OR CL,CL                     { Any bytes left ? }
   JZ @NoBytesLeft

   CMP CL,2                     { Byte count < 2 ? }
   JB @DoByteBlit               { Yes, can't do a word blit (Shit !)
                                  so that means that there's only
                                  1 byte left. }

@CopyWord:
   MOVSW                        { Otherwise, write word }
   TEST CL,1
   JZ @NoBytesLeft              { No, so no more blits this line }


@DoByteBlit:
   MOVSB                        { Store the last byte }

@NoBytesLeft:
   ADD BX,320                   { Advance BX to next scan line }
   DEC DH                       { Reduce Y count }
   JNZ @Outer                   { if <>0 then go to Outer }

   POP DS                       { Otherwise, restore Data Segment }

@StupidUser:
End;









{
This routine checks if the data contained within a Shape will
"Collide" with the background. (Background data is held within
the Source Bitmap)

This command is very useful for games that need accurate
Shape to background collision detection.

Expects : X and Y specify the horizontal and vertical position
          of a shape pointed to by DataPtr.

Returns : If the Shape has collided with ANY background (represented
          by colours 1-255) on the SOURCE Bitmap then BlitColl is TRUE.

Corrupts : AX,BX,CX,DX,SI,DI,ES
}

Function BlitColl(x,y :integer; Var dataptr) : boolean; Assembler;
Asm
   MOV AX,x
   MOV BX,y
   CALL CalculateOffset         { On exit, BX will hold screen "Offset" }

   MOV ES,SourceBitmapSegment

   PUSH DS
   PUSH BP
   LDS SI,DataPtr


   MOV DX,[SI]             { DL= Width, DH = Height }
   INC SI
   INC SI                  { Make SI point to sprite data }

   CLD                     { Make sure writes are descending }

   MOV CL,DL

@Outer:
   MOV DI,BX               { DI = Offset into Source Bitmap }
   MOV DL,CL

{ Check if any long words can be checked }

   CMP DL,4                { Is width at least 4 bytes ? }
   JB @CantCheckLong       { No }
   SHR DL,2                { Otherwise, divide width by 4 so that
                             DL will hold number of LONGs to check }


@CheckLong:
   DB $66; LODSW           { LODSD : Load EAX from DS:SI }
   DB $66; OR AX,AX        { OR EAX,EAX }
   JZ @NoCheckBackLong     { If EAX is zero then no point in checking
                             background is there ? }

   DB $66
   MOV BP,AX               { Make a copy of EAX }
   DB $66
   XOR AX,[ES:DI]          { XOR EAX, [ES:DI]  (Xor EAX with Background) }
   DB $66
   CMP BP,AX               { Is EAX unaffected by the XOR - i.e.
                             No collision }
   JNZ @CollisionOccurred


@NoCheckBackLong:
   ADD DI,4                { Bump DI to next long word }
   DEC DL                  { Reduce long word count }
   JNZ @CheckLong          { And now do the collision check for long word }



   MOV DL,CL               { Restore DL to it's previous contents }
   AND DL,3                { Mask out all but bits 0 & 1 }


{ Any words left to be checked ? }

@CantCheckLong:
   CMP DL,2                { Is there at least 2 bytes left to move ? }
   JB @CantCheckWord       { No }

@CheckWord:
   LODSW                   { Read word from DS:SI into AX }
   OR AX,AX                { Is Shape data non zero ? }
   JZ @CantCheckWord       { Yes, so can't be a collision }

   MOV BP,AX
   XOR AX,[ES:DI]          { Otherwise, check background too }
   CMP BP,AX               { Is AX different ? }
   JNZ @CollisionOccurred  { Yes, so this means a collision }
   ADD DI,2                { Otherwise add 1 byte }

@CantCheckWord:
   TEST CL,1               { Is there a single byte left to check }
   JZ @AllChecksDone       { Nope }
   LODSB                   { Otherwise, read it }
   OR AL,AL                { Zero ? }
   JZ @AllChecksDone       { Yes, so basically no more checks to do }

   MOV CH,AL
   XOR AL,[ES:DI]          { No, so check background byte }
   CMP CH,AL               { Is AL different ? }
   JNZ @CollisionOccurred  { Yes, so a collision has occurred }


@AllChecksDone:
   ADD BX,320              { 320 is the number of bytes in one scan-line }
   DEC DH                  { Reduce vertical count (Counts from height of Shape) }
   JNZ @Outer              { If <>0 then check for next line of Shape }
   MOV AL,False            { If all lines have been done then this means
                             that no collision has occurred }

   JMP @Exit               { And exit. Don't insert a RET here -
                             you'll crash the program ! }

@CollisionOccurred:
   MOV AL,True             { This part is only reached if a collision has
                             occurred. }

@Exit:
   POP BP                 { Restore Base Pointer }
   POP DS                 { Restore data segment }
End;






{
De-allocate memory for an Shape.

Expects  : DataPtr is an Shape pointer.

Returns  : A crash if you're not careful !! :-(

Corrupts : The assembler part uses AX,DI and ES. Don't know about
           the Pascal part however.
}

Procedure FreeShape(DataPtr:pointer);
Var ImWidth,
    ImHeight: byte;
Begin
     Asm
     LES DI,DataPtr
     MOV AX,[ES:DI]
     MOV ImWidth,AL
     MOV ImHeight,AH
     End;
     FreeMem(DataPtr,ExtShapeSize(ImWidth,ImHeight));
End;






{
Load in a .IMG file from disk.

WARNING! This is NOT the IMG file type used by some paint packages!
It is a non-standard file (albeit very simple) format that NEWGRAPH
writes, so trying to load a shape created from a paint package
etc. will not work.


Expects  :  FileName to be a valid MS-DOS path.
            DataPtr to be a valid pointer to where data will be stored.

Returns  :  Nothing, although sprite may have loaded into memory.

Corrupts :  Don't know.

}

Procedure LoadShape(FileName:String; Var DataPtr: Pointer);
Var F: File;
    DestSeg,
    DestOffset,
    ImgSize: word;
    ShapeWidth,
    ShapeHeight: byte;

Begin
     Assign(F,FileName);
     Reset(F,1);
     BlockRead(F,ShapeWidth,1);       { Read in width & height }
     BlockRead(F,ShapeHeight,1);


     {
     Calculate number of bytes that need to be reserved for the
     Shape.
     }

     ImgSize:= ExtShapeSize(ShapeWidth,ShapeHeight);
     If ImgSize < MaxAvail Then
        Begin

        GetMem(DataPtr,ImgSize);
        GetPtrData(DataPtr,DestSeg,DestOffset);

        Reset(F,1);
        BlockRead(F,Mem[DestSeg:DestOffset], ImgSize);
        Close(F);
        End
     Else
         Asm
         DB $66
         MOV WORD [OFFSET DataPtr],0         { Signal no memory claimed }
         DW 0
     End;

End;








{
Write an Shape to disk, where you could convert it if you like
to a PCX. With the reg. version, there is a command to do this.

Expects :  FileName is a standard DOS filename.
           P is a pointer to where the sprite data exists in memory.

Returns :  Nothing.

Corrupts : Don't know.
}

Procedure SaveShape(FileName:string; DataPtr:Pointer);
Var F: File;
    SourceSeg, SourceOffset: word;
Begin
     Assign(F,FileName);
     Rewrite(F,1);
     GetPtrData(DataPtr,SourceSeg,SourceOffset);

     BlockWrite(F, Mem[SourceSeg:SourceOffset],
                   ExtShapeSize(mem[SourceSeg:SourceOffset],
                   mem[SourceSeg:SourceOffset+1]));
     Close(F);
End;









{
***************************************
PCX LOAD AND SAVE ROUTINES - WHICH WORK
***************************************
}



{
This will put a mode 13h 256 colour PCX at position X,Y and
show a defined area. Useful for low res multimedia applications. :-)
This PCX loader can handle PCX's of variable dimensions up to
width 320 and height 200 so you could design sprites
with a graphics package and save them as a PCX then grab them
off the screen as Shapes. Also, this PCX loader is far faster than
Norman Yen's effort and intelligently uses memory. (Note: How can
a program dumbly use memory? Hmm?)

Expects: Filename is an MS-DOS filespec relating to the PCX's name,
         i.e. 'C:\WORK\SHEEP.PCX' (Oh well explained Scott :^( )
         ThePalette is a PaletteType record used to hold the PCX's
         palette data.
         X,Y specifies the top left coordinates on screen of where
         the PCX is to be drawn. X should be in the range of 0 to
         319, Y should be in the range of 0 to 199. The picture
         will be clipped as necessary.

Returns: Your program will halt with an error message if the PCX file
         does not exist, or if the PCX is not of the correct "type".
         (I.E. It's not mode 13h or it's not 256 colour etc.).
}

Procedure LocatePCX(filename:string; Var ThePalette: PaletteType;
          x,y,widthtoshow,heighttoshow:word);

var PCXFile: file;

    ReadingFromMem  : Boolean;      { If True it means All/Some PCX
                                      Data is in RAM }
    MemRequired     : longint;      { Size of PCX bitmap data }
    BytesRead       : longint;      { Number of PCX bytes read }
    PCXFileSize     : longint;      { How many bytes PCX uses }
    Count           : integer;      { I is a general counter used to set
                                      the PCX's palette and then count
                                      scan lines }
    RedVal          : byte;         { Used for ColourMap, Palette values }
    GreenVal        : byte;         { which define a colour }
    BlueVal         : byte;

    MemoryAccessVar : pointer;      { Pointer to read bitmap data }
    BufferSeg,                      { Where PCX will be loaded to }
    BufferOffset    : word;

    VidOffset       : word;         { Screen offset }

    Width,Height,                   { Width is number of horizontal bytes to grab
                                      Height is number of vertical bytes to grab }
    N,Bytes             : word;     { N counts up to Bytes }
    RunLength,c     : byte;         { RunLength is the Run Length Encoding
                                      byte, C is the character read from
                                      PCX data }
    PastHorizontalLimit : boolean;  { Set true this means no more
                                     horizontal pixel writes to do, advance 
                                     to next line as soon as poss.}

begin
    assign(PCXFile,FileName);

{$i-}
    reset (PCXFile,1);
{$i+}
    If IOResult = 0 Then
       Begin

       blockread (PCXFile, header, sizeof (header));       { Read in PCX header }

       if (header.manufacturer=10) and (header.version=5) and
          (header.bits_per_pixel=8) and (header.colour_planes=1) then
          begin
               seek (PCXFile, filesize (PCXFile)-769);     { Move to palette data }
               blockread (PCXFile, c, 1);                  { Read Colourmap type }
               if (c=12) then                              { 12 is correct type }
               begin
                    {
                    Read palette data and write to palette
                    structure.
                    }

                    for Count:=0 to 255 do
                        Begin
                          BlockRead(PCXFile,RedVal,1);
                          BlockRead(PCXFile,GreenVal,1);
                          BlockRead(PCXFile,BlueVal,1);

                          ThePalette.RedLevel[Count]:=RedVal SHR 2;
                          ThePalette.GreenLevel[Count]:=GreenVal SHR 2;
                          ThePalette.BlueLevel[Count]:=BlueVal SHR 2;
                      End;


                  seek (PCXFile, 128);

                  {
                  If entire size of PCX is less than 64K in length then
                  it can be stored in a memory buffer and uncompacted
                  from there. However, if PCX exceeds 64K then it must
                  be split into several chunks. If your machine does
                  not have 64K left for the buffer used (You're in trouble !!)
                  then the system will read the PCX from disk continually,
                  which works OK but is very slow. So there.
                  }

                  MemRequired:=Filesize(PCXFile)-897;
                  PCXFileSize:=MemRequired;
                  BytesRead:=0;

                  If (MemRequired < 65528) And (MaxAvail > MemRequired) Then
                     Begin
                     getmem(MemoryAccessVar,MemRequired);
                     GetPtrData(MemoryAccessVar, BufferSeg, BufferOffset);
                     BlockRead(PCXFile,Mem[BufferSeg:BufferOffset],MemRequired);
                     ReadingFromMem:=True;
                     End
                  Else

                  {
                  If the PCX occupies more than approx. 64K bytes then it
                  is necessary to read the data into memory in 64K chunks
                  which is still considerably faster than the
                  final method (continual reading from disk)
                  }

                      If (MaxAvail > 65527) Then
                         Begin
                         GetMem(MemoryAccessVar,65528);
                         GetPtrData(MemoryAccessVar, BufferSeg, BufferOffset);
                         BlockRead(PCXFile,Mem[BufferSeg:BufferOffset],65528);
                         BytesRead:=65528;
                         MemRequired:=65528;
                         ReadingFromMem:=True;
                         End
                      Else
                          { CLUCK!! Oh well, system is just going to have
                          to read from disk as there is not even 64K
                          memory left. (A very bad situation) }

                          ReadingFromMem:=False;

                  {
                  Find out width & height of PCX.
                  }

                  width:=(header.xmax - header.xmin)+1;
                  height:=(header.ymax - header.ymin)+1;
                  bytes:=header.bytes_per_line;

                  {
                  Adjust width & height of PCX if necessary so that PCX
                  "fits" on screen.

                  }

                  if widthtoshow > width Then
                     widthtoshow:=width;

                  if (widthtoshow + x) > 320 Then
                     widthtoshow:=width-x;

                  if heighttoshow > height Then
                     heighttoshow:=height;

                  if (heighttoshow + y)> 200 Then
                     heighttoshow:=height-y;


                  {
                  Do all scan lines.
                  }

                  for Count:=0 to (heighttoshow-1) do
                  begin
                      n:=0;
                      PastHorizontalLimit:=False;
                      vidoffset:= SourceBitmapOffset+((Y+Count)* 320)+X;

                      while (n<bytes) do
                      begin

                           { Display any more pixels width wise from PCX ? }

                           If N >= WidthToShow Then
                              PastHorizontalLimit:=True;

                           If ReadingFromMem Then
                               Begin
                               c:=Mem[BufferSeg:BufferOffset];
                               Inc(BufferOffset);
                               If BufferOffset = 65528 Then
                                  Begin
                                  { End of buffer has been reached, so
                                    it's time to load another part of the
                                    PCX }

                                  If (PCXFileSize - BytesRead)> 65527 Then
                                     Begin
                                     BlockRead(PCXFile,Mem[BufferSeg:0],65528);
                                     Inc(BytesRead,65528);
                                     End
                                  Else
                                      { Load last chunk of PCX }

                                      Begin
                                      BlockRead(PCXFile,Mem[BufferSeg:0],
                                      (PCXFileSize - BytesRead));
                                      End;

                                  {
                                  Now reset buffer pointer to start
                                  }

                                  BufferOffset:=0;
                                  End;
                               End
                            Else
                                BlockRead(PCXFile,c,1);

{
At this point one element of data has been read, and stored in
variable C. If bits 6 & 7 of C are set then this means to the system
a "run of bytes" has been found. (i.e. a number sequence - for example,
four 1's, twenty 15's, any sequence of identical numbers).

In this case, the 6 least significant bits of C indicate how long the run
of bytes is. For example, if a sequence of five bytes has been found
the run = 5. Of course, using 6 bits limits you to a maximum run length
of 63 bytes but that should be more than enough for most pictures.

Quite a simple method of compaction eh? Definitely the easiest format to
understand!

}

                            if ((c and 192)=192) then
                            begin

                               { Get the 6 least significant bits }
                               RunLength:=c and 63;

                               { get the run byte }

                               If ReadingFromMem Then
                                  Begin
                                  c:=Mem[BufferSeg:BufferOffset];
                                  Inc(BufferOffset);

                               { Time to read in more data from disk ? }

                                  If BufferOffset = 65528 Then
                                     Begin
                                     If (PCXFileSize - BytesRead)> 65527 Then
                                        Begin
                                        BlockRead(PCXFile,Mem[BufferSeg:0],65528);
                                        Inc(BytesRead,65528);
                                        End
                                     Else
                                         Begin
                                         BlockRead(PCXFile,Mem[BufferSeg:0],
                                         (PCXFileSize - BytesRead));
                                     End;

                                     BufferOffset:=0;
                                     End;
                                  End
                               Else
                                   BlockRead(PCXFile,c,1);

                               {
                               Can't do blit if past the horizontal limit
                               of the window.
                               }

                               If Not PastHorizontalLimit Then
                                  Begin
                                  If n+RunLength > widthtoshow Then
                                     fillchar(Mem[SourceBitmapSegment:VidOffset],WidthToShow-n,c)
                                  else
                                      fillchar(Mem[SourceBitmapSegment:VidOffset],RunLength,c);

                                  inc(vidoffset,RunLength);
                               End;

                               inc(n,RunLength);
                               end
                            else
                                begin
                                If Not PastHorizontalLimit Then
                                   Begin
                                   mem [SourceBitmapSegment:vidoffset]:=c;
                                   inc (vidoffset);
                                End;
                                inc (n);
                            end;

                      end;

                  end;

                  If ReadingFromMem Then
                     freemem(MemoryAccessVar,MemRequired);
               end
          else
              Begin
              DirectVideo:=False;
              Writeln('The PCX''s ColourMap is not of the correct type !');
              Close(PCXFile);
              Halt(0);
              End;
          end
       Else
           Begin
           DirectVideo:=False;
           Writeln('PCX unsuitable for loading.');
           Close(PCXFile);
           Halt(0);
       End;

       close (PCXFile);  { Do this anyway ! }

       end
    Else
        Begin
        DirectVideo:=False;
        Writeln('File not found ?');
        Close(PCXFile);
        Halt(0);
        End;

end;













{
What this does is load a PCX at the TOP LEFT of the source Bitmap,
very quickly. If you need to put the PCX somewhere else use LocatePCX.


Expects:  FileName to be a standard MS-DOS filename, relating to a
          320 x 200 PCX.
          ThePalette to be of type Palette. This holds the colour
          information of the PCX file you are loading.

You can then use SetAllPalette to set the VGA palette so that
the pic can display properly.
}

Procedure LoadPCX(FileName:string; Var ThePalette: PaletteType);
Begin
     LocatePCX(Filename,ThePalette,0,0,320,200);
End;






{
Home grown PCX packer.

This PCX routine is able to cope with the full 256 colours,
unlike some other SWAG PCX packers I could mention.. !

Expects:    FileName is the name of the PCX to save.
            ThePalette is a PaletteType variable, which has been
            initialised by, for example, the GetAllPalette routine.
            X,Y specify the horizontal and vertical positions of where to
            begin grabbing the PCX data from.
            PCXWidth and PCXHeight specify the width & height of the
            window to grab. Easy eh?

            For example, to grab one half of the VGA screen you could use:
            SaveAreaAsPCX('1STHALF.PCX',MyPalette,0,0,160,200);

            And the other half with :

            SaveAreaAsPCX('2NDHALF.PCX',MyPalette,160,0,160,200);

            These files can then be loaded into a paint package such
            as PC Paintbrush or Neopaint (great program!) and manipulated.

            Use the SAVEPCX routine below to save an entire PCX screen.


Returns:    Program will halt if the PCX is not found.


P.S. This routine manages to save a 256 colour screen properly,
     unlike some other PCX writing routines I could mention. Do you
     programmers actually TEST your code before sending it into the
     SWAG ? (Like, are there any GIF loaders that work ?!!)
}


Procedure SaveAreaAsPCX(filename:string;ThePalette: PaletteType;
          x,y, PCXWidth,PCXHeight: word);

Var f: File;                    { File for writing PCX to }
    ColourMapID: byte;           { Always holds 12, for the PCX }
    ColourCount: byte;           { Counts up to number of colours on
                                  screen (255) }
    RedValue: byte;             { Palette Values of a colour }
    GreenValue: byte;
    BlueValue: byte;

    LastOffset: word;           { Used as a latch for VidOffset }
    VidOffset: word;            { Offset into Source Bitmap }
    VerticalCount: byte;        { Number of scan lines to use }
    LastByte : byte;            { The last byte read from Source Bitmap }
    NewByte: byte;              { The current byte }
    RunLength : byte;           { Counter for run length compression }
    ByteCount: word;            { Counts up to bytes per scan line (320) }



Begin
     Assign(f,filename);
     Rewrite(f,1);

     With header do
     Begin
          Manufacturer := 10;
          Version := 5;
          Encoding :=0;
          Bits_per_pixel:=8;    { 8 bits = 256 colours }
          XMin:=0;
          YMin:=0;

          {
          Can't save a PCX more than 320 x 200 in size.
          }

          if (PCXwidth + x) > 320 Then
             PCXwidth:=320-x;
          if (PCXheight+ y) > 200 Then
             PCXheight:=200-y;

          XMax:=(PCXWidth-1);
          YMax:=(PCXHeight-1);
          Hres:=320;                        { Hres/Vres could be used to
                                              determine screen mode -
                                              probably :-( }
          VRes:=200;

          Colour_planes:=1;                 { Mode 13h is not planar }
          Bytes_per_line:=PCXWidth;         { One byte per pixel }
          Palette_type:=12;                 { Dunno what 12 is for }
     End;

     BlockWrite(F,Header,SizeOf(Header));

     Asm
     MOV AX,X
     MOV BX,Y
     CALL CalculateOffset
     MOV VidOffset,BX
     End;

     For VerticalCount:=0 to PCXHeight-1 do
     Begin
          LastOffset:=VidOffset;
          ByteCount:=0;
          LastByte:=0;

          Repeat
                NewByte:=Mem[SourceBitmapSegment:Vidoffset];

                {
                If the last byte read is equal to the new byte read
                then a run of bytes has been identified and so the
                system needs to count how many identical bytes (up
                to a total of 63) follow. When finished, the
                system writes this count to disk PLUS a value of
                192 (which is the signal to the PCX reader that
                a run of bytes follows) then writes the byte that
                was prevalent in the run.

                For example, say in the data stream there were 10
                values :

                0 1 2 6 9 8 7 7 7 4

                When the system gets to 8 it would then compare
                that number with the next value (7) and see that 8 is
                not equal to 7, then the computer would move to said 7
                (after the 8) and compare it to the next digit, which
                is also a 7.

                As a match has been found, the system counts the
                number of 7s there, which is (all together now !)
                3!! and then adds 192 to the result.. to give 195.

                As stated before, bits 6 + 7 of the byte have
                been set in order to "flag" to the PCX reader that
                a run of bytes have been found.

                The value 195 is written to disk, then value 7 so the
                PCX reader that loads this file knows what value (and
                how many times) to write to the screen during unpacking.

                I hope this has explained one of the PCX mysteries. If
                it hasn't I typed all that for nothing!! :-)
                }

                If NewByte = LastByte Then
                   Begin

                   RunLength:=0;
                   While (NewByte = LastByte) and (RunLength < 63)
                      and (ByteCount <> PCXWidth) do
                      Begin
                      Inc(RunLength);
                      Inc(ByteCount);

                      {
                      Move to next byte on Source Bitmap
                      }

                      Inc(vidoffset);

                      NewByte:=Mem[SourceBitmapSegment:Vidoffset];
                   End;


                   Asm
                   OR Byte Ptr RunLength, 192
                   End;

                   BlockWrite(f,RunLength,1);
                   BlockWrite(f,LastByte,1);

                   LastByte:=NewByte;
                   End
                Else

                { How to deal with colours > 191. }
                    If (NewByte > 191) Then
                       Begin
                       Inc(ByteCount);
                       Inc(VidOffset);                { Point to next byte on screen }
                       RunLength:=193;
                       BlockWrite(f,RunLength,1);     { Write run length byte of 1  ! }
                       BlockWrite(f,NewByte,1);       { The ONLY way to get round }
                       LastByte:=NewByte;
                       End
                    Else
                        Begin
                        Inc(ByteCount);
                        Inc(vidoffset);
                        BlockWrite(f,NewByte,1);
                        LastByte:=NewByte;
                        End;

          Until ByteCount = PCXWidth;

          VidOffset:=LastOffset+320;
     End;

     {
     12 is Colourmap ID.
     }

     ColourMapID:=12;
     BlockWrite(f,ColourMapID,1);

     {
     Now write Palette R,G,B values to disk. The only reason
     I didn't implement :

     BlockWrite(F,Palette,SizeOf(Palette))

     was that all the palette entries had to be shifted LEFT
     twice (To represent a 16.7 million colour palette..) 
     
                                DAMN!
     }

     For ColourCount:=0 to 255 do
         Begin

         RedValue:=ThePalette.   RedLevel[ColourCount] SHL 2;
         GreenValue:=ThePalette. GreenLevel[ColourCount] SHL 2;
         BlueValue:=ThePalette.  BlueLevel[ColourCount] SHL 2;

         BlockWrite(F,RedValue,1);
         BlockWrite(F,GreenValue,1);
         BlockWrite(F,BlueValue,1);
     End;

     Close(F);         { That's it - it's not over, not over yet .. :-) }
End;






{
Save a PCX file to disk.

Expects  :  Filename is the MS-DOS filespec , i.e. "C:\PICS\MYFILE.PCX"
           ThePalette specifies a PaletteType record to save to disk in
           the PCX file.

Returns  :  Nothing

Corrupts :  Don't know !!
}


Procedure SavePCX(filename:string;ThePalette: PaletteType);
Begin
     SaveAreaAsPCX(filename,ThePalette,0,0,320,200);
End;





{
**********************
MISCELLANEOUS ROUTINES
**********************
}

{
Wait for a certain number of vertical retraces, specified by
the number in TimeOut. (A vertical retrace occurs when the
monitor begins to draw the screen; If you wait for this
retrace and then update the screen your graphics will not
flicker - well not as much as before ;-) )


Corrupts AL,CX,DX
}


Procedure Vwait(TimeOut:word); Assembler;
Asm
         MOV CX,TimeOut         { CX = Number of times to wait }

         MOV DX,$3DA            { Port $3DA holds vertical & horizontal
                                  retrace status bits }
@WaitEnd:
         IN AL,DX               { Read port }
         TEST AL,8              { Test for bit 3 being set: If
                                  it is then that means the system may
                                  be in the middle of it's refresh
                                  and so writing to the screen now may
                                  cause flicker. }
         JNZ @WaitEnd           { If set, go back to @waitend }

{
When the routine gets to here it's the end of the retrace.
}

@WaitStart:
         IN AL,DX               { Read port again }
         TEST AL,8              { Is the bit set ? }
         JZ @WaitStart          { No ! So go back }

         DEC CX
         JNZ @WaitEnd          { Reduce count in CX, if <>0 go back
                                  to WaitEnd ! }

End;






{
Clear the Source Bitmap with Colour 0 (Always).

Expects : SourceBitmapSegment, SourceBitmapOffset to point to the source
          Bitmap (of course).

Returns : Nothing.

Corrupts : AX,CX,DI,ES


}

Procedure Cls; Assembler;
Asm
     MOV ES,SourceBitmapSegment
     MOV DI,SourceBitmapOffset

     MOV CX,4000         { 4000 x 16 byte moves are executed }
     DB $66
     XOR AX,AX           { XOR EAX,EAX - Colour 0 used to clear screen }

@ClearLoop:
     DB $66; STOSW       { STOSD }
     DB $66; STOSW
     DB $66; STOSW
     DB $66; STOSW
     DEC CX
     JNZ @ClearLoop

End;





{
Clear the screen with the graphics colour specified.

Expects : CurrentColour set to non-zero value
          Source Bitmap initialised with Bitmap

Returns : Nothing

Corrupts : AX,BX,CX,DI,ES
}

Procedure CCls(TheColour : byte); Assembler;
Asm
   MOV ES,SourceBitmapSegment
   MOV DI,SourceBitmapOffset

   MOV CX,4000
   MOV AH,TheColour
   MOV AL,AH
   MOV BX,AX

   DB $66; SHL AX,16            { SHL EAX,16 -> Move AH & AL into
                                  upper word of EAX}
   MOV AX,BX                    { Now EAX is fully set }

@FillLoop:
   DB $66; STOSW                { STOSD }
   DB $66; STOSW
   DB $66; STOSW
   DB $66; STOSW
   DEC CX
   JNZ @FillLoop                { You could use LOOP but I heard this
                                  method is faster }
End;





{
This is the initialisation part of the unit.
}

Begin
     SetSourceBitmapAddr($a000,0);
     DoubleBufferOff;
     Cls;                            { Flush video mem }
     MoveTo(0,0);                    { Graphics Cursor to top left }
     SetColour(1);                   { Use Colour 1 }
     UseFont(Font8x8);               { standard 8 x 8 }

     Writeln('NewGraph unit (C) 1995, 1996 Scott Tunstall. All rights');
     Writeln('reserved. Unauthorised editing/duplication of this code');
     Writeln('is PROHIBITED.');
     Writeln;

End.  { of unit }

[Back to GRAPHICS SWAG index]  [Back to Main SWAG index]  [Original]