CSC 161 Grinnell College Spring, 2015
 
Imperative Problem Solving and Data Structures
 
 

Laboratory Exercise on Transforming Pictures

In this digital age, images commonly consist of a large number of dots or pixels, arranged in a grid pattern.

Pixels

Although several image formats exist, a common specification for a pixel involves a red, green, and blue component, and each color component may take values between 0 and 255. Since the values 0..255 comprise one byte (the size of a char in C), each component is commonly stored as a char value. In this context, negative values are not meaningful, so each component is considered an unsigned char. Further, the red, green, and blue (R/G/B) components naturally fit together as part of a logical package — a struct in the context of C. With this in mind, MyroC defines a pixel as follows:

**
 * @brief Struct for a pixel
 */
typedef struct
{
  unsigned char R; // The value of the red component 
  unsigned char G; // The value of the green component 
  unsigned char B; // The value of the blue component 
} Pixel;

Within this framework, R/G/B values of 0 correspond to black and R/G/B values of 255 correspond to white. This leads to the following natural definitions:

Pixel blackPix = {0, 0, 0};
Pixel whitePix = {255, 255, 255);

Pictures

Perhaps the most conceptually-simple structure for a picture involves a two-dimensional array of R/G/B pixels. Each picture has a height and a width, and an overall picture is just a two-dimensional array with those dimensions. When working with a Scribbler 2 robot, the camera takes a picture that is 192 pixels high and 256 pixels wide. Of course, other cameras or images may have a different dimensions.

A pragmatic detail: You may recall from working with one- and two-dimensional arrays that the declaration of a two-dimensional array allocates space, but the array name just gives the base address, not the height and width dimensions, We cannot infer the dimensions of the array given only the variable name. For this reason, it is convenient to store the dimensions of an image together with the two-dimensional array. Thus, in much processing, the height, width, and pixel array are naturally part of a single package, so a picture is defined as a struct:

/**
 * @brief Struct for a picture object
 * @note the picture size is always 256 in width and 192 in height
 */
typedef struct 
{
  int height; /* The height of the image -- set to 192 for robot camera */
  int width;  /* The width of the image  -- set to 256 for robot camera */
  Pixel pix_array[256][192]; /* The array of pixels comprising the image */
} Picture;

Creating a White Picture with a Black Dot in the Middle

Building on this background, program white-picture-with-black-dot.c creates a white picture, places a [2 pixel by 2-pixel] black dot in the middle, and displays the result using rDisplayPicture from the MyroC library.

  1. Copy white-picture-with-black-dot.c to your account, compile and run it, and review how the program works.

    1. Note that this program does not utilize a Scribbler 2 robot, so no rConnect or rDisconnect statements are needed!
    2. The program uses MyroC.h, which includes definitions of Pixel and Picture. What happens if you define Pixel and Picture (as given above) in this program as well?
    3. Variables whitePix and blackPix are declared and initialized at the start of the program. Review how a struct is defined and initialized, and explain what the expression = {0, 0, 0} does. What values are assigned to which fields within the whitePix struct?
    4. Review how the program places a black dot in the middle of the picture. Enhance the program to place a red dot near the top middle of the picture, a green dot near the left middle of the picture, and a blue dot near the middle bottom of the picture.
    5. Review the definition of rDisplayPicture in the MyroC header file.
      • Where is the text, "display of myArt", displayed?
      • What happens if the parameter 10 is changed to 20? to -3? to 0;

Technical Aside

Since image processing is used in a wide variety of applications, several common formats are used to store image data. The approach here, with an array of R/G/B pixels, is conceptually simple. However, other formats are possible as well.

Some Initial Experiments with Pictures

  1. Write a program drawBox.c that creates a picture with a solid green box in the middle of a white background. You likely will need loops to set each pixel within the box to green. Display the resulting picture.

  2. Write a function,

    void colorToGray (Picture * pic)
    

    which takes in a Picture * and then converts each pixel to its grayscale value. A grayscale pixel is a pixel where the RGB are all set to 30% of the red value + 59% of the green value + 11% of the blue value (source: wikipedia.org/Grayscale). Your function should then display the Picture. Test this out on a few pictures you take with your robot.

    Notes:

    • When a picture is represented in a grayscale, the red, green, and blue values of each pixel should be identical. Thus, in this problem, you will need to compute the composite value: 30% of the red value + 59% of the green value + 11% of the blue value. Then, this composite should be assigned to the red, green, and blue components of a pixel.
    • The colorToGray is intended to change the corresponding picture in main memory. Thus, the calling context will need to provide the address of the picture (.e.g., &pix), and code within colorToGray will need to reference the original picture (e.g., *pic).
  3. Write a function, void setPictureMax (Picture * pic), which finds what the highest RGB value for each pixel is and sets that pixel to just that value (e.g. a pixel of RGB (50,135,85) will have a new RGB of (0,135,0)). That is, setPictureMax should not change the RGB value of the color with the highest value, but the other two colors should be set to 0.

    Your function should change the parameter variable, so the original picture in the calling procedure (e.g., in main) is changed. The main program then can display the Picture. Test this out on a few pictures you take with your robot.

  4. Write a program flip-picture that takes a picture from the robot (using rTakePicture), displays the picture for 5 seconds, flips the picture upside down, and finally displays the result for 5 seconds

    Notes:

      Flipping a picture upside down requires swapping pixels in one row (e.g., near the top) with pixels in a corresponding row (e.g., near the bottom). In the swap, the column index of the swapped pixels will be the same, but the corresponding pixels in two rows will be interchanged.

    • If a and b are two values of type T, then interchanging the values in these variables cannot be done easily with simple assignments:

      a = b;
      b = a;
      

      In this code, the original value of a is overwritten in the first assignment, so there is no way to give b the original value of a. Instead, in normal processing, interchanging the values of two variables requires an additional storage location:

      T temp = a;   // declare temporary variable strong data of the relevant type
      a = b;
      b = temp;
      

Homework

Transforming Pixel Arrays

  1. Write a program create-negative.c that changes each RGB value V of each pixel to 255 - V, and display the result.

  2. Write a program left-right-picture that takes a picture from the robot (using rTakePicture), displays the picture for 5 seconds, flips the picture left-to-right, and finally displays the result for 5 seconds

Find Bright Light

Consider the brightness of a pixel as being the sum of its R, G, and B values.

  1. Write a program find-brightest.c that takes a picture from the Scribbler 2's camera, and finds the coordinates of the brightest pixel. (If there are two pixels with the same brightness, the program could report any of the bright pixels — your choice.)

  2. Modify the program from the previous step, so that once the brightest pixel is found, the program draws a small red circle around that location. (If the circle would go outside the picture, just draw the part of the circle within the picture itself.) Be sure your program displays the resulting picture with the identifying red circle.

  3. Modify the previous two programs to consider the collective brightness of each 3-by-3 collection of pixels. That is, brightness will be computed as the sum of all pixels in a 3-by-3 square. Once found, the program should draw a small circle around this bright region.

Feedback Welcome

Development of laboratory exercises is an iterative process. Prof. Walker welcomes your feedback! Feel free to talk to him during class or stop by his office.