This program allows you to edit the photo itself by entering the
photo name, path, filter type as parameters.
Examples: changing the color of the photo, applying blur, and
mirror effects.
Language: C
I have posted the code below for testing.
Create the files filter.c, helpers.c, bmp.h, and helpers.h in
the same folder.
Parameters for filter effects:
-b (Blur)
-g (Grayscale)
-r (Reflection)
-s (Sepia)
-e (Edge)
Compile.
Run the program with the command which is in the "How to use"
section. The parameter is set to -g.
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include "helpers.h"
int main(int argc, char *argv[])
{
// Define allowable filters
char *filters = "bgrs";
// 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;
// Grayscale
case 'g':
grayscale(height, width, image);
break;
// Reflection
case 'r':
reflect(height, width, image);
break;
// Edges
case 'e':
edges(height, width, image);
break;
// Sepia
case 's':
sepia(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;
}
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;
helpers.h
#include "bmp.h"
// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width]);
// Convert image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width]);
// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width]);
// Detect edges
void edges(int height, int width, RGBTRIPLE image[height][width]);
// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width]);
helpers.c
/*
description: Photo filters: Grayscale, blur, reflect and sepia
@file filter.c
@author Jeremy B.
@version 1.0 3 Nov 2020
*/
#include "helpers.h"
#include <math.h>
// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
//2D arrays calculating color for each pixel and do an average of it to replace on the original
//divide sum of colors value by 3.0 for average being rounded
// row
for (int i = 0; i < height; i++)
{
// column
for (int j = 0; j < width; j++)
{
RGBTRIPLE pixel = image[i][j];
int avg = round((pixel.rgbtRed + pixel.rgbtGreen + pixel.rgbtBlue) / 3.0);
image[i][j].rgbtRed = image[i][j].rgbtGreen = image[i][j].rgbtBlue = avg;
}
}
return;
}
// Convert image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width])
{
//2D arrays calculating color for each pixel using cs50 - pset4 given formula for sepia pixel
//pixel value must not go over 255 and should be capped at 255 if over, value must be rounded
//row
for (int i = 0; i < height; i++)
{
//column
for (int j = 0; j < width; j++)
{
RGBTRIPLE pixel = image[i][j];
int sepiaRed = round(.393 * pixel.rgbtRed + .769 * pixel.rgbtGreen + .189 * pixel.rgbtBlue);
int sepiaGreen = round(.349 * pixel.rgbtRed + .686 * pixel.rgbtGreen + .168 * pixel.rgbtBlue);
int sepiaBlue = round(.272 * pixel.rgbtRed + .534 * pixel.rgbtGreen + .131 * pixel.rgbtBlue);
//if value over 255, capped at 255 and original value replacement
if (sepiaRed > 255)
{
sepiaRed = 255;
image[i][j].rgbtRed = sepiaRed;
}
else
{
image[i][j].rgbtRed = sepiaRed;
}
if (sepiaGreen > 255)
{
sepiaGreen = 255;
image[i][j].rgbtGreen = sepiaGreen;
}
else
{
image[i][j].rgbtGreen = sepiaGreen;
}
if (sepiaBlue > 255)
{
sepiaBlue = 255;
image[i][j].rgbtBlue = sepiaBlue;
}
else
{
image[i][j].rgbtBlue = sepiaBlue;
}
}
}
return;
}
// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{
//setting swap
RGBTRIPLE swap[height][width];
//2D array with an invert reading row and target pixel to swap
for (int i = 0; i < height; i++)
{
//target pixel to swap at first position in a row
int target = 0;
//reverse reading, target current pixel
for (int j = width - 1; j >= 0; j--, target++)
{
swap[i][target] = image[i][j];
}
}
//swap loop
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
image[i][j] = swap[i][j];
}
}
return;
}
// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
//set swap color
RGBTRIPLE swap[height][width];
//image row
for (int i = 0; i < height; i++)
{
//image column
for (int j = 0; j < width; j++)
{
//set sum RGB
float sum_red = 0;
float sum_blue = 0;
float sum_green = 0;
//set counter
int counter = 0;
//set 2 area array, row and column for looking around current pixel
int row_area[] = {i - 1, i, i + 1};
int col_area[] = {j - 1, j, j + 1};
//loop row area, up - current - down, around targeted pixel
for (int row = 0; row < 3; row++)
{
//loop column area, left side - current - right side, around targeted pixel
for (int col = 0; col < 3; col++)
{
//set current
int row_current = row_area[row];
int col_current = col_area[col];
if (row_current >= 0 && row_current < height && col_current >= 0 && col_current < width)
{
RGBTRIPLE pixel = image[row_current][col_current];
//sum total of current pixel iterations (and around area) for each colors
sum_red += pixel.rgbtRed;
sum_green += pixel.rgbtGreen;
sum_blue += pixel.rgbtBlue;
//counter++
counter++;
}
}
}
//determine value of total / count into swap
swap[i][j].rgbtRed = round(sum_red / counter);
swap[i][j].rgbtGreen = round(sum_green / counter);
swap[i][j].rgbtBlue = round(sum_blue / counter);
}
}
//swap loop
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
image[i][j] = swap[i][j];
}
}
return;
}
// Detect edges
void edges(int height, int width, RGBTRIPLE image[height][width])
{
//set swap
RGBTRIPLE swap[height][width];
//sobel operator algorithm
//Gx kernel 3x3
int Gx[3][3] =
{
{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}
};
//Gy Kernel 3x3
int Gy[3][3] =
{
{-1, -2, -1},
{0, 0, 0},
{1, 2, 1}
};
//image row
for (int i = 0; i < height; i++)
{
//image column
for (int j = 0; j < width; j++)
{
//set Gx RGB
float Gx_red = 0;
float Gx_blue = 0;
float Gx_green = 0;
//set Gy RGB
float Gy_red = 0;
float Gy_blue = 0;
float Gy_green = 0;
//set 2 area array, row and column for looking around current pixel
int row_area[] = {i - 1, i, i + 1};
int col_area[] = {j - 1, j, j + 1};
//loop row area, up - current - down, around targeted pixel
for (int row = 0; row < 3; row++)
{
//loop column area, left side - current - right side, around targeted pixel
for (int col = 0; col < 3; col++)
{
//set current
int row_current = row_area[row];
int col_current = col_area[col];
if (row_current >= 0 && row_current < height && col_current >= 0 && col_current < width)
{
RGBTRIPLE pixel = image[row_current][col_current];
//sum total of current pixel / position iterations (and around area) for each colors * kernels Gx and Gy positions
Gx_red += Gx[row][col] * pixel.rgbtRed;
Gx_green += Gx[row][col] * pixel.rgbtGreen;
Gx_blue += Gx[row][col] * pixel.rgbtBlue;
Gy_red += Gy[row][col] * pixel.rgbtRed;
Gy_green += Gy[row][col] * pixel.rgbtGreen;
Gy_blue += Gy[row][col] * pixel.rgbtBlue;
}
}
}
//Gy and Gx square root caluculation
int GxGyRed = round(sqrt((Gx_red * Gx_red) + (Gy_red * Gy_red)));
int GxGyGreen = round(sqrt((Gx_green * Gx_green) + (Gy_green * Gy_green)));
int GxGyBlue = round(sqrt((Gx_blue * Gx_blue) + (Gy_blue * Gy_blue)));
//determine value to swap, if value over 255, value is capped at 255
if (GxGyRed > 255)
{
GxGyRed = 255;
swap[i][j].rgbtRed = GxGyRed;
}
else
{
swap[i][j].rgbtRed = GxGyRed;
}
if (GxGyGreen > 255)
{
GxGyGreen = 255;
swap[i][j].rgbtGreen = GxGyGreen;
}
else
{
swap[i][j].rgbtGreen = GxGyGreen;
}
if (GxGyBlue > 255)
{
GxGyBlue = 255;
swap[i][j].rgbtBlue = GxGyBlue;
}
else
{
swap[i][j].rgbtBlue = GxGyBlue;
}
}
}
//swap loop
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
image[i][j] = swap[i][j];
}
}
return;
}