Example 3
Better compressor
and
multi-format loader

 

In the last part I said, "So we shall, in the next part, propose a new format that will make the compression even more efficient."

So, then, how can we make the compression more efficient? Remember that our compression was good on solid areas of colour but a bit rubbish when it came down to dithered or complicated images. Consider:

  HEADER:  (26 bytes)-"BudgieScrn (compressed 2)"
           <cr>

  MODE:    (1 byte)-Screen mode.
  PALETTE: (16 * 6 bytes)-Bytes giving R, G & B components of
           first flash colour.
           Bytes giving R, G, & B components of second flash
           colour.

  IMAGE DATA:
           If byte = 255, then loop data follows. Next byte
           is the colour. Third byte is a count (0 - 254)
           of how many pixels of this colour to fill in.

           If byte <> 255, then byte is regular screen
           memory. Just stick it straight to the screen...
Immediately, two things should be noticed:
  1. If the legitimate pair is colour 15, then the byte will be 255 causing a loop. Thus, in a sequence of aligned doubles of colour 15 with something in between, it could begin to get wasteful. However such a sequence seems unlikely. If the colour 15 double extends for further pixels, a loop would be required.
  2. This will better cope with dithered images, as two pixels will therefore take one byte instead of four (previously, loop count and colour for each pixel).
It sounds horribly complex doesn't it? However in reality all we need is a look-ahead and something to fiddle occurances of colour 15 doubles. For a change, we shall present the save code in BASIC because it is easier to follow (and additionally I have not coded an assembler version yet! :-) ). As usual, the loader is more or less a reverse of the saver, and that is in assembler.

The program is commented, so shouldn't need any further explanation. Converting it to assembler is left as an exercise for the reader.

REM >Ssaver2
REM Compress sprites using BudgieSoft compression algorithm
REM (type 2).
REM
REM by Richard Murray Winter 1997
REM
REM Downloaded from: http://www.heyrick.co.uk/assembler/

:
ON ERROR PRINT REPORT$+" at "+STR$(ERL/10) : END
:
REM Open output file (put your own path here, or read from
REM command line).
x%=OPENOUT("IDEFS::Willow.$.WWW.WWWsite.assembler.sw.screen2/raw")
:
REM Write out the identification header...
BPUT#x%, "BudgieScrn (compressed 2)"
:
REM ...and the current screenmode.
BPUT#x%, MODE
:
REM Build up palette entries, and write out.
FOR loop% = 0 TO 15
  SYS "OS_ReadPalette", loop%, 16 TO ,,f%, s%
  r1% = (f% >> 8) AND 255      : REM First flash colour
                                 REM red component
  g1% = (f% >> 16) AND 255     : REM First flash colour
                                 REM green component
  b1% = (f% >> 24) AND 255     : REM First flash colour
                                 REM blue component
  r2% = (s% >> 8) AND 255      : REM Second flash colour
                                 REM red component
  g2% = (s% >> 16) AND 255     : REM Second flash colour
                                 REM green component
  b2% = (s% >> 24) AND 255     : REM Second flash colour
                                 REM blue component
  BPUT#x%, r1% : BPUT#x%, g1% : BPUT#x%, b1%
  BPUT#x%, r2% : BPUT#x%, g2% : BPUT#x%, b2%
