Because this example is obviously targetted at older machines (the Iyonix apparently doesn't have a 16 colour mode), I have not altered it. The code is not 32bit safe.
What we shall do here is to jump straight in with an example program. Something that you will find useful (I mean - what is the point of writing "Hello World" a few thousand times? When you learn to code it is much better to learn on useful projects).
This project is a 16-colour screen mode compressor. It can be adapted to other modes, but I'll leave that as an exercise for the advanced. After all - ChangeFSI does a marvellous job of reducing the number of colours in an image, and for demos with 'real life screenshots', 16 colours is usually adequate (unless you are plugging the latest 16 million colour game you wrote - in which case you won't need this utility!).
Average savings depend on the image. It does badly with dithered images, it does well with images that don't have loads of fiddly things (a screenshot of a program running in an untextured Desktop, for example). It has been designed to work with MODE 12 and MODE 35. For others, it is a case of suck it and see - but there is no real reason why other 16 colour MODEs would not work.
Right. Well... In the old days when it was safe to leave your car unlocked and your daughter unchained, screen memory started at &2000000 and worked upwards. You needed to know a few modes such as 12 and 13 and 15... Like, who do you know that uses MODE 8?
Modern life, however, is not so simple. We would be foolish to assume the screen start position and/or size. We would also be foolish to assume the 'default' palette. So all of this information has to be examined.
16 colour modes use 4bpp, or 4 bits per pixel. Thus, one byte equals two pixels.
The compression system is appropriately crude. We will work on a "better" scheme later.
However for now we shall be using a kind of Run-Length-Encoding. We treat the screen memory as a
sequence of bytes, and we encode it into a set of byte counts and byte values. For example,
assume screen memory is:
If you don't get it, study the above for a few moments. It is quite simple once you understand.
By way of example,
4122141628151728; and you can
see how it becomes inefficient with oft changing bytes (such as dithered images), the output is
two bytes larger than the input.
Our format is to be defined as:
Header: "BudgieScrn (compressed 1)"+<newline> which is 26 bytes. Mode: 1 byte giving mode number. Palette: 16 entries, six bytes each. Giving red, green and blue values for first flash colour and second flash colour for each entry. Image: Data as defined above.We will leave the screen size validation up to the loader instead of encoding it into the image.
So... Let us begin:
REM >Ssaver BudgieSoft screen saver REM V1.01 © 1997 Richard Murray REM REM Assembler programming example 1 REM Downloaded from: http://www.heyrick.co.uk/assembler/ : ON ERROR PRINT REPORT$+" at "+STR$(ERL/10) : END : DIM code% &400 FOR pass% = 4 TO 6 STEP 2 P%=0 O%=code% [ OPT pass%This is a fairly standard beginning. The thing to note is that we are using offset assembly. In this way, our code is compiled as a transient utility instead of an "absolute" application. It should also be loadable. The divide-by-ten in the error message is so that 'real lines' match up to !Edit lines, so if you get an error just press F5 and enter that number.
MOV R7, R14We now 'save' the return address in register 7. This is so that it does not get overwritten by BL (Branch with Loop) stuff. Note, if you want to fiddle with the code - remember register 7 is special.
MOV R0, #&8C SWI "XOS_Find" BVS exit MOV R6, R0Our program is designed to be called as "SSaver <filename>". Conveniently the OS_Find call (opens/closes files) has a flag to look for the filename in the entry parameters. So this stuff attempts to open our file with write access. If it fails the V flag (oVerflow) is set. The BVS command (Branch if V Set) will carry us out to a safe exit. Otherwise we can assume all went well, and move our file handle into register 6 for safe keeping.
By now we have our file open and are ready to start building up our image data.
MOV R0, #2 MOV R1, R6 ADR R2, header_text MOV R3, #26 SWI "XOS_GBPB" BVS errorThis sets up a call to OS_GBPB to write the header; which is defined at the end as ".header_text". The BVS takes us to an error handler in case we don't have write-access after all!
MOV R0, #&87 SWI "XOS_Byte" MOV R0, R2 MOV R1, R6 BL write_byteIf you were to look up OS_Byte &87 in a manual, you might wonder about my sanity. After all - what relevance could "get character at cursor position" have?
Frankly - none. :-) However in register 2 it returns the current MODE number, and THAT is what we are looking for. We then set up a call to a routine that outputs a byte for us, defined later as ".write_byte".
MOV R5, #0 .col_loop MOV R0, R5 MOV R1, #16 SWI "OS_ReadPalette" MOV R1, R6 MOV R0, R2, LSR #8 ; red 1st BL write_byte MOV R0, R2, LSR #16 ; green 1st BL write_byte MOV R0, R2, LSR #24 ; blue 1st BL write_byte MOV R0, R3, LSR #8 ; red 2nd BL write_byte MOV R0, R3, LSR #16 ; green 2nd BL write_byte MOV R0, R3, LSR #24 ; blue 2nd BL write_byte ADD R5, R5, #1 CMP R5, #16 BLT col_loopThis looks complicated and I'm not going to explain it to verbosely. However what we are trying to do here is to read the palette (care of OS_ReadPalette). It returns two registers for the six colour values. As we are using 32 bit words, we can cram several 8 bit values into one word. Hence the LSR, which is a Logical Shift Right. LSR takes a value (say %110110100) and shifts it (say two places) to produce an output (such as %001101101). We loop through the 16 colours and extract the six values for each, writing them to the file as we are going along.
The header is now complete.
ADR R0, vdu_block ADD R1, R0, #12 SWI "OS_ReadVduVariables" LDR R2, [R1] LDR R3, [R1, #4] ADD R3, R3, R2 SWI "XOS_RemoveCursors"Even though we are not going to write the screen dimensions to the file; we do need to know where the screen starts and ends. This calls "OS_ReadVduVariables" asking for the screen base address and the size of the screen. Adding the two gives us our result, with the data held in the memory area defined later as ".vdu_block". While we are at it, we switch the cursor off.
MOV R1, R6 .main_loop LDRB R0, [R2] BL write_byte MOV R4, #1 .loop2 LDRB R5, [R2, #1]! ADD R4, R4, #1 CMP R5, R0 BNE skip CMP R4, #&FF BLT loop2 .skip SUB R0, R4, #1 BL write_byte CMP R2, R3 BLT main_loopHere is the main loop. What it does is it outputs the value of the current byte (ie: the colour) and then it reads through the memory looking to see how long it continues for. As a byte can only hold 256 different values, it loops up to a maximum of 255. Once a value has been established, it will write it out to the file and then branch back to the main loop; repeating until the end of screen memory is reached.
MOV R0, #&20 BL write_byte MOV R0, #&20 BL write_byteA really early version of this software messed up by trying to read a little beyond the end of the screen (and hence the end of the file), so it became practice to add two spaces to the end of the file - just in case - even though the <cough>bug</cough> was fixed.
MOV R0, #0 MOV R1, R6 SWI "XOS_Find" SWI "XOS_RestoreCursors"This wraps up, closing our file and restoring the cursor. It falls through to ".exit" which follows next.
.exit MOV PC, R7Leave the program - places stored return address (in register 7) into the Program Counter (register 15).
.write_byte SWI "XOS_BPut" MOVVC PC, R14Simple routine to write a byte of data. Not terribly efficient, however you could change it to move the file handle here instead of beforehand (using MOV R1, R6). It returns by way of function returning (move return address into Program Counter); but only if the V flag is clear. If something went wrong, however, then we fall through to ".error"...
.error MOV R2, R0 MOV R1, R6 MOV R0, #0 SWI "XOS_Find" SWI "XOS_RestoreCursors" MOV R0, R2 ORR PC, R7, #1<<28This closes the file, restores the cursor and exits with the V flag set (error condition). It is the 'standard' error handler for this program.
.vdu_block EQUD 149 EQUD 7 EQUD -1 EQUD 0 EQUD 0This is the block for the OS_ReadVduVariables command. The 149 and 7 are the codes for the values we wish to read. The -1 terminates the list. The two 0's are where the information is placed by the OS.
.header_text EQUS "BudgieScrn (compressed 1)" EQUB 10 EQUB 0 ALIGNThis is the header.
] NEXT : OSCLI("Save <Obey$Dir>.Saver "+STR$~(code%)+" "+STR$~(O%)) OSCLI("SetType <Obey$Dir>.Saver &FFC") : ENDAnd finally the denouement. Close the loop, save the file and exit.
There you have it!
To use it, try an Obey file similar to:
| Obey file to run SSaver | ScreenLoad <Obey$Dir>.mypiccy | <Obey$Dir>.SSaver <Obey$Dir>.comp_piccyIn the next section, we shall create a loader. Before looking at that, why not see if you can fiddle with the above to make it work in reverse?