4.3 Modifying SBM Images

4.3.1 Reading a SBM File

Let’s open with poke the cute image we created in the last section, p.sbm:

$ poke p.sbm
[…]
(poke) dump
76543210  0011 2233 4455 6677 8899 aabb ccdd eeff  0123456789ABCDEF
00000000: 5342 4d05 07ff ffff ff63 47ff 6347 ffff  SBM......cG.cG..
00000010: ffff ffff ffff ffff 6347 ffff ffff 6347  ........cG....cG
00000020: ffff ffff ffff ff63 47ff ffff ff63 47ff  .......cG....cG.
00000030: ffff ffff ffff 6347 ff63 47ff ffff ffff  ......cG.cG.....
00000040: ffff ffff ff63 47ff ffff ffff ffff ffff  .....cG.........
00000050: ffff ffff 6347 ffff ffff ffff ffff ffff  ....cG..........
00000060: ffff ff63 47ff ffff ffff ffff ffff       ...cG.........

You can see the P in the ASCII column, right? If it wasn’t for the header, it would be pictured almost straight. This is because dump shows 16 bytes per row, and our image has lines that are 15 bytes long. This is a happy coincidence: you definitely shouldn’t expect to see ASCII art in the dump output of SBM files in general! :)

Now let’s read the image’s metadata from the header: pixels per line and how many lines are contained in the image:

(poke) var ppl = byte @ 3#B
(poke) ppl
5UB
(poke) var lines = byte @ 4#B
(poke) lines
7UB

All right, our image is 7x5. Knowing that each pixel occupies three bytes, and that each line contains ppl pixels, and that we have lines lines, we can map the entire image data using an array type specifier:

(poke) var image_data = byte[3][ppl][lines] @ 5#B
(poke) image_data
[[[255UB,255UB,255UB],[255UB,99UB,71UB], …]…]

4.3.2 Painting Pixels

The “P is for poke” slogan was so successful and widely appraised that the recutils7 chaps wanted to do a similar campaign “R is for recutils”. For that purpose, they asked us for a tomato-colored SBM image with an R in it.

Our creative department got at it, and after a lot of work they came with the following design:

  | 0 | 1 | 2 | 3 | 4 |
  +---+---+---+---+---+
0 |   | * | * |   |   |
1 |   | * |   | * |   |
2 |   | * |   | * |   |
3 |   | * | * |   |   |
4 |   | * | * |   |   |
5 |   | * |   | * |   |
6 |   | * |   | * |   |

Observe that this design really looks like our P (so much for a creative department). The bitmap has exactly the same dimensions, and difference are just three pixels, that pass from being background pixels to foreground pixels.

Therefore, it makes sense to read our p.sbm and use it as a base, completing the missing pixels. We saw in the last section how to read a SBM image. This time, however, we will copy the image first to a memory IO space to avoid overwriting p.sbm:

$ poke p.sbm
[…]
(poke) .mem scratch
The current IOS is now `*scratch*'.
(poke) .info ios
  Id	Type	Mode	Bias		Size		Name
* #1	MEMORY		0x00000000#B	0x00001000#B	*scratch*
  #0	FILE	rw	0x00000000#B	0x0000006e#B	./p.sbm
(poke) copy :from_ios 0 :from 0#B :to 0#B :size iosize (0)
(poke) dump
76543210  0011 2233 4455 6677 8899 aabb ccdd eeff  0123456789ABCDEF
00000000: 5342 4d05 07ff ffff ff63 47ff 6347 ffff  SBM......cG.cG..
00000010: ffff ffff ffff ffff 6347 ffff ffff 6347  ........cG....cG
00000020: ffff ffff ffff ff63 47ff ffff ff63 47ff  .......cG....cG.
00000030: ffff ffff ffff 6347 ff63 47ff ffff ffff  ......cG.cG.....
00000040: ffff ffff ff63 47ff ffff ffff ffff ffff  .....cG.........
00000050: ffff ffff 6347 ffff ffff ffff ffff ffff  ....cG..........
00000060: ffff ff63 47ff ffff ffff ffff ffff 0000  ...cG...........
00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................

Good. Now let’s map the contents of the image, both header information and the sequence of pixels:

(poke) var ppl = byte @ 3#B
(poke) var lines = byte @ 4#B
(poke) var image_data = byte[3][ppl][lines] @ 5#B

Let’s modify the image. Since the dimensions of the new image are exactly the same, the header remains the same. It is the pixel sequence that is different. We basically need to turn the pixels at coordinates (4,2), (5,3) and (6,3) from background pixels to foreground pixels.

Remember how we would change the value of some integer in the IO space? First, we would map it into a variable, change the value, and then poke it back to the IO space. Something like this:

(poke) var n = int @ offset
(poke) n = n + 1
(poke) int @ offset = n

This three-steps process is necessary because in the n = n + 1 above we are modifying the value of the variable n, not the integer actually stored at offset offset in the current IO space. Therefore we have to explicitly poke it back if we want the IO space to be updated as well.

Array values (and, as we shall see, other “composited” values) are different: when they are the result of the application of the map operator, the resulting values are mapped themselves.

When a Poke value is mapped, updating their elements have a side effect: the area corresponding to the updated element, in whatever IO space it is mapped on, is updated as well!

Why is this? The map operator, regardless of the kind of value it is mapping, always returns a copy of the value found stored in the IO space. We already saw how this worked with integers. However, in Poke values are copied around using a mechanism called “shared value”. This means that when a composite value like an array is copied, its elements are shared by both the original value and the new value.

We can depict this graphically for better understanding. A Poke array like:

(poke) var a = [1,2,3]

is stored in memory like this:

   +---+---+---+
 a | 1 | 2 | 3 |
   +---+---+---+

If we make a copy of the array in another variable b:

(poke) var b = a

we get

   +---+---+---+      +---+---+---+
 a | 1 | 2 | 3 |    b | 1 | 2 | 3 |
   +---+---+---+      +---+---+---+

Note how each of the integer elements has been copied to the new value. The resulting two arrays can then be modified independently:

(poke) a[1] = 5

resulting in:

   +---+---+---+      +---+---+---+
 a | 1 | 5 | 3 |    b | 1 | 2 | 3 |
   +---+---+---+      +---+---+---+

However, consider the following array whose elements are also arrays:

(poke) var a = [[1,2],[3,4],[5,6]]

This array is stored in memory like this:

   +---+---+---+
 a |   |   |   |
   +---+---+---+
     |   |   |   +---+---+
     |   |   +---| 5 | 6 |
     |   |       +---+---+
     |   |
     |   |       +---+---+
     |   +-------| 3 | 4 |
     |           +---+---+
     |
     |           +---+---+
     +-----------| 1 | 2 |
                 +---+---+

If now we make a copy of the same array into another variable b:

(poke) var b = a

The elements of the array are copied “by shared value”, i.e.

   +---+---+---+           +---+---+---+
 a |   |   |   |         b |   |   |   |
   +---+---+---+           +---+---+---+
     |   |   |   +---+---+   |   |   |
     |   |   +---| 5 | 6 |---+   |   |
     |   |       +---+---+       |   |
     |   |                       |   |
     |   |       +---+---+       |   |
     |   +-------| 3 | 4 |-------+   |
     |           +---+---+           |
     |                               |
     |           +---+---+           |
     +-----------| 1 | 2 |-----------+
                 +---+---+

The elements are indeed shared between the original array and the copy! If now we modify any of the shared values, this will be reflected in both containing values:

(poke) a[1][1] = 9
(poke) a
[[1,2],[3,9],[5,6]]
(poke) b
[[1,2],[3,9],[5,6]]

or graphically:

   +---+---+---+           +---+---+---+
 a |   |   |   |         b |   |   |   |
   +---+---+---+           +---+---+---+
     |   |   |   +---+---+   |   |   |
     |   |   +---| 5 | 6 |---+   |   |
     |   |       +---+---+       |   |
     |   |                       |   |
     |   |       +---+---+       |   |
     |   +-------| 3 | 9 |-------+   |
     |           +---+---+           |
     |                               |
     |           +---+---+           |
     +-----------| 1 | 2 |-----------+
                 +---+---+

Back to our image, it follows that if we wanted to change the color of some SBM pixel stored at offset offset, we would do this:

(poke) var a = byte[3] @ offset
(poke) a[1] = 10

There is no need to poke the array back explicitly: the side effect of assigning 10 to a[1] is that the byte at offset offset+1 is poked.

Generally speaking, mapped values can be handled in exactly the same way than non-mapped values. This is actually a very central concept in poke. However, it is possible to check whether a given value is mapped or not using the 'mapped attribute.

As we said, simple values such as integers and strings are never mapped, regardless of where they come from. Both ppl and lines are integers, therefore:

(poke) ppl'mapped
0
(poke) lines'mapped
0

However, image_data is an array that was the result of the application of a map operator, so:

(poke) image_data'mapped
1

When a value is mapped, you can ask for the offset where it is mapped, and the IO space where it is mapped, using the attributes 'offset and 'ios respectively. Therefore:

(poke) image_data'ios
1
(poke) image_data'offset
40UL#b

In other words, image_data is mapped in the IO space with id 1 (the *scratch* buffer) at offset 40 bits, or 5 bytes. We already knew that, because we mapped the image data ourselves, but in other situations these attributes are most useful. We shall see how later.

Well, at this point it should be clear how to paint pixels. First, let’s define our background and foreground pixels:

(poke) var bga = [255UB, 255UB, 255UB]
(poke) var fga = [255UB, 99UB, 71UB]

Then, we just update the pixels in the image data using the right coordinates:

(poke) image_data[4][2] = fga
(poke) image_data[5][3] = fga
(poke) image_data[6][3] = fga
(poke) dump
76543210  0011 2233 4455 6677 8899 aabb ccdd eeff  0123456789ABCDEF
00000000: 5342 4d05 07ff ffff ff63 47ff 6347 ffff  SBM......cG.cG..
00000010: ffff ffff ffff ffff 6347 ffff ffff 6347  ........cG....cG
00000020: ffff ffff ffff ff63 47ff ffff ff63 47ff  .......cG....cG.
00000030: ffff ffff ffff 6347 ff63 47ff ffff ffff  ......cG.cG.....
00000040: ffff ffff ff63 47ff 6347 ffff ffff ffff  .....cG.cG......
00000050: ffff ffff 6347 ffff ffff 6347 ffff ffff  ....cG....cG....
00000060: ffff ff63 47ff ffff ff63 47ff ffff 0000  ...cG....cG.....
00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................

4.3.3 Cropping the R

Looking at our new image, we realize that the first and the last column are all background pixels. We are aware that the recutils project is always short of resources, so we would like to modify the image to remove these columns, cropping it so it looks like this:

  | 0 | 1 | 2 |
  +---+---+---+
0 | * | * |   |
1 | * |   | * |
2 | * |   | * |
3 | * | * |   |
4 | * | * |   |
5 | * |   | * |
6 | * |   | * |

In order to perform this operation we need to rework the stream of pixels to reflect the desired result, and then to update the header metadata accordingly.

4.3.4 Shortening and Shifting Lines

Let’s think in term of lines. In the original image, each line has 5 pixels, that we can enumerate as:

     +----+----+----+----+----+
line | p0 | p1 | p2 | p3 | p4 |
     +----+----+----+----+----+

What we want is to crop out the first and the last column, so the resulting line would look like:

     +----+----+----+
line | p1 | p2 | p3 |
     +----+----+----+

Let’s get the first line from the original image_data:

(poke) var l0 = image_data[0]
(poke) l0
[[255UB,255UB,255UB],[255UB,99UB,71UB],…]

We could create the corresponding cropped line, by doing something like this:

(poke) var cl0 = [l0[1],l0[2],l0[3]]

And the same for the other lines. However, Poke provides a better way to easily obtain sub portions of arrays. It is called trimming. Given an array like the line l0, we can obtain the desired portion of it by issuing:

(poke) l0[1:4]
[[255UB,99UB,71UB],[255UB,99UB,71UB]]

Note how the limits of the semi-open interval specified in the trim reflect array indexes, and hence they are 0 based. Also, the left limit is closed and the right limit is open. The result of an array trimming is always another array, which may be empty:

(poke) l0[1:3]
[[255UB,99UB,71UB]]

Armed with this new operation, we can very easily mount the sequence of pixels for our cropped image:

(poke) var l0 = image_data[0]
(poke) var l1 = image_data[1]
(poke) var l2 = image_data[2]
(poke) var l3 = image_data[3]
(poke) var l4 = image_data[4]
(poke) var l5 = image_data[5]

And then update the lines in the mapped image data:

(poke) image_data[0] = l0[1:4]
(poke) image_data[0] = l1[1:4]
(poke) image_data[1] = l2[1:4]
(poke) image_data[2] = l3[1:4]
(poke) image_data[3] = l4[1:4]
(poke) image_data[4] = l5[1:4]

4.3.5 Updating the Header

The last step is to update the header to reflect the new dimensions of the image:

(poke) byte[] @ 0#B = ['S','B','M']
(poke) byte @ 3#B = 3
(poke) byte @ 4#B = 7

And we are done. Note how this time we wrote the magic bytes as an array, to save some typing and silly manual offset arithmetic. You may have noticed that the type specifier we used this time in the map is byte[] instead of byte[3]. This type specifier denotes an array of any number of bytes, which certainly includes arrays of three bytes, like in the example.

4.3.6 Saving the Result

And finally, let’s write out the new file as r.sbm:

(poke) save :from 0#B :size 5#B + image_data'size :file "r.sbm"

Footnotes

(7)

http://www.gnu.org/s/recutils