I made a minimal complete example for convolution with which I got the algorithm running you described.
I did it the straight forward way. This is rather suitable for learning but not for serial usage (lacking any optimization for keeping the code clear and readable).
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef unsigned char uint8;
typedef unsigned int uint;
typedef struct {
uint w, h;
uint8 *data;
} Image;
uint newImage(Image *pImg, uint w, uint h)
{
uint size = w * h * 3;
if (!pImg) return 0;
assert(!pImg->data);
pImg->data = malloc(size);
pImg->w = pImg->data ? w : 0; pImg->h = pImg->data ? h : 0;
if (!pImg->data) {
fprintf(stderr,
"Allocation of %u bytes for image data failed!
", size);
return 0;
}
return size;
}
void fillImage(Image *pImg, uint8 r, uint8 g, uint8 b)
{
if (!pImg || !pImg->data) return;
{ uint size = pImg->w * pImg->h * 3, i;
for (i = 0; i < size; i += 3) {
pImg->data[i] = r; pImg->data[i + 1] = g; pImg->data[i + 2] = b;
}
}
}
void freeImage(Image *pImg)
{
if (!pImg) return;
free(pImg->data);
pImg->data = 0;
}
int readPPM(FILE *f, Image *pImg)
{
char buffer[32] = ""; uint w = 0, h = 0, t = 0, size = 0, i = 0;
if (!pImg) return 0;
assert(!pImg->data);
/* parse header */
if ((i = 1, !fgets(buffer, sizeof buffer, f))
|| (i = 2, strcmp(buffer, "P6
") != 0)
|| (i = 3, fscanf(f, "%u %u %u", &w, &h, &t) != 3)
|| (i = 4, t != 255)) {
fprintf(stderr, "Not a PPM image! (%u)
", i);
return -1;
}
/* allocate appropriate memory */
if (!(size = newImage(pImg, w, h))) return -1;
/* read data */
if (fread(pImg->data, 1, size, f) != size) {
fprintf(stderr, "Not enough data in PPM image!
");
return -1;
}
/* done */
return 0;
}
void writePPM(FILE *f, Image *pImg)
{
if (!pImg || !pImg->data) return;
fprintf(f, "P6
%u %u 255
", pImg->w, pImg->h);
{ uint size = pImg->w * pImg->h * 3, i;
for (i = 0; i < size; i += 3) {
fprintf(f, "%c%c%c",
pImg->data[i], pImg->data[i + 1], pImg->data[i + 2]);
}
}
}
#define GET_PIXEL(P_IMG, ROW, COL, C)
((P_IMG)->data[((ROW) * (P_IMG)->w + (COL)) * 3 + (C)])
void convolute(
Image *pImg, uint dim, int *mat,
Image *pImgOut)
{
if (!pImg || !pImg->data) return;
assert(dim & 1); /* dim Mat must be odd */
{ int offs = -(dim / 2);
unsigned i, j;
for (i = 0; i < pImg->h; ++i) {
for (j = 0; j < pImg->w; ++j) {
unsigned iM, jM;
uint8 *pixelOut = pImgOut->data + (i * pImg->w + j) * 3;
int r = 0, g = 0, b = 0;
for (iM = 0; iM < dim; ++iM) {
for (jM = 0; jM < dim; ++jM) {
int mIJ = mat[iM * dim + jM];
r += mIJ
* (int)GET_PIXEL(pImg,
(pImg->h + i + offs + iM) % pImg->h,
(pImg->w + j + offs + jM) % pImg->w,
0);
g += mIJ
* (int)GET_PIXEL(pImg,
(pImg->h + i + offs + iM) % pImg->h,
(pImg->w + j + offs + jM) % pImg->w,
1);
b += mIJ
* (int)GET_PIXEL(pImg,
(pImg->h + i + offs + iM) % pImg->h,
(pImg->w + j + offs + jM) % pImg->w,
2);
}
}
#if 1 /* colored output */
pixelOut[0] = (uint8)abs(r);
pixelOut[1] = (uint8)abs(g);
pixelOut[2] = (uint8)abs(b);
#else /* gray level output */
pixelOut[0] = pixelOut[1] = pixelOut[2]
= abs(r) + abs(g) + abs(b) / 3;
#endif /* 1 */
}
}
}
}
int main(int argc, char **argv)
{
enum { Dim = 3 };
#if 0
int mat[Dim * Dim] = {
0, -1, 0,
-1, 4, -1,
0, -1, 0
};
#endif
int mat[Dim * Dim] = {
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
};
FILE *f = 0;
const char *file, *outFile;
/* read command line arguments */
if (argc <= 2) {
fprintf(stderr, "Missing command line arguments!
");
printf("Usage:
"
" $ %s <IN_FILE> <OUT_FILE>
",
argv[0]);
return -1;
}
file = argv[1]; outFile = argv[2];
/* read PPM image */
if (!(f = fopen(file, "rb"))) {
fprintf(stderr, "Cannot open input file '%s'!
", file);
return -1;
}
Image img = { 0, 0, NULL };
if (readPPM(f, &img)) return -1;
fclose(f); f = 0;
/* make output image */
Image imgOut = { 0, 0, NULL };
newImage(&imgOut, img.w, img.h);
/* convolute image */
convolute(&img, Dim, mat, &imgOut);
/* write PPM image */
if (!(f = fopen(outFile, "wb"))) {
fprintf(stderr, "Cannot create output file '%s'!
", outFile);
return -1;
}
writePPM(f, &imgOut);
fclose(f);
/* done */
return 0;
}
I compiled and tested it with VS2013 on Windows 10 as well as gcc in cygwin:
$ gcc -o edge-detect edge-detect.c
$ ./edge-detect.exe fluffyCat.64x64.ppm edge-detect-out.ppm
$
fluffyCat.64x64.ppm looks like this:
edge-detect-out.ppm looks like this:
Some notes:
I used the ancient X11 PPM format because
- It can be read and written with minimal code and is, thus, best fitting for such samples.
- It is supported in GIMP. Thus, creating and viewing is easy.
The code is inspired on Creating, Compiling, and Viewing ppm Images and, probably, cannot handle any flavor of PPM.
Attention! When GIMP saves PPM it includes a comment which the reader in the sample code cannot read. I simply removed this comment with a text editor.
GIMP settings for saving: Raw data.
A common danger in such image procesing algorithms is the handling of border pixels (where matrix may be applied to neighbour non existing pixels outside the image). I simply solved it by wrapping the image around (using the resp. index modulo width/height of image).
In the convolution, I used abs()
to keep output in positive range. Unfortunately, I cannot say whether this is fully correct. (It's 22 years ago since I heart about image processing in the University.)