I've been taking cs50 for about under a month now and I've finally finished with "filter" in pset4. The code passes all the green checks on check50. Our task was to implement functions in helpers.c so that a user can apply grayscale, reflection, blur, or edge detection filters to their images. We were asked not to modify any of the function signatures or any files other than helpers.c.
My question is, how I could refactor the code in the edge detection block so that it is shorter with less repeated code? If there is a solution involving pointers, I'd much prefer that if possible! Any tips on other blocks are welcome as well. Thank you!
Here are links to the outline of cs50/pset4/filter and information on the sobel operator:
cs50/pset4/filter
Sobel
Files provided that we were asked not to modify:
bmp.h
// BMP-related data types based on Microsoft's own #include <stdint.h> /** * Common Data Types * * The data types in this section are essentially aliases for C/C++ * primitive data types. * * Adapted from http://msdn.microsoft.com/en-us/library/cc230309.aspx. * See http://en.wikipedia.org/wiki/Stdint.h for more on stdint.h. */ typedef uint8_t BYTE; typedef uint32_t DWORD; typedef int32_t LONG; typedef uint16_t WORD; /** * BITMAPFILEHEADER * * The BITMAPFILEHEADER structure contains information about the type, size, * and layout of a file that contains a DIB [device-independent bitmap]. * * Adapted from http://msdn.microsoft.com/en-us/library/dd183374(VS.85).aspx. */ typedef struct { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } __attribute__((__packed__)) BITMAPFILEHEADER; /** * BITMAPINFOHEADER * * The BITMAPINFOHEADER structure contains information about the * dimensions and color format of a DIB [device-independent bitmap]. * * Adapted from http://msdn.microsoft.com/en-us/library/dd183376(VS.85).aspx. */ typedef struct { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } __attribute__((__packed__)) BITMAPINFOHEADER; /** * RGBTRIPLE * * This structure describes a color consisting of relative intensities of * red, green, and blue. * * Adapted from http://msdn.microsoft.com/en-us/library/aa922590.aspx. */ typedef struct { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } __attribute__((__packed__)) RGBTRIPLE;
filter.c
#include <getopt.h> #include <stdio.h> #include <stdlib.h> #include "helpers.h" int main(int argc, char *argv[]) { // Define allowable filters char *filters = "begr"; // Get filter flag and check validity char filter = getopt(argc, argv, filters); if (filter == '?') { fprintf(stderr, "Invalid filter.\n"); return 1; } // Ensure only one filter if (getopt(argc, argv, filters) != -1) { fprintf(stderr, "Only one filter allowed.\n"); return 2; } // Ensure proper usage if (argc != optind + 2) { fprintf(stderr, "Usage: filter [flag] infile outfile\n"); return 3; } // Remember filenames char *infile = argv[optind]; char *outfile = argv[optind + 1]; // Open input file FILE *inptr = fopen(infile, "r"); if (inptr == NULL) { fprintf(stderr, "Could not open %s.\n", infile); return 4; } // Open output file FILE *outptr = fopen(outfile, "w"); if (outptr == NULL) { fclose(inptr); fprintf(stderr, "Could not create %s.\n", outfile); return 5; } // Read infile's BITMAPFILEHEADER BITMAPFILEHEADER bf; fread(&bf, sizeof(BITMAPFILEHEADER), 1, inptr); // Read infile's BITMAPINFOHEADER BITMAPINFOHEADER bi; fread(&bi, sizeof(BITMAPINFOHEADER), 1, inptr); // Ensure infile is (likely) a 24-bit uncompressed BMP 4.0 if (bf.bfType != 0x4d42 || bf.bfOffBits != 54 || bi.biSize != 40 || bi.biBitCount != 24 || bi.biCompression != 0) { fclose(outptr); fclose(inptr); fprintf(stderr, "Unsupported file format.\n"); return 6; } int height = abs(bi.biHeight); int width = bi.biWidth; // Allocate memory for image RGBTRIPLE(*image)[width] = calloc(height, width * sizeof(RGBTRIPLE)); if (image == NULL) { fprintf(stderr, "Not enough memory to store image.\n"); fclose(outptr); fclose(inptr); return 7; } // Determine padding for scanlines int padding = (4 - (width * sizeof(RGBTRIPLE)) % 4) % 4; // Iterate over infile's scanlines for (int i = 0; i < height; i++) { // Read row into pixel array fread(image[i], sizeof(RGBTRIPLE), width, inptr); // Skip over padding fseek(inptr, padding, SEEK_CUR); } // Filter image switch (filter) { // Blur case 'b': blur(height, width, image); break; // Edges case 'e': edges(height, width, image); break; // Grayscale case 'g': grayscale(height, width, image); break; // Reflect case 'r': reflect(height, width, image); break; } // Write outfile's BITMAPFILEHEADER fwrite(&bf, sizeof(BITMAPFILEHEADER), 1, outptr); // Write outfile's BITMAPINFOHEADER fwrite(&bi, sizeof(BITMAPINFOHEADER), 1, outptr); // Write new pixels to outfile for (int i = 0; i < height; i++) { // Write row to outfile fwrite(image[i], sizeof(RGBTRIPLE), width, outptr); // Write padding at end of row for (int k = 0; k < padding; k++) { fputc(0x00, outptr); } } // Free memory for image free(image); // Close infile fclose(inptr); // Close outfile fclose(outptr); return 0; }
My helpers.c
#include "helpers.h"
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
// Prototypes
void swap(RGBTRIPLE *a, RGBTRIPLE *b);
// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
// Iterate through the height or also known as each row
for (int i = 0; i < height; i++)
{
// Iterate through the width or also known as each pixel/column
for (int j = 0; j < width; j++)
{
// Calculate the average of the R, G, and B values and round to nearest integer
int average = round(((double) image[i][j].rgbtBlue + (double) image[i][j].rgbtGreen + (double) image[i][j].rgbtRed) / 3);
// Set the values of R, G, and B to the average, making them the same, to produce the correct shade of gray
image[i][j].rgbtBlue = average;
image[i][j].rgbtGreen = average;
image[i][j].rgbtRed = average;
}
}
return;
}
// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{
// Iterate through the height or also known as each row
for (int i = 0; i < height; i++)
{
// Iterate through the width or also known as each pixel/column
for (int j = 0; j < width; j++)
{
// Perform swap up until the middle
if (j < width / 2)
{
swap(&image[i][j], &image[i][width - (j + 1)]);
}
}
}
return;
}
// Function to swap two elements
void swap(RGBTRIPLE *a, RGBTRIPLE *b)
{
RGBTRIPLE temp;
temp = *a;
*a = *b;
*b = temp;
}
// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
// Initialize copy of image
RGBTRIPLE temp[height][width];
// Make a copy of image to preserve original values
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
temp[i][j] = image[i][j];
}
}
// Iterate through the height or also known as each row
for (int i = 0; i < height; i++)
{
// Iterate through the width or also known as each pixel/column
for (int j = 0; j < width; j++)
{
// Variable that counts how many numbers added to arrive at the sum
int count = 0;
// Sum variables for each colour
double sum_blue = 0;
double sum_green = 0;
double sum_red = 0;
// Loop to check the surrounding pixels within 1 column and 1 row
for (int k = i - 1; k <= i + 1; k++)
{
for (int l = j - 1; l <= j + 1; l++)
{
// Only adds pixels that are within the image boundaries
if (k >= 0 && l >= 0 && k < height && l < width)
{
sum_blue += temp[k][l].rgbtBlue;
sum_green += temp[k][l].rgbtGreen;
sum_red += temp[k][l].rgbtRed;
count++;
}
}
}
// Use the averages from the surrounding pixels and set the new colour values for the iterated pixel
image[i][j].rgbtBlue = round(sum_blue / count);
image[i][j].rgbtGreen = round(sum_green / count);
image[i][j].rgbtRed = round(sum_red / count);
}
}
return;
}
// Detect edges
void edges(int height, int width, RGBTRIPLE image[height][width])
{
RGBTRIPLE temp[height][width];
// Make copy of image
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
temp[i][j] = image[i][j];
}
}
// Sobel Operator matrices for Gx and Gy
int kernel_Gx[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int kernel_Gy[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
// Iterate through the height or also known as each row
for (int i = 0; i < height; i++)
{
// Iterate through the width or also known as each pixel/column
for (int j = 0; j < width; j++)
{
// Initialize values for weighted sums in the x direction
double gx_blue = 0;
double gx_green = 0;
double gx_red = 0;
// Initialize values for weighted sums in the y direction
double gy_blue = 0;
double gy_green = 0;
double gy_red = 0;
// Counter to detect what row of the 3x3 the loop is iterating
int row = 0;
// Loop to check the surrounding pixels within 1 row
for (int k = i - 1; k <= i + 1; k++)
{
// Counter to detect what column of the 3x3 grid the loop is iterating
int column = 0;
// Loop to check the surrounding pixels within 1 column
for (int l = j - 1; l <= j + 1; l++)
{
// Only adds pixels that are within the image boundaries
if (k >= 0 && l >= 0 && k < height && l < width)
{
// Calculate Gx
gx_blue += (kernel_Gx[row][column] * temp[k][l].rgbtBlue);
gx_green += (kernel_Gx[row][column] * temp[k][l].rgbtGreen);
gx_red += (kernel_Gx[row][column] * temp[k][l].rgbtRed);
// Calculate Gy
gy_blue += (kernel_Gy[row][column] * temp[k][l].rgbtBlue);
gy_green += (kernel_Gy[row][column] * temp[k][l].rgbtGreen);
gy_red += (kernel_Gy[row][column] * temp[k][l].rgbtRed);
}
column++;
}
row++;
}
// Combine Gx and Gy
int sobel_blue = round(sqrt(pow(gx_blue, 2) + pow(gy_blue, 2)));
int sobel_green = round(sqrt(pow(gx_green, 2) + pow(gy_green, 2)));
int sobel_red = round(sqrt(pow(gx_red, 2) + pow(gy_red, 2)));
// Set the new colour values for the iterated pixel and cap at 255 if necessary
image[i][j].rgbtBlue = (sobel_blue > 255) ? 255 : sobel_blue;
image[i][j].rgbtGreen = (sobel_green > 255) ? 255 : sobel_green;
image[i][j].rgbtRed = (sobel_red > 255) ? 255 : sobel_red;
}
}
return;
}
LONGinstead ofint32_t? \$\endgroup\$typedef int32_t LONGin one of the fileswe were asked not to modify.) \$\endgroup\$