PPM/PGM の手書き読み書きから、グレースケール・フィルタ・エッジ検出まで、外部ライブラリ最小で実装する。
unsigned char img[H][W] — 各要素 0〜255 が1ピクセルの明度unsigned char img[H][W][3] — 赤・緑・青の3チャンネルimg[i][j] は行優先で連続。画像バッファと相性が非常に良い。#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); // ヘッダ後の1バイトの改行を飛ばす size_t n = (size_t)(*w) * (*h); unsigned char *data = malloc(n); fread(data, 1, n, fp); fclose(fp); return data; }
// 書き出し: P6 がバイナリPPM fprintf(fp, "P6\n%d %d\n255\n", w, h); fwrite(rgb_data, 3, (size_t)w * h, fp); // RGB順
convert input.png output.pgm、またはGIMP/Preview.app で「PGM/PPMとしてエクスポート」で作成できる。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; }
// 3x3カーネル専用のシンプル版 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; // 画像外は端のピクセルをクランプ(clamp) 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]; } } // 0〜255にクリップ 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 と dst を別バッファにすること。#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 でコンパイル。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) { // 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'); } }
// 最小値と最大値を0と255に伸ばす 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)); } }
#include 1つでPNG/JPG/BMP/GIFの読み込みが可能。#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); // 第5引数=1: 強制的にグレースケール1ch に変換 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 を使えば PNG で保存もできる: stbi_write_png("out.png", w, h, 1, data, w);./togray input.ppm output.pgm のように動くCLIツールを作れ。PPM(P6) を読み、輝度計算してPGMで書き出す。