Homework 1

Raster graphics

Modified

DOWNLOADS

Overview

The purpose of the assignment is to experience implementing some of the fundamental low-level graphics operations. Later homeworks will use OpenGL, a high-level API, to implement interactive graphics programs.

The following assignments examine a simple format for representing graphics (PPM) and implements basic graphics operations (e.g. line-drawing, clipping) in that format. This will provide an opportunity to investigate details of graphics implementations usually hidden by graphics APIs and hardware.

You are free to use any language but C or C++ provides simple, formatted input as required for some of the assignment. IUS labs have installed Visual Studio 2008 which is the recommended development platform for compatibility.

ASSIGNMENT (4 parts)

  1. Convert some picture of your choice to PPM. Input the PPM file, implement and perform the following image processing operations and output as a PPM file.
    1. Filter out the blue component.
    2. Flip the image vertically.
    3. One other operation of your choice (shrink, enlarge, darken, stretch, blur, etc.)
  2. Implement an API for 2D graphics operations and output results as a PPM file. Use the API to implement drawing (with clipping) of:
    1. lines,
    2. circles,
    3. rectangles.
  3. Implement scaling, translation and rotation transformations.
  4. Extend the API for point operations. Use the API to implement the Sierpinski gasket of the text.

 

DISCUSSION

For this assignment, we will be working with PPM files. PPM is a simple graphics format that directly defines the RGB values of each pixel of the raster or array making up the image, a single, maximally blue pixel is coded in PPM as:

0 0 255

where red and green value are minimal. The following examples each define a single pixel of the designated color using the PPM format:

red

255 0 0

green

0 255 0

blue

0 0 255

white

255 255 255

black

0 0 0

We're using PPM because it is relatively easy to read, understand and manipulate, and is not compressed or otherwise encoded; however most images (JPEG, GIF, etc.) can be converted to PPM.

 

Example

The following is the display (circle at left) and contents of a PPM formatted image file:

P3      # Magic number defining the PPM format
11 10 # Image dimensions: width height
255    # Maximum color value
255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255
255 255 255 255 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255
255 255 255 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255 255 255 255
0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255
0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255
0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255
0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255
0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255
255 255 255 0 0 0 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 0 0 0 255 255 255 255 255 255
255 255 255 255 255 255 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255 255 255 255 255 255 255 255 255

 

255 is the maximal value for red, green or blue. A pixel value of 255 255 255 is white, 0 0 0 is black.

 

Questions (in class)

  1. Give a maximally green only pixel encoding.
  2. Give the PPM image file encoding for a red image of width=5, height=4, having a green vertical line at the center.

 

Convert image to PPM

The following is a session that converts a photo from JPEG to PPM, assuming you've installed ImageMagick and compiled the PBMtoPPM program below:

convert photo.jpg ppm:photo.pbm

PBMtoPPM photo.pbm > photo.ppm

ImageMagick converts other image file formats to PBM but not PPM. The PBMtoPPM program below converts from PBM format (uses more compact binary but hard for humans to read) to PPM:

#include <stdio.h>
#include <io.h>
#include <stdlib.h>
 
int main(int argc, char *argv[]) {
      char magic[3];
      unsigned int w,h,color, row, col;
      unsigned char r,g,b;
      FILE *fp;
 
      if(argc==1) {
            perror("\r\n\r\nPBMtoPPM: File path required.\r\n\r\n");
            exit(-1);
      }
 
      if( (fp = fopen( argv[1], "rb" ))==NULL) {
            perror( "\r\n\r\nPBMtoPPM: Open failed on input file.\r\n\r\n" );
            exit( -1 );
      }
      fscanf(fp, "%s", magic, 3);
      printf("%s\r\n","P3");
      fscanf(fp, "%u%u%u ",&w,&h,&color);
      printf("%u %u\r\n%u\r\n",w,h,color);
 
      for(row=1;row<=h;row++) {
            for(col=1;col<=w;col++){
                fscanf(fp, "%c",&r,1);
                fscanf(fp, "%c",&g,1);
                fscanf(fp, "%c",&b,1);
                printf("%u %u %u\r\n", r,g,b);
            }
      }
}

 

