[WP]no.015 C言語でbmp編集

数時間くらいで作成。初めてgoto使った気がします。


画像解析も、画像を読み込めなければ意味がありません。
というわけで、ビットマップファイルbmpの読み込みと保存。

windowsでもlinuxでもコンパイルできるはず。


bmpファイルヘッダ構造体を定義せずに書いていったのですが、やはり読みにくい。
簡易的に読み書きできればいいやー感でやりました。ごめんなさい

しかも構造体で書いた方が動作早そうじゃない?


こちらのサイトにBMPのバイナリ構造についての解説があります。

ルーチェ's Homepage
http://www.ruche-home.net/program/bmp/struct


こちらのサイトに沿って記述し、また出力ファイルは、適当なビュアーで表示できたので問題ないでしょう。

wikipediaにもありました(2015/09/04)。

wikipedia
https://ja.wikipedia.org/wiki/Windows_bitmap



BMPファイルにはさまざまなパターンがあり、無圧縮やRLE圧縮、jpeg圧縮されたもの、パレットを持つもの等があります。
当然、全てのパターンに対応すべきですが、ここでは「24bit無圧縮」のみとします。他はエラーで弾くようにします。

もちろん、出力も「24bit無圧縮」のみです。ファイルが大きくなるので、サイズは320x240か256x256に抑えるべきです。


ファイルが読み込めなかった場合、そのbmpファイルがパレットを持っているかどうか(256色bmp等)確かめてください。
windows標準のmspaintで「24bit無圧縮」に変換できます。


2015/09/06 改良しました。
http://shonen9th.blog.fc2.com/blog-entry-54.html


…実用性を求めるならば、C++のオープンソースライブラリとか

libbmp24
http://doscoy.github.io/libbmp24/



画像はlibeasybmp.hに定義したImage構造体によって管理します。
横ピクセル数、縦ピクセル数、データ本体。
1ピクセルはRGB構造体で管理します。1色8bit、3色で24bit。


loadbmp,savebmp関数の他にcreateimage関数、freeimage関数があります。
createimageはImage構造体のデータ領域を確保、freeimageは構造体の解放をします。
mallocとfreeみたいなやつ。

ヘッダ
libeasybmp.h

typedef struct defRGB{
unsigned char b;
unsigned char g;
unsigned char r;
}RGB;

typedef struct defImage{
int width;
int height;
RGB *data;
}Image;

Image* createimage(int,int);
void freeimage(Image *);

Image* loadbmp(const char *);
void savebmp(Image *,const char *);


libeasybmp.cを示す前に、テストプログラム。

テストプログラムその1
よく分からないグラデーションの画像を書き出します。

#include <stdio.h>
#include "libeasybmp.h"

void main(){
const int width=73;
const int height=43;
Image *img;
int i,x,y;

printf("ready\n");

img = createimage(width,height);
for (y=0;y<height;y++)
for (x=0;x<width;x++){
i=x+(height-y-1)*width;
img->data[i].r=x*255/width;
img->data[i].g=y*255/height;
img->data[i].b=128;
}
savebmp(img,"testsave.bmp");

freeimage(img);
}

書き込む際、データは左下から右方向へ進んでいることに気を付ける。

つまり、data[0]は左下の要素、data[width-1]は右下、data[width*height-1]は右上。

上のテストプログラムでは、分かりやすいように(x,y)=(0,0)の時、左上に来るようになっています。
出力される画像の左上は、暗い青(R,G,B)=(0,0,128)のはずです。


テストプログラムその2
Lenna.bmpというありがちな画像を上下左右反転して出力します。

#include <stdio.h>
#include "libeasybmp.h"

void swapc(char *a,char *b){
char t;
t=*a;
*a=*b;
*b=t;
}

void main(){
Image *img;
char c;
int i,size;

if ((img = loadbmp("Lenna.bmp"))==NULL){
return;
}

printf("width:%d height:%d\n",
img->width,img->height);

size=(img->width)*(img->height);
for (i=0;i<size/2;i++){
swapc(&(img->data[i].r),&(img->data[size-i-1].r));
swapc(&(img->data[i].g),&(img->data[size-i-1].g));
swapc(&(img->data[i].b),&(img->data[size-i-1].b));
}

savebmp(img,"Lenna_b.bmp");

freeimage(img);
}

補足無し。


2015/09/06修正
-Wallオプションを付与してコンパイルしたところ、Warningの山だったので修正。

