Skip to content

Instantly share code, notes, and snippets.

@mhofwell
Last active November 14, 2023 14:09
Show Gist options
  • Save mhofwell/5f0b9af1fc4916892bb16126062be96c to your computer and use it in GitHub Desktop.
Save mhofwell/5f0b9af1fc4916892bb16126062be96c to your computer and use it in GitHub Desktop.
Applying Filters by Manipulating Pixel Values!
// 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;
// This program enables you to apply some filters to your .bmp images
// greyscale, blur, and sepia filters work.
#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;
// 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;
}
#include "helpers.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
// Convert image to grayscale
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
// Get average RGB values!
int average = round((float)(image[i][j].rgbtBlue + image[i][j].rgbtRed + image[i][j].rgbtGreen) / 3);
image[i][j].rgbtBlue = average;
image[i][j].rgbtRed = average;
image[i][j].rgbtGreen = average;
}
}
return;
}
// Convert image to sepia
void sepia(int height, int width, RGBTRIPLE image[height][width])
{
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
// use the Sepia conversion
int sepiaRed = round(.393 * image[i][j].rgbtRed + .769 * image[i][j].rgbtGreen + .189 * image[i][j].rgbtBlue);
int sepiaGreen = round(.349 * image[i][j].rgbtRed + .686 * image[i][j].rgbtGreen + .168 * image[i][j].rgbtBlue);
int sepiaBlue = round(.272 * image[i][j].rgbtRed + .534 * image[i][j].rgbtGreen + .131 * image[i][j].rgbtBlue);
if (sepiaBlue > 255)
{
sepiaBlue = 255;
}
if (sepiaRed > 255)
{
sepiaRed = 255;
}
if (sepiaGreen > 255)
{
sepiaGreen = 255;
}
// store the result in the image pixel.
image[i][j].rgbtBlue = sepiaBlue;
image[i][j].rgbtRed = sepiaRed;
image[i][j].rgbtGreen = sepiaGreen;
}
}
return;
}
// Reflect image horizontally
void reflect(int height, int width, RGBTRIPLE image[height][width])
{
int half = round(width / 2);
for (int i = 0; i < height; i++)
{
for (int j = 0; j < half; j++)
{
// initialize a temp struct to hold an RGB value
RGBTRIPLE tempRGB;
// put the select pixel in temp slot
tempRGB.rgbtBlue = image[i][j].rgbtBlue;
tempRGB.rgbtRed = image[i][j].rgbtRed;
tempRGB.rgbtGreen = image[i][j].rgbtGreen;
// switch the selected pixel with its opposite.
image[i][j].rgbtBlue = image[i][width - 1 - j].rgbtBlue;
image[i][j].rgbtRed = image[i][width - 1 - j].rgbtRed;
image[i][j].rgbtGreen = image[i][width - 1 - j].rgbtGreen;
// assign the selected pixel to its opposite.
image[i][width - 1 - j].rgbtBlue = tempRGB.rgbtBlue;
image[i][width - 1 - j].rgbtRed = tempRGB.rgbtRed;
image[i][width - 1 - j].rgbtGreen = tempRGB.rgbtGreen;
}
}
return;
}
// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width])
{
RGBTRIPLE tempIMG[height][width];
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int redAvg = 0;
int blueAvg = 0;
int greenAvg = 0;
int blueSum = image[i][j].rgbtBlue;
int redSum = image[i][j].rgbtRed;
int greenSum = image[i][j].rgbtGreen;
int count = 1;
// check top left
if ((i - 1) >= 0 && (j - 1) >= 0)
{
blueSum += image[i - 1][j - 1].rgbtBlue;
redSum += image[i - 1][j - 1].rgbtRed;
greenSum += image[i - 1][j - 1].rgbtGreen;
count++;
}
// check top center
if ((i - 1) >= 0)
{
blueSum += image[i - 1][j].rgbtBlue;
redSum += image[i - 1][j].rgbtRed;
greenSum += image[i - 1][j].rgbtGreen;
count++;
}
// check top right
if ((i - 1) >= 0 && (j + 1) < width)
{
blueSum += image[i - 1][j + 1].rgbtBlue;
redSum += image[i - 1][j + 1].rgbtRed;
greenSum += image[i - 1][j + 1].rgbtGreen;
count++;
}
// check right
if ((j + 1) < width)
{
blueSum += image[i][j + 1].rgbtBlue;
redSum += image[i][j + 1].rgbtRed;
greenSum += image[i][j + 1].rgbtGreen;
count++;
}
// check bottom right
if ((i + 1) < height && (j + 1) < width)
{
blueSum += image[i + 1][j + 1].rgbtBlue;
redSum += image[i + 1][j + 1].rgbtRed;
greenSum += image[i + 1][j + 1].rgbtGreen;
count++;
}
// check bottom middle
if ((i + 1) < height)
{
blueSum += image[i + 1][j].rgbtBlue;
redSum += image[i + 1][j].rgbtRed;
greenSum += image[i + 1][j].rgbtGreen;
count++;
}
// check bottom left
if ((i + 1) < height && (j - 1) >= 0)
{
blueSum += image[i + 1][j - 1].rgbtBlue;
redSum += image[i + 1][j - 1].rgbtRed;
greenSum += image[i + 1][j - 1].rgbtGreen;
count++;
}
// check left
if ((j - 1) >= 0)
{
blueSum += image[i][j - 1].rgbtBlue;
redSum += image[i][j - 1].rgbtRed;
greenSum += image[i][j - 1].rgbtGreen;
count++;
}
// get RGB average of all surrounding pixels
redAvg = round((float)redSum / count);
blueAvg = round((float)blueSum / count);
greenAvg = round((float)greenSum / count);
// assign those to a slot in a temp array that mirrors our image[]
tempIMG[i][j].rgbtBlue = blueAvg;
tempIMG[i][j].rgbtRed = redAvg;
tempIMG[i][j].rgbtGreen = greenAvg;
}
}
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
// copy the temp array items to the image and overwrite their original values.
image[i][j].rgbtBlue = tempIMG[i][j].rgbtBlue;
image[i][j].rgbtRed = tempIMG[i][j].rgbtRed;
image[i][j].rgbtGreen = tempIMG[i][j].rgbtGreen;
}
}
return;
}
#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]);
// Blur image
void blur(int height, int width, RGBTRIPLE image[height][width]);
filter:
clang -fsanitize=signed-integer-overflow -fsanitize=undefined -ggdb3 -O0 -Qunused-arguments -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow -o filter filter.c helpers.c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment