Problem Set 4: Filter (More/Less)
Evidence of a 5-point Submission
- Minimal iterations through the image; at most once for
grayscaleandsepia, at most twice forblurandedges, and at most one-half (up towidth / 2) forreflect - Minimal casting, rounding, square rooting; no more casts or rounds than are strictly necessary. You should only need a single
(float)to do the math needed in this assignment - Clean handling of edge cases;
blurhandles its edge cases by changing the bounds for the innermost two for loops, NOT by only running the contents of those two loops based on a condition (note that edges does have to use an inner condition) - Ideal edges implementation; uses a 2D array to store the kernel, uses the
&&operator to avoid extra conditional statements, efficient iteration in inner loops
Evidence of a 4-point Submission
Like a 5-point submission, but with some small mistakes, such as:
- A few extraneous roundings, castings, or other arithmetical operations
- Might use three lines to copy a pixel from one place to another instead of one; i.e., might do
image[i][j].rgbtRed = new_image[i][j].rgbtRed image[i][j].rgbtGreen = new_image[i][j].rgbtGreen image[i][j].rgbtBlue = new_image[i][j].rgbtBlueinstead of
image[i][j] = new_image[i][j] - Might use a condition instead of different loop bounds to execute the inner two
forloops inblur - Might not use a helper function to handle certain things, akin to the max/min helper functions in the staff solution
Evidence of a 3-point Submission
Like a 4-point submission, but with one or two more serious errors, such as:
- Duplicates the image where it isn’t necessary; i.e., makes a new array in any function except for
blurand/oredges - Extraneous iteration;
grayscaleorsepiaiterate more than once, orbluroredgesiterate more than twice, orreflectiterates further thanwidth / 2 - Excess conditionals that make the code much harder to read
Evidence of a 2-point Submission
- Makes a copy of the input image in any function except
blurandedges -
grayscalefunction may unnecessarily use three separate variables to store a given pixel’s red, green, and blue values, may use unnecessary conditionals or computation to calculate the average- Unnecessary parentheses
- More than one
round - More than one cast
-
sepiafunction may use aforloop to iterate over the 3 RGB values, may use unnecessary conditionals or computation to calculate the sepia values- Unnecessary rounding
- Uses unnecessary conditionals that add extra computation
-
reflectfunction copies RGB values instead of the entire pixels, uses awhileloop to iterate over half of the image -
blurfunction iterates over the height and width three or more times, may include many conditions to check for edge cases -
edgesfunction iterates the height and width three or more times, may include unnecessary conditional statements
Evidence of a 1-point Submission
-
grayscalefunction may iterate over the height and width more than once, may not use theroundfunction (manually rounding) -
sepiafunction may iterate over the height and width more than once -
reflectfunction iterates over more than half of the image (more thanwidth / 2) -
blurfunction hard-codes edge cases -
edgesfunction hard-codes many cases, repeats large portions of code (instead of grouping cases together)
Example Implementations (Worse vs. Better)
grayscale Function
Worse Implementation
The example below uses three unnecessary variables for RGB values, casts more than once and includes an unnecessary conditional when calculating the average
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int red = image[i][j].rgbtRed;
int green = image[i][j].rgbtGreen;
int blue = image[i][j].rgbtBlue;
float avg = (((float) red + (float) green + (float) blue) / 3);
int max_color_value = 255;
int average = 0;
if (avg < max_color_value)
{
average = round(avg);
}
else
{
average = max_color_value;
}
image[i][j].rgbtRed = average;
image[i][j].rgbtGreen = average;
image[i][j].rgbtBlue = average;
}
}
Better Implementation
The example below uses one variable to reference the given pixel and calculates the average in a neat and efficient way (no extraneous rounding or casting)
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
RGBTRIPLE pixel = image[i][j];
int avg = round((float) (pixel.rgbtRed + pixel.rgbtGreen + pixel.rgbtBlue) / 3);
image[i][j].rgbtRed = avg;
image[i][j].rgbtGreen = avg;
image[i][j].rgbtBlue = avg;
}
}
sepia Function
Worse Implementation
The lengthy example below unnecessarily rounds three times for each sepia value, inconsistently initializes some variables outside of the for loop and some variables inside the for loop. May also be improved by eliminating the else statements (instead, just using the if statements to reassign the sepiaColor value)
int sepiaRed;
int sepiaGreen;
int sepiaBlue;
for (int i = 0; i < height; i += 1)
{
for (int j = 0; j < width; j += 1)
{
int red = image[i][j].rgbtRed;
int green = image[i][j].rgbtGreen;
int blue = image[i][j].rgbtBlue;
sepiaRed = round(.393 * red) + round(.769 * green) + round(.189 * blue);
if (sepiaRed > 255)
{
image[i][j].rgbtRed = 255;
}
else
{
image[i][j].rgbtRed = sepiaRed;
}
sepiaGreen = round(.349 * red) + round(.686 * green) + round(.168 * blue);
if (sepiaGreen > 255)
{
image[i][j].rgbtGreen = 255;
}
else
{
image[i][j].rgbtGreen = sepiaGreen;
}
sepiaBlue = round(.272 * red) + round(.534 * green) + round(.131 * blue);
if (sepiaBlue > 255)
{
image[i][j].rgbtBlue = 255;
}
else
{
image[i][j].rgbtBlue = sepiaBlue;
}
}
}
Better Implementation
The example below uses three variables for each RGB value, a min helper function, and rounds in each computation just once
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int red = image[i][j].rgbtRed;
int green = image[i][j].rgbtGreen;
int blue = image[i][j].rgbtBlue;
image[i][j].rgbtRed = min(255, round(red * .393 + green * .769 + blue * .189));
image[i][j].rgbtGreen = min(255, round(red * .349 + green * .686 + blue * .168));
image[i][j].rgbtBlue = min(255, round(red * .272 + green * .534 + blue * .131));
}
}
reflect Function
Worse Implementation
The example below uses a while loop instead of a for loop (does not iterate over half the width)
int left = 0;
int right = width - 1;
RGBTRIPLE temp;
for (int i = 0; i < height; i++)
{
while (left < right)
{
temp = image[i][left];
image[i][left] = image[i][right];
image[i][right] = temp;
left++;
right--;
}
left = 0;
right = width - 1;
}
The example copies over each RGB value individually (instead of the whole pixel). Also includes unnecessary parentheses and an unnecessary return statement
for (int i = 0; i < width / 2; i++)
{
for (int j = 0; j < height; j++)
{
int buffer_red = image[j][i].rgbtRed;
int buffer_green = image[j][i].rgbtGreen;
int buffer_blue = image[j][i].rgbtBlue;
image[j][i].rgbtRed = image[j][(width - 1) - i].rgbtRed;
image[j][i].rgbtGreen = image[j][(width - 1) - i].rgbtGreen;
image[j][i].rgbtBlue = image[j][(width - 1) - i].rgbtBlue;
image[j][(width - 1) - i].rgbtRed = buffer_red;
image[j][(width - 1) - i].rgbtGreen = buffer_green;
image[j][(width - 1) - i].rgbtBlue = buffer_blue;
}
}
return;
Better Implementation
The example performs no excess iteration and contains no extraneous variables.
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width / 2; j++)
{
RGBTRIPLE tmp = image[i][j];
image[i][j] = image[i][width - 1 - j];
image[i][width - 1 - j] = tmp;
}
}
blur Function
Worse Implementation
This example iterates over the image an unnecessary number of times (inner-two-most for loops unnecessarily iterate over the entire image again). Additionally, the counter variable is a float (an int makes more sense here) and unnecessary variables are declared when calculating the average
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int red = 0;
int green = 0;
int blue = 0;
float counter = 0.0;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (abs(j - x) <= 1 && abs(i - y) <= 1)
{
red = red + copy[y][x].rgbtRed;
green = green + copy[y][x].rgbtGreen;
blue = blue + copy[y][x].rgbtBlue;
counter = counter + 1;
}
}
}
int red_average = round(red / counter);
int green_average = round(green / counter);
int blue_average = round(blue / counter);
image[i][j].rgbtRed = red_average;
image[i][j].rgbtGreen = green_average;
image[i][j].rgbtBlue = blue_average;
}
}
Better Implementation
This example handles both edge pixels and inner pixels in the same, robust way. Also begins and ends indexing in the for loop at the proper pixel indices using min and max helper functions (no extra calculation needed)
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int red = 0, green = 0, blue = 0, count = 0;
for (int row = max(0, i - 1); row <= min(height - 1, i + 1); row++)
{
for (int col = max(0, j - 1); col <= min(width - 1, j + 1); col++)
{
count++;
red += image[row][col].rgbtRed;
green += image[row][col].rgbtGreen;
blue += image[row][col].rgbtBlue;
}
}
new_image[i][j].rgbtRed = round((float) red / count);
new_image[i][j].rgbtGreen = round((float) green / count);
new_image[i][j].rgbtBlue = round((float) blue / count);
}
}
edges Function
Worse Implementation
The example below includes unnecessary variables and an if-else statement that could be avoided with a better choice of starting and ending indexing variables and conditional sub-statements
int gx[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int gy[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int redSumX = 0;
int greenSumX = 0;
int blueSumX = 0;
int redSumY = 0;
int greenSumY = 0;
int blueSumY = 0;
int a = 0;
int b = 0;
for (int m = i - 1; m < i + 2; m++)
{
for (int n = j - 1; n < j + 2; n++)
{
if (m < 0 || n < 0 || m >= height || n >= width)
{
b++;
if (b > 2)
{
b = 0;
a++;
}
}
else
{
redSumX += (imagecopy[m][n].rgbtRed * gx[a][b]);
greenSumX += (imagecopy[m][n].rgbtGreen * gx[a][b]);
blueSumX += (imagecopy[m][n].rgbtBlue * gx[a][b]);
redSumY += (imagecopy[m][n].rgbtRed * gy[a][b]);
greenSumY += (imagecopy[m][n].rgbtGreen * gy[a][b]);
blueSumY += (imagecopy[m][n].rgbtBlue * gy[a][b]);
b++;
if (b > 2)
{
b = 0;
a++;
}
}
}
}
// the rest of the function beneath here...
Better Implementation
The example below uses a 2D matrix to store the multiplier values, and includes an efficient choice of for loop indices and conditional sub-statements to avoid repeating unnecessary code
int xkernel[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int ykernel[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int xred = 0, xgreen = 0, xblue = 0, yred = 0, ygreen = 0, yblue = 0;
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
int xadj = xkernel[x + 1][y + 1];
int yadj = ykernel[x + 1][y + 1];
if (i + x < height && i + x >= 0 && j + y < width && j + y >= 0)
{
xred += xadj * pixel.rgbtRed;
xgreen += xadj * pixel.rgbtGreen;
xblue += xadj * pixel.rgbtBlue;
yred += yadj * pixel.rgbtRed;
ygreen += yadj * pixel.rgbtGreen;
yblue += yadj * pixel.rgbtBlue;
}
}
}
// the rest of the function beneath here...