#include <stdio.h>

#include "libeasybmp.h"

void swapc(void *a,void *b){
char t;
t=*(char*)a;
*(char*)a=*(char*)b;
*(char*)b=t;
}

int main(int argc,char **argv){
Image *img;
int i,size;

if (argc !=2){
printf("usage:exe filename\n");
return 0;
}

if ((img = loadbmp(argv[1]))==NULL){
return 0;
}

printf("width:%d height:%d\n",
img->width,img->height);

size=(img->width)*(img->height);
for (i=0;i<size/2;i++){
swapc(&(img->data[i].r),&(img->data[size-i-1].r));
swapc(&(img->data[i].g),&(img->data[size-i-1].g));
swapc(&(img->data[i].b),&(img->data[size-i-1].b));
}

savebmp(img,"result.bmp");

freeimage(img);

return 0;
}








中身
テストプログラムで網羅した範囲以外はあまり検証してないです。

libeasybmp.c


#include <stdio.h>
#include <stdlib.h>
#include "libeasybmp.h"

typedef unsigned char byte;

// private
// dataにsizeサイズのnumberの値を書き込む
static void byteset(byte *data,int number,int size){
int i;
for (i=0;i<size;i++){
data[i]=(byte)(number&0xFF);
number=number>>8;
}
}
// private
// dataをsizeサイズのint型とみなし、読み込む
static int byteget(byte *data,int size){
int i,r=0;
for (i=size-1;0<=i;i--)
r = (r<<8)|(int)data[i];
return r;
}

Image* createimage(int w,int h){
Image *img = (Image*)malloc(sizeof(Image));
if (img == 0) return 0;
img->width =w;
img->height=h;

img->data = (RGB*)malloc(sizeof(RGB)*(w*h)+3); // 余分な3byte
return img;
}
void freeimage(Image *img){
free(img->data);
free(img);
}

Image* loadbmp(const char *filename){
FILE *fp;
byte buff[36];
Image *image;
int offbits;//int filesize
int bit_pix,cpxtype;
int i,l;

if (filename == 0){
printf("loadbmp:filename null\n");
return 0;
}
if ((fp = fopen(filename,"r")) == 0){
printf("loadbmp:failed open file\n");
return 0;
}
// read header + infosize
fread(buff,1,18,fp);

// isbmp
if (buff[0]!='B' || buff[1]!='M'){
printf("loadbmp:not bmp file\n");
return 0;
}
//filesize = byteget(buff+2,4);
offbits = byteget(buff+10,4);// イメージデータオフセット

// 情報ヘッダのサイズが40のタイプでなければ読み込まない(思考放棄)
if (byteget(buff+14,4)!=40){
printf("loadbmp:not INFO type\n");
goto loadbmp_close;
}
// 続きを読み込む(INFO)
fread(buff,1,36,fp);

// bitfield持ってるor圧縮されたファイルは読み込まない(思考放棄)
cpxtype = byteget(buff+12,4);
if (cpxtype==3){
printf("loadbmp:bitfields\n");
goto loadbmp_close;
}
if (cpxtype!=0){
printf("loadbmp:compressed bmp\n");
goto loadbmp_close;
}
// bit per pixel
bit_pix=byteget(buff+10,2);
// パレットbmpも読み込まない(思考放棄)
if (bit_pix!=16 && bit_pix!=24 && bit_pix!=32){
printf("loadbmp:palette bmp\n");
goto loadbmp_close;
}

// image構造体作成
image = createimage(byteget(buff,4),byteget(buff+4,4));

// イメージデータサイズは信用しない
// イメージデータオフセットの適応
if (offbits!=0)
fseek(fp,offbits,SEEK_SET);

// note:行データは4nbyteでなければならない
l = ((image->width)*3+3)/4;
for (i=0;i<(image->height);i++)
fread(&(image->data[i*(image->width)]),4,l,fp);

// 最後も同様に操作すると一般にオーバーフローするよ
// image型に助長を含ませる対策

loadbmp_close:
fclose(fp);
return image;
}


