LoginSignup
1
0

More than 3 years have passed since last update.

Visual Studio 2019のC#にて、Bitmapオブジェクトを、Windows環境下において直接Windows Bitmapファイルにバイナリを叩いて出力する。コードのサンプルです。

Last updated at Posted at 2019-07-21

表題の通り
Visual Studio 2019のC#にて、
Windows環境下において
Bitmapオブジェクトを、直接Windows Bitmapファイルにバイナリを叩いて出力する。
コードのサンプルでございます。

※Android環境の場合は
https://qiita.com/oyk3865b/items/5283d16c94061f78f08d
をご覧下さい

特段、NuGetなどで他のプラグインや
別途外部dllを必要としないようにしています。

要は、バイナリを以下のように、直接叩いています。
1.Bitmapのピクセル情報をバイナリ配列に書き出す。
2.Bitmapのヘッダーの書き込み。
3.4の倍数のルールに従いBitmapバイナリをファイルに書き出す。

wine/Monoランタイム環境などで、
bmp.Save(Output_Path, Imaging.ImageFormat.Bmp)のコードが
使用できない場合があり、

それに依存しないで、Bitmapをファイルに書き出すことにしたため
作成いたしました。

※私は素人ですし
今回のコードは、あくまで最低限の動作する部分にとどめていますので、汚いです。
例外処理や、解放など至らぬ点が、多々ございます。ご了承ください。
もし実際に参考にされる際は、必要個所を必ず訂正してからにしてください。

下の画像は、今回コードでの作成例です。
参考画像
●もともとBitmap形式の画像を取り込んで(元ファイル)、
今回のコードにて出力した結果(出力ファイル)との
バイナリ比較画像です

[参考サイト]
Bitmapデータを、バイナリレベルで触るにあたって調べたレポート1
※リンク先は、私のブログです。気を付けてください。

clsBMPF.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;

public class clsBMP
{
    //★Visual Studio C# 2019において、
    //Bitmapオブジェクトを直接Windows bitmapファイルとして出力させる方法についてのコード。
    //wine/Monoランタイム環境だとバグってしまう。などがあり
    //bmp.Save(Output_Path, Imaging.ImageFormat.Bmp)に頼らずに、Bitmapオブジェクトから、Windows bitmapファイルへと直接出力させる方法についてのコード。

    //★今回は、取り急ぎ作成したコードなので、適時修正して使用されたし


    //■ヘッダー情報関係
    //http://oyk3865b.blog13.fc2.com/blog-entry-1394.html
    //△冒頭の構文=BfType(ファイルタイプ)------------------------------------------------------------------------------------------------------------
    readonly byte[] bmp_header_start = new byte[] {0x42, 0x4D}; 


    //△ヘッダー間隔用のオブジェクト------------------------------------------------------
    readonly byte[] bmp_1st_obj = new byte[] { 0, 0, 0, 0 };


    //△BfOffBits------------------------------------------------------
    readonly byte[] bmp_2nd_obj = new byte[] { 0x36, 0, 0, 0 };


    //△BiSize------------------------------------------------------
    readonly byte[] bmp_3rd_obj = new byte[] { 0x28, 0, 0, 0 };


    //△BiPlanes_BiBitCount------------------------------------------------------
    readonly byte[] bmp_4th_obj = new byte[] { 0x1, 0, 0x18, 0 };

    public int direct_bitmpa_to_file(Bitmap bm, string Output_bmp_Path)
    {   //Bitmapオブジェクトを直接Windows Bitmapファイルとして出力する。

        //■bm=元画像を格納
        //■Output_bmp_Path=出力先のパスを格納

        //戻り値の初期化
        int Convert_Bitmap_to_bmp_ret = 0;
        //1ピクセルごとのピクセルサイズを取得
        int pixelSize = 3; //初期値は3(1ピクセルにつき3バイトのものとする。)
        if (bm.PixelFormat.Equals(System.Drawing.Imaging.PixelFormat.Format24bppRgb)) {
            //24bppなら、1ピクセル24bit=3バイトを要する
            pixelSize = 3;

        } else { //★他のPixelFormatの場合→もう一度Bitmapを作成する。
            //今回は、32bppのものは対応させていない。
            using (Bitmap bm_2 = new Bitmap(bm)) {
                //bmを一旦解放する。
                if (bm != null) { bm.Dispose(); }
                bm = null;
                //24bpsで、再作成する。
                bm = bm_2.Clone(new Rectangle(0, 0, bm_2.Width, bm_2.Height), System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                bm_2.Dispose();
            }
            pixelSize = 3;
        }

        //ピクセル情報を格納
        System.Drawing.Imaging.BitmapData bmpDate = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bm.PixelFormat);
        IntPtr ptr = bmpDate.Scan0;
        //全ピクセル数を算出
        int pixel_count = bmpDate.Stride * bm.Height;
        //全ピクセル格納用のバイナリ配列作成
        byte[] pixels = new byte[pixel_count];
        //画像の全ピクセル情報をバイナリに取得する。
        System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, pixels.Length);


