- ベストアンサー
BMP画像の画像データ本体をテキストデータとして保存したい
BMP画像を読み込んで、ヘッダ箇所などの本体と関係ない部分を読み飛ばして、画像本体をテキストファイルとして保存するプログラムを作ったのですが、実際の画像の大きさ(512x480)と出力結果(質問箇所最下に記載)の画像の大きさが異なってしまいます。あまり画像に詳しくないので、どこが間違っているのか分かりません。どこを修正すべきか教えてください。以下が作成したプログラムです。 public class bmpTotxt { public static void main(String[] args){ try { FileReader fr = new FileReader("./1.bmp"); BufferedReader br = new BufferedReader(fr); FileWriter fw = new FileWriter("./pic.txt"); int i,count=0,len=0,width=512*4,height=0; while((i = br.read()) != -1){ /* 画像本体箇所なら */ if(len >= 54){ fw.write(i+" "); len++; count++; if(count == width){ fw.write("\n"); height++; count = 0; } } else{ len++; } } System.out.println("ヘッダ長:"+54); System.out.println("画像データ長:"+(len-54)); System.out.println("width:"+width/4); System.out.println("height:"+height); fr.close(); fw.close(); } catch (Exception e) { e.printStackTrace(); } } } /* 出力結果 ヘッダ長:54 画像データ長:648480 width:512 height:316 */
- みんなの回答 (3)
- 専門家の回答
質問者が選んだベストアンサー
> 66 77 54 64 11 > 0 0 0 0 0 54 > 0 0 0 40 0 0 > 0 0 2 0 0 224 > 1 0 0 1 0 24 > 0 0 0 0 0 0 > 64 11 0 > 0 0 0 0 0 0 0 > 0 0 0 0 0 0 0 > 0 0 0 > 画像データが特殊なんでしょうか? 10進ダンプを見る限り,画像データは「全く正常」です. (バイナリファイルをダンプするときは,10進じゃなくて16進にしてくださいね. 変換が面倒だから.(苦笑)) BITMAPFILEHEADER bfType = 0x4D42 = 'B' | ('M' << 8); bfSize = 0x000B4036 = 737334 bfReserved1 = 0x0000 = 0 bfReserved2 = 0x0000 = 0 bfOffBits = 0x00000036 = 54 BITMAPINFOHEADER biSize = 0x00000028 = 40 biWidth = 0x00000200 = 512 biHeight = 0x000001E0 = 480 biPlanes = 0x0001 = 1 biBitCount = 0x0018 = 24 biCompression = 0x00000000 = 0 = BI_RGB biSizeImage = 0x000B4000 = 737280 biXPelsPerMeter = 0x00000000 = 0 biYPelsPerMeter = 0x00000000 = 0 biClrUsed = 0x00000000 = 0 biClrImportant = 0x00000000 = 0 > biWidth = 0 > biBitCount = 24.0 > bfOffbits = 54 > 1行バイト数(width) = 0 > > と計算され、1行バイト数が0となり、うまくいきません。 > 画像データが特殊なんでしょうか? biWidth のバイト列は10進ダンプ中の "0 2 0 0" の部分ですが, ちゃんと4バイト読んで biWidth に変換していますか? たぶん引数なしの FileInputStream.read() で最初の1バイト (0) だけを読んで, それをそのまま biWidth に代入しているだけなんじゃないんですか? FileInputStream in = new FileInputStream(…); biWidth = in.read(); // 誤り biBitCount (2バイト) も bfOffBits (4バイト) も,2バイト目 (以後) が すべて0なので,ちゃんと読めてるように見えるだけで,もしこれらの値が 256以上になったら biWidth と同じバグが発症します. (実際には biBitCount も bfOffBits もそういう値になる可能性はほとんど ないので発症しないだけ.) 複数バイト整数を読むには,そのバイト数だけ (引数なしの) read() を呼んで それらの戻り値を一つの整数に変換するか,read(byte [] b, int off, int len) を使って複数バイトをまとめてバイト配列に読み込んで整数に変換しなきゃ. ●参考 FileInputStream in = new FileInputStream(…); byte [] buffer = new byte[4]; // 2または4バイト整数読み込み用バッファ (途中略) // biWidth (4バイト) を読む. int biWidth = readInteger(in, buffer, 4); // biHeight (4バイト) を読む. int biHeight = readInteger(in, buffer, 4); // biPlanes (2バイト) を読む. short biPlanes = (short)readInteger(in, buffer, 2); (以下略) //------------------------------------------------------------------------ // 機能 :nバイト整数 (リトルエンディアン) を読む (n≦4). // 入力 :(1) in:バイナリファイルからの入力ストリーム. // (2) buffer[0 ~ n-1]:読み込み用バッファ. // (3) n (≦4):読み込む整数のバイト長. // 戻り値:読み込んだ整数値. //------------------------------------------------------------------------ private static int readInteger(FileInputStream in, byte [] buffer, int n) throws IOException, InvalidBmpFileException { // buffer[] にnバイト読み込む. if(in.read(buffer, 0, n) < n) { // ファイルが途中で終わっている場合:不正な BMP ファイル throw new InvalidBmpFileException(); } // buffer[0 ~ n-1] を整数値に変換して返す. return convertLittleEndian(buffer, 0, n); } //------------------------------------------------------------------------ // 機能 :nバイトのバイト列 bytes[index ~ index+(n-1)] を整数に変換する. // (リトルエンディアン) // 戻り値:変換後の整数値. //------------------------------------------------------------------------ private static int convertLittleEndian(byte [] bytes, int index, int n) { int value = 0; // 変換後の整数値 int i = index + n; while(--i >= index) { // byte 型は符号付なので,符号拡張を防ぐため 0xFF でマスクする. value = (value << 8) | (bytes[i] & 0xFF); } return value; }
その他の回答 (2)
- noocyte
- ベストアンサー率58% (171/291)
> 小さな画像(16x16や61x26)についてはうまく動作しました。 > しかし、512x480のような大きな画像については、うまくいきません。 もし幅または高さが256以上の画像の場合にうまくいかないということであれば,たぶん… ヘッダ構造体のほとんどのメンバは複数 (2または4) バイト整数ですが, その読み込み方法は理解してますか? 「リトルエンディアン」って知ってます? ・ヘッダが数種類存在するのは #1 さんのおっしゃるとおりですが, 普通は BITMAPINFOHEADER だけを想定しておけば十分なはずです. (今時 BITMAPCOREHEADER を使っている BMP ファイルは見たことがないし, BITMAPV4HEADER,BITMAPV5HEADER は BITMAPINFOHEADER の拡張 (上位互換) なので.) ・ビットマップデータ本体は,ヘッダの直後から始まるとは限らない点に注意. ヘッダの直後にカラーテーブルが存在する場合もあるし,ヘッダ (+カラーテーブル) の後に (無駄な) 隙間が存在する可能性も (仕様上は) あります. 正しいビットマップデータの開始位置は BITMAPFILEHEADER.bfOffBits. BMPファイルのフォーマット http://www5d.biglobe.ne.jp/~noocyte/Programming/Windows/BmpFileFormat.html
補足
とりあえず、読み出したヘッダ情報は以下のようになりました。 読み出した画像は、512x480。 66 77 54 64 11 0 0 0 0 0 54 0 0 0 40 0 0 0 0 2 0 0 224 1 0 0 1 0 24 0 0 0 0 0 0 64 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ここから、 biWidth = 0 biBitCount = 24.0 bfOffbits = 54 1行バイト数(width) = 0 と計算され、1行バイト数が0となり、うまくいきません。 画像データが特殊なんでしょうか? ここ↓ を参考にプログラムをつくりました。 http://www.snap-tck.com/room03/c02/cg/cg02_02.html
- Bonjin
- ベストアンサー率43% (418/971)
まず、BMPファイルは「バイナリファイル」と呼ばれる類のファイルです。これを「テキストファイル」を読み込むためのFileReaderで読み込んでいること自体が間違いです(JavaDocをちゃんと読みましょう)。FileInputStreamやRandomAccessFileを使ってファイルを読み込んでください。 あと、どこからBMPファイルのファイルフォーマットの情報を得たのか知りませんが、BMPファイルのヘッダ情報は常に54バイトなわけではありません。BMPのバージョン等によって変わります。データ開始のオフセットはヘッダ情報から取得するようにしてください。
補足
アドバイス通りに FileInputStreamに変更 データ開始のオフセットをヘッダ情報から取得 1行バイト数=int([int({biWidth×biBitCount+7}/8)+3]/4)×4 と変更しましたところ、小さな画像(16x16や61x26)についてはうまく動作しました。 しかし、512x480のような大きな画像については、うまくいきません。 おそらくヘッダ情報からうまく取り出せていないものと思われますが、 画像の大きさが変化するとヘッダ情報のオフセットは変化するんでしょうか?
お礼
回答ありがとうございました。 ご指摘された点が間違っていたようでした。 以上の点を修正して完成させたいと思います。 詳細な回答ありがとうございました。