Grayscale

A example operation on a PPM file converts the image to grayscale by computing the average of each pixel. For example, a blue pixel encoded as RGB of:

0 0 255

would be averaged by (0+0+255)/3 to yield a grayscale pixel of:

85 85 85

#include <stdio.h>
#include <io.h>
#include <stdlib.h>
 
char magic[3];
unsigned int w,h,color, row, col;
unsigned int r,g,b,average;
FILE *fp;
 
int main(int argc, char *argv[]) {
 
      if(argc==1) {
            perror("\r\n\r\nGrayScale: File path required.\r\n\r\n");
            exit(-1);
      }
 
      if( (fp = fopen( argv[1], "r" ))==NULL) {
            perror( "\r\n\r\nGrayScale: Open failed on input file.\r\n\r\n" );
            exit( -1 );
      }
      fscanf(fp, "%s",magic,3);
      printf("%s\r\n","P3");
      fscanf(fp, "%u%u%u ",&w,&h,&color);
      printf("%u %u\r\n%u\r\n",w,h,color);
 
      for(row=1;row<=h;row++) {
            for(col=1;col<=w;col++){
                fscanf(fp, "%u%u%u",&r,&g,&b);
                average=(r+g+b)/3;
                printf("%u %u %u\r\n", average, average, average);
            }
      }
}

 

Example - Convert a photo from JPEG to PBM then PBM to PPM, color to grayscale and display.

convert photo.jpg ppm:photo.pbm

PBMtoPPM photo.pbm > photo.ppm

grayscale photo.ppm > gray.ppm

imdisplay gray.ppm

 

IMPLEMENTING A 2D GRAPHICS API

PPM defines the image raster only. Graphic operations, such as line drawing, are generally implemented as part of an API that provides a level of abstraction above that of the screen raster.

Graphics operations - Converting from world to display

We generally prefer to work in some system corresponding to the problem, a world system of meters, pounds or whatever but display the images on a 2D raster of pixels.

Need a conversion operation from world to display (screen) coordinates. The operation assumes a proportional conversion of values from a continuous domain to a discrete range; a world value in the center of the domain should be converted to a display value in the center of the range.

 

Suppose we are working in the Cartesian coordinate system where x and y range from values 0.0 to 6.0 (meters, pounds or whatever).

 

Window below defines the minimum and maximum values viewed of the world coordinate system.

Any values outside the window are clipped, that is, will not be displayed.

The point (5.0, 2.0) is outside the window rectangle so would not be displayed.

0.0,6.0                          6.0, 6.0
1.0, 5.0     5.0,5.0

1.0,3.0      5.0,3.0

       Window           *
                      5.0, 2.0

0.0, 0.0                         6.0, 0.0

       World coordinates

 

Questions (in class)

  1. What part of a line from (3.0, 0.0) to (3.0, 6.0) should be visible?
  2. Give a function prototype for the API to set the window parameters.

 

Display defines the display area used, usually assumed that (0,0) is the upper-left corner.

The raster image of the display points (0,0) to (400,600).

0,0                         400,0

     

0, 600                    400,600

            Display coordinates

 

Questions (in class)

  1. Give a function prototype for the API to set the display parameters. Assume the top-left corner is 0,0.
  2. Define data structure(s) for representing the above display for the PPM image file format.

 

Viewport defines the part of the display area used.

The raster image of the display points (0,0) to (400,600).

0,0                         400,0
100,100   200,100

100,300   200,300

       Viewport

0, 600                    400,600

            Display coordinates

 

Questions (in class)

  1. What part of a line from (150, 0) to (150, 600) should be visible?
  2. Give a function prototype for the API to set the viewport parameters.
  3. Where would world coordinate point (1.0, 1.0) be in display coordinates?
  4. Where would world coordinate point (5.0, 5.0) be in display coordinates?

 

0.0,6.0                          6.0, 6.0
1.0, 5.0     5.0,5.0

1.0,1.0      5.0,1.0

        Window

0.0, 0.0                         6.0, 0.0

