{=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=-=-=-=-                                                     -=-=-=-=-=-=
-=-=-=                 Sprite Extensions for GraphPro                  =-=-=-
=-=-                                                                     -=-=
-=-=-=                     Coded by: Chris Lattner                    -=-=-=-
=-=-=-=-=-=-                        1996                         -=-=-=-=-=-=
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=}

Unit Sprite;                      { Sprite handling extensions for GraphPro }

{$A+ Align all data on word boundaries       }
{$B- Enable short circuit boolean evaluation }
{$D+ Enable debugging information            }
{$E- Disable x87 emmulation                  }
{$F- Do not force far calls                  }
{$G+ Enable 286 instructions                 }
{$I- Disable IO checking                     }
{$N- Disable x87 support                     }
{$O- Disable overlay support                 }
{$Q- Disable overflow checking               }
{$R- Disable range checking                  }
{$S- Disable stack checking                  }
{$T- Disable type checking on pointers       }
{$V- Disable VAR type checking               }
{$X+ Enable Extended syntax                  }
{$Y- Disable symbol reference info           }

INTERFACE
USES GraphPro;

CONST
  STStandard = 1;                { The possible STypes        }
  STRLE      = 2;
  STCompiled = 3;

TYPE
  ByteArray = ARRAY[0..65534] OF BYTE;
  SpriteType = RECORD
    SType         : BYTE;        { The type of sprite         }
    Height, Width : INTEGER;     { Height and Width of sprite }
    Data          : ^ByteArray;  { Pointer to the color data  }
    DataLen       : WORD;        { Length of Data             }
  END;

PROCEDURE CreateSprite(VAR Sprite : SpriteType; Width, Height : INTEGER);
PROCEDURE KillSprite  (VAR Sprite : SpriteType);
PROCEDURE CopySprite  (Source : SpriteType; VAR Dest : SpriteType);

PROCEDURE GetSprite   (VAR Sprite : SpriteType; X1, Y1, X2, Y2 : INTEGER);
PROCEDURE DrawSprite  (VAR Sprite : SpriteType; X, Y : INTEGER);
PROCEDURE DrawSpriteC (VAR Sprite : SpriteType; X, Y : INTEGER);

PROCEDURE DrawRLESprite(VAR Sprite : SpriteType; X, Y : INTEGER);
PROCEDURE DrawRLESpriteC(VAR Sprite : SpriteType; X, Y : INTEGER);
PROCEDURE ConvertSpriteToRLE(VAR Sprite : SpriteType);

PROCEDURE DrawCompiledSprite(VAR Sprite : SpriteType; X, Y : INTEGER);
PROCEDURE ConvertSpriteToCompiled(VAR Sprite : SpriteType);

PROCEDURE SaveSprite(Sprite : SpriteType; Filename : STRING);
PROCEDURE LoadSprite(VAR Sprite : SpriteType; Filename : STRING);

IMPLEMENTATION

PROCEDURE KillSprite(VAR Sprite : SpriteType);
BEGIN
  WITH Sprite DO
  BEGIN
    FREEMEM(Data, DataLen);
    Width := 0; Height := 0; Data := NIL;
  END;
END;

PROCEDURE CreateSprite(VAR Sprite : SpriteType; Width, Height : INTEGER);
BEGIN
  IF Sprite.Data <> NIL THEN KillSprite(Sprite);
  Sprite.Width := Width;
  Sprite.Height := Height;
  Sprite.DataLen := Width*Height;
  Sprite.SType   := STStandard;
  GETMEM(Sprite.Data, Sprite.DataLen);
END;