void savebmp(Image *image,const char *filename){
FILE *fp;
byte buff[52];
int imagesize;
int i,l;
if (image == 0 || filename == 0) return;
if ((fp = fopen(filename,"w")) == 0) return;

// note:行データは4nbyteでなければならない
l = ((image->width)*3+3)/4;

imagesize=l*4*(image->height);
// fileHeader
buff[0] ='B';
buff[1] ='M';
byteset(buff+2,54+imagesize*3,4); // filesize
buff[6] =0;buff[7]=0; // reserved
buff[8] =0;buff[9]=0; // reserved
byteset(buff+10,0,4);// offBits
// infomationHeader INFO
buff[14] =40;buff[15]=0;buff[16]=0;buff[17]=0; // size
byteset(buff+18,image->width,4); // width
byteset(buff+22,image->height,4);// height
buff[26]=1;buff[27]=0; // plane
buff[28]=24;buff[29]=0; // bit per pixel (3byte)
buff[30]=0;buff[31]=0;buff[32]=0;buff[33]=0;// RGB
byteset(buff+34,imagesize*3,4);
buff[38]=0;buff[39]=0;buff[40]=0;buff[41]=0;//dpm
buff[42]=0;buff[43]=0;buff[44]=0;buff[45]=0;//dpm
buff[46]=0;buff[47]=0;buff[48]=0;buff[49]=0;//noPalette
buff[50]=0;buff[51]=0;buff[52]=0;buff[53]=0;//clrImpt

fwrite(buff,1,54,fp);

for (i=0;i<(image->height);i++)
fwrite(&(image->data[i*(image->width)]),4,l,fp);
//本来は末端はゴミではなく0で埋めるべき

// 最後も同様に操作すると一般にオーバーフローするよ
// image型に助長を含ませる対策

fclose(fp);
}

ちなみに、unsigned charをbyteと定義しています。


読み込み

始めに、先頭から18byteのみ読み出します。ファイルヘッダと、情報ヘッダのヘッダサイズ部分です。
「このファイルは'B''M'Pか?」「特定のタイプのヘッダを使用しているか?」といった情報を引っ張り出します。

無事に一般的なサイズ40のヘッダを使用していると分かった場合、36byte読み出します。情報ヘッダ部分です。
「データは無圧縮か?」「1ピクセル当り24ビットか?」「パレットを持っていないか?」「画像サイズは?」など。


処理が面倒な形式を弾いたところで、画像バッファをサイズに基づき作成します。
あとは構造体に流し込むだけ。

ただし、行データは(4の倍数)byteに余分な文字が付加されて整形されていることに注意。

このプログラムでは、余分な文字を含めて行データ丸ごと構造体に突っ込んでいます。
余分な文字は最大でも3byteですから、オーバーフローを防ぐためには…?createimage関数を見るのです!


書き込み

ヘッダ部分書き込みについて、仕様に従って代入するだけなので省略。

読み込み同様、行データは(4の倍数)byteに整形しなければならないことに注意。
でもやっぱり読み込み同様、何も考えず(4の倍数)byte書き出します。
整形部分の空きビットは使われないはずなので、別の行のデータの断片が入っても問題ない…と思う。
ただし、本来は0を挿入すべき。

最終行は後続の行が無いからオーバーフローが発生する?読み込みと同様で、オーバーフローは回避します。多分。






ところで、mallocって何と読めば良いのでしょう?ざっと挙げると、

「まろっく」
「エムあろっく」
「エムあろけーと」

正式な読み方なんて無いですよね?


そもそも上に示したコードはchar=1byte、int=4byteと勝手に決めているので…




2015/09/03 極めて問題のあるコードを修正。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

tag : C 画像解析

コメントの投稿

非公開コメント

プロフィール

舞葉(ぶよう)

Author:舞葉(ぶよう)
github.io
はてなブログ(競プロ)

古い記事のソースコードは色分けしていないので、高機能テキストエディタに貼り付けたほうが見やすいかも。

検索フォーム
このブログについて
自分がつまづいた話題、なんとなく書きたいと思ったこと、ググったけど殆ど資料なかったぞオイ な話等をアップする予定。通りすがりでも、参考になっていただければと。プログラムの例外入力、メモリリークは責任負いません。投稿された記事は修正・削除する場合があります。
カテゴリ
タグ

HSP3アルゴリズムとデータ構造c++RubyJavaUnity画像解析C機械学習C#LinuxcodeIQKinectMinecraftTonyuSystemraspberrypiPythonHTML5音声制御Simulinkruby俺ルール通信制御Javascriptシミュレーション

counter-shinobi
固定記事
最新記事
最新コメント
月別アーカイブ
ブロとも申請フォーム

この人とブロともになる

アクセスランキング
[ジャンルランキング]
コンピュータ
1139位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
191位
アクセスランキングを見る>>