NEXT
:
REM Get our screen sizes. This is not written to file. The
REM loader will have to figure out it's own screen dimensions.
REM Then switch off the cursor.
DIM b% 19
b%!0=149 : b%!4=7 : b%!8=-1
SYS "OS_ReadVduVariables", b%, b%+12
base%=b%!12 : length%=b%!16 :end%=base%+length%
SYS "XOS_RemoveCursors"
:
:
REM Now read from base% to end% (via posn%) calculating the
REM data, writing it out to file as necessary.
:
posn% = base%
REPEAT
  REM Read four pixels (two bytes) at a time.
  REM If they match, begin a loop.
  col1% = posn%?0
  col2% = posn%?1
  IF col1% = col2% THEN
    REM They match, so...
    BPUT#x%, 255
    BPUT#x%, col1%
    counter% = 0
    REPEAT
      col1% = posn%?0
      IF col1% = col2% THEN
        counter%+=1
        posn%+=1
      ENDIF
    UNTIL counter% = 255 OR (col1% <> col2%)
    BPUT#x%, counter%
  ELSE
    REM Otherwise write out pixels.
    BPUT#x%, col1%
    IF col1% = 255 THEN
      REM A little hack for colour 15 double (byte = 255).
      BPUT#x%, col1%
      BPUT#x%, 0
    ENDIF
    posn% += 1
  ENDIF
  REM Until end.
UNTIL posn% >=end%
:
REM Close file and exit.
CLOSE#x%
END
Apart from a few little changes in the loader routine, it is very similar to the existing loader. The differences, mainly, are:
  .main_loop
    Read a byte.
    Is it '255'?
      If so, branch with link to read_loop.
      Write byte (pixel pair) to screen.
    Look to see if we are at the end of screen.
      If not, branch to main_loop.

    Close and exit etc etc.

  .read_loop
    Read colour byte.
    Read length byte.
  .loop2
    Subtract 1 from length.
    Write colour byte to screen.
    Length is zero?
      If not, branch to loop2.
    End of screen?
      If not, branch to main_loop.
    Otherwise exit.
I'll leave you to compare the above flow with the program itself. It is commented so you shouldn't have too many problems.

Download example: ssaver2.basic

 

 

To current date there is no type 2 saver in assembler. I use the BASIC code mentioned previously. So instead I will treat you to a loader that handles all three versions.

The code is commented, but it is not split into annotated chunks as before. You should be able to follow what is going on.

REM >Sloader2 BudgieSoft screen loader (all versions)
REM Version 0.21
REM
REM by Richard Murray Winter 1997
REM
REM Downloaded from: http://www.heyrick.co.uk/assembler/

