2017年5月16日火曜日

C# バッキングストアとストリームについて

C# Magick.NETを使ってみる
目次→http://1studying.blogspot.jp/2017/07/c-magicknetmokuzi.html#kuw02

様々なストレージメディア(バッキングストア)に対してデータの読み書きを行う際、
ストリームを使用します。
(ストリームからバイナリデータをバイト配列で取得、書き出しが行えます。)


バッキングストア(backing store)とは…


「ストリーム」から見たストレージメディア(ファイル、ネットワーク、メモリ等)の事を、
「バッキングストア」と言います。
「ストリーム」はストレージメディアを「バッキングストア」として利用できます。
「バッキングストア」は必ずStreamクラスを実装しているので、
Streamクラスから継承されたメソッドを使用する事により、
プログラムからは同じ操作でバイト配列の読み書きを行えます。



ストリーム(Stream)とは…


「ストリーム」を使用して「バッキングストア」からバイト配列の読み書きを行います。
「ストリーム」には以下の様な物があります。
ファイル」のバイト単位読み書き
  →「System.IO.FileStream」クラス
  「ファイルデータ」を「バッキングストア」として使用する「ストリーム」です。
・「ネットワーク」を介したバイトの送受信
  →「System.Net.Sockets.NetworkStream」クラス
  「ネットワーク上データ」を「バッキングストア」として使用する「ストリーム」です。
メモリ配列」のバイト単位読み書き
  →「System.IO.MemoryStream」クラス
  「メモリー」を「バッキングストア」として使用する「ストリーム」です。

「System.IO.FileStream」クラスも
「System.Net.Sockets.NetworkStream」クラスも
「System.IO.MemoryStream」クラスも
「System.IO.Stream」クラスを継承している為、同じ使用方法で操作できます。

あと、特殊な「ストリーム」として以下の物があります。
他の「ストリーム」に「バッファリング」を持たせる
  →「System.IO.BufferedStream」クラス
  主に「NetworkStream」とセットで使用されます。
  (「FileStream」は内部でバッファリングされる為必要無し。
   「MemoryStream」は読み書きが早い為、バッファリングの必要無し。)

・使用する際の注意
「System.IO.Stream」はあくまでバイトの読み書きを行う為の抽象クラスです。
抽象クラスの為、そのまま「new」して使う事はできません。
「Stream stream = new MemoryStream();」
のようにキャストして使用します。
あと注意点として、
テキストの読み書きによく使用される「StreamReader」や「StreamWriter」クラスは、
「Stream」の名前が入っているのですが、バイトの読み書きを行うのではなく、
エンコードを指定した文字の読み書きを行うクラスです。
「System.IO.Stream」を継承していない事に注意して下さい。



Streamクラス…


「System.IO.Stream」クラスについて説明します。
・プロパティ
  「CanRead」→読み取り可能か
  「CanWrite」→書き込み可能か
  「Length」→ストリームの長さをbyte単位で取得
  「CanSeek」→シークが可能かどうか。
         ランダムアクセスも可 true
         シーケンシャルアクセスのみしか出来ない false
  「Position」→ストリームの現在位置を取得、設定する(ポインタ的なやつ)。
・メソッド
  「ReadAsync」→非同期で読み取り。
  「WriteAsync」→非同期で書き込み。
  「Close」→ストリームを閉じ、リソース解放。(使わない。Disposeを使う。)
  「Dispose」→ストリームを閉じ、リソース解放。
        (Closeよりこちらを使うか、usingを使った方が良いです。)
  「Flush」→ストリームにまだメディアに書き出されていないバッファがあれば、書き込み。
       (ストリームを閉じる時にも裏側で呼ばれています)
  「Read」→次の文字を読み取る。
  「ReadByte」→1バイト読み取り。位置が末尾の場合-1を返す。
  「Seek」→ストリームの現在位置を特定の位置へ移動。
  「SetLength」→ストリームの長さをbyte単位で設定。
  「Write」→指定した値をストリームに書き込む。
  「WriteByte」→ストリームの現在位置に1バイト書き込み。
  「CopyToAsync」→ストリームから非同期で読み取り、別のストリームに書き込む。



