; Copyright 1996 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > Sources.SprAdjSize

;;----------------------------------------------------------------------------
;; AdjustManyRows or Columns
;;----------------------------------------------------------------------------

        MakeSpriteErrorBlock InvalidRowOrCol,,InvRC
        MakeSpriteErrorBlock NoRoomToInsert,,NoIRoom

input_out_of_range
        ADR     R0, ErrorBlock_InvalidRowOrCol
        addr    r1, Title
        BL      copy_error_one          ; Always sets the V bit
        Pull    "R1-R11,PC"

no_room_to_insert
        ADR     R0, ErrorBlock_NoRoomToInsert
        addr    r1, Title
        BL      copy_error_one          ; Always sets the V bit
        Pull    "R1-R11,PC"

; ----------------------------------------------------------
Go_InsertDeleteRows
        Push    "R1-R11,R14"
        Debug   id,"Entry:",R3,R4
;
; see if reason code indicated a sprite name or sprite pointer in R2
        BLVC    findsprite              ; R2 --> sprite
        Pull    "R1-R11,PC",VS
;
; Check for insertion/deletion
;
        CMP     R4, #0
        Pull    "R1-R11,PC",EQ
        BMI     delete_many_rows
        B       insert_many_rows

; ----------------------------------------------------------
delete_many_rows
; Updated to work with new format sprites
;
; Check that the call is trying to start deleting at a row inside the sprite
        RSB     R4, R4, #0
        CMP     R3, #0
        BLT     input_out_of_range
        LDR     R6, [R2, #spHeight]
        ADD     R6, R6, #1
        CMP     R3, R6
        BGE     input_out_of_range
;
; Check that the call is trying to finish deleting at a row inside the sprite
        ADD     R3, R3, R4
        SUBS    R3, R3, #1
        BMI     input_out_of_range
        CMP     R3, R6
        BGE     input_out_of_range
;
; Check for all rows being deleted
        CMP     R4, R6
        BGE     input_out_of_range

        LDR     R5, [R2, #spWidth] ; R5 = sprite width (in words)
        ADD     R5, R5, #1
        MOV     R5, R5, LSL #2     ;   in bytes
        MUL     R0, R4, R5         ; Get length of block to delete
        LDR     R7, [R2, #spImage] ; Get offset to image R7, and mask R8
        LDR     R8, [R2, #spTrans]
        Debug   id,"In range. Details are: (W,H,I,T,size,start)",R5,R6,R7,R8,R0,R3

; Now check for mask
        CMP     R7, R8             ; If they're the same then there is no mask
        BEQ     deleting_rows_no_mask
;
; Delete the mask rows
        ;First, are we new or old format?
        LDR     R9, [R2, #spMode]
        MOVS    R9, R9, LSR #27

        BEQ     %FT10

        LDR     R5, [R2, #spWidth] ; R5 = mask width - 1 (in words)
        Push    R8
        BL      GetMaskspWidth     ; Change it 'cos we're new format
        Pull    R8
        ADD     R5, R5, #1

        MOV     R5, R5, LSL #2     ; mask row width in bytes
        MUL     R0, R4, R5         ; Get length of block to delete

10      SUB     R9, R6, R3         ; Get address of starting row (from top of sprite)
        SUB     R9, R9, #1
        MUL     R9, R5, R9
        ADD     R9, R2, R9         ; Add to mask start
        ADD     R9, R8, R9
        LDR     R10, [R1, #saFree] ; Get address of last word to move down
        ADD     R10, R10, R1
        BL      move_memory_down   ; Move the memory from [R9+R0 to R10] down to [R9 to R10-R0]
;
; Account for loss of mask rows in the sprite area block and sprite block
        LDR     R9, [R1, #saFree]  ; Move the 'free space' pointer back by size deleted in bytes
        SUB     R9, R9, R0
        STR     R9, [R1, #saFree]
        LDR     R9, [R2, #spNext]  ; Move offset to next sprite back by size in bytes
        SUB     R9, R9, R0
        STR     R9, [R2, #spNext]

;Finally redo our R5 stuff
        LDR     R5, [R2, #spWidth] ; R5 = sprite width (in bytes)
        ADD     R5, R5, #1
        MOV     R5, R5, LSL #2     ;   in bytes
        MUL     R0, R4, R5         ; Get length of block to delete
;
; Delete the image rows
deleting_rows_no_mask
        SUB     R9, R6, R3         ; Get address of starting row (from top of sprite)
        SUB     R9, R9, #1
        MUL     R9, R5, R9
        ADD     R9, R2, R9         ; Add to image start
        ADD     R9, R7, R9
        LDR     R10, [R1, #saFree] ; Get address of last word to move down
        ADD     R10, R10, R1
        BL      move_memory_down   ; Move the memory from [R9+R0 to R10] down to [R9 to R10-R0]
;
; Account for loss of mask rows in the sprite area block and sprite block
        LDR     R9, [R1, #saFree]  ; Move the 'free space' pointer back by size deleted in bytes
        SUB     R9, R9, R0
        STR     R9, [R1, #saFree]

        CMP     R7, R8             ;Only change the ptr if there is a mask
        LDRNE   R8, [R2, #spTrans] ; Move the mask pointer back by size in bytes
        SUBNE   R8, R8, R0
        STRNE   R8, [R2, #spTrans]

        LDR     R9, [R2, #spNext]  ; Move offset to next sprite back by size in bytes
        SUB     R9, R9, R0
        STR     R9, [R2, #spNext]
;
; Remove rows from data
        Debug   id, "Height, delta",R6,R4
        SUB     R6, R6, #1
        SUB     R6, R6, R4
        STR     R6, [R2, #spHeight]
;
; Exit
        CLRV
        Pull    "R1-R11,PC"

; ----------------------------------------------------------
insert_many_rows
; Updated to work with new format sprites
;
; Check that the call is trying to start inserting at a row inside the sprite
        CMP     R3, #0
        BLT     input_out_of_range
        LDR     R6, [R2, #spHeight] ; R6 = sprite height
        ADD     R6, R6, #1
        CMP     R3, R6
        BGT     input_out_of_range
;
        LDR     R5, [R2, #spWidth] ; R5 = sprite width (in bytes)
        ADD     R5, R5, #1
        MOV     R5, R5, LSL #2     ;   in bytes
        MUL     R0, R4, R5         ; Get length of block to insert
        LDR     R7, [R2, #spImage] ; Get offset to image R7, and mask R8
        LDR     R8, [R2, #spTrans]
        LDR     R9, [R1, #saFree]  ; Check that the end of the block after insertion
        ADD     R9, R9, R0
        CMP     R7, R8             ;    (allowing for mask)
        BEQ     little_skip
        LDR     R10, [R2, #spMode]
        MOVS    R10, R10, LSR #27
        ADDEQ   R9, R9, R0         ; Old format, so sprite is same size as image
        BEQ     little_skip

        Push    R5                 ;New format, so we need a quick conversion
        LDR     R5, [R2, #spWidth]
        Push    R8
        BL      GetMaskspWidth
        Pull    R8
        ADD     R5, R5, #1
        MOV     R5, R5, LSL #2
        MUL     R10, R4, R5
        ADD     R9, R9, R10
        Pull    R5                 ;End of quick conversion

little_skip
        LDR     R10, [R1, #saEnd]  ; is before the end of the block
        CMP     R9, R10
        BGT     no_room_to_insert
        Debug   id,"In range. Details are: (W,H,I,T,size,start)",R5,R6,R7,R8,R0,R3
;
; Now check for mask
        CMP     R7, R8             ; If they're the same then there is no mask
        BEQ     inserting_rows_no_mask
;
; Insert the mask rows
;                        Debug   ag,"Eh up, we've got a mask"

        ;First, are we a new or old format sprite?
        LDR     R9, [R2,#spMode]
        MOVS    R9, R9, LSR #27
;                        DebugIf NE,ag,"And it's a new format one at that!"
        BEQ     %FT10

        LDR     R5, [R2, #spWidth]
;                        DebugIf NE,ag,"Image width-1 (words) is ",R5
        Push    R8
        BL      GetMaskspWidth     ;Convert it to 1bpp values (words)
        Pull    R8
;                        DebugIf NE,ag,"Mask (1bpp) width-1 (words) is now ",R5
        ADD     R5, R5, #1
        MOV     R5, R5, LSL #2     ;(bytes)
        MUL     R0, R4, R5
10
;                        Debug   ag,"The grand total of bytes to be moved up by is ",R0
        SUB     R9, R6, R3         ; Get address of starting row (from top of sprite)
        MUL     R9, R5, R9
        ADD     R9, R2, R9         ; Add to mask start
        ADD     R9, R8, R9
;                        Debug   ag,"Moving from ",R9
        LDR     R10, [R1, #saFree] ; Get address of last word to move down
        ADD     R10, R10, R1
;                        Debug   ag,"Moving amount ",R10
        BL      move_memory_up     ; Move the memory from [R9 to R10] up to [R9+R0 to R10+R0]
;
; Account for gain of mask rows in the sprite area block and sprite block
        LDR     R9, [R1, #saFree]  ; Move the 'free space' pointer forward by size inserted in bytes
        ADD     R9, R9, R0
        STR     R9, [R1, #saFree]
        LDR     R9, [R2, #spNext]  ; Move offset to next sprite forward by size in bytes
        ADD     R9, R9, R0
        STR     R9, [R2, #spNext]

;Finally redo our R5 stuff
        LDR     R5, [R2, #spWidth] ; R5 = sprite width (in bytes)
        ADD     R5, R5, #1
        MOV     R5, R5, LSL #2     ;   in bytes
        MUL     R0, R4, R5         ; Get length of block to delete
;                        Debug   ag,"After the rehash of R5, it is (bytes) ",R5
;                        Debug   ag,"and the amount to be moved by is ",R0
;
; Insert the image rows
inserting_rows_no_mask
        SUB     R9, R6, R3         ; Get address of starting row (from top of sprite)
        MUL     R9, R5, R9
        ADD     R9, R2, R9         ; Add to image start
        ADD     R9, R7, R9
        LDR     R10, [R1, #saFree] ; Get address of last word to move up
        ADD     R10, R10, R1
        BL      move_memory_up     ; Move and clear the memory from [R9 to R10] up to [R9+R0 to R10+R0]
;
; Account for loss of mask rows in the sprite area block and sprite block
        LDR     R9, [R1, #saFree]  ; Move the 'free space' pointer forward by size deleted in bytes
        ADD     R9, R9, R0
        STR     R9, [R1, #saFree]

        CMP     R7, R8
        LDRNE   R8, [R2, #spTrans] ; Move the mask pointer forward by size in bytes
        ADDNE   R8, R8, R0
        STRNE   R8, [R2, #spTrans]

        LDR     R9, [R2, #spNext]  ; Move offset to next sprite forward by size in bytes
        ADD     R9, R9, R0
        STR     R9, [R2, #spNext]
;
; Add rows to data
        Debug   id, "Height, delta",R6,R4
        SUB     R6, R6, #1
        ADD     R6, R6, R4
        STR     R6, [R2, #spHeight]
;
; Exit
        CLRV
        Pull    "R1-R11,PC"

; ----------------------------------------------------------

Go_InsertDeleteColumns
        Push    "R1-R11,R14"
        Debug   id,"Entry:",R3,R4
;
; see if reason code indicated a sprite name or sprite pointer in R2
        BLVC    findsprite              ; R2 --> sprite
        Pull    "R1-R11,PC",VS

;
; Check for insertion/deletion
;
        CMP     R4, #0
        Pull    "R1-R11,PC",EQ
        BMI     delete_many_columns
        B       insert_many_columns

; ----------------------------------------------------------
delete_many_columns
; Updated to work with new format sprites
;
; Check that the call is trying to start deleting at a column inside the sprite
        RSB     R4, R4, #0
        CMP     R3, #0
        BLT     input_out_of_range
;
; Get log2bpp for the sprite
        Push    "R1-R2"
        LDR     R0, [R2, #spMode]
        MOV     R1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        Debug   id,"Read mode variable log2bpp for mode:",R2,R0

        ;unknown modes will return CS. It's not safe to proceed from here,
        ;so exit.
        SETV    CS

        MOVVC   R5, R2
        Pull    "R1-R2"
        Pull    "R1-R11,PC",VS

        Push    "R3-R5"                 ; We'll want these later...
;
; Make R3 = left bit to delete, R4 = right bit to delete (if deleting zeroth column
;       then also delete left-hand wastage
        ADD     R4, R3, R4
        MOV     R3, R3, LSL R5          ;       R3 = left-bit of column to delete
        MOV     R4, R4, LSL R5          ;       R4 = right-bit

        SUB     R4, R4, #1
        LDR     R5, [R2, #spLBit]       ;       Account for left-hand wastage
        CMP     R3, #0                  ;           (but only for R3 if it is non-zero)
        ADDNE   R3, R3, R5
        ADD     R4, R4, R5
        Debug   id, "Deleting from bit to bit ",R3,R4
;
; Check that the call is trying to finish deleting at a column inside the sprite
        LDR     R5, [R2, #spWidth]      ;       Get number of bits to be deleted after the
        SUB     R5, R4, R5, LSL #5      ;           start of the last word in each sprite row
        LDR     R6, [R2, #spRBit]       ;       If it is greater than the last bit used then
        CMP     R5, R6                  ;           an attempt is being made to delete past
        Pull    "R3-R5",GT
        BGT     input_out_of_range      ;           the end of the sprite row
        BLT     not_deleting_rhcolumn

; Check for all columns being deleted
        CMP     R3, #0
        Pull    "R3-R5",EQ
        BEQ     input_out_of_range

; NOTE: The following commented-out chunk of code appears to be superfluous. If preceded by
; a CMP R5,R6 (which might be the expected intention), the routine breaks, messing up when the
; deleted columns include the leftmost column (ie 0). It is left in the source purely to prevent
; someone putting it back in. WT
;        RSBEQ   R6, R6, #31             ;       If it is equal (i.e. deleting the last column)
;        ADDEQ   R4, R4, R6              ;           then delete the right-hand wastage too
;        MOVEQ   R6, #31                 ;       And set RHwastage = 0, for deletion
;        STREQ   R6, [R2, #spRBit]

not_deleting_rhcolumn
        Debug   id, "Deleting from bit to bit ",R3,R4
;
; Okay, now to delete the bits from R3 to R4 in both the image (and the mask)
;
; Delete the columns from the mask

        LDR     R6, [R2, #spTrans]      ;       Check there is a mask
        LDR     R7, [R2, #spImage]
        CMP     R6, R7
        BEQ     %FT99                   ;Branch if no mask

        ;Ok, so we have a mask, but is it new or old format?
        LDR     R5, [R2, #spMode]
        MOVS    R5, R5, LSR #27         ;Isolate the 'tell-tale' bits
        BEQ     %FT98                   ;Skip the next bit if old format

        ;First of all, 'fix' R3, R4, R5 to be 1bpp width
        LDMFD   R13, {R3-R5}            ;       Read but don't pull!
        ADD     R4, R3, R4
;        MOV     R3, R3, LSL #0          ;       R3 = left-bit of column to delete
;        MOV     R4, R4, LSL #0          ;       R4 = right-bit

        SUB     R4, R4, #1
        LDR     R5, [R2, #spLBit]       ;       Account for left-hand wastage
        CMP     R3, #0                  ;           (but only for R3 if it is non-zero)
        ADDNE   R3, R3, R5
        ADD     R4, R4, R5
        Debug   id, "Mask deleting from bit to bit ",R3,R4
98
; First a counter for the rows, and a pointer to the start of the row
        LDR     R5, [R2, #spWidth]      ;load word width of image
        LDR     R8, [R2,#spRBit]
        BL      GetMaskspWidth          ;Work out width of 1bpp mask in words
        MOV     R0, R5                  ;Put it in r0 for the Bl

        LDR     R5, [R2, #spHeight]     ;       Number of rows - 1
        ADD     R6, R6, R2

        BL      delete_columns_from_data ;      Note this returns change in size in R0
        Debug   id,"Change in size after mask op is noted as ",R0
;
; Rearrange the pointers
        LDR     R9, [R2, #spNext]
        SUB     R9, R9, R0
        STR     R9, [R2, #spNext]
        LDR     R9, [R1, #saFree]
        SUB     R9, R9, R0
        STR     R9, [R1, #saFree]
;
; Delete the columns from the image
99
        Pull    "R3-R5"                 ;       Unstack 'em this time
        ADD     R4, R3, R4
        MOV     R3, R3, LSL R5          ;       R3 = left-bit of column to delete
        MOV     R4, R4, LSL R5          ;       R4 = right-bit

        SUB     R4, R4, #1
        LDR     R5, [R2, #spLBit]       ;       Account for left-hand wastage
        CMP     R3, #0                  ;           (but only for R3 if it is non-zero)
        ADDNE   R3, R3, R5
        ADD     R4, R4, R5
        Debug   id, "Image deleting from bit to bit ",R3,R4

; First a counter for the rows, and a pointer to the start of the row
        LDR     R0, [R2, #spWidth]      ;load word width of image
        LDR     R5, [R2, #spHeight]     ;       Number of rows - 1

        LDR     R6, [R2, #spImage]      ;       Source for deletion
        ADD     R6, R6, R2

        LDR     R8, [R2,#spRBit]
        BL      delete_columns_from_data ;      Note this returns change in size in R0
        Debug   id,"Change in size after image op is noted as ",R0
;
; Rearrange the pointers
        LDR     R8, [R2, #spImage]
        LDR     R9, [R2, #spTrans]
        CMP     R8, R9
        SUBNE   R9, R9, R0
        STRNE   R9, [R2, #spTrans]
        LDR     R9, [R2, #spNext]
        SUB     R9, R9, R0
        STR     R9, [R2, #spNext]
        LDR     R9, [R1, #saFree]
        SUB     R9, R9, R0
        STR     R9, [R1, #saFree]
;
; Completed the image/mask.
; Now adjust number of columns/width in words/LBit/RBit etc.
        LDR     R7, [R2, #spWidth]      ;       R7 = width in bits - 32
        MOV     R7, R7, LSL #5
        LDR     R5, [R2, #spLBit]       ;       R5 = First bit column used
        LDR     R6, [R2, #spRBit]       ;       R6 = Number of bit columns used - 1
        ADD     R6, R6, R7
        ADD     R7, R7, #32             ;       R7 = width in bits
        Debug   id, "Width (bits), LBit, columns used:",R7,R5,R6
        CMP     R3, #0                  ;       Change LBit if deleted first column
        STREQ   R3, [R2, #spLBit]
        Debug   id, "Last bit column before deletion, after LH wastage (perhaps):",R6
        SUB     R8, R4, R3              ;       R8 = number of bit columns deleted
        ADD     R8, R8, #1
        SUB     R6, R6, R8              ;       R6 = last bit column used after columns deleted
        Debug   id, "Last bit column after deletion:",R6
        AND     R8, R6, #31             ;       R8 = RBit
        STR     R8, [R2, #spRBit]
        MOV     R6, R6, LSR #5          ;       R6 = number of words used - 1
        STR     R6, [R2, #spWidth]
        Debug   id, "RBit and Words-1:",R8,R6
;
; Exit
        CLRV
        Pull    "R1-R11,PC"
; ----------------------------------------------------------
insert_many_columns
;
; Check that the call is trying to start inserting at a column inside the sprite
        CMP     R3, #0
        BLT     input_out_of_range
;
; Get log2bpp for the sprite
        Push    "R1-R2"
        LDR     R0, [R2, #spMode]
        MOV     R1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        Debug   id,"Read mode variable log2bpp for mode:",R2,R0
        SETV    CS
        MOVVC   R5, R2
        Pull    "R1-R2"
        Pull    "R1-R11,PC",VS
;
; Make R3 = left bit to start inserting at, R4 = number of bits to insert
        MOV     R3, R3, LSL R5          ;       R3 = left-bit of column to delete
        LDR     R6, [R2, #spLBit]       ;       Account for left-hand wastage
        ADD     R3, R3, R6
        MOV     R4, R4, LSL R5          ;       R4 = number of bits
;
; Get change in width in words of the new sprite versus the old sprite
        LDR     R5, [R2, #spRBit]       ;       Check the bit to insert at is inside the sprite
        LDR     R6, [R2, #spWidth]
        ADD     R6, R5, R6, LSL #5      ;       R6 = current right-most bit column + 1
        ADD     R6, R6, #1
        Debug   id,"Insert at bit, Last bit=",r3,r6
        CMP     R3, R6                  ;       If this is before the insertion point then trying
        BGT     input_out_of_range      ;           to insert off the end of the sprite
        ADD     R5, R4, R5              ;       Work out the right-most bit after insertion
        MOV     R5, R5, LSR #5          ;       Find out if it intrudes on a new word or not
        LDR     R6, [R2, #spHeight]     ;       Find total number of words extra required for image
        ADD     R6, R6, #1
        MUL     R5, R6, R5
        LDR     R6, [R2, #spImage]      ;       More room is required if there is also a mask
        LDR     R0, [R2, #spTrans]
        MOV     R7, R5                  ;       No mask, so only 1 times the extra space is needed
        CMP     R6, R0
        BEQ     skip_past               ;       No mask, so skip the next, mask-only, bit.

        ;There is a mask, so more memory needs to be allocated...
        LDR     R8, [R2, #spMode]       ;
        MOVS    R8, R8, LSR #27         ; Ah, but is it a new or old format sprite?
        MOVEQ   R7, R5, LSL #1          ;       Old format, so 2 times the extra space is needed,
        DebugIf EQ,ag,"Old format mask, so R7,R5 are",R7,R5
        BEQ     skip_past               ;       and we can skip the new format gumph

        Push    "R5-R6,R8"              ; Well, it must be new format then!
        Debug   ag,"New format mask."
        LDR     R5, [R2, #spWidth]
        BL      GetMaskspWidth          ; Returns R5=width of 1bpp mask, with R8=new spRBit

        Push    "R1-R2"
        LDR     R0, [R2, #spMode]
        MOV     R1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        Debug   id,"Read mode variable log2bpp for mode:",R2,R0

        ADD     R5, R8, R4, LSR R2      ; R5 = rightmost bit after insertion
        Pull    "R1-R2"

        MOV     R5, R5, LSR #5          ; Convert it back to words
        LDR     R6, [R2, #spHeight]
        ADD     R6, R6, #1
        MUL     R5, R6, R5              ; R5 = total extra memory required due to mask insertions
        Debug   ag,"Extra space required for mask (words)=",R5
        ADD     R7, R7, R5              ; Add this onto the memory required for the sprite insertions
        Debug   ag,"Total space required (words)=",R7
        Pull    "R5-R6,R8"

;
; Check there is enough spare room for the extra columns
skip_past
        Debug   ag,"Here, R7 = total space required (words) =",R7
        Debug   ag,"      R5 = sprite space requird (words) =",R5
        MOV     R0, R7                  ;       Don't take our R7 value away!
        LDR     R8, [R1, #saFree]       ;       Get last byte required
        ADD     R7, R8, R7, LSL #2
        LDR     R8, [R1, #saEnd]        ;       Check it is inside the sprite area
        Debug   id,"NewFree,End=",r7,r8
        CMP     R7, R8
        BGT     no_room_to_insert       ;       If it isn't then give an error
        MOV     R7, R0                  ;       Grab our R7 value back
;
; Start insertion stage by moving the mask and rest of sprites above expanded image
        Debug   id, "Inserting n bits starting at:",R4,R3
        Debug   id, "Requires extra memory of words:",R5
        MOV     R0, R5, LSL#2           ;       Bytes to move memory up by (ie amount to be inserted in image)
        LDR     R9, [R2, #spTrans]      ;       Memory to move up (start)
        CMP     R9, R6                  ;       If no mask then insert before next sprite
        LDREQ   R9, [R2, #spNext]
        ADD     R9, R9, R2
        LDR     R10, [R1, #saFree]      ;       Memory to move up (end)
        ADD     R10, R10, R1
        BL      move_memory_up
        Debug   ag,"Moved memory up above image insertion point."
;
; Now insert columns into the image
        Push    "R1,R5"
        LDR     R5, [R2, #spHeight]
        LDR     R1, [R2, #spWidth]      ;       Required for the insert_columns routine
        Debug   ag,"Inserting columns in data. Height (lines), width (words) =",R5,R1
        BL      insert_columns_in_data  ;       Insert R4 bit columns in image R6 at column R3. No rows-1 = R5
        Debug   ag,"Inserted columns in image."
        Pull    "R1,R5"
;
; Adjust the pointers
        MOV     R9, R7                  ;       Again, keep our R7 safe
        LDR     R7, [R2, #spNext]       ;       Adjust offset to next sprite
        ADD     R7, R7, R0
        STR     R7, [R2, #spNext]
        LDR     R7, [R1, #saFree]       ;       Adjust offset to free block of sprite area
        ADD     R7, R7, R0
        STR     R7, [R1, #saFree]
        LDR     R7, [R2, #spTrans]      ;       If no mask then don't adjust the mask pointer
        Push    R4
        CMP     R6, R7
        BEQ     %FT99                   ;       Skip the next bit if no mask!
        ADD     R6, R7, R0
        STR     R6, [R2, #spTrans]
        MOV     R7, R9                  ;       Again, grab our R7 back
;
; Move other sprites up beyond expanded mask
        Debug   ag,"Well, we'd better do the mask now..."
        Debug   ag,"R7 and R5 are",R7,R5
        SUB     R0, R7, R5              ;       R0 is the amount to move the mask by (total-sprite)
        MOV     R0, R0, LSL #2          ;       Words to bytes conversion
        Debug   ag,"So, amount (bytes) to move mask up by is",R0
        LDR     R9, [R2, #spNext]       ;       Memory to move up (start)
        ADD     R9, R9, R2
        LDR     R10, [R1, #saFree]      ;       Memory to move up (end)
        ADD     R10, R10, R1
        BL      move_memory_up
        Debug   ag,"Moved memory up above mask insertion point."
;
; Insert columns into the mask
        LDR     R7, [R2, #spRBit]       ;       Keep spRBit safe!
        Push    "R1, R7-R8"

        LDR     R5, [R2, #spMode]
        MOVS    R5, R5, LSR #27         ; New or old format sprite?
        LDREQ   R1, [R2, #spWidth]      ;       Old format
        DebugIf EQ,ag,"Old format sprite"
        BEQ     next_bit                ; Skip the next chunk if sprite is old format

        Debug   ag,"New format sprite"
        Push    "R0,R2"
        LDR     R0, [R2, #spMode]
        MOV     R1, #VduExt_Log2BPP
        SWI     XOS_ReadModeVariable
        Debug   id,"Read mode variable log2bpp for mode:",R2,R0
        MOV     R4, R4, LSR R2      ; R4 converted to 1 bpp
        MOV     R3, R3, LSR R2
        Pull    "R0,R2"

        LDR     R5, [R2, #spWidth]
        BL      GetMaskspWidth
        Debug   ag,"spWidth of mask is",R5
        Debug   ag,"spRBit of mask is",R8
        MOV     R1, R5                  ;       Needed for insert_columns routine
        STR     R8, [R2, #spRBit]

next_bit
        LDR     R5, [R2, #spHeight]
        Debug   ag,"just before inserting columns, spWidth of mask is",R1
        Debug   ag,"just before inserting columns, spRBit of mask is",R8
        BL      insert_columns_in_data  ;       Insert R4 bit columns in mask R6 at column R3. No rows-1 = R5
        Debug   ag,"Inserted columns in mask."
        Pull    "R1, R7-R8"
        STR     R7, [R2, #spRBit]
;
; Adjust the pointers
        LDR     R7, [R2, #spNext]       ;       Adjust offset to next sprite
        ADD     R7, R7, R0
        STR     R7, [R2, #spNext]
        LDR     R7, [R1, #saFree]       ;       Adjust offset to free block of sprite area
        ADD     R7, R7, R0
        STR     R7, [R1, #saFree]
;
; Exit
99
;
; Change data in the sprite block
        Pull    R4
        LDR     R9, [R2, #spRBit]       ;       Work out right-most bit after insertion
        ADD     R9, R4, R9
        LDR     R10, [R2, #spWidth]     ;       R10 = words used -1
        ADD     R10, R10, R9, LSR #5
        STR     R10, [R2, #spWidth]
        AND     R9, R9, #31             ;       R9 = last bit used
        STR     R9, [R2, #spRBit]
        CLRV
        Pull    "R1-R11,PC"
; ----------------------------------------------------------
; Move the memory from [R9+R0 to R10] down to [R9 to R10-R0]
move_memory_down
        Push    "R1,R3,R9,R14"
        ADD     R1, R9, R0
        Debug   id,"Moving memory down from,to,finish:",R1,R9,R10
01
        CMP     R1, R10
        BGE     %FT02
        LDR     R3, [R1], #4
        STR     R3, [R9], #4
        B       %BT01
02
        Debug   id,"Moved memory down. From,to,finish are now:",R1,R9,R10
        Pull    "R1,R3,R9,PC"

; --------------------------------------------------------
; Move the memory from [R9 to R10] up to [R9+R0 to R10+R0]
move_memory_up
        Push    "R1,R3,R4,R9,R14"
        ADD     R1, R10, R0
        Debug   id,"Moving memory up from,to,finish:",R10,R1,R9
        CMP     r9, r10
01
        LDRCC   R3, [R10, #-4]!
        STRCC   R3, [R1, #-4]!
        CMP     r9, r10
        BCC     %BT01
        MOV     lr, #0
        CMP     r9, r1
02
        STRCC   lr, [r1, #-4]!
        CMP     r9, r1
        BCC     %BT02

        Debug   id,"Moved memory up. From,to,finish are now:",R10,R1,R9
        Pull    "R1,R3,R4,R9,PC"

; --------------------------------------------------------
; Delete columns from an image/mask
delete_columns_from_data
;       R0 = Width of image/mask on entry, change in size on exit
;       R5 = Number of rows in mask
;       R6 = Source for deletion
;       R8 = spRBit
        Push    "R1-R11,R14"
        Push    R0                      ;push it separately
        Push    R8                      ;this separate too
        MOV     R7, R6                  ;       Destination for deletion = source
        Debug  id,"Start of line with R0, start, end, source, dest, R8:",R0,R3,R4,R6,R7,R8
;
; Now for all the rows...
01
;
; Counter to see how many bits have been copied
        MOV     R10, #0
;
; Transfer source to destination until at first word in the row to worry about
        SUBS    R9, R3, #32
02
        BMI     %FT99
        LDR     R8, [R6], #4            ;       Copy whole word from source to destination
        STR     R8, [R7], #4
        ADD     R10, R10, #32           ;       Increase 'bits copied' by 1 word = 32 bits
        SUBS    R9, R9, #32             ;       Decrement counter by 1 word = 32 bits
        B       %BT02                   ;       Loop until all the whole words have been done
;
; Now load the source word at the word including the first column to delete
99
        LDR     R8, [R6]                ;       R8 = word including first column to delete
        RSB     R9, R9, #0              ;       R9 = number of unwanted bits in R8
        MOV     R8, R8, LSL R9          ;       Clear the unwanted bits
        MOV     R8, R8, LSR R9
;
; Skip forward along the source until at the last column to delete
        SUB     R10, R4, R10            ;       R10 = bits left before last bit to copy in row
02
        CMP     R10, #32                ;       If less than 32 then already at the right place
        BLT     %FT99
        ADD     R6, R6, #4              ;       Else move on the source pointer
        SUB     R10, R10, #32           ;           and remove 1 word from the bits left counter
        B       %BT02
;
; Now at the word which includes the last column to delete. Bit twiddling from now on
99
;
; R8 = combination of word including first column to delete and last column to delete
;
        LDR     R11, [R6], #4
        MOV     R11, R11, LSR R10       ;       Remove unwanted bits from the front
        MOV     R11, R11, LSR #1
        RSB     R9, R9, #32             ;       Shift it up past the 32-R9 bits correct in R8
        ORR     R8, R8, R11, LSL R9
;
; If new R9=bottom 5 bits of R3 is bigger than R4 then we need to store this word and move on
        Debug   id,"Middle with R9,R10,R6,R7",R9,R10,R6,R7
        CMP     R9, R10                 ;       Did the last shift shift bits out of the top of R8?
        STRGT   R8, [R7],#4             ;       If so then store the word
        RSBGT   R9, R9, #32             ;          Get bits shifted out into bottom of R8
        MOVGT   R8, R11, LSR R9
        ADDGT   R9, R9, R10             ;          R9 = number of valid bits in R8
        RSBGT   R9, R9, #31
        ADDLE   R9, R9, #31             ;       Else R9 = number of valid bits in R8
        SUBLE   R9, R9, R10
;
; Now we have considered all the words from the start of the row to the one including the last deleted column
        MOV     R10, R4, LSR #5         ;       R10 = number of source words examined - 1
        LDR     R0, [R13, #4]           ;       Read R0 directly off the stack
        Debug   id,"R0 read off stack as ",R0
        MOV     R11, R0                 ;       R11 = number of source words in a row - 1
02
        CMP     R10, R11                ;       While source words examined < source words in a row
        BGE     %FT99
        LDR     R0, [R6], #4            ;       R0 = next source word
        ORR     R8, R8, R0, LSL R9      ;       Add in bottom bits above the R9 valid bits
        STR     R8, [R7], #4            ;       Store R8 in destination
        RSB     R9, R9, #32             ;       New R8 = top R9 bits of R0
        MOV     R8, R0, LSR R9
        RSB     R9, R9, #32
        ADD     R10, R10, #1            ;       Source words examined ++
        B       %BT02                   ;       End while
;
; Finished loop. Any valid bits left over?
99
        LDR     R10, [R13]              ; read R8 off the stack into R10
        Debug   id,"Read R8 off stack into R10 as",R10
        RSB     R10, R10, #31
        CMP     R9, R10
        STRGT   R8, [R7], #4
        Debug   id,"End of line with line, valid bits, data, source, dest:",R5,R9,R8,R6,R7
;
; Loop for all the rows in the sprite image/mask
        SUBS    R5, R5, #1
        BPL     %BT01
;
; Finished all the deletion. Note it must still shuffle down the memory from the end of
; the old image/mask to the end of the new image/mask
        Pull    R8
        Pull    R0                      ;Need to do this to restore stack
        SUB     R0, R6, R7
        MOV     R9, R7
        LDR     R10, [R1, #saFree]
        ADD     R10, R10, R1
        BL      move_memory_down
;
; Return R0 = change in size of data
        Pull    "R1-R11,PC"
; --------------------------------------------------------
; Insert columns from an image/mask
insert_columns_in_data
;       R1 = spWidth
;       R5 = Number of rows in mask/image - 1
;       R6 = Offset from R2 of source for insertion
;       R3 = bit column to insert before
;       R4 = number of columns to insert
;       R0 = change in length of image/mask in bytes after insertion
        Push    "R0-R12,R14"
        Debug   id,"Entry:R0,R3,R4,R5,R6",R0,R3,R4,R5,R6
;
; Get R6 = source pointer to just after the end of the last row
        ADD     R9, R5, #1
        MOV     R10, R1
        ADD     R10, R10, #1
        MOV     R10, R10, LSL #2        ;R10 = spWidth in bytes
        MLA     R6, R10, R9, R6
;
; Get R7 = destination pointer to just after the end of the finished sprite
        ADD     R6, R6, R2
        ADD     R7, R6, R0
        Debug  id,"Start of line with start,number, source, dest:",R3,R4,R6,R7
;
; Make R3 be the bit column in the destination just after the end of the inserted columns
        ADD     R3, R3, R4
;
; Now for all the rows...
02
;
; R10 = Bit number of start of destination word pointed at [R7-4]. R11 = 32- (new RBit after insertion + 1) = waste bits in word
        MOV     R10, R1
        LDR     R11, [R2, #spRBit]
        ADD     R10, R11, R10, LSL #5
        ADD     R10, R10, R4
        AND     R11, R10, #31
        ADD     R11, R11, #1
        RSB     R11, R11, #32
        BIC     R10, R10, #31
;
; R9 = number of bits in R8 which will be valid
        LDR     R9, [R2, #spRBit]
        ADD     R9, R9, #1
;
; Get the source word on the current line into R8 Its bottom R9 bits are needed/valid.
        Debug   id,"Entry to first loop.source,dest,flag,r9,r10,r11:",R6,R7,R12,R9,R10,R11
01
; Registers have the following meaning at this point:
; r3 = 1st bit column after inserted section
; r4 = width of inserted section
; r6 = source word + 4
; r7 = destination word + 4
; r9 = number of LSBits at r6 unused as yet (1 to 32)
; r10 = 1st column of destination word
; r11 = amount of MSBits to zero out of destination word

        LDR     R8, [R6, #-4]!
;
; Make R8 have its top R9 bits valid, all other bits 0
        RSB     R9, R9, #32             ;       R9 = 32 - x
        MOV     R8, R8, LSL R9          ;       Shifts bits [0,x-1] up to [32-x,32-x+x-1]=[32-x,31]
        RSB     R9, R9, #32             ;       Restore R9
;
; Read in the bits from the word before it, to make all of R8 valid (ignores LH wastage)
        LDR     R14, [R6, #-4]
        ORR     R8, R8, R14, LSR R9
;
; R8 now contains the last 32 bits of pixel bit data from the row of the sprite
; Shift it right to allow for RH wastage in destination sprite
        MOV     R8, R8, LSR R11
;
; If we have shifted out some of this word then move source
; pointer forward again to reread this word next time
        ADD     R9, R9, R11
        CMP     R9, #32
        ADDGT   R6, R6, #4              ;       Have shifted R14-32 bits out. Hence move source pointer back
        SUBGT   R9, R9, #32             ;           so we reread it next time.
;
; Loop until we have got a word which has some bits of inserted columns in it
        CMP     R3, R10
        STRLE   R8, [R7, #-4]!
        SUBLE   R10, R10, #32
        MOVLE   R11, #0
        BLE     %BT01
        Debug   id,"First loop complete.source,dest,flag,r10,R3,R9:",R6,R7,R12,R10,R3,R9

; Registers have the following meaning at this point:
; r3 = 1st bit column after inserted section
; r4 = width of inserted section
; r6 = source word
; r7 = destination word
; r8 = 32 source bits extracted and ready to use from previous step aligned for destination
; r9 = number of LSBits at r6 unused as yet (1 to 32)
; r10 = 1st column of destination word

        ; Mask off bottom (r3 AND 31) bits for hole
        ANDS    r14, r3, #31
        MOVEQ   r14, #32
        MOV     r8, r8, LSR r14
        MOV     r8, r8, LSL r14

        ; Move back a word if all of this word has been masked off
        ADD     r9, r9, r14
        CMP     r9, #32
        ADDGT   r6, r6, #4

        ; Remove the bits which have been blanked from the number of bits to insert
        SUB     r14, r4, r14

        ADD     r14, r14, #32
        B       %FT03

01
        STR     R8, [R7, #-4]!          ;       Store another word
        MOV     R8, #0
        SUB     R10, R10, #32
03
        SUBS    R14, R14, #32           ;       Decrease counter of bits inserted
        BGT     %BT01

        SUB     r14, r3, r4             ; start column of insertion
        ANDS    r14, r14, #31           ; distance into source word

        ; If extracts at least a bit from source word then do so
        RSBNE   r14, r14, #32
        LDRNE   r11, [r6, #-4]!
        MOVNE   r11, r11, LSL r14
        ORRNE   r8, r8, r11, LSR r14

        ; Store it away
        STR     r8, [r7, #-4]!
        SUBS    r10, r10, #32

; Keep copying words of data until we have finished the row
01
        BLT     %FT99
        LDR     R8, [R6, #-4]!
        STR     R8, [R7,#-4]!
        SUBS    R10, R10, #32
        B       %BT01
;
; Finished a row, so loop for all rows
99
        Debug   id,"Finished Row with source,dest:",R5,R6,R7
        SUBS    R5, R5, #1
        BPL     %BT02
;
; Finished all the rows. Exit time.
        Debug   id,"Finished the rows with source,dest:",R6,R7
        Pull    "R0-R12,PC"


; *****************************************************************************
;
;       GetMaskspWidth - convert spWidth for data to spWidth for mask (1bpp masks)
;
;   NOTE: This routine should be identical to the same routine in VduGrafH except it does
;         not return the updated PSR
;
;       Internal routine.
;
; in:   R5 = spWidth (ie width in words-1)
;       (expects R2->sprite)
;
; out:  R5 = spWidth (words -1) for mask data
;       R8 = Last bit (spRBit) used in mask data

; should only be called for new format sprites, but will cope with old too

GetMaskspWidth ROUT
        Push    "R0, LR"
        Debug   ag,"Entered GetMaskspWidth with R5,R8",R5,R8

        LDR     LR, [R2, #spMode]       ; fetch the sprite mode
        MOVS    LR, LR, LSR #27         ; isolate the sprite type and test for =0

        Pull    "R0, PC",EQ         ; if an old format sprite, return R5 unchanged

        ; treat any T>max sprites as 32bpp
        CMP     LR, #SpriteType_MAX
        MOVCS   LR, #SpriteType_Substitute

        ; bugfix 9/8/93: get log2bpp this way
        ADRL    R0, NSM_bpptable
        LDRB    LR, [R0, LR]            ; get the log2bpp to LR

        RSB     LR, LR, #5              ; and change to 5-log2bpp

        MOV     R5, R5, LSL LR          ; number of pixels for full words

        RSB     LR, LR, #5              ; now switch back to log2bpp
        LDR     R0, [R2, #spRBit]
        ADD     R0, R0, #1
        ADD     R5, R5, R0, LSR LR

        ANDS    LR, R5, #&1F            ; fit exactly in a number of words ?
        SUB     R8, LR, #1              ; alter the last bit used for the mask data
                                        ; fix bug MED-01130....
        AND     R8, R8, #&1F            ; ....bring back into range 00-1F (may be -1 here)
        MOVNE   LR, #1                  ; if not, add an extra word
        ADD     R5, LR, R5, LSR #5      ; add the whole number of words
        SUB     R5, R5, #1              ; returns as words-1

        Debug   ag,"Left GetMaskspWidth with R5,R8",R5,R8

        Pull    "R0, PC"

NSM_bpptable
        ; note, yes - I know this could be type-1, but at some point some new type
        ; will break the relationship so it's a table from day 1 to cope with this
        DCB     99,  0, 1, 2,  3,   4,   5
        ;    T= 0    1  2  3   4    5    6
        ; cols= old, 2, 4, 16, 256, 32k, 16M
        ALIGN

        END