:
ON ERROR PRINT REPORT$+" at "+STR$(ERL/10) : END
:
PROCassemble
OSCLI("Save <Obey$Dir>.Loader "+STR$~(code%)+" "+STR$~(O%))
OSCLI("SetType <Obey$Dir>.Loader &FFC")
END
:
DEFPROCassemble
  DIM code% &1000
  FOR pass% = 4 TO 6 STEP 2
    P%=0
    O%=code%
    [ OPT pass%

      MOV    R10, R14
      MOV    R0, #&4C
      SWI    "XOS_Find"
      BVS    exit
      MOV    R5, R0

      ; Skip header
      MOV    R8, #0
    .skip_header
      MOV    R1, R5
      BL     read_byte
      ADD    R8, R8, #1
      CMP    R8, #23
      BLT    skip_header

      MOV    R1, R5
      BL     read_byte

      CMP    R0, #10      ; Newline - version 0.
      BEQ    version_zero

      CMP    R0, #49      ; '1' - version 1.
      BEQ    version_one

      CMP    R0, #50      ; '2' - version 2.
      BEQ    version_two

      ADR    R0, unknown_string
      SWI    "OS_PrettyPrint"
      SWI    "OS_NewLine"
      SWI    "OS_NewLine"
      B      exit

    .unknown_string
      EQUS   "Unable to load this screen - the format"
      EQUS   " has not been recognised..."
      EQUB   0
      ALIGN


    ; *** VERSION ZERO HANDLER ***

    .version_zero
      B      load_old_format


    ; *** VERSION ONE HANDLER ***

    .version_one
      MOV    R1, R5
      BL     read_byte
      BL     read_byte

      B      load_old_format


    ; *** VERSION TWO HANDLER

    .version_two
      MOV    R1, R5
      BL     read_byte
      BL     read_byte

      B      load_new_format



    ; *** OLD FORMAT LOADER ***

    .load_old_format
      BL     switch_screen_mode
      BL     get_screen_info
      BL     load_palette

    .old_loop
      MOV    R1, R5
      BL     read_byte
      MOV    R4, R0
      BL     read_byte
    .old_loop2
      SUB    R0, R0, #1
      STRB   R4, [R2, #1]!
      CMP    R0, #0
      BGT    old_loop2
      CMP    R2, R3
      BLT    old_loop

      B      exit



    ; *** NEW FORMAT LOADER ***

    .load_new_format
      BL     switch_screen_mode
      BL     get_screen_info
      BL     load_palette

    .new_loop
      MOV    R1, R5
      BL     read_byte
      CMP    R0, #255
      BLEQ   read_loop
      STRB   R0, [R2, #1]!
      CMP    R2, R3
      BLT    new_loop

      B      exit



    ; *** RESOURCES ***

    .switch_screen_mode
      MOV    R12, R14
      MOV    R0, #&87
      SWI    "XOS_Byte"
      MOV    R1, R5
      BL     read_byte
      CMP    R0, R2
      SWINE  &100 + 22
      SWINE  "OS_WriteC"

      MOV    PC, R12

    .get_screen_info
      ADR    R0, vdu_block
      ADD    R1, R0, #12
      SWI    "OS_ReadVduVariables"
      LDR    R2, [R1]
      LDR    R3, [R1, #4]
      ADD    R3, R3, R2
      SUB    R2, R2, #1

      MOV    PC, R14

    .load_palette
      MOV    R12, R14
      MOV    R4, #0
      ADR    R7, os_block
    .col_loop
      MOV    R1, R5
      STRB   R4, [R7, #0]
      MOV    R0, #17
      STRB   R0, [R7, #1]
      BL     read_byte
      STRB   R0, [R7, #2]
      BL     read_byte
      STRB   R0, [R7, #3]
      BL     read_byte
      STRB   R0, [R7, #4]
      MOV    R0, #&0C
      MOV    R1, R7
      SWI    "OS_Word"
      MOV    R1, R5
      MOV    R0, #18
      STRB   R0, [R7, #1]
      BL     read_byte
      STRB   R0, [R7, #2]
      BL     read_byte
      STRB   R0, [R7, #3]
      BL     read_byte
      STRB   R0, [R7, #4]
      MOV    R0, #&0C
      MOV    R1, R7
      SWI    "OS_Word"
      ADD    R4, R4, #1
      CMP    R4, #16
      BLT    col_loop
      MOV    R0, #0

      MOV    PC, R12

    .exit
      MOV    R2, R0
      MOV    R1, R5
      MOV    R0, #0
      SWI    "XOS_Find"
      MOV    PC, R10

    .read_byte
      SWI    "XOS_BGet"
      MOVVC  PC, R14

    .read_loop                ; Read and deal with loop data.
      MOV     R1, R5
      BL      read_byte       ; Read colour
      MOV     R4, R0
      BL      read_byte       ; Read length
    .loop2
      SUB     R0, R0, #1
      STRB    R4, [R2, #1]!
      CMP     R0, #0          ; More to loop?
      BGT     loop2
      CMP     R2, R3          ; End of screen?
      BLT     new_loop
      B       exit

    .vdu_block
      EQUD   149
      EQUD   7
      EQUD   -1
      EQUD   0
      EQUD   0

    .os_block
      EQUD   0
      EQUB   0
      ALIGN
    ]
  NEXT
ENDPROC
This concludes our series on creating compressed screen software. If you wish to write an assembler saver for type 2 files, please do so. Likewise, bug reports and such would also be welcomed.

Download example: sloader2.basic
Download example compressed screen (type 2): screen2.raw

 

 

By way of comparison, here is a type 1 version of the above image. It is a moderately balanced image, a textured JPEG and textured windows alongside plain windows.

Download example compressed screen: compscrn.raw

The type 1 screen is 159771 bytes.
The type 2 screen is 123369 bytes.


Return to assembler index
Copyright © 1999 Richard Murray