2012/04/25

今更ながらC言語を勉強した

Objective-Cを勉強するにあたって、その前段階としてC言語の勉強をしたので、一応成果的なものを晒しておきます。
※Objective-C内にCが書けたりするのは、Objective-CがCのコンパイラ拡張だからだって初めて知った
※つまりObjective-Cを理解するにはCを理解しておいた方がよいということ

今回ポインタやら構造体について理解できたので、Objective-C以外でも役に立ちそう。
今更だけどC言語やってよかった。

で、その成果ですが、「画像フィルター」的なものをつくってみました。
※当然コマンドラインのアプリケーションですが何か?

以下ソースです。
/* bmpfilter.c
 *
 * Simple BMP filter.
 * 2012/04/24 @keygx
 *
 * compile: $ gcc bmpfilter.c -o bmpfilter
 *
 * usage1: ./bmpfilter <.bmp filename> 
 *               flag: 1 -> Sepia
 *               flag: 2 -> Nega
 *               flag: 3 -> Mosaic
 *               flag: 4 -> Edge
 *               flag: 5 -> Instagram
 *
 * usage2: ./bmpfilter
 *               Interactive Mode
 *
 */

#include 
#include 
#include 

#define HEADER_SIZE 54
#define SIZE_POSI 18
#define SEPIA_FRAME 0.02
#define INSTAGRAM_FRAME 0.02
#define MIN_FRAME 10
#define MOSAIC 10

struct BMPFile {
    char name[256];
    int w;
    int h;
    char flag;
    unsigned char header[HEADER_SIZE];
}bmpfile;

void sepia(struct BMPFile *bmp);
void nega(struct BMPFile *bmp);
void mosaic(struct BMPFile *bmp);
void edge(struct BMPFile *bmp);
void instagram(struct BMPFile *bmp);
void output(struct BMPFile *bmp, unsigned char *img);


int main(int argc, char *argv[]) {
    struct BMPFile *bmp = &bmpfile;
    FILE *fp;
    
    // 起動時パラメータの有無で処理を変える
    if(argc < 3) {
        printf("変換するBMPファイル名を入力してください (*****.bmp)\n");
        scanf("%255s", bmp->name);
        
        printf("フィルタの種類を指定してください (1:セピア, 2:ネガ, 3:モザイク, 4:エッジ検出, 5:インスタグラム風)\n");
        scanf("%*c%c", &bmp->flag);
    } else {
        strcpy(bmp->name, argv[1]);
        bmp->flag = *argv[2];
    }
    
    printf("LOG>> InputFile: %s\n", bmp->name);
    printf("LOG>> InputFlag: %c\n", bmp->flag);
    
    // 指定されたBMPファイルのデータを取得
    if(!(fp = fopen(bmp->name, "rb"))) {
        printf("\n=エラー========\n");
        printf("ファイルが見つかりません\n");
        printf("============\n");
        return 1;
    }
    fp = fopen(bmp->name, "rb");
    fseek(fp, 0, SEEK_SET);
    fseek(fp, SIZE_POSI, 0);  // サイズデータの位置まで移動
    fread(&bmp->w, 4, 1, fp); // ヘッダから画像のwidthを取得
    fread(&bmp->h, 4, 1, fp); // ヘッダから画像のheightを取得
    fseek(fp, 0, SEEK_SET);
    fread(bmp->header, 1, HEADER_SIZE, fp); // ヘッダデータの取得
    fclose(fp);
    
    // 値が負の場合もあるので絶対値をとる
    bmp->w = abs(bmp->w);
    bmp->h = abs(bmp->h);
    
    printf("LOG>> OriginSize: W=%d H=%d\n", bmp->w, bmp->h);
    
    // 各画像処理へ
    if(bmp->flag == '1') {
        // セピア
        sepia(bmp);
    } else if(bmp->flag == '2') {
        // ネガ
        nega(bmp);
    } else if(bmp->flag == '3') {
        // モザイク
        mosaic(bmp);
    } else if(bmp->flag == '4') {
        // エッジ検出
        edge(bmp);
    } else if(bmp->flag == '5') {
        // インスタグラム風
        instagram(bmp);
    } else {
        printf("\n=エラー============\n");
        printf("フィルタの指定が正しくありません\n");
        printf("================\n");
        return 2;
    }
    
    return 0;
}


