|
Example 1
|
|
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:
111111111142351111121111111222222222222222111111111111A114121315511271F2C1
If you don't get it, study the above for a few moments. It is quite simple once you understand.
By way of example, 11112246885788 becomes 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, R0
Our 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 error
This 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_byte
If 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_loop
This 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_loop
Here 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_byte
A 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, R7
Leave the program - places stored return address (in register 7) into the Program Counter
(register 15).
.write_byte
SWI "XOS_BPut"
MOVVC PC, R14
Simple 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<<28
This 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 0
This 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
ALIGN
This is the header.
]
NEXT
:
OSCLI("Save <Obey$Dir>.Saver "+STR$~(code%)+" "+STR$~(O%))
OSCLI("SetType <Obey$Dir>.Saver &FFC")
:
END
And 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?