[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]