World

0,0                                                       400,0
100,300   200,300
 
100,300   200,300

100,100   200,100

 
100,500

Viewport

200,500

0, 600                                              400,600

Display

World to Display Conversion algorithm

Each visible world point can be converted into a display point by a four-step algorithm.

Note the orientation of the display has its origin 0,0 in the upper-left corner. We ignore that initially to simplify the conversion; the viewport assumes y origin 0,0 at lower-left (e.g. yviewporttop > yviewportbottom) to coincide with the world system orientation.

  1. First, translate (x, y) to the window world coordinate bottom left corner:

xtranslatewindow = x - xwindowleft = x - 1.0

ytranslatewindow = y - ywindowbottom = y - 1.0

  1. Scale the window world coordinates proportionally to the size of the display viewport:

xscale = (xviewportright-xviewportleft)/(xwindowright-xwindowleft) = (200-100)/(5.0-1.0)

yscale = (yviewporttop-yviewportbottom)/(ywindowtop-ywindowbottom) = (300-100)/(5.0-1.0)

  1. Compute the world point to display pixel position by translating to bottom left position of the viewport:

    xdisplay = xtranslatewindow * xscale + xviewportleft

    ydisplay = ytranslatewindow * yscale + yviewportbottom

     

  2. Finally, convert the display origin to coincide with the world coordinate orientation. The display has y=0 at the top-left while we have treated the display having y=0 at bottom-left (i.e. the image will be upside down).

    y'display = Maximum vertical display value - ydisplay = 600 - ydisplay

0.0,6.0                          6.0, 6.0
1.0, 5.0     5.0,5.0

1.0,1.0      5.0,1.0

        Window

0.0, 0.0                         6.0, 0.0

World

0,0                                                       400,0
100,300   200,300
 
100,300   200,300

100,100   200,100

 
100,500

Viewport

200,500

0, 600                                              400,600

Display

Example - Note that we've defined the viewport y-coordinates starting at (0,0) in the lower-left. The display had (0,0) in upper-left. It is simpler to do the world-to-display conversion relative to the viewport, then adjust to the display.

The world point (1.0, 1.0) should convert into the viewport above at display point (100, 500).

xtranslatewindow = x - xwindowleft = 1.0 - 1.0 = 0.0

ytranslatewindow = y - ywindowbottom = 1.0 - 1.0 = 0.0

xscale = (xviewportright-xviewportleft)/(xwindowright-xwindowleft)
          = (200-100)/(5.0-1.0) = 25.0

yscale = (yviewporttop-yviewportbottom)/(ywindowtop-ywindowbottom)
          = (300-100)/(5.0-1.0) = 50.0

xdisplay = xtranslatewindow * xscale + xviewportleft = 0.0*25.0+100 = 100

ydisplay = ytranslatewindow * yscale + yviewportbottom = 0.0*50.0+100 = 100

y'display = Maximum vertical display value - ydisplay = 600 - ydisplay = 600 - 100 = 500

 

Questions (in class)

  1. Can x and y world coordinates be converted independently (one at a time)?
  2. Convert a world coordinate point (1.0, 6.0) to display coordinates.
  3. Give function prototype(s) for the API to convert world coordinate point (1.0, 6.0).
  4. Give code for converting world coordinate point (1.0, 6.0) to display coordinates using your API.
  5. Give code for setting a world coordinate point (1.0, 6.0) to black in the display data structure(s) using your API.

 

Clipping

World values outside the window should be clipped, that is not converted to the visible display (viewport).

The point (0.5, 3.0) is outside the window rectangle of (1.0, 1.0), (5.0, 5.0) so has no valid transformation to the viewport.

Another, though less efficient, option is to translate all world coordinate points to the display but clip those display points outside the viewport.

Using this approach, clipping can be naturally done as part the world-to-display conversion. A less than elegant solution (i.e. hack) to handling world values that do not map into the viewport is to define an extra row (column) in the display data structure, any invisible world points are assigned display points mapped to the extra, invisible display area; the extra row (column) is of course, not output as part of the image.

Lines

