Let’s recap the structure of the header of a Stupid BitMap:
SBM header +-------+-------+-------+-------+-------+ | 'S' | 'B' | 'M' | ppl | lines | +-------+-------+-------+-------+-------+ byte0 byte1 byte2 byte3 byte4
The header is composed of five fields, which actually compose three different logical fields: a magic number, the number of pixels per line, and the number of lines.
We could of course abstract the header using an array of five bytes, like this:
type SBM_Header = byte[5];
However, this would not capture the properties of the fields themselves, which would need to be remembered by the user: which of these five bytes correspond to the magic number? Is the pixels per line number signed or unsigned? etc.
Poke provides a much better way to abstract collections of heterogeneous data: struct types. Using a struct type we can abstract the SBM header like this:
type SBM_Header = struct { byte[3] magic; uint<8> ppl; uint<8> lines; }
Note how the struct has three named fields: magic
, ppl
and lines
. magic
is an array of three bytes, while
ppl
and lines
are both unsigned integers.
Once defined, struct types can be referred by name. For example, we can map the SBM header at the beginning of our file p.sbm like this:
(poke) SBM_Header @ 0#B SBM_Header { magic=[0x53UB,0x42UB,0x4dUB], ppl=0x5UB, lines=0x7UB }
The value resulting from the mapping is a struct value. The fields of struct values are accessed using the familiar dot-notation:
(poke) var header = SBM_Header @ 0#B (poke) header.ppl * header.lines 35UB
The total number of pixels in the image is 35. Note how both
header.ppl
and header.lines
are indeed unsigned byte
values, and thus the result of the multiplication is also an unsigned
byte. This could be problematic if the image contained more than 255
pixels, but this can be prevented by using a cast:
(poke) header.ppl as uint * header.lines 35U
Now the second operand header.lines
is promoted to a 32-bit
unsigned value before the multiplication is performed. Consequently,
the result of the operation is also 32-bit wide (note the suffix of
the result.)
Remember when we wanted to crop a SBM image by removing the first and last row? We updated the header in a byte by byte manner, like this:
(poke) byte @ 3#B = 3 (poke) byte @ 4#B = 7
Now that we have the header mapped in a variable, updating it is much more easy and convenient. The dot-notation is used to update the contents of a struct field, by placing it at the left hand side of an assignment:
(poke) header.ppl = 3 (poke) header.lines = 7
This updates the pixel per line and the number of lines, in the IO space:
(poke) dump :size 5#B 76543210 0011 2233 4455 6677 8899 aabb ccdd eeff 0123456789ABCDEF 00000000: 5342 4d03 07 SBM..