/* セピア調に変換 */
void sepia(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    int i, j, color, frame;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
        
    // セピア
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            color = (img[i][j][0] + img[i][j][1] + img[i][j][2]) / 3; // モノクロ化
            img[i][j][0] = color; // B
            img[i][j][1] = color; // G
            img[i][j][2] = color; // R
            
            img[i][j][0] *= 0.4;
            img[i][j][1] *= 0.7;
            img[i][j][2] *= 0.9;
        }
    }
    
    // フレームをつける
    frame = bmp->w * SEPIA_FRAME;
    if(frame < MIN_FRAME){
        frame = MIN_FRAME;
    }
    // 下枠
    for(i=0; iw; j++) {
            img[i][j][0] = 230;
            img[i][j][1] = 238;
            img[i][j][2] = 238;
        }
    }
    // 上枠
    for(i=bmp->h-1; i>=bmp->h-frame; i--) {
        for(j=0; jw; j++) {
            img[i][j][0] = 230;
            img[i][j][1] = 238;
            img[i][j][2] = 238;
        }
    }
    // 左枠
    for(i=0; ih; i++) {
        for(j=0; jh; i++) {
        for(j=bmp->w-1; j>=bmp->w-frame; j--) {
            img[i][j][0] = 230;
            img[i][j][1] = 238;
            img[i][j][2] = 238;
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 1 / SEPIA\n");
}


/* ネガ調に変換 */
void nega(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    int i, j;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    // ネガ
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            img[i][j][0] = 255 - img[i][j][0]; // B
            img[i][j][1] = 255 - img[i][j][1]; // G
            img[i][j][2] = 255 - img[i][j][2]; // R
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 2 / NEGA\n");
}


/* モザイク調に変換 */
void mosaic(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    unsigned char img_new[bmp->h][bmp->w][3];
    int i, j, size, x, y, count;
    int b, g, r;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    size = MOSAIC;
    
    // モザイク
    for(i=0; ih; i+=size) {
        for(j=0; jw; j+=size) {
            
            count = 0;
            b = 0;
            g = 0;
            r = 0;
            
            // 指定範囲の色を加算
            for(y=i; y bmp->h || x > bmp->w) {
                        break;
                    }
                    b += img[y][x][0];
                    g += img[y][x][1];
                    r += img[y][x][2];
                    count++;
                }
            }
            // 各色の平均を求める
            b = b / count;
            g = g / count;
            r = r / count;
            
            // 新しい配列に代入
            for(y=i; y bmp->h || x > bmp->w) {
                        break;
                    }
                    img_new[y][x][0] = b;
                    img_new[y][x][1] = g;
                    img_new[y][x][2] = r;
                }
            }
        }
    }
    
    // ファイル出力
    output(bmp, img_new);
    
    printf("LOG>> OK: 3 / MOSAIC\n");
}


/* エッジ検出に変換 */
void edge(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    unsigned char img_temp[bmp->h][bmp->w][3];
    int i, j;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    // 画像データの配列を複製
    memcpy(img_temp, img, sizeof(img));
    
    // エッジ検出
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            img[i][j][0] = abs(img[i][j][0] - img_temp[i][j-1][0]);
            img[i][j][1] = abs(img[i][j][1] - img_temp[i][j-1][1]);
            img[i][j][2] = abs(img[i][j][2] - img_temp[i][j-1][2]);
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 4 / EDGE\n");
}