        //ファイルサイズの算出(横は4の倍数でないといけない)
        int width_size = bm.Width * 3; //幅1行のバイナリサイズを算出(幅px*3色)
        //幅バイナリ値は4の倍数に直してから、高さpxをかけ本体サイズを算出。
        Int32 bitmap_filesize = ((((width_size + 3) / 4) * 4) * bm.Height);
        //出来るBitmapの全ファイル・サイズを格納
        Int32 bitmap_all_filesize = bitmap_filesize + 54; //ヘッダーなど本体以外で54バイト消費


        //■出力bmpバイナリの初期化
        //出力bmpのバイナリ格納用。
        List<byte> ary_bmp_file = new List<byte> ();
        //出力bmpのバイナリ各オブジェクトの、バイト開始位置を格納用。
        List<byte> ary_bmp_byte_head = new List<byte>();


        //■ファイルを作成して書き込む 
        //ファイルが存在しているときは、消してから書き込みする 
        if (System.IO.File.Exists(Output_bmp_Path)) { System.IO.File.Delete(Output_bmp_Path); }

        using (System.IO.FileStream fs = new System.IO.FileStream(Output_bmp_Path,
                System.IO.FileMode.Create,
                System.IO.FileAccess.Write)) {

            //◎冒頭の書き込み
            ary_bmp_file.AddRange(bmp_header_start);


            //◎全ファイルサイズの格納(BfSize)
            //ファイルサイズ値を、バイト配列に変換
            ary_bmp_byte_head.AddRange(BitConverter.GetBytes(bitmap_all_filesize));
            if (ary_bmp_byte_head.Count < 4) { //必ず、4バイトにする。
                //空隙は0で埋める。
                for (int i = ary_bmp_byte_head.Count + 1; i <= 4; i++) {
                    ary_bmp_byte_head.Add(0);
                }
            }
            ary_bmp_file.AddRange(ary_bmp_byte_head.ToArray()); //書き加える。
            ary_bmp_byte_head.Clear(); //不要情報を、クリア。


            //◎空白4バイトの書き込み
            ary_bmp_file.AddRange(bmp_1st_obj);

            //◎BfOffBitsの書き込み
            ary_bmp_file.AddRange(bmp_2nd_obj);

            //◎BiSizeの書き込み
            ary_bmp_file.AddRange(bmp_3rd_obj);

            //◎画像幅の指定(BiWidth)
            //幅の値を、バイト配列に変換
            ary_bmp_byte_head.AddRange(BitConverter.GetBytes(bm.Width));
            if (ary_bmp_byte_head.Count < 4) { //必ず、4バイトにする。
                for (int i = ary_bmp_byte_head.Count + 1; i <= 4; i++)
                {
                    ary_bmp_byte_head.Add(0);
                }
            }
            ary_bmp_file.AddRange(ary_bmp_byte_head); //書き加える。
            ary_bmp_byte_head.Clear(); //不要情報を、クリア。

            //◎画像高さの指定(BiHeight)
            //高さの値を、バイト配列に変換
            ary_bmp_byte_head.AddRange(BitConverter.GetBytes(bm.Height));
            if (ary_bmp_byte_head.Count < 4)
            {    //必ず、4バイトにする。
                for (int i = ary_bmp_byte_head.Count + 1; i <= 4; i++)
                {
                    ary_bmp_byte_head.Add(0);
                }
            }
            ary_bmp_file.AddRange(ary_bmp_byte_head); //書き加える。
            ary_bmp_byte_head.Clear(); //不要情報を、クリア。

            //◎BiPlanes_BiBitCountの書き込み
            ary_bmp_file.AddRange(bmp_4th_obj);

            //◎空白4バイトの書き込み
            ary_bmp_file.AddRange(bmp_1st_obj);


            //◎画像ファイルサイズの格納(BiSizeImage)
            //ファイルサイズ値を、バイト配列に変換
            ary_bmp_byte_head.AddRange(BitConverter.GetBytes(bitmap_filesize));
            for (int i = ary_bmp_byte_head.Count + 1; i <= 4; i++)
            {
                ary_bmp_byte_head.Add(0);
            }
            ary_bmp_file.AddRange(ary_bmp_byte_head); //書き加える。
            ary_bmp_byte_head.Clear(); //不要情報を、クリア。

            //◎空白4バイトの書き込み×4
            for (int i = 0; i < 4; i++) {
                ary_bmp_file.AddRange(bmp_1st_obj);
            }

            //一旦、バイト型配列の内容をファイルに書き出す
            fs.Write(ary_bmp_file.ToArray(), 0, ary_bmp_file.Count);


            //配列を、一旦、クリアする。
            ary_bmp_file.Clear();
            //■ヘッダーの作成------------ここまで-----------------


            //Bitmapは、左下から書き込む
            for (int bmp_y = bmpDate.Height - 1; bmp_y >= 0; bmp_y--)
            { //縦軸分のループ(下から)

                //横幅の分だけ配列にコピーする。
                //このコピー部分は、ループでなくて、配列のコピーでも良い
                for (int bmp_x = 0; bmp_x <= (bmpDate.Width - 1); bmp_x++)
                {   //横軸分のループ(左から)
                    //その座標の点にある、ピクセル(ドット)の色を取得する。
                    long pos = bmp_y * bmpDate.Stride + bmp_x * pixelSize;
                    ary_bmp_file.Add(pixels[pos]); //R
                    ary_bmp_file.Add(pixels[pos + 1]); //G
                    ary_bmp_file.Add(pixels[pos + 2]); //B
                }

                //4の倍数でない場合
                if (ary_bmp_file.Count % 4 != 0) {
                    //横方向は、4の倍数とする。
                    while ((ary_bmp_file.Count % 4) != 0){
                        ary_bmp_file.Add(0); //0で埋める
                    }
                }


                //一旦、バイト型配列の内容をファイルに書き出す
                fs.Write(ary_bmp_file.ToArray(), 0, ary_bmp_file.Count);


                //配列を、一旦、クリアする。
                ary_bmp_file.Clear();
            }


            //作成チェック
            if (fs.Length != bitmap_all_filesize)
            {
                MessageBox.Show("ファイルサイズ相違エラー");
                Convert_Bitmap_to_bmp_ret = 0;
            }
            else
            {
                //成功フラグ
                Convert_Bitmap_to_bmp_ret = 1;
            }


            //■書き込みの後処理
            fs.Close(); //ファイルを閉じる 
        }

