Filter is a C program that allows you to apply filters to BMP images.
Usage:
Usage: filter [OPTIONS] <infile> <outfile>
Review Goals:
General coding comments, style, potential undefined behavior, et cetera.
How can I eliminate the use of the non-standard packed attribute from the code?
Code:
bmp.h:
// BMP-related data types based on Microsoft's own
#include <stdint.h>
// These data types are essentially aliases for C/C++ primitive data types.
// Adapted from http://msdn.microsoft.com/en-us/library/cc230309.aspx.
// See https://en.wikipedia.org/wiki/C_data_types#stdint.h for more on stdint.h.
typedef uint8_t BYTE;
typedef uint32_t DWORD;
typedef int32_t LONG;
typedef uint16_t WORD;
// 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;
// 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;
// The RGBTRIPLE 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:
#ifdef _POSIX_C_SOURCE
#undef _POSIX_C_SOURCE
#endif
#ifdef _XOPEN_SOURCE
#undef _XOPEN_SOURCE
#endif
#define _POSIX_C_SOURCE 200819L
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <getopt.h>
#include "helpers.h"
#define ARRAY_CARDINALITY(x) (sizeof (x) / sizeof (*(x)))
static void help(void)
{
puts("Usage: filter [OPTIONS] <infile> <outfile>\n"
"\n\tTransform your BMP images with powerful filters.\n\n"
"Options:\n"
" -s, --sepia Apply a sepia filter for a warm, vintage look.\n"
" -r, --reverse Create a horizontal reflection for a mirror effect.\n"
" -g, --grayscale Convert the image to classic greyscale.\n"
" -b, --blur Add a soft blur to the image.\n"
" -h, --help displays this message and exit.\n");
exit(EXIT_SUCCESS);
}
static void apply_filter(char filter, size_t height, size_t width,
RGBTRIPLE image[height][width])
{
struct filter_map {
const char filter_char;
void (* const func)(size_t x, size_t y, RGBTRIPLE image[x][y]);
} const filter_map[] = {
{ 'b', blur },
{ 'g', grayscale },
{ 'r', reflect },
{ 's', sepia }
};
for (size_t i = 0; i < ARRAY_CARDINALITY(filter_map); ++i) {
if (filter_map[i].filter_char == filter) {
filter_map[i].func(height, width, image);
return;
}
}
}
static int write_image(const BITMAPFILEHEADER *restrict bf,
const BITMAPINFOHEADER *restrict bi,
FILE *restrict out, size_t height,
size_t width, const RGBTRIPLE image[height][width])
{
// Write outfile's BITMAPFILEHEADER and BITMAPINFOHEADER.
if (fwrite(bf, sizeof *bf, 1, out) != 1
|| fwrite(bi, sizeof *bi, 1, out) != 1) {
fputs("Failed to write to file.\n", stderr);
return -1;
}
// Determine padding for scanlines
const size_t padding = (4 - (width * sizeof (RGBTRIPLE)) % 4) % 4;
const int pad_byte = 0x00;
// Write new pixels to outfile
for (size_t i = 0; i < height; ++i) {
// Write row to outfile, with padding at the end.
if (fwrite(image[i], sizeof image[i][0], width, out) != width
|| fwrite(&pad_byte, 1, padding, out) != padding) {
fputs("Failed to write to file.\n", stderr);
return -1;
}
}
return 0;
}
static void *read_image(BITMAPFILEHEADER *restrict bf,
BITMAPINFOHEADER *restrict bi, size_t *restrict height,
size_t *restrict width, FILE *restrict inptr)
{
// Read infile's BITMAPFILEHEADER and BITMAPINFOHEADER.
if (fread(bf, sizeof *bf, 1, inptr) != 1
|| fread(bi, sizeof *bi, 1, inptr) != 1) {
fputs("Failed to read file.\n", stderr);
return NULL;
}
// 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) {
fputs("Unsupported file format.\n", stderr);
return NULL;
}
// Get image's dimensions
*height = (size_t) abs(bi->biHeight);
*width = (size_t) bi->biWidth;
// Allocate memory for image
RGBTRIPLE(*image)[*width] = calloc(*height, *width * sizeof (RGBTRIPLE));
if (!image) {
fputs("Not enough memory to store image.\n", stderr);
return NULL;
}
// Determine padding for scanlines
const long int padding = (4 - (*width * sizeof (RGBTRIPLE)) % 4) % 4;
// Iterate over infile's scanlines
for (size_t i = 0; i < *height; i++) {
// Read row into pixel array
if (fread(image[i], sizeof image[i][0], *width, inptr) != *width) {
fputs("Failed to read file.\n", stderr);
return NULL;
}
// Skip over padding
fseek(inptr, padding, SEEK_CUR);
}
return image;
}
static int process_image(char filter, FILE *restrict inptr, FILE *restrict outptr)
{
// Image's BITMAPFILEHEADER
BITMAPFILEHEADER bf;
// image's BITMAPINFOHEADER
BITMAPINFOHEADER bi;
size_t height = 0;
size_t width = 0;
void *const image = read_image(&bf, &bi, &height, &width, inptr);
if (!image) {
return -1;
}
apply_filter(filter, height, width, image);
if (write_image(&bf, &bi, outptr, height, width, image) == -1) {
return -1;
}
free(image);
return 0;
}
static char parse_args(int argc, char *argv[])
{
// Define allowable filters
static const struct option long_options[] = {
{ "grayscale", no_argument, NULL, 'g' },
{ "reverse", no_argument, NULL, 'r' },
{ "sepia", no_argument, NULL, 's' },
{ "blur", no_argument, NULL, 'b' },
{ "help", no_argument, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
// Get filter flag and check validity
const int filter = getopt_long(argc, argv, "grsbh", long_options, NULL);
if (filter == '?') {
fputs("Invalid filter.\n", stderr);
return '\0';
}
if (filter == -1 || filter != 'h') {
if (argc != optind + 2) {
fputs("Usage: filter [OPTIONS] <infile> <outfile>\n"
"Try filter -h for help.\n", stderr);
return '\0';
}
return (char) filter;
}
// Ensure single flag
if (getopt_long(argc, argv, "grsbh", long_options, NULL) != -1) {
fputs("Only one filter allowed.\n", stderr);
return '\0';
}
if (filter == 'h' && optind == argc) {
return (char) filter;
}
fputs("Usage: filter [OPTIONS] <infile> <outfile>\n"
"Try filter -h for help.\n", stderr);
return '\0';
}
int main(int argc, char *argv[])
{
const char filter = parse_args(argc, argv);
if (filter == '\0') {
return EXIT_FAILURE;
} else if (filter == 'h') {
help();
}
const char *const infile = argv[optind];
const char *const outfile = argv[optind + 1];
FILE *const inptr = fopen(infile, "rb");
if (!inptr) {
fprintf(stderr, "Could not open %s.\n", infile);
return EXIT_FAILURE;
}
FILE *const outptr = fopen(outfile, "wb");
if (!outptr) {
fclose(inptr);
fprintf(stderr, "Could not create %s.\n", outfile);
return EXIT_FAILURE;
}
if (process_image(filter, inptr, outptr) == -1) {
fclose(inptr);
fclose(outptr);
return EXIT_FAILURE;
}
fclose(inptr);
fclose(outptr);
return EXIT_SUCCESS;
}
helpers.c:
#include "helpers.h"
#include <math.h>
#include <string.h>
static inline int min(int x, int y)
{
return x < y ? x : y;
}
void grayscale(size_t height, size_t width, RGBTRIPLE image[height][width])
{
for (size_t i = 0; i < height; ++i) {
for (size_t j = 0; j < width; ++j) {
const int average = (image[i][j].rgbtBlue + image[i][j].rgbtRed +
image[i][j].rgbtGreen + 1) / 3;
image[i][j].rgbtRed = image[i][j].rgbtGreen = image[i][j].rgbtBlue = (BYTE) average;
}
}
}
void sepia(size_t height, size_t width, RGBTRIPLE image[height][width])
{
for (size_t i = 0; i < height; ++i) {
for (size_t j = 0; j < width; ++j) {
const int sepia_red = round(0.393 * image[i][j].rgbtRed +
0.769 * image[i][j].rgbtGreen +
0.189 * image[i][j].rgbtBlue);
const int sepia_green = round(0.349 * image[i][j].rgbtRed +
0.686 * image[i][j].rgbtGreen +
0.168 * image[i][j].rgbtBlue);
const int sepia_blue = round(0.272 * image[i][j].rgbtRed +
0.534 * image[i][j].rgbtGreen +
0.131 * image[i][j].rgbtBlue);
image[i][j].rgbtRed = (BYTE) min(255, sepia_red);
image[i][j].rgbtBlue = (BYTE) min(255, sepia_blue);
image[i][j].rgbtGreen = (BYTE) min(255, sepia_green);
}
}
}
static inline void swap(RGBTRIPLE *restrict lhs, RGBTRIPLE *restrict rhs)
{
RGBTRIPLE tmp = *lhs;
*lhs = *rhs;
*rhs = tmp;
}
void reflect(size_t height, size_t width, RGBTRIPLE image[height][width])
{
for (size_t i = 0; i < height; ++i) {
size_t start = 0;
size_t end = width - 1;
while (start < end) {
swap(&image[i][start], &image[i][end]);
--end;
++start;
}
}
}
void blur(size_t height, size_t width, RGBTRIPLE image[height][width])
{
RGBTRIPLE temp[height][width];
memcpy(temp, image, sizeof temp);
for (int i = 0; i < (int) height; ++i) {
for (int j = 0; j < (int) width; ++j) {
int blue = 0, red = 0, green = 0;
double count = 0;
for (int k = i - 1; k < i + 2; ++k) {
/*
* Check for top and bottom edge.
*/
if (k < 0 || k >= (int) height) {
continue;
}
for (int l = j - 1; l < j + 2; ++l) {
/*
* Check for left and right edge.
*/
if (l < 0 || l >= (int) width) {
continue;
}
red += temp[k][l].rgbtRed;
green += temp[k][l].rgbtGreen;
blue += temp[k][l].rgbtBlue;
++count;
}
}
image[i][j].rgbtRed = (BYTE) round(red / count);
image[i][j].rgbtBlue = (BYTE) round(blue / count);
image[i][j].rgbtGreen = (BYTE) round(green / count);
}
}
}
helpers.h:
#include "bmp.h"
#include <stddef.h>
// Convert image to grayscale
void grayscale(size_t height, size_t width, RGBTRIPLE image[height][width]);
// Convert image to sepia
void sepia(size_t height, size_t width, RGBTRIPLE image[height][width]);
// Reflect image horizontally
void reflect(size_t height, size_t width, RGBTRIPLE image[height][width]);
// Blur image
void blur(size_t height, size_t width, RGBTRIPLE image[height][width]);
makefile:
CFLAGS := -std=c17
CFLAGS += -no-pie
# CFLAGS += -g3
# CFLAGS += -ggdb
CFLAGS += -Wall
CFLAGS += -Wextra
CFLAGS += -Warray-bounds
CFLAGS += -Wconversion
CFLAGS += -Wmissing-braces
CFLAGS += -Wno-parentheses
CFLAGS += -Wno-format-truncation
CFLAGS += -Wpedantic
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wwrite-strings
CFLAGS += -Winline
CFLAGS += -s
CFLAGS += -O2
CFLAGS += -D_FORTIFY_SOURCE=2
BINDIR := bin
BIN := $(BINDIR)/filter
SRCS := $(wildcard src/*.c)
OBJS := $(patsubst src/%.c, obj/%.o, $(SRCS))
LDLIBS := -lm
all: $(BIN)
$(BIN): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS) $(LDLIBS)
obj/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@ $(LDLIBS)
clean:
$(RM) -rf $(OBJS)
fclean:
$(RM) -rf $(BIN)
.PHONY: clean all fclean
.DELETE_ON_ERROR:
Or you could instead clone this github repository: Filter