A parametric line equation in 2D that passes through points (x0,y0) and (x1,y1) can be written as:

x = x0+t*(x1-x0)

y = y0+t*(y1-y0)

As t progresses constantly along the distance of the line, so do the x and y components; thus we can compute t as a function of x:

t = (x-x0)/(x1-x0)

The resulting algorithm for displaying a line given world coordinate (x0,y0) and (x1,y1) is:

for x = x0 to x1 by |x1-x0|/|xviewportright-xviewportleft| do

t = (x-x0)/(x1-x0)

y = y0+t*(y1-y0)

display( convertx( x ), converty( y ) )

where the convert function performs the corresponding world-to-display conversion on x or y described above. Here we assumed x0 < x1, if not swap the start and end points.

The x world unit increment of |x1-x0|/|xviewportright-xviewportleft| corresponds to 1 pixel steps on the display.

A line of 5 world units on the x-axis with a viewport of 100 horizontal pixels should increment by 1/20 world units so that the line is drawn using each available pixel. However, for lines extending beyond the clipping area, the calculated increment is greater than one pixel and will produce noticeable gaps in the line. Using this method, clipping should be done in world coordinates, though not required for this assignment.

The above works as long as  x0 ¹ x1 in which case t should be computed as function of y.

In general, the visually best line (the fewest breaks) is produced by computing t as a function of the x or y component having the greatest difference between the start and end points. For example, the line from (1.0, 1.0) to (3.0, 2.0) should use the x component while the line from (1.0, 1.0) to (2.0, 3.0) should use the y component.

Recursive line-drawing solution (credited to former student Guy Cobb)

The above solution suffers from multiple special cases (e.g. if x0 > x1 swap the start and end points). A simpler, recursive solution avoids special cases. The algorithm divides the world coordinate line, draws each endpoint and midpoint, terminating when the endpoints are within some predefined tolerance. Tolerance for line drawing can be assigned the minimum of xtolerance and ytolerance.

xtolerance = (xwindowright-xwindowleft)/(xviewportright-xviewportleft)

ytolerance =(ywindowtop-ywindowbottom)/(yviewporttop-yviewportbottom)

You'll need to implement the point function, which colors a single pixel that is within the viewport.

line( x0, y0, x1, y1)

distance = sqrt((x0-x1)2 + (y0-y1)2)

if(distance<TOLERANCE) return

xmid = (x0 + x1)/2

ymid=(y0 + y1)/2

point(x0,y0)

point(x1,y1)

point(xmid,ymid)

line(x0, y0, xmid, ymid)

line(xmid, ymid, x1, y1)

Circles

An inefficient method that avoids gaps if a small enough increment is used in stepping q from 0 to 2p defines:

(x, y) = (R*cos q, R*sin q)

where R is the circle radius in the world system drawn centered at the world origin.

Transformations

Three fundamental graphics transformations on world coordinates are:

  1. scaling
  2. translation
  3. rotation

Chapter 4 of the text discusses the 3D case in detail, we will examine the 2D case.

All 2D transformations can be defined by a 3x3 matrix. The transformation matrix and a point are multiplied to produce the transformed point.

Representation

2D points, [x,y]=[6,5], are represented as homogenous coordinates [6,5,1].

2D transformations are represented by a 3x3 matrix.

The utility of this representation will be discussed in Chapter 4 but basically it allows points and transformations to be represented and the mathematics to work.

Identity - The 2D identity matrix defined using homogenous coordinates is:


I=
1 0 0
0 1 0
0 0 1

Scaling - Scaling increases or decreases the size of an object along one axis.

The matrix to scale a point in the x-axis by 2 and the y-axis by 3 is:


S=
2 0 0
0 3 0
0 0 1

A point, p=[6, 5], can be represented by:


p=
6
5
1

and can then be scaled by:


Sp=
2 0 0
0 3 0
0 0 1

*
6
5
1

=
12
15
1

where the square figure shown centered at the origin has been scaled by a factor of 2 on the x-axis and 3 on the y-axis.

 

Translation - Translation changes the position of an object. The matrix to translate in the x-axis by 4 and the y-axis by -3 is:


T=
1 0  4
0 1 -3
0 0  1

The point, p=[12, 15], can then be translated by:


Tp=
1 0  4
0 1 -3
0 0  1

*
12
15
1

=
16
12
  1

where the figure resulting from the scaling above has been translated to the lower right figure (i.e. 4 on the x-axis and -3 on the y-axis).

 

Rotation - Rotation of an object about the world origin. The matrix to rotate a point by p/4 radians is:


R=
cos p/4   -sin p/4  0
sin p/4    cos p/4  0
0            0           1

The point, p=[16, 12], can then be rotated by:


Rp=
cos p/4   -sin p/4  0
sin p/4    cos p/4  0
0            0           1

*
16
12
  1

=
0.707 -0.707 0
0.707  0.707  0
0         0        1

*
16
12
  1

=
  2.82
19.79
  1

where the figure resulting from the translation above has been rotated counter-clockwise by p/4.

 

Concatenation of transformation operations - Several transformation operations can be applied to individually to a point.

For example, the three operations above of S, T and R can be applied to transform point p=[6,5] to point pRTS=[2.82, 19.79]by:


pS=Sp=
2 0 0
0 3 0
0 0 1

*
6
5
1

=
12
15
  1

 


pTS=TpS=
1 0  4
0 1 -3
0 0  1

*
12
15
  1

=
16
12
  1

 


pRTS=RpTS=
cos p/4   -sin p/4  0
sin p/4    cos p/4  0
0            0           1

*
16
12
  1

=
0.707 -0.707 0
0.707  0.707  0
0         0        1

*
16
12
  1

=
  2.82
19.79
  1

These transformations must to be applied to each point of a figure.

Rather than performing three multiplications for each point, the three transformations can be first concatenated and then applied. For a figure of 1000 points, the number of multiplications for three transformations is reduced from 3000 to 1000; the more transformations, the greater the savings.

The above transformations S, T and R can be concatenated by:


RTS=
cos p/4   -sin p/4  0
sin p/4    cos p/4  0
0            0           1

*
1 0  4
0 1 -3
0 0  1

*
2 0 0
0 3 0
0 0 1

=
1.414 -2.121 4.949
1.414  2.121 0.707
0         0        1

Note the desired order of the operations when applied individually to a point are:

  • Scale by 2, 3
  • Translate by 4, -3
  • Rotate by p/4

requires the multiplication order of: R*(T*(S*p))

However, when forming the concatenation duplicating the above order, the operations must be concatenated in reverse order of:

  • Rotate by p/4
  • Translate by 4, -3
  • Scale by 2, 3

The point, p=[6, 5], can then be scaled, translated and rotated by multiplying the concatenation, RTS, of the above transformations:


R*(T*(S*p))=pRTS=
1.414 -2.121 4.949
1.414  2.121 0.707
0         0        1

*
6
5
1

=
  2.82
19.79
  1

Each world coordinate point must be multiplied by the concatenated transformations to produce a transformed world coordinate point. One obvious place to perform the multiplication is in the world-to-display conversion.

 

Implementation - The above transformations can be concatenated to matrix C and point p transformed to point p' by the following:

C=I                        where I is the identity matrix
C=C*R
C=C*T
C=C*S
p'=C*p

A partial API and User implementation could be as follows:

API

C=Ø

identity()


C=
1 0 0
0 1 0
0 0 1

scale(xs, ys)


S=
xs 0  0
0  ys 0
0  0  1

C=C*S

translate(xt, yt)
                :

worldToDisplay(worldPt)
       transformedPt=C*worldPt   
                   :    

User

identity()
rotate(p/4)
translate(4,-3)
scale(2,3)

worldToDisplay(6, 5)

where we assume that worldToDisplay transforms each world point using the concatenation C, converts the transformed point to display coordinates and writes the display raster pixel.

 

TEST

  1. Convert some picture of your choice to PPM. Input the PPM file, implement and perform the following image processing operations and output as a PPM file.
    1. Filter out the blue component.
    2. Flip the image vertically.
    3. One other operation of your choice (shrink, enlarge, darken, etc.)
  2. Implement an API supporting clipped line and circle operations, output as PPM file.
    1. window at lower-left (-4.0, -4.0) and upper-right (4.0, 4.0)
    2. viewport at lower-left (100, 100) and upper-right (300, 300)
    3. raster display (PPM file) of 401x401 points.
    4. draw lines from:
      1. (-4.0, 4.0) to (4.0, -4.0)
      2. (-4.0, 4.0) to (2.0, -4.0)
      3. (2.0, 0.0) to (2.1, 4.0)
      4. (0.0, 0.1) to (2.0, 0.0)
    5. draw a circle of radius 2.0 centered at (0.0, 0.0) and another of radius 4.0 centered at (0.0, 2.0)
    6. draw a rectangle with lower left (-4.0, -4.0) and upper-right (4.0, 4.0).

Your results when displayed (e.g. using ImageMagick imdisplay) should appear as at right.

  1. Extend API to perform transformations to scale, translate and rotate a figure.
    1. Perform the following transformations to first scale, then translate, and finally rotate the figure of TEST 2 above to produce the rightmost figure. The figures below illustrate the intermediate results of concatenating successive transformations. Remember to concatenate transformation in the reverse order they are to be applied. To first scale, then translate, and finally rotate requires concatenating the matrices for rotation, then translation, and lastly scaling.
    2. Reverse the order of the transformations. The difference with that of 3a. will not be great but should be noticeable. In particular, the rectangle, when first rotated then scaled is distorted and is no longer a rectangle.
       
    1. Rotate by p/4.

     

    1. Rotate by p/4.
    2. Translate by 0.4 on the x-axis and -0.3 on the y-axis.

    1. Rotate by p/4.
    2. Translate by 0.4 on the x-axis and -0.3 on the y-axis.
    3. Scale by 0.5 on the x-axis and 0.3 on the y-axis.

  2. Extend API to support point display. Test by implementing the Sierpinski gasket algorithm as described on p. 43 of the text, output as PPM file. It should appear as in Figure 2.2 on p. 45 of the text.

    The algorithm is simple.

    1. Pick an initial point inside the triangle, p0.
    2. Select one of the 3 vertices at random, v1.
    3. Compute a point halfway between initial point and the randomly selected vertex, p1.
    4. Display the computed point, p1.
    5. Replace the initial with the computed point.
    6. Go to Step 2.

HINTS

  1. Matrix definitions in Java and C/C++.

    double A[][]={{1,2,3},{4,5,6},{7,8,9}};

    defines the matrix A:

    1.0 2.0 3.0
    4.0 5.0 6.0
    7.0 8.0 9.0

     A*A result is then:

    30.0    36.0    42.0
    66.0    81.0    96.0
    102.0 126.0 150.0

  2. Represent 2D points and transformations in homogenous coordinates (the apparent extra row and column).
    1. Point p=(3,4) is represented in Java as: double p[]={3,4,1}
    2. Rotation by p/4 is represented by the matrix:
      cos p/4   -sin p/4  0
      sin p/4    cos p/4  0
      0            0           1

      or in Java as: 

      double R[][]= {{Math.cos(3.1417/4), -Math.sin(3.1417/4), 0},{Math.sin(3.1417/4), Math.cos(3.1417/4),0}, {0,0,1}}; 
       

  3. Each world coordinate point must be multiplied by the concatenated transformations to produce a transformed world coordinate point. The obvious place to perform the multiplication is in the world-to-display conversion.
     

EMAIL

  1. To  with subject: YOUR NAME: B481 and Homework 1.
  2. Send a Web accessible link to a zip file named HW1.zip containing:

    Files stored on your W: drive are Web accessible by: http://homepages.ius.edu/username/filename

    To setup your IUS account to publish Web pages:
    1. Login to the IUS LAN.
    2. Click http://www.ius.edu/SpinWeb to run a setup utility and follow instructions.

    Ftp files by:

    1. ftp webftp.ius.edu
    2. ads/username
    3. password

     

  3. Instructions on executing each.