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」のクリックイベントのコードを表示させておきます。
1
2
3
4
5
6
7
8
9
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」のクリックイベントのコードを以下の形にします。
「button1」クリックイベントで「MemoryStream」を使ってみる
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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」のクリックイベントのコードを以下の形にします。
「button2」クリックイベントで「FileStream」を使ってみる
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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」を使わないのであれば、
1
2
3
4
//ファイル内容を全てbyte[]へ読み込む
byte[] byteArray = System.IO.File.ReadAllBytes(@"./test.bmp");
//byte[]を全てファイルへ書き込む
System.IO.File.WriteAllBytes(@"./test.bmp", byteArray);
の様に書いた方がスマートです。
また、
通常の「Bitmap画像」ファイルへの読み書きは、
1
2
3
4
//「Bitmap画像」読み込み
var myBitmap = new Bitmap(@"./test.bmp");
//「Bitmap画像」書き出し
myBitmap.Save(@"./test.bmp");
のようにもっとシンプルに行うのが正しいです。

ファイルを「ストリーム」で「バイナリ」として呼びたい時のみ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ファイルストリーム生成
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パターン
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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へ