Enable more compiler warnings:
gcc -std=c17 -fPIC -gdwarf-4 -Wall -Wextra -Wwrite-strings -Wno-parentheses -Wpedantic -Warray-bounds -Wconversion -Wstrict-prototypes -fanalyzer main.c image.c -o image -lm main.c: In function ‘main’: main.c:7:20: warning: passing argument 1 of ‘save_image’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 7 | save_image("identity.ppm", *ema); | ^~~~~~~~~~~~~~ In file included from main.c:2: image.h:34:22: note: expected ‘char *’ but argument is of type ‘const char *’ 34 | int save_image(char *filename, Image image); | ~~~~~~^~~~~~~~ main.c:9:20: warning: passing argument 1 of ‘save_image’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 9 | save_image("sobel.ppm", *sob); | ^~~~~~~~~~~ In file included from main.c:2: image.h:34:22: note: expected ‘char *’ but argument is of type ‘const char *’ 34 | int save_image(char *filename, Image image); | ~~~~~~^~~~~~~~ main.c:4:14: warning: unused parameter ‘argc’ [-Wunused-parameter] 4 | int main(int argc, char **argv) | ~~~~^~~~ image.c: In function ‘create_image’: image.c:18:40: warning: conversion to ‘long unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 18 | image->pixels = malloc(width*height*sizeof(Pixel)); | ^ image.c: In function ‘load_image’: image.c:52:52: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-conversion] 52 | fread(image->pixels, sizeof(Pixel),image->width*image->height, fp); | ~~~~~~~~~~~~^~~~~~~~~~~~~~ image.c: In function ‘save_image’: image.c:64:52: warning: conversion to ‘size_t’ {aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-conversion] 64 | fwrite(image.pixels, sizeof(Pixel), image.width*image.height, fp); | ~~~~~~~~~~~^~~~~~~~~~~~~ image.c: In function ‘convolve’: image.c:124:47: warning: conversion to ‘unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 124 | int ir = modulo(row + r_off, image.height); | ~~~~~^~~~~~~ image.c:124:22: warning: conversion to ‘int’ from ‘unsigned int’ may change the sign of the result [-Wsign-conversion] 124 | int ir = modulo(row + r_off, image.height); | ^~~~~~ image.c:125:47: warning: conversion to ‘unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 125 | int ic = modulo(col + c_off, image.width); | ~~~~~^~~~~~ image.c:125:22: warning: conversion to ‘int’ from ‘unsigned int’ may change the sign of the result [-Wsign-conversion] 125 | int ic = modulo(col + c_off, image.width); | ^~~~~~ image.c: In function ‘apply_kernel’: image.c:152:53: warning: conversion from ‘double’ to ‘uint8_t’ {aka ‘unsigned char’} may change value [-Wfloat-conversion] 152 | conv->pixels[row*image.width + col].r = accumulator.r; | ^~~~~~~~~~~ image.c:153:53: warning: conversion from ‘double’ to ‘uint8_t’ {aka ‘unsigned char’} may change value [-Wfloat-conversion] 153 | conv->pixels[row*image.width + col].g = accumulator.g; | ^~~~~~~~~~~ image.c:154:53: warning: conversion from ‘double’ to ‘uint8_t’ {aka ‘unsigned char’} may change value [-Wfloat-conversion] 154 | conv->pixels[row*image.width + col].b = accumulator.b; | ^~~~~~~~~~~ image.c: In function ‘sobel_x’: image.c:165:39: warning: conversion to ‘long unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 165 | sx.weights = malloc(sizeof(double)*sx.size*sx.size); | ^ image.c:165:47: warning: conversion to ‘long unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 165 | sx.weights = malloc(sizeof(double)*sx.size*sx.size); | ^ image.c:161:20: warning: unused parameter ‘n’ [-Wunused-parameter] 161 | Kernel sobel_x(int n) | ~~~~^ image.c: In function ‘sobel_y’: image.c:181:40: warning: conversion to ‘long unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 181 | sy.weights = malloc(sy.size*sy.size*sizeof(double)); | ^ image.c:177:20: warning: unused parameter ‘n’ [-Wunused-parameter] 177 | Kernel sobel_y(int n) | ~~~~^ image.c: In function ‘sobel’: image.c:201:16: warning: passing argument 1 of ‘save_image’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 201 | save_image("sobel_x.ppm", *sobx); | ^~~~~~~~~~~~~ image.c:56:22: note: expected ‘char *’ but argument is of type ‘const char *’ 56 | int save_image(char *filename, Image image) | ~~~~~~^~~~~~~~ image.c:202:16: warning: passing argument 1 of ‘save_image’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] 202 | save_image("sobel_y.ppm", *soby); | ^~~~~~~~~~~~~ image.c:56:22: note: expected ‘char *’ but argument is of type ‘const char *’ 56 | int save_image(char *filename, Image image) | ~~~~~~^~~~~~~~ image.c: In function ‘modulo’: image.c:240:13: warning: conversion to ‘unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 240 | mod += m; | ^~ image.c:240:16: warning: conversion to ‘int’ from ‘unsigned int’ may change the sign of the result [-Wsign-conversion] 240 | mod += m; | ^ image.c:241:12: warning: conversion to ‘unsigned int’ from ‘int’ may change the sign of the result [-Wsign-conversion] 241 | return mod; | ^~~ In function ‘apply_kernel’: image.c:152:17: warning: dereference of NULL ‘conv’ [CWE-476] [-Wanalyzer-null-dereference] 152 | conv->pixels[row*image.width + col].r = accumulator.r; | ~~~~^~~~~~~~ ‘sobel’: events 1-2 | | 193 | Image *sobel(Image image) | | ^~~~~ | | | | | (1) entry to ‘sobel’ | 194 | { | 195 | Image *conv = create_image(image.width, image.height); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (2) calling ‘create_image’ from ‘sobel’ | +--> ‘create_image’: event 3 | | 9 | Image *create_image(int width, int height) | | ^~~~~~~~~~~~ | | | | | (3) entry to ‘create_image’ | ‘create_image’: event 4 | | 14 | return NULL; | | ^~~~ | | | | | (4) ‘0’ is NULL | <------+ | ‘sobel’: events 5-6 | | 195 | Image *conv = create_image(image.width, image.height); | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (5) returning to ‘sobel’ from ‘create_image’ |...... | 199 | sobx = apply_kernel(image, sx); | | ~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (6) calling ‘apply_kernel’ from ‘sobel’ | +--> ‘apply_kernel’: events 7-8 | | 138 | Image *apply_kernel(Image image, Kernel kernel) | | ^~~~~~~~~~~~ | | | | | (7) entry to ‘apply_kernel’ | 139 | { | 140 | Image *conv = create_image(image.width, image.height); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (8) calling ‘create_image’ from ‘apply_kernel’ | +--> ‘create_image’: event 9 | | 9 | Image *create_image(int width, int height) | | ^~~~~~~~~~~~ | | | | | (9) entry to ‘create_image’ | ‘create_image’: event 10 | | 14 | return NULL; | | ^~~~ | | | | | (10) ‘conv’ is NULL | <------+ | ‘apply_kernel’: events 11-12 | | 140 | Image *conv = create_image(image.width, image.height); | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (11) return of NULL to ‘apply_kernel’ from ‘create_image’ | 141 | double max = kernel_max(kernel); | | ~~~~~~~~~~~~~~~~~~ | | | | | (12) calling ‘kernel_max’ from ‘apply_kernel’ | +--> ‘kernel_max’: events 13-15 | | 110 | double kernel_max(Kernel kernel) | | ^~~~~~~~~~ | | | | | (13) entry to ‘kernel_max’ |...... | 113 | for (int index = 0; index < kernel.size*kernel.size; index++) { | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (14) following ‘true’ branch... | 114 | if (kernel.weights[index] > 0) | | ~~~~~~~~~~~~~~ | | | | | (15) ...to here | <------+ | ‘apply_kernel’: events 16-17 | | 141 | double max = kernel_max(kernel); | | ^~~~~~~~~~~~~~~~~~ | | | | | (16) returning to ‘apply_kernel’ from ‘kernel_max’ | 142 | double min = kernel_min(kernel); | | ~~~~~~~~~~~~~~~~~~ | | | | | (17) calling ‘kernel_min’ from ‘apply_kernel’ | +--> ‘kernel_min’: events 18-20 | | 101 | double kernel_min(Kernel kernel) | | ^~~~~~~~~~ | | | | | (18) entry to ‘kernel_min’ |...... | 104 | for (int index = 0; index < kernel.size*kernel.size; index++) { | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (19) following ‘true’ branch... | 105 | if (kernel.weights[index] < 0) | | ~~~~~~~~~~~~~~ | | | | | (20) ...to here | <------+ | ‘apply_kernel’: events 21-26 | | 142 | double min = kernel_min(kernel); | | ^~~~~~~~~~~~~~~~~~ | | | | | (21) returning to ‘apply_kernel’ from ‘kernel_min’ | 143 | double alpha = max - min; | 144 | for (int row = 0; row < image.height; row++) { | | ~~~~~~~~~~~~~~~~~~ | | | | | (22) following ‘true’ branch... | 145 | for (int col = 0; col < image.width; col++) { | | ~~~ ~~~~~~~~~~~~~~~~~ | | | | | | | (24) following ‘true’ branch... | | (23) ...to here | 146 | Accumulator accumulator = convolve(image, kernel, row, col); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (25) ...to here | | (26) calling ‘convolve’ from ‘apply_kernel’ | +--> ‘convolve’: events 27-31 | | 119 | Accumulator convolve(Image image, Kernel kernel, int row, int col) | | ^~~~~~~~ | | | | | (27) entry to ‘convolve’ |...... | 122 | for (int r_off = -kernel.size/2; r_off <= kernel.size/2; r_off++) { | | ~~~~~~~~~~~~~~~~~~~~~~ | | | | | (28) following ‘true’ branch... | 123 | for (int c_off = -kernel.size/2; c_off <= kernel.size/2; c_off++) { | | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ | | | | | | | (30) following ‘true’ branch... | | (29) ...to here | 124 | int ir = modulo(row + r_off, image.height); | | ~~~~~~~~~~~~ | | | | | (31) ...to here | <------+ | ‘apply_kernel’: events 32-33 | | 146 | Accumulator accumulator = convolve(image, kernel, row, col); | | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (32) returning to ‘apply_kernel’ from ‘convolve’ |...... | 152 | conv->pixels[row*image.width + col].r = accumulator.r; | | ~~~~~~~~~~~~ | | | | | (33) dereference of NULL ‘conv’ | In function ‘sobel_x’: image.c:166:19: warning: dereference of possibly-NULL ‘sx.weights’ [CWE-690] [-Wanalyzer-possible-null-dereference] 166 | sx.weights[0] = -1; | ~~~~~~~~~~~~~~^~~~ ‘sobel’: events 1-2 | | 193 | Image *sobel(Image image) | | ^~~~~ | | | | | (1) entry to ‘sobel’ |...... | 197 | Kernel sx = sobel_x(3); | | ~~~~~~~~~~ | | | | | (2) calling ‘sobel_x’ from ‘sobel’ | +--> ‘sobel_x’: events 3-5 | | 161 | Kernel sobel_x(int n) | | ^~~~~~~ | | | | | (3) entry to ‘sobel_x’ |...... | 165 | sx.weights = malloc(sizeof(double)*sx.size*sx.size); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (4) this call could return NULL | 166 | sx.weights[0] = -1; | | ~~~~~~~~~~~~~~~~~~ | | | | | (5) ‘sx.weights’ could be NULL: unchecked value from (4) | In function ‘sobel_y’: image.c:182:19: warning: dereference of possibly-NULL ‘sy.weights’ [CWE-690] [-Wanalyzer-possible-null-dereference] 182 | sy.weights[0] = -1; | ~~~~~~~~~~~~~~^~~~ ‘sobel’: events 1-2 | | 193 | Image *sobel(Image image) | | ^~~~~ | | | | | (1) entry to ‘sobel’ |...... | 198 | Kernel sy = sobel_y(3); | | ~~~~~~~~~~ | | | | | (2) calling ‘sobel_y’ from ‘sobel’ | +--> ‘sobel_y’: events 3-5 | | 177 | Kernel sobel_y(int n) | | ^~~~~~~ | | | | | (3) entry to ‘sobel_y’ |...... | 181 | sy.weights = malloc(sy.size*sy.size*sizeof(double)); | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | | | | | (4) this call could return NULL | 182 | sy.weights[0] = -1; | | ~~~~~~~~~~~~~~~~~~ | | | | | (5) ‘sy.weights’ could be NULL: unchecked value from (4) |
These should be trivial to fix.
Though, this:
"warning: passing argument 1 of ‘save_image’ discards ‘const’ qualifier from pointer target type"
is a non-C spec warning as string literals such as "identity.ppm" are not const. The warning comes up due to compiling as C++ or telling the C compiler to treat string literals as const. This can create false concerns. In this case, though it is a good idea to change int save_image(char *filename, Image image); to int save_image(const char *filename, Image image);. – chux - Reinstate Monica
argv[1] could be NULL:
In main(), argv[1] is used without checking if it was a valid pointer. Check if argc is equal to 2 instead of casting it to void.
#if 0 int main(int argc, char **argv) { (void) argc; .. } #else int main(int argc, char **argv) { if (argc != 2) { /* Handle error here. */ } /* It is safe to use argv[1] now. */ } #endif
The functions that are not used outside of a translation unit should be defined as having internal linkage:
kernel_min(), kernel_max(), apply_kernel(), sobel_y(), sobel_x(), modulo() (and perhaps some more) are specific to improc.c. Their declarations should be removed from improc.h, and they should be defined with the static keyword.
Don't include unnecessary header files:
improc.c doesn't use anything from unistd.h or string.h.
main.c doesn't use anything from stdlib.h or math.h.
Check for overflow:
In create_image():
image->pixels = malloc(width*height*sizeof(Pixel));
width * height *sizeof(Pixel) can be greater than SIZE_MAX. Whilst that does not invoke undefined behavior and would simply wrap-around, it would allocate less memory than expected and you'd consider it a successful operation.
On the other hand, width * height * sizeof(Pixel) can evaluate to 0, which would pass a size of 0 to malloc().
The C standard (C17 7.22.3/1) says:
If the size of the space requested is zero, the behavior is implementation defined: either a null pointer is returned, or the behavior is as if the size were some nonzero value, except that the returned pointer shall not be used to access an object.
I wouldn't rely on it.
Also consider allocating to the referenced object, and not the type. If the type of image->pixels changes, you wouldn't have to change anything in the call to malloc().
On a side-note, malloc() and fopen() aren't required to set errno. You may want to fall back to fprintf()/fputs() in case it is not set. (This also requires setting errno to 0 before calling malloc())
Good job on not casting the return of malloc() and family.
free(NULL) is a NO-OP:
According to the C standard:
If ptr is a null pointer, no action occurs.
Checking for NULL simply adds more clutter to the code. Consider allowing free(NULL).
Simplify:
#if 0 if (magic[0] != 'P' || magic[1] != '6') #else if (strcmp(magic, "P6")) #endif
Check the return value of library functions:
fread(), fwrite(), and fscanf() have their return values ignored.
In sobel_x() and sobel_y(), the return value of malloc() is ignored.
Do not return an error code if the you don't intend to check for it:
In grayscale(), and other functions, the call to create_image() can return NULL if there was a memory allocation error, but there's no check for it. If NULL is returned, a subsequent operation would invoke undefined behavior by dereferencing it.
In main(), the return value of load_image() is ignored. I receive a segmentation fault if I feed improc.c to the executable.
As another side-note, grayscale() and perpetual_grayscale() are quite similar. They can be merged into a single function.
Eliminate unused parameters:
In sobel_x() and sobel_y(), n goes unused. The compiler did raise warnings, but you didn't cater to them.
Valgrind reports memory leaks:
valgrind ./image boxes_1.ppm ==4581== Memcheck, a memory error detector ==4581== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==4581== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info ==4581== Command: ./image boxes_1.ppm ==4581== ==4581== ==4581== HEAP SUMMARY: ==4581== in use at exit: 47,836 bytes in 10 blocks ==4581== total heap usage: 21 allocs, 11 frees, 71,700 bytes allocated ==4581== ==4581== LEAK SUMMARY: ==4581== definitely lost: 208 bytes in 6 blocks ==4581== indirectly lost: 47,628 bytes in 4 blocks ==4581== possibly lost: 0 bytes in 0 blocks ==4581== still reachable: 0 bytes in 0 blocks ==4581== suppressed: 0 bytes in 0 blocks ==4581== Rerun with --leak-check=full to see details of leaked memory ==4581== ==4581== For lists of detected and suppressed errors, rerun with: -s ==4581== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
The pointer returned by malloc() must be later passed to free(). I do not see that in any part of your code.