        //バイナリ・メモリの占有を開放する。
        ary_bmp_file.Clear();
        ary_bmp_file = null;

        ary_bmp_byte_head.Clear();
        ary_bmp_byte_head = null;


        //画像の解放
        bmpDate = null;
        pixels = null;
        ptr = IntPtr.Zero;
        if (bm != null) { bm.Dispose(); }
        bm = null;

        //大量処理時の謎のタイムアウト防止用
        Application.DoEvents();

        return Convert_Bitmap_to_bmp_ret;
    }
}

例えば、
以下のようなコードで、ファイルを選択して
変換することが出来ます。

Form1.cs
        private void Form1_Load(object sender, EventArgs e)
        {   //ここは適当に記述しています。
            using (OpenFileDialog fileDialog = new OpenFileDialog())
            {   //bmpに直接変換したい画像ファイルを選択できるようにしている。
                fileDialog.Filter = "画像データ (*.bmp;*jpeg;*.jpg;*.png)|*.bmp;*jpeg;*.jpg;*.png";
                fileDialog.Multiselect = false;

                //「開く」ボタンが選択された時の処理
                if (fileDialog.ShowDialog() == DialogResult.OK)
                {
                    //変換したい画像のパスを取得
                    string fileName = fileDialog.FileName;  //こんな感じで選択されたファイルのパスが取得できる

                    //Windows Bitmapは、今回は仮に、最初のファイルと同じフォルダに「test.bmp」として生成される。
                    clsBMP cls = new clsBMP();
                    using (System.Drawing.Bitmap bm = new System.Drawing.Bitmap(fileName)) { 
                        cls.direct_bitmpa_to_file(bm,
                            Path.Combine(Path.GetDirectoryName(fileName), "test.bmp"));
                    }
                    //おしまいのメッセージ
                    MessageBox.Show("END");
                }
            }

            //さようなら
            Application.Exit();
        }

以上のコードを実行すると
指定した画像ファイルが、そのまま
指定した画像のフォルダに、「test.bmp」として出力されてあると思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0