Image
We want to manipulate images as a whole. Images are a collection of 64 Color pixels.
❎ Create a public structure image::Image containing a unique unnamed field consisting of an array of 64 Color. Structures with unnamed fields are declared as follows, and fields are accessed like tuple fields (.0 to access the first field, .1 to access the second field, …):
struct Image([Color; 64]);
Here, if im is of type Image, im.0 will designate the array contained the Image object.
❎ Create a public function pub fn new_solid(color: Color) -> Self on Image which returns an image filled with the color given as an argument.
Default trait
Unfortunately, the trait Default cannot be derived automatically for Image because of a temporary technical limitation of the Rust language: arrays with more than 32 entries cannot have Default derived automatically. However, nothing prevents us from implementing the Default trait manually.
❎ Implement the Default trait for Image by making it return an image filled with the default color.
Individual pixel access
We want to be able to access an individual pixel of an image by using an expression such as my_image[(row, column)]. For doing so, we want to implement two traits, Index and IndexMut, which allow indexing into our data structure. Fortunately, Rust lets us use any type as an index, so a (usize, usize) couple looks perfectly appropriate.
❎ Implement the core::ops::Index<(usize, usize)> trait on Image with output type Color.
❎ Implement the core::ops::IndexMut<(usize, usize)> trait on Image. Note that you do not specify the output type as it is necessarily identical to the one defined in Index, as IndexMut can only be implemented on types also implementing Index with the same index type.
Row access
Since we will display the image one row at a time on the led matrix, it might be useful to have a method giving access to the content of one particular row.
❎ Add a pub fn row(&self, row: usize) -> &[Color] on Image referencing the content of a particular row.
Note how the reference will stay valid no longer than the image itself; for this reason, Rust lets us take a reference inside a structure without any risk for the reference to become invalid if the image is destroyed.
Gradient
For visual testing purpose, we would like to be able to build a gradient from a given color to black. Each pixel should receive the reference color divided by (1 + row * row + col).
❎ Using the image[(row, col)] utilities defined in previous steps, implement a pub fn gradient(color: Color) -> Self function returning an image containing a gradient.
Image as an array of bytes
We already know from the 4SE07 lab that we will receive image bytes from the serial port, and that we will build the image byte by byte. It would be much easier if we could also see the image as a slice and access the individual bytes.
Remember that Rust is allowed to reorder, group, or otherwise rearrange fields in a struct. It means that so far we have no idea of how the Color type is organized in memory. Maybe each field is stored on 32 bits instead of 8, or maybe g is stored first, before r and b. We will use a representation clause to force Rust to make each field 8 bits wide, to have a one byte alignment only on the structure, and to keep r, g and b in the order we have chosen.
❎ Force Rust to use a C compatible representation for Color by using the appropriate repr(C) attribute. It will ensure all properties mentioned above.
Concerning the Image type itself, we do not have much to do. We already know that Rust arrays are guaranteed to be layed out according to the size and alignment requirements of the element type. In our case, it means that the three bytes of the first pixel will be immediately followed by the three bytes of the second pixel, and so on.
However, to guarantee that Rust uses the same representation for Image as the one it uses for the inner array, we need to request that the Image type is transparent, i.e., that it uses the same representation as its unique non-zero-sized field.
❎ Add a repr(transparent) attribute on the Image type to ensure that it keeps the same representation as its unique element.
To see an image as an immutable array of bytes, we will implement the AsRef<[u8; 192]> trait. This way, using my_image.as_ref() will return a reference to an array of 192 (8 rows × 8 columns × 3 color bytes) individual bytes.
❎ Implement AsRef<[u8; 192]> for Image. You will need to use core::mem::transmute(), which is an unsafe function, in order to convert self to the desired return value.
❎ Since we know we will need a mutable reference to the individual bytes, implement AsMut<[u8; 192]> the same way.
Congratulations, you now have a rock-solid Image type which will make the rest of the job easier.