準備


試しに「ストリーム」を使ってみます。
その為の準備を行います。

「ファイル→新規作成→プロジェクト」
「テンプレート→VisualC#→Windowsクラシックデスクトップ→Windowsフォームアプリケーション」
を作成します。


私はプロジェクトの名前を「testStream」としました。

次に、
「Form1」のデザイナへ
「button1」、「button2」、「pictureBox1」を配置します。




配置したら、「button1」と「button2」をダブルクリックして、
「button1」と「button2」のクリックイベントのコードを表示させておきます。
        private void button1_Click(object sender, EventArgs e)
        {

        }

        private void button2_Click(object sender, EventArgs e)
        {

        }

準備が出来ました。



使ってみる


実際に「ストリーム」を試します。
「System.IO.FileStream」と「System.IO.MemoryStream」を使ってみます。


・「MemoryStream」を使ってみる
150*150pixelの塗りつぶし「Bitmap画像」作成し、
「Bitmap画像」→「MemoryStream」→「byte[]」
へと変換させています。
その後、
「byte[]」→「MemoryStream」→「Bitmap画像」
へと変換を戻し、円を描画して表示させます。

ビットマップ画像を一度、バイト配列としてメモリ領域へ書き出し、
そのメモリ領域のバイト配列をビットマップ画像として読み込み
ビットマップ画像を表示させる。
処理的には遠回りした事を行っています。

「button1」のクリックイベントのコードを以下の形にします。
        private void button1_Click(object sender, EventArgs e)
        {
            var myBitmap = new Bitmap(150, 150);
            using (Graphics g = Graphics.FromImage(myBitmap))
            {
                g.FillRectangle(Brushes.Green, g.VisibleClipBounds); //塗りつぶし
            }

            //Bitmap画像→ストリームに保存
            var ms = new System.IO.MemoryStream(); // 本来はusingを使います
            myBitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            //ストリーム→byte[]へ変換
            byte[] byteArray = ms.ToArray();
            ms.Dispose(); //ストリーム閉じる

            //~~~~〜〜〜〜

            //byte[]→ストリームへ変換
            var ms2 = new System.IO.MemoryStream(byteArray); // 本来はusingを使います
            //ストリーム→Bitmap画像へ変換
            var myBitmap2 = new Bitmap(ms2);
            ms2.Dispose(); //ストリーム閉じる

            using (Graphics g = Graphics.FromImage(myBitmap2))
            {
                g.DrawEllipse(new Pen(Color.Black, 5), 25, 25, 100, 100); //円描画
            }

            pictureBox1.Image = myBitmap2;
        }
↓「実行」して「button1」を押下。

このように表示されます。

今回、
「MemoryStream」を使い、メモリを「ストリーム」として扱いました。
「MemoryStream」を使用し、
「Bitmap画像」から「byte[]」への相互変換をおこないましたが、
大きな画像を扱う場合は「MemoryStream」分のメモリが余計に使用されます。
メモリの使用量が気になる場合は、
この後説明する「「FileStream」をつかってみる」のコード内で使用している
「ImageConverter」クラスの
「ConvertTo()」メソッドと「ConvertFrom()」メソッドを
使用しても良いかもしれません。


・「FileStream」を使ってみる
150*150pixelの塗りつぶし「Bitmap画像」作成し、
「Bitmap画像」→「byte[]」
へと変換後、
「FileStream」を使い「test.bmp」ファイルへ「byte[]」を保存しています。
その後、
「FileStream」を使い「test.bmp」ファイルから「byte[]」を読み込みます。
「byte[]」→「Bitmap画像」
へと変換を戻し、四角を描画して表示させます。
処理的には遠回りした事を行っています。