PROCEDURE GetSprite(VAR Sprite : SpriteType; X1, Y1, X2, Y2 : INTEGER); ASSEMBLER;
VAR ScrIndex : INTEGER;
ASM
  mov DX, X2
  sub DX, X1                 { Calculate DeltaX -> X2-X1+1              }
  inc DX
  mov BX, Y2
  sub BX, Y1                 { Calculate DeltaY -> Y2-Y1+1              }
  inc BX
  push BX                    { Save a copy for later.                   }
  push DX
  db $66; mov AX, WORD [Sprite]
  db $66; push AX
  push DX
  push BX
  call CreateSprite          { CreateSprite(Sprite, X2-X1+1, Y2-Y1+1)   }

  mov AX, X1
  mov BX, Y1              { Y1 is an index into the ScreenY array       }
  add BX, BX              { Multipy by two because each entry is a word }
  add AX, DS:[BX+OFFSET Screen.YTable]
  mov ScrIndex, AX           { ScrIndex := Y1*Screen.Width+X1           }

  pop DX                     { Restore XDelta }
  pop BX                     { Restore YDelta }

  mov AX, Screen.Width       { Can't get this with DS mangled...        }
  push DS
  les DI, Sprite             { VAR parameters are actually passed as    }
  les DI, ES:[DI+SpriteType.Data] { pointers to the variable...         }
  lds SI, Screen.Buffer
  add SI, ScrIndex
  sub AX, DX                 { Calculate Screen.Width-Sprite.Width      }
@YLoop:                      { FOR Y := Y1 TO Y2 DO                     }
  mov CX, DX                 { Copy the correct number of pixels!       }
  rep movsb                  { Do one scan line.                        }
  add SI, AX                 { Increment the ScrIndex to the next line  }
  dec BX
  jnz @YLoop                 { Next YLoop...                            }
  pop DS
END;

PROCEDURE DrawSprite(VAR Sprite : SpriteType; X, Y : INTEGER); ASSEMBLER;
ASM
  les DI, Sprite
  cmp ES:[DI+SpriteType.SType], STStandard
  je @DrawSprite
  cmp ES:[DI+SpriteType.SType], STRLE
  jne @NotRLE
  pop BP
  jmp DrawRLESprite
@NotRLE:
  cmp ES:[DI+SpriteType.SType], STCompiled
  jne @NotCompiled
  pop BP
  jmp DrawCompiledSprite
@NotCompiled:

@DrawSprite:
  mov AX, X
  mov BX, Y               { Y1 is an index into the ScreenY array       }
  add BX, BX              { Multipy by two because each entry is a word }
  add AX, DS:[BX+OFFSET Screen.YTable]
  mov BX, AX              { ScrIndex := Y*Screen.Width+X                }

  mov AX, Screen.Width       { Save this for later when we can't get to DS }
  push DS
  les DI, Screen.Buffer
  add DI, BX                 { Add DI, ScrIndex...                      }
  lds SI, Sprite
  mov DX, DS:[SI+SpriteType.Width]  { Grab XDelta }
  mov BX, DS:[SI+SpriteType.Height] { Grab YDelta }
  sub AX, DX                 { Calculate Screen.Width-Sprite.Width      }
  lds SI, DS:[SI+SpriteType.Data]
@YLoop:                      { FOR YI := 0 TO Sprite.Height-1 DO }
  mov CX, DX                 { Copy the correct number of pixels!       }
  rep movsb                  { Do one scan line.                        }
  add DI, AX                 { Inc ScrIndex to the next scanline        }
  dec BX
  jnz @YLoop                 { Next YLoop...                            }
  pop DS
END;

PROCEDURE DrawSpriteC(VAR Sprite : SpriteType; X, Y : INTEGER); ASSEMBLER;
VAR
  Height, Width,
  SkippedPixels,
  Index, YI : INTEGER;
ASM
  xor AX, AX
  mov Index, AX
  mov SkippedPixels, AX

  mov CX, X
  mov DX, Y
  cmp CX, Screen.Clip.X2
  jg @TrivialReject
  cmp DX, Screen.Clip.Y2
  jg @TrivialReject

  les DI, Sprite
  mov AX, ES:[DI+SpriteType.Height]           { Make a local copy of these   }
  mov BX, ES:[DI+SpriteType.Width]
  mov Height, AX
  mov Width, BX
  add CX, BX
  add DX, AX

  cmp CX, Screen.Clip.X1
  jl @TrivialReject
  cmp DX, Screen.Clip.Y1
  jl @TrivialReject

  mov AX, Screen.Clip.Y1                      { Clip the Top                 }
  cmp AX, Y                     { IF Screen.Clip.Y1 > Y THEN         }
  jle @NoTopClip                              { Calculate the difference     }
  sub AX, Y                     {   XI := Screen.Clip.Y1-Y           }
  sub Height, AX                {   DEC(Height, XI)                  }
  add Y, AX                     {   INC(Y, XI)                       }
  mul Width                                   { Increase the pointer to the  }
  add Index, AX                               {   sprite data appropriately  }
@NoTopClip:                     {   INC(Index, XI*Sprite.Width)      }

  mov AX, X                                   { Clip the Right side          }
  add AX, Width
  dec AX
  cmp Screen.Clip.X2, AX        { IF Screen.Clip.X2 < X+Width-1 THEN }
  jge @NoRightClip              { BEGIN                              }
  sub AX, Screen.Clip.X2        {   XI := X+Width-1-Screen.Clip.X2   }
  sub Width, AX                               { Reduce the width             }
  add SkippedPixels, AX                       { Increase the number skipped  }
@NoRightClip:                   { END;                               }

  mov AX, Screen.Clip.X1                      { Clip the Left side           }
  cmp AX, X                     { IF Screen.Clip.X1 > X THEN         }
  jle @NoLeftClip               { BEGIN                              }
  sub AX, X                     {   XI := Screen.Clip.X1-X           }
  add Index, AX                               { Increase the sprite offset   }
  add SkippedPixels, AX                       { Make sure we skip these      }
  sub Width, AX                               { The sprite is now narrower   }
  add X, AX                                   { Start it at the clip boundary}
@NoLeftClip:

  mov AX, Y
  add AX, Height
  dec AX                                      { Clip the bottom              }
  cmp Screen.Clip.Y2, AX        { IF Screen.Clip.Y2 < Y+Height-1 THEN }
  jge @NoBottomClip
  sub AX, Screen.Clip.Y2                      { Reduce the height            }
  sub Height, AX                {   DEC(Height, Y+Height-1-Screen.Clip.Y2)   }
@NoBottomClip:

  les DI, Screen.Buffer
  add DI, X
  mov BX, Y       { Y is an index into the ScreenY array        }
  add BX, BX      { Multipy by two because each entry is a word }
  add DI, DS:[BX+OFFSET Screen.YTable]

  push DS
  mov DX, Screen.Width
  lds SI, Sprite
  mov AX, Height
  lds SI, DS:[SI+SpriteType.Data]
  mov YI, AX
  mov AX, Width
  mov BX, SkippedPixels
  add SI, Index
  sub DX, AX
@MainLoop:                      { FOR YI := 0 TO Height-1 DO                 }
  mov CX, AX                    {  FOR XI := 0 TO Width-1 DO                 }
  shr CX, 1
  rep movsw                     {   SetPixel(XI+X, YI+Y, Sprite.Data^[Index])}
                                {   INC(Index)                               }
  adc CX, CX
  rep movsb
  add SI, BX                    { INC(Index, SkippedPixels)                  }
  add DI, DX                                  { Skip invisible pixels        }
  dec YI
  jnz @MainLoop
  pop DS
@TrivialReject:
END;

PROCEDURE DrawRLESprite(VAR Sprite : SpriteType; X, Y : INTEGER); ASSEMBLER;
VAR I : INTEGER;
ASM
  push DS
  les DI, Sprite
  mov AX, [ES:DI+SpriteType.Height]   { FOR I := 0 TO Sprite.Height-1 DO }
  mov I, AX

  mov BX, Y
  add BX, BX
  mov AX, [BX+OFFSET Screen.YTable]
  add AX, X                     { ScreenPtr := (Y+I)*Screen.Width+X }

  mov DX, [ES:DI+SpriteType.Width]
  add DX, AX

  mov BX, Screen.Width
  les SI, ES:[DI+SpriteType.Data]  { Need ES for Sprite...               }
  add SI, I
  add SI, I
  lds DI, Screen.Buffer            { Need access to DS for Screen.Buffer }
  add DX, DI
  add DI, AX

  mov AX, DS             { Swap segment registers }
  mov CX, ES
  mov DS, CX
  mov ES, AX
  mov AX, DI
  mov CH, 0              { Clear the high byte of CX                }
@YLoop:
@XLoop:
  cmp DI, DX
  jae @OuttaXLoop        { WHILE J < Sprite.Width DO                }
  mov CL, DS:[SI]        {   Count := Sprite.Data^[SpritePtr]       }
  inc SI                 {   INC(SpritePtr)                         }
  shr CL, 1              { Set the carry flag if Bit #0 is set      }
  jnc @TransparentRun

@DataRun:                { IF (Count AND 1) = 1 THEN                }
                         {   WHILE Count > 0 DO                     }
  rep movsb   { Screen.Buffer^[ScreenPtr] := Sprite.Data^[SpritePtr] }
  jmp @XLoop             {   END;                                   }

@TransparentRun:         { ELSE                                     }
  add DI, CX             {   ScreenPtr := ScreenPtr + (Count SHR 1) }
  jmp @XLoop             { END;                                     }

@OuttaXLoop:
  add AX, BX             { DI := DI + Screen.Width }
  mov DI, AX
  add DX, BX             { End of next scanline    }
  dec I                  { Next YLoop }
  jnz @YLoop
  pop DS
END;

PROCEDURE DrawRLESpriteC(VAR Sprite : SpriteType; X, Y : INTEGER); ASSEMBLER;
VAR
  I              : INTEGER;
  LeftClip, RightClip,
  Height,
  ScreenOffs,
  StartLine,
  SavedSI,
  ScreenWidth    : WORD;
ASM
  mov AX, Screen.Width     { Copy this into a local variable so that }
  mov ScreenWidth, AX      {  it will be available after we trash DS }

  les SI, Sprite
  mov CX, ES:[SI+SpriteType.Width]   { RightClip := Sprite.Width }
  mov RightClip, CX
  mov DX, ES:[SI+SpriteType.Height]  { Height := Sprite.Height   }
  mov Height, DX

  mov AX, X
  mov BX, Y
  cmp AX, Screen.Clip.X2
  jg @TriviallyReject
  cmp BX, Screen.Clip.Y2
  jg @TriviallyReject

  add CX, AX
  add DX, BX
  cmp CX, Screen.Clip.X1
  jl @TriviallyReject
  cmp DX, Screen.Clip.Y1
  jge @DontTriviallyReject
@TriviallyReject:
  jmp @GetOutOfHere                                    { Trivially Reject }
@DontTriviallyReject:

  mov DI, FALSE                  { Code := FALSE                          }
  cmp DX, Screen.Clip.Y2         { IF (Y+Height > Screen.Clip.Y2) THEN    }
  jle @DontClipBottom                 { Clip the bottom          }
  mov DX, Screen.Clip.Y2
  sub DX, Y
  inc DX
  mov Height, DX                 {   Height := Screen.Clip.Y2-Y+1         }
  mov DI, TRUE                   {   Code := TRUE                         }
@DontClipBottom:                 { END                                    }

  cmp CX, Screen.Clip.X2         { IF (X+RightClip > Screen.Clip.X2) THEN }
  jle @DontClipRight                  { Clip the right           }
  mov CX, Screen.Clip.X2
  sub CX, X
  inc CX
  mov RightClip, CX              {   RightClip := Screen.Clip.X2-X+2      }
  mov DI, TRUE                   {   Code := TRUE                         }
@DontClipRight:                  { END                                    }

  mov CX, 0                      { StartLine := 0                         }
  cmp BX, Screen.Clip.Y1         { IF (Y < Screen.Clip.Y1) THEN           }
  jge @DontClipTop                    { Clip the top             }
  mov AX, Screen.Clip.Y1
  sub AX, BX                     {   StartLine := Screen.Clip.Y1-Y        }
  mov CX, AX                          { Update the starting line }
  mov AX, CX                     {   Height    := Height-StartLine        }
  sub Height, AX                      { Reduce the height        }

  mov AX, Screen.Clip.Y1         {   Y := Screen.Clip.Y1                  }
  mov Y, AX                           { Set the new Y value      }
  mov DI, TRUE                   {   Code := TRUE                         }
@DontClipTop:                    { END                                    }
  mov StartLine, CX

  mov CX, 0                      { LeftClip := 0                          }
  mov AX, X
  cmp AX, Screen.Clip.X1         { IF (X < Screen.Clip.X1) THEN           }
  jge @DontClipLeft                   { Clip the left            }
  mov CX, Screen.Clip.X1
  sub CX, AX                     {   LeftClip := Screen.Clip.X1-X         }
  mov DI, TRUE                   {   Code := TRUE                         }
@DontClipLeft:                   { END                                    }
  mov LeftClip, CX

  or DI, DI                      { IF Code = FALSE THEN                   }
  jnz @DontTriviallyAccept            { Trivially Accept         }

  push WORD [Sprite+2]
  push WORD [Sprite]
  push X
  push Y
  call DrawRLESprite                  {   DrawRLESprite(Sprite, X, Y)          }
  jmp @GetOutOfHere
@DontTriviallyAccept:            { END                                    }

  les DI, Screen.Buffer
  lds SI, Sprite                  { DS:SI now points to Sprite }

  mov BX, Y
  add BX, BX
  mov AX, DS:[BX+OFFSET Screen.YTable]
  add AX, X
  add AX, LeftClip
  add AX, DI
  mov ScreenOffs, AX

  lds SI, DS:[SI+SpriteType.Data] { DS:SI now points to Sprite.Data }

  shl StartLine, 1
  mov SavedSI, SI
  add StartLine, SI
  mov I, 0
@ILoop:                        { FOR I := 0 TO Height-1 DO                 }
  mov BX, StartLine
  add StartLine, 2
  mov SI, DS:[BX]              {   SpritePtr := Sprite.Data^[(StartLine+I)*2]+        }
  add SI, SavedSI              {                Sprite.Data^[(StartLine+I)*2+1] SHL 8 }

  mov AX, ScreenWidth          {   ScreenPtr := (Y+I)*ScreenWidth+X+LeftClip }
  xor BX, BX                   {   J := 0                                  }
  mov DI, ScreenOffs
  add ScreenOffs, AX

  mov CH, 0
  mov CL, DS:[SI]              {   Count := Sprite.Data^[SpritePtr]        }

  inc SI                       {   INC(SpritePtr)                          }
  xor DX, DX
  shr CL, 1
  rcl DX, 1

  mov AX, BX
@ThisWhileLoop:
  add AX, CX

  cmp AX, LeftClip             {   WHILE J+(Count SHR 1) <= LeftClip DO    }
  ja @SkipThisWhileLoop        {   BEGIN                                   }

  or DX, DX                    {     IF (Count AND 1) = 1 THEN             }
  jz @DontUpdateSpritePtr            { Block of pixels }
  add SI, CX                   {        SpritePtr := SpritePtr + Count     }
@DontUpdateSpritePtr:
  mov BX, AX                   {     J := J + Count                        }
  mov CL, DS:[SI]              {     Count := Sprite.Data^[SpritePtr]      }
  inc SI                       {     INC(SpritePtr)                        }

  xor DL, DL
  shr CL, 1
  rcl DX, 1
  jmp @ThisWhileLoop           {    END                                    }
@SkipThisWhileLoop:

{ cmp AX, LeftClip             {    IF J+(Count SHR 1) > LeftClip THEN     }
  jbe @SkipSplitRun                 { If a run is split...    }

  or DX, DX                    {      IF (Count AND 1) = 1 THEN            }
  jz @SkipUpdateSpritePtr           { Block of pixels }
  mov AX, LeftClip
  sub AX, BX
  add SI, AX                   {        SpritePtr := SpritePtr + LeftClip-J}

@SkipUpdateSpritePtr:
  shr DX, 1
  rcl CX, 1         { Reassemble Count to include carry bit }

  mov AX, BX
  sub AX, LeftClip
  add AX, AX
  add CX, AX                   {       Count := Count+((J-LeftClip) SHL 1) }
@SkipSplitRun:

  mov BX, LeftClip             {    J := LeftClip                          }

  xor DX, DX
  shr CL, 1
  rcl DX, 1

  mov AX, BX
@MainWhileLoop:
  add AX, CX

  cmp AX, RightClip            {   WHILE J+(Count SHR 1) <= RightClip DO   }
  ja @SkipMainWhileLoop        {   BEGIN                                   }

  or DL, DL                    {     IF (Count AND 1) = 0 THEN             }
  jnz @PixelRun                      { Transparant run }
  add DI, CX                   {        ScreenPtr := ScreenPtr + Count     }
  mov CL, 0 { Don't execute the rep movsb }

@PixelRun:                     {     ELSE                { Block of pixels }
  rep movsb                    {       WHILE Count > 0 DO...               }
  mov BX, AX

  mov CL, DS:[SI]              { Count := Sprite.Data^[SpritePtr]          }
  inc SI                       {   INC(SpritePtr)                          }

  xor DX, DX
  shr CL, 1
  rcl DX, 1
  jmp @MainWhileLoop           {    END                                    }
@SkipMainWhileLoop:

{ cmp AX, RightClip            {   IF J+(Count SHR 1) > RightClip THEN     }
  jbe @SkipRightSplitRun              { If a run is split...    }
  or DX, DX                    {     IF (Count AND 1) = 1 THEN             }
  jz @SkipRightSplitRun               { Ignore transparent runs }
                               {     BEGIN          { Clip block of pixels }
  mov CX, RightClip                   { Just move what's left   }
  sub CX, BX                   {       Count := RightClip-J                }
  rep movsb                    {       WHILE Count > 0 DO...               }

@SkipRightSplitRun:

  inc I
  mov AX, Height
  cmp I, AX
  jb @ILoop

  mov AX, SEG @Data
  mov DS, AX
@GetOutOfHere:
END;

PROCEDURE DrawCompiledSprite(VAR Sprite : SpriteType; X, Y : INTEGER); ASSEMBLER;
ASM
  push DS
  mov BX, Y
  add BX, BX
  mov DI, WORD [Screen.YTable+BX]
  add DI, X
  mov BX, Screen.Width
  lds AX, Screen.Buffer
  add DI, AX

  les SI, Sprite
  call dword [ES:SI+SpriteType.Data]
  pop DS
END;

PROCEDURE ConvertSpriteToRLE(VAR Sprite : SpriteType);
VAR
  SpriteSrc, SpriteDest : WORD;
  NewSprite : SpriteType;
  X, Y, I   : INTEGER;
  RunCount  : INTEGER;
  RunType   : BOOLEAN;
BEGIN
  IF Sprite.SType <> STStandard THEN EXIT;
  NewSprite.Width   := Sprite.Width;
  NewSprite.Height  := Sprite.Height;
  NewSprite.DataLen := Sprite.Width*Sprite.Height*3 DIV 2 + Sprite.Height*2;
  NewSprite.SType   := STRLE;
  GETMEM(NewSprite.Data, NewSprite.DataLen);

  SpriteSrc := 0; SpriteDest := Sprite.Height*2;  { Skip past the list }
  FOR Y := 1 TO Sprite.Height DO
  BEGIN
    NewSprite.Data^[(Y-1)*2  ] := SpriteDest AND 255; { Update the list      }
    NewSprite.Data^[(Y-1)*2+1] := SpriteDest DIV 256; { They are split bytes }
    RunCount := 0;
    RunType := Sprite.Data^[SpriteSrc] <> 0;    { FALSE = Transparent }
    FOR X := 1 TO Sprite.Width DO
    BEGIN
      IF (RunType <> (Sprite.Data^[SpriteSrc] <> 0)) OR (RunCount > 126) THEN
      BEGIN
        NewSprite.Data^[SpriteDest] :=
          (RunCount SHL 1) + (BYTE(RunType) AND 1);
        INC(SpriteDest);
        IF RunType = TRUE THEN
          FOR I := SpriteSrc-RunCount TO SpriteSrc-1 DO
          BEGIN
            NewSprite.Data^[SpriteDest] := Sprite.Data^[I];
            INC(SpriteDest);
          END;
        RunCount := 0;
        RunType := Sprite.Data^[SpriteSrc] <> 0;
      END;
      INC(RunCount);
      INC(SpriteSrc);
    END;
    IF RunCount > 0 THEN
    BEGIN
      NewSprite.Data^[SpriteDest] := (RunCount SHL 1) + (BYTE(RunType) AND 1);
      INC(SpriteDest);
      IF RunType = TRUE THEN
        FOR I := SpriteSrc-RunCount TO SpriteSrc-1 DO
        BEGIN
          NewSprite.Data^[SpriteDest] := Sprite.Data^[I];
          INC(SpriteDest);
        END;
      RunCount := 0;
      RunType := Sprite.Data^[SpriteSrc] <> 0;
    END;
  END;
  KillSprite(Sprite);
  Sprite := NewSprite;
  Sprite.DataLen := SpriteDest;             { Resize the sprite data to the }
  GETMEM(Sprite.Data, Sprite.DataLen);      { exact amount needed           }
  Move(NewSprite.Data^, Sprite.Data^, Sprite.DataLen); { Copy the data      }
  KillSprite(NewSprite);
END;

PROCEDURE ConvertSpriteToCompiled(VAR Sprite : SpriteType);
VAR
  SpriteSrc,                           { Current pointer into source sprite }
  SpriteDest : WORD;                   { Current pointer into dest sprite   }
  NewSprite  : SpriteType;             { Temporary sprite                   }
  Value      : BYTE;                   { Color of current pixel             }
  X, Y       : INTEGER;                { Current pixel being compiled       }
BEGIN
  IF Sprite.SType <> STStandard THEN EXIT;
  NewSprite.Width := Sprite.Width;
  NewSprite.Height := Sprite.Height;
  NewSprite.DataLen := Sprite.Width*Sprite.Height*5;           { Worst Case }
  NewSprite.SType   := STCompiled;
  GETMEM(NewSprite.Data, NewSprite.DataLen);        { Get a chunk of memory } 

  SpriteSrc := 0; SpriteDest := 0;
  FOR Y := 0 TO Sprite.Height-1 DO                  { Step over each pixel  }
  BEGIN
    FOR X := 0 TO Sprite.Width-1 DO
    BEGIN
      Value := Sprite.Data^[SpriteSrc];             { Speedup...            }
      IF Value <> 0 THEN                        { Ignore blank pixels!      }

      IF X = 0 THEN          { Special case of no offset from start of line }
      BEGIN
        NewSprite.Data^[SpriteDest  ] := $C6;   { Opcode for MOV            }
        NewSprite.Data^[SpriteDest+1] := $05;   { MOD/RM value for 0 offset }
        NewSprite.Data^[SpriteDest+2] := Value; { Value...                  }
        INC(SpriteDest, 3);
      END
      ELSE IF X < 128 THEN                      { Can use a byte offset!    }
      BEGIN
        NewSprite.Data^[SpriteDest  ] := $C6;   { Opcode for MOV            }
        NewSprite.Data^[SpriteDest+1] := $45;   { MOD/RM value for byte ofs }
        NewSprite.Data^[SpriteDest+2] := Lo(X); { Offset                    }
        NewSprite.Data^[SpriteDest+3] := Value; { Value...                  }
        INC(SpriteDest, 4);
      END
      ELSE
      BEGIN                                     { Must use a word offset... }
        NewSprite.Data^[SpriteDest  ] := $C6;   { Opcode for MOV            }
        NewSprite.Data^[SpriteDest+1] := $85;   { MOD/RM value for word ofs }
        NewSprite.Data^[SpriteDest+2] := Lo(X); { Low byte of the offset    }
        NewSprite.Data^[SpriteDest+3] := Hi(X); { High byte of the offset   }
        NewSprite.Data^[SpriteDest+4] := Value; { Value...                  }
        INC(SpriteDest, 5);
      END;
      INC(SpriteSrc);
    END;
    IF Y < (Sprite.Height-1) THEN           { Don't do this on the last line}
    BEGIN                                   { Add DI, BX }
      NewSprite.Data^[SpriteDest  ] := $01; { Opcode for Add r/m16, r16     }
      NewSprite.Data^[SpriteDest+1] := $DF; { MOD/RM byte for WORD BX       }
      INC(SpriteDest, 2);
    END;
  END;
  NewSprite.Data^[SpriteDest] := $CB;       { Add in our RetF               }
  INC(SpriteDest);

  KillSprite(Sprite);                       { Don't need this anymore!      }
  Sprite := NewSprite;
  Sprite.DataLen := SpriteDest;             { Resize the sprite data to the }
  GETMEM(Sprite.Data, Sprite.DataLen);      { exact amount needed           }
  Move(NewSprite.Data^, Sprite.Data^, Sprite.DataLen); { Copy the data      }
  KillSprite(NewSprite);
END;

PROCEDURE SaveSprite(Sprite : SpriteType; Filename : STRING);
VAR
  F : FILE;                                   { The file             }
  Header : ARRAY[0..3] OF WORD;               { The file header      }
BEGIN
  IF Pos('.', Filename) = 0 THEN              { Add a default        }
    Filename := Filename + '.SPR';            { extension to filename}

  ASSIGN(F, Filename);                        { Set the filename     }
  REWRITE(F, 1);                              { Open the file        }
  IF IOResult <> 0 THEN EXIT;
  Header[0] := Sprite.SType;                  { Copy the variables   }
  Header[1] := Sprite.Height;                 { into the header      }
  Header[2] := Sprite.Width;
  Header[3] := Sprite.DataLen;
  BlockWrite(F, Header[0], 8);                { Write header to file }
  BlockWrite(F, Sprite.Data^, Sprite.DataLen);{ Write data portion   }
                                              { to a file            }
  CLOSE(F);                                   { Close file           }
END;

PROCEDURE LoadSprite(VAR Sprite : SpriteType; Filename : STRING);
VAR
  F : FILE;                                   { The file             }
  Header : ARRAY[0..3] OF WORD;               { The file header      }
BEGIN
  IF Sprite.Data <> NIL THEN                  { Replace an existing  }
    KillSprite(Sprite);                       { Sprite               }

  IF Pos('.', Filename) = 0 THEN              { Add a default        }
    Filename := Filename + '.SPR';            { extension to filename}

  ASSIGN(F, Filename);                        { Set the filename     }
  RESET(F, 1);                                { Open the file        }
  IF IOResult <> 0 THEN EXIT;
  BlockRead(F, Header[0], 8);                 { Write header to file }
  Sprite.SType   := Header[0];                { Copy the variables   }
  Sprite.Height  := Header[1];                { from the header      }
  Sprite.Width   := Header[2];
  Sprite.DataLen := Header[3];
  GetMem(Sprite.Data, Sprite.DataLen);        { Allocate memory      }
  BlockRead(F, Sprite.Data^, Sprite.DataLen); { Read data portion    }
                                              { from a file          }
  CLOSE(F);                                   { Close file           }
END;

PROCEDURE CopySprite(Source : SpriteType; VAR Dest : SpriteType);
BEGIN
  IF Dest.Data <> NIL THEN                    { Overwrite the sprite }
    KillSprite(Dest);

  Dest := Source;                             { Copy the header      }
  GetMem(Dest.Data, Dest.DataLen);            { Allocate some memory }
  ASM
    push DS
    les DI, Dest                              { ES:DI = Dest         }
    lds SI, Source.Data                       { DS:SI = Source.Data^ }
    mov CX, Source.DataLen                    { CX = Sprite length   }
    les DI, ES:[DI+SpriteType.Data]           { ES:DI = Dest.Data^   }

    shr CX, 1
    sbb BX, BX                                { BX = -1 if we need   }
    shr CX, 1                                 { to copy an extra byte}
    db $66; rep movsw                         { rep movsd            }
    adc CX, CX                                { CX = 1 for a word    }
    add CX, CX
    sub CX, BX                                { Add an extra byte    }
    rep movsb                                 { Finish last 0-3 pixs }

    pop DS
  END;
END;

BEGIN
END.
