Roll your own PPM/PGM I/O, grayscale conversion, convolution filters, and edge detection β with minimal external libraries.
unsigned char img[H][W] β each value 0β255unsigned char img[H][W][3] β three channels#include <stdio.h> int save_pgm(const char *path, int w, int h, const unsigned char *data) { FILE *fp = fopen(path, "wb"); if (!fp) return -1; fprintf(fp, "P5\n%d %d\n255\n", w, h); fwrite(data, 1, (size_t)w * h, fp); fclose(fp); return 0; }
unsigned char *load_pgm(const char *path, int *w, int *h) { FILE *fp = fopen(path, "rb"); if (!fp) return NULL; char magic[3]; int maxval; if (fscanf(fp, "%2s %d %d %d", magic, w, h, &maxval) != 4) { fclose(fp); return NULL; } fgetc(fp); // skip the single newline after the header size_t n = (size_t)(*w) * (*h); unsigned char *data = malloc(n); fread(data, 1, n, fp); fclose(fp); return data; }
fprintf(fp, "P6\n%d %d\n255\n", w, h); fwrite(rgb_data, 3, (size_t)w * h, fp); // RGB order
convert input.png output.pgm), or export as PGM/PPM from GIMP or Preview.app.Y = 0.299 R + 0.587 G + 0.114 B (ITU-R BT.601).void to_gray(int w, int h, const unsigned char *rgb, unsigned char *gray) { for (int i = 0; i < w * h; i++) { int r = rgb[3*i + 0]; int g = rgb[3*i + 1]; int b = rgb[3*i + 2]; gray[i] = (unsigned char)(0.299 * r + 0.587 * g + 0.114 * b); } }
void invert(int w, int h, unsigned char *img) { for (int i = 0; i < w * h; i++) img[i] = 255 - img[i]; }
void binarize(int w, int h, unsigned char *img, int threshold) { for (int i = 0; i < w * h; i++) img[i] = (img[i] >= threshold) ? 255 : 0; }
void convolve3(int w, int h, const unsigned char *src, unsigned char *dst, const double k[3][3]) { for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { double sum = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int ny = y + dy, nx = x + dx; if (ny < 0) ny = 0; if (ny >= h) ny = h - 1; if (nx < 0) nx = 0; if (nx >= w) nx = w - 1; sum += src[ny * w + nx] * k[dy + 1][dx + 1]; } } if (sum < 0) sum = 0; if (sum > 255) sum = 255; dst[y * w + x] = (unsigned char)sum; } } } double blur[3][3] = { {1/9.0, 1/9.0, 1/9.0}, {1/9.0, 1/9.0, 1/9.0}, {1/9.0, 1/9.0, 1/9.0} }; convolve3(w, h, src, dst, blur);
src and dst separate.#include <math.h> void sobel(int w, int h, const unsigned char *src, unsigned char *dst) { static const int Gx[3][3] = {{-1,0,1},{-2,0,2},{-1,0,1}}; static const int Gy[3][3] = {{-1,-2,-1},{0,0,0},{1,2,1}}; for (int y = 1; y < h - 1; y++) { for (int x = 1; x < w - 1; x++) { int sx = 0, sy = 0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int v = src[(y + dy) * w + (x + dx)]; sx += v * Gx[dy + 1][dx + 1]; sy += v * Gy[dy + 1][dx + 1]; } } double mag = sqrt((double)sx * sx + (double)sy * sy); if (mag > 255) mag = 255; dst[y * w + x] = (unsigned char)mag; } } }
gcc -O2 sobel.c -o sobel -lm. Run on a PGM and open the output in any image viewer to see only the edges light up.void histogram(int w, int h, const unsigned char *img, int hist[256]) { for (int i = 0; i < 256; i++) hist[i] = 0; for (int i = 0; i < w * h; i++) hist[img[i]]++; } void print_histogram(const int hist[256]) { int maxv = 0; for (int i = 0; i < 256; i++) if (hist[i] > maxv) maxv = hist[i]; for (int i = 0; i < 256; i += 4) { int sum = 0; for (int k = 0; k < 4; k++) sum += hist[i + k]; int bars = maxv ? sum * 40 / maxv : 0; printf("%3d |", i); for (int b = 0; b < bars; b++) putchar('#'); putchar('\n'); } }
void stretch_contrast(int w, int h, unsigned char *img) { int lo = 255, hi = 0; for (int i = 0; i < w * h; i++) { if (img[i] < lo) lo = img[i]; if (img[i] > hi) hi = img[i]; } if (hi == lo) return; for (int i = 0; i < w * h; i++) { img[i] = (unsigned char)((img[i] - lo) * 255 / (hi - lo)); } }
#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int w, h, ch; unsigned char *data = stbi_load(argv[1], &w, &h, &ch, 1); // last arg = 1: force single-channel grayscale if (!data) { fprintf(stderr, "load failed\n"); return 1; } FILE *fp = fopen("out.pgm", "wb"); fprintf(fp, "P5\n%d %d\n255\n", w, h); fwrite(data, 1, (size_t)w * h, fp); fclose(fp); stbi_image_free(data); return 0; }
stb_image_write.h and call stbi_write_png("out.png", w, h, 1, data, w);../togray input.ppm output.pgm: read P6 PPM, convert via luminance formula, write PGM.