/* インスタグラム風に変換 */
void instagram(struct BMPFile *bmp) {
    FILE *fp;
    unsigned char img[bmp->h][bmp->w][3];
    int i, j, b, g, r, frame;
    
    // ファイルから読む
    fp = fopen(bmp->name, "rb");
    fseek(fp, HEADER_SIZE, 0);
    fread(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    // インスタグラム風
    // 明るさ
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            b = img[i][j][0] * 1.1; // B
            g = img[i][j][1] * 1.1; // G
            r = img[i][j][2] * 1.1; // R
            
            img[i][j][0] = (b > 255) ? 255 : b;
            img[i][j][1] = (g > 255) ? 255 : g;
            img[i][j][2] = (r > 255) ? 255 : r;
        }
    }
    // 色合い
    for(i=0; ih; i++) {
        for(j=0; jw; j++) {
            img[i][j][0] = img[i][j][0] + (255 - img[i][j][0]) * 0.25; // B
            img[i][j][2] = img[i][j][2] + (255 - img[i][j][2]) * 0.15; // R
        }
    }
    
    // フレームをつける
    frame = bmp->w * INSTAGRAM_FRAME;
    if(frame < MIN_FRAME){
        frame = MIN_FRAME;
    }
    // 下枠
    for(i=0; iw; j++) {
            img[i][j][0] = 20;
            img[i][j][1] = 10;
            img[i][j][2] = 5;
        }
    }
    // 上枠
    for(i=bmp->h-1; i>=bmp->h-frame; i--) {
        for(j=0; jw; j++) {
            img[i][j][0] = 20;
            img[i][j][1] = 10;
            img[i][j][2] = 5;
        }
    }
    // 左枠
    for(i=0; ih; i++) {
        for(j=0; jh; i++) {
        for(j=bmp->w-1; j>=bmp->w-frame; j--) {
            img[i][j][0] = 20;
            img[i][j][1] = 10;
            img[i][j][2] = 5;
        }
    }
    
    // ファイル出力
    output(bmp, img);
    
    printf("LOG>> OK: 5 / INSTAGRAM\n");
}


/* ファイル出力 */
void output(struct BMPFile *bmp, unsigned char *img) {
    FILE *fp;
    
    // 拡張子".bmp"を削除
    bmp->name[strlen(bmp->name)-4] = '\0';
    
    // ファイル名おの変更
    switch(bmp->flag){
        case '1':
            strcat(bmp->name, "_sepia.bmp");
            break;
        case '2':
            strcat(bmp->name, "_nega.bmp");
            break;
        case '3':
            strcat(bmp->name, "_mosaic.bmp");
            break;
        case '4':
            strcat(bmp->name, "_edge.bmp");
            break;
        case '5':
            strcat(bmp->name, "_instagram.bmp");
            break;
    }
    
    // ファイル出力
    fp = fopen(bmp->name, "wb");
    fwrite(bmp->header, 1, HEADER_SIZE, fp);
    fwrite(img, 1, bmp->w * bmp->h * 3, fp);
    fclose(fp);
    
    printf("LOG>> OutputFile: %s\n", bmp->name);
    
}

※コードの最後にSyntaxHighlighterの不具合?でゴミ(タグ)が見えてますが気にしない方向でお願いします。
※対応フォーマットはWindowsBMP形式のみです。
※例え拡張子が.bmpでもヘッダやデータがおかしなモノはダメです。

画像処理って難しいと思ってましたが、「ActionScript入門Wiki」のわかりやすい解説とサンプルコード(もちろんASですが)がとても参考になり、Photoshopや多くのカメラアプリのエフェクトも裏側ではこんなことやってるのかな?とか想像しつつCに移植して、実行しては「おーっ!」ってやってました。
画像処理、楽しいですね!

引き続き本題のObjective-Cの勉強をやっていきます。

■参考サイト

C言語で、画像を読み込むにはどのようにすれば良いですか?
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1020829250

Bitmapファイルフォーマット
http://www.umekkii.jp/data/computer/file_format/bitmap.cgi

C言語による画像処理プログラミング
http://coconut.sys.eng.shizuoka.ac.jp/bmp/

ActionScript入門Wiki(画像処理)
http://www40.atwiki.jp/spellbound/pages/166.html