「button2」のクリックイベントのコードを以下の形にします。
       private async void button2_Click(object sender, EventArgs e)
        {
            var myBitmap = new Bitmap(150, 150);
            using (Graphics g = Graphics.FromImage(myBitmap))
            {
                g.FillRectangle(Brushes.LawnGreen, g.VisibleClipBounds); //塗りつぶし
            }

            //Bitmap画像→byte[]へ変換
            var converter = new ImageConverter();
            byte[] byteArray = (byte[])converter.ConvertTo(myBitmap, typeof(byte[]));

            //ファイルを新規作成(ファイルストリーム生成)
            using (var fs = new System.IO.FileStream(@"./test.bmp", System.IO.FileMode.Create))
            {
                //byte[]→ストリームへ書き込み
                await fs.WriteAsync(byteArray, 0, byteArray.Length);
            }

            //~~~~〜〜〜〜

            byte[] byteArray2;
            //ファイルを開く(ファイルストリーム生成)
            using (var fs = new System.IO.FileStream(@"./test.bmp", System.IO.FileMode.Open))
            {
                byteArray2 = new byte[fs.Length];
                //ストリーム→byte[]へ読み込み
                await fs.ReadAsync(byteArray2, 0, byteArray2.Length);
            }

            //byte[]→Bitmap画像へ変換
            Bitmap myBitmap2 = (Bitmap)converter.ConvertFrom(byteArray2);

            using (Graphics g = Graphics.FromImage(myBitmap2))
            {
                g.DrawRectangle(new Pen(Color.Black, 5), 25, 25, 100, 100); //四角描画
            }

            pictureBox1.Image = myBitmap2;
        }
↓「実行」して「button2」を押下。

このように表示されます。
プロジェクトフォルダの「/bin/Debug」フォルダ内に
緑色で塗りつぶされた画像の「test.bmp」ファイルが作成されます。

今回、
「FileStream」を説明するにあたり、
「byte[]」を経由したいが為にかなり回りくどいやり方をしました。
「FileStream」を使わないのであれば、
//ファイル内容を全てbyte[]へ読み込む
byte[] byteArray = System.IO.File.ReadAllBytes(@"./test.bmp");
//byte[]を全てファイルへ書き込む
System.IO.File.WriteAllBytes(@"./test.bmp", byteArray);
の様に書いた方がスマートです。
また、
通常の「Bitmap画像」ファイルへの読み書きは、
//「Bitmap画像」読み込み
var myBitmap = new Bitmap(@"./test.bmp");
//「Bitmap画像」書き出し
myBitmap.Save(@"./test.bmp");
のようにもっとシンプルに行うのが正しいです。

ファイルを「ストリーム」で「バイナリ」として呼びたい時のみ
            // ファイルストリーム生成
            var fs = new System.IO.FileStream("./test.bmp", System.IO.FileMode.Open); // 本来はusingを使います
            // バッファ用にbyte[]生成
            byte[] byteArray = new byte[fs.Length];
            // byte[](バッファ)へデータを読み込む
            await fs.ReadAsync(byteArray, 0, (int)fs.Length);
            // ファイルストリームを閉じる
            fs.Dispose();
            // byte[](バッファ)からメモリストリーム生成
            var ms = new System.IO.MemoryStream(byteArray); // 本来はusingを使います
            // メモリストリームからBitmap画像生成
            var myBitmap = new Bitmap(ms);
            // メモリストリームを閉じる
            fs.Dispose();
のような形をとります。



メモ
ストリーム→byte[]変換、3パターン
 var ms = new System.IO.MemoryStream();
とした場合…

//パターン1
byte[] byteArray = ms.GetBuffer(); 

//パターン2
byte[] byteArray = new byte[ms.Length];
await ms.ReadAsync(byteArray, 0, byteArray.Length);
か
ms.Read(byteArray, 0, byteArray.Length);

//パターン3
byte[] byteArray = new byte[ms.Length];
await ms.ReadAsync(byteArray, 0, (int)ms.Length);
か
ms.Read(byteArray, 0, (int)ms.Length);

//ストリームはusingするか、閉じ忘れないようにしましょう。
ms.Dispose();
他に「メモリストリーム」はクリップボードの読み書きにもよく使われます。





以下のサイトを参考にしています。
http://heppoco-programer-life.dreamlog.jp/archives/1214566.html



0 件のコメント:

コメントを投稿

↑Topへ