このページの説明には一部誤りが有ることが分かっています。
PNM(PPM/PGM/PBM)形式について、改めて書き直しています。以下を参照ください。

画像ファイルの扱い方 (4) -PPM/PGM/PBM形式 (3)

作成:

PPM/PGM/PBM形式出力関数

前回の続きで今度は出力関数です。
前々回で説明したように、 UNIX 以外の環境では、 この関数に渡すファイルポインタはファイルをオープンするときには必ずバイナリモードで開くようにしてください。 UNIX 環境ではバイナリモードを指定しても何の問題もない(というかテキストモードがバイナリモードと同じ)ので 常にバイナリモードで開くようにしておくといいでしょう。

#include <stdio.h>

/* ppmファイル出力関数                                        */
/* ファイルポインタ、画像データ、出力形式を指定して出力する   */
/* エラーが発生した場合NULLを返し、                           */
/* 成功すると出力したデータのポインタを返す                   */
/* グレースケールおよび2値形式で出力するときはrの値を参照する */

Picture* putPpm(FILE* fp,Picture* pPic,int type){
  int           i,j,k;            /* ループ用変数           */
  unsigned char tmp;              /* 作業用変数             */
  unsigned char *r,*g,*b;         /* 作業に使用するポインタ */

  if(fp == NULL   ||
     pPic == NULL ||
     (type < 1 || type >6))       /* 引数が異常             */
    return NULL;
  r = pPic->r;
  g = pPic->g;
  b = pPic->b;
  /* ファイルヘッダの出力 */
  if(fprintf(fp,"P%d\n%d %d\n",type,pPic->x,pPic->y) < 0)
    return NULL;
  if(type == 2 || type == 3 || type == 5 || type == 6){
    if(fprintf(fp,"255\n") < 0)
      return NULL;
  }
  /* 画像データの出力 */
  switch(type){
  case 1:                         /* 2値ascii形式 */
    for(i=0 ; i<pPic->y ; i++){
      for(j=0 ; j<pPic->x ; j++){
        if(*(r++) < 128)
          tmp = 1;
        else
          tmp = 0;
        if(fprintf(fp,"%d\n",tmp) < 0)
          return NULL;
      }
    }
    break;
  case 2:                         /* グレースケールascii形式 */
    for(i=0 ; i<pPic->y ; i++){
      for(j=0 ; j<pPic->x ; j++){
        if(fprintf(fp,"%d\n",*(r++)) < 0)
          return NULL;
      }
    }
    break;
  case 3:                         /* フルカラーascii形式 */
    for(i=0 ; i<pPic->y ; i++){
      for(j=0 ; j<pPic->x ; j++){
        if(fprintf(fp,"%d %d %d\n",*(r++),*(g++),*(b++)) < 0)
          return NULL;
      }
    }
    break;
  case 4:                         /* 2値raw形式 */
    for(i=0 ; i<pPic->y ; i++){
      for(j=0 ; j<(pPic->x-1)/8 ; j++){
        tmp = 0;
        for(k=7 ; k>=0 ; k--){
          if(*(r++) < 128){
            tmp += 1 << k;
          }
        }
        if(putc(tmp,fp) == EOF)
          return NULL;
      }
      tmp = 0;
      for(k=7 ; k>=7-((pPic->x-1)%8) ; k--){
        if(*(r++) < 128){
          tmp += 1 << k;
        }
      }
      if(putc(tmp,fp) == EOF)
        return NULL;
    }
    break;
  case 5:                         /* グレースケールraw形式 */
    for(i=0 ; i<pPic->y ; i++){
      for(j=0 ; j<pPic->x ; j++){
        if(putc(*(r++),fp) == EOF)
          return NULL;
      }
    }
    break;
  case 6:                         /* フルカラーraw形式 */
    for(i=0 ; i<pPic->y ; i++){
      for(j=0 ; j<pPic->x ; j++){
        if(putc(*(r++),fp) == EOF)
          return NULL;
        if(putc(*(g++),fp) == EOF)
          return NULL;
        if(putc(*(b++),fp) == EOF)
          return NULL;
      }
    }
    break;
  }
  return pPic;                    /* 出力完了 */
}

※ Rayさんからご指摘がありました。
この関数ではグレーおよび2値形式で出力する際rつまり赤の値のみを参照しています。ご注意ください。
もう少し工夫して、 RGB から輝度を抽出するようにしてもよかったのですが、 そうするとソースがややこしくなってしまうというのと、 ただめんどくさいかったのでそのままにしておいたのを忘れてました。 (このソースは数ヶ月前に書いたものですので・・・)
実際に使うときは、使用目的に都合のよい形に変更してください。
参考までに輝度は以下の式で求まります。
y=0.299×r+0.587×g+0.114×b
(あくまで数式ですので、Cで書くときはキャストとか端数を四捨五入するとかしないといけません)
他には画像データを入れる構造体にグレースケールの情報を入れるような領域を作る、
グレースケールの画像を扱う時は別の構造体や関数を作って処理する、etc...。

とりあえず、 PPM 形式の説明はこれで終わります。 なんだか中途半端な終わり方ですみません。
これが画像を扱う上での前準備ですが、 一度読み込んでしまえば、画像もただの変数に入った数値データ列でしかありません。
これが出来てしまえばあとは、画像処理などを行うプログラムもすぐに出来ると思います。
画像処理をしたくても、ファイル形式は考えて分かるものではありませんからね。 そこで躓いていた人の助けになれば幸いです。

でも100%は信頼しないでください(笑)。
実際のソフト(Paint Shop Pro)で読み込めるか等を試して大丈夫だったので、 たぶん大丈夫だとは思いますが、どこか間違っていたらお知らせください。
テストの光景・・・心臓の悪い方はクリックしないでください(笑)。G○N○Xさんに怒られるかな?
いや、昔作った画像ファイルが残ってたんで使っただけで、他意はないんですよ。