VB.NET で画像を扱うときに必ずと言っていいほど出会うのが Image.FromStream と Using の取り合わせです。「Using を抜けたらストリームは勝手に閉じられる?」「ストリームを開いたままにして後で再利用したい」――本記事はその疑問を明確に解消し、現場で迷わないための安全な実装パターンと落とし穴回避策を、.NET Framework 4.0/4.8(WinForms)前提で徹底解説します。
結論(最初に答え)
Image.FromStream(Stream, Boolean, Boolean)は渡されたストリームを自動では閉じません。Using … End Usingが破棄するのはUsingで宣言したImageオブジェクトのみ(サンプルではMyImage)。InputStreamは開いたままです。Imageの生存中は、原則として元ストリームを閉じない(GDI+ の動作上、画像が内部的にストリームへ依存するため)。
ストリームを早く閉じたい場合は、画像をクローン(New Bitmap(original))して依存を切るのが定石です。- 処理が済んだら、ストリームは自分で
Close()またはDispose()を呼んで解放します。
対象と前提
対象は VB.NET / WinForms、.NET Framework 4.0 および 4.8。System.Drawing(GDI+)ベースの画像処理を想定しています。WPF の BitmapImage や .NET 6+ の System.Drawing.Common のクロスプラットフォーム事情は範囲外です。
なぜ Image.FromStream はストリームを閉じないのか
System.Drawing.Image は内部で GDI+ を利用しており、イメージデータの解凍やメタデータの参照を遅延的に行う可能性があります。つまり、コンストラクターや FromStream 呼び出し時点で全データを完全にメモリへコピーしない場合があり、その間は元のストリームが有効であることが前提になります。したがってフレームワーク側は「あなたが渡したストリームを勝手に閉じる」ことはしませんし、できません。
Using が解放するのはだれか
Using は IDisposable を実装する「自分自身」を確実に破棄する構文です。次のコードでは破棄対象は MyImage のみであり、InputStream ではありません。
Using MyImage As Image = Image.FromStream(InputStream, False, False)
' 画像処理
End Using ' ここで破棄されるのは MyImage(Image)だけ。InputStream は開いたまま
Image.FromStream の引数(2 つの Boolean)の正しい理解
第 2, 第 3 引数は「ストリームを閉じる・閉じない」を制御するものではありません。
| 引数 | 型 | 意味 | ポイント |
|---|---|---|---|
| useEmbeddedColorManagement | Boolean | 画像に埋め込まれたカラープロファイル(ICC プロファイル等)を使用するか | 色再現性が重要なら True。処理コストはわずかに増加 |
| validateImageData | Boolean | 読み込み時に画像データを検証するか(壊れたデータを早期検出) | 信頼できない入力なら True で堅牢性向上。完全読み込みが促進されることもあるが、ストリーム非依存になる保証ではない |
代表的なコードパターンとベストプラクティス
① ストリームを「意図的に」開いたままにする(もっともシンプル)
質問のケース。Image は Using 内だけで使い、ストリームは後で活用(または自前でクローズ)します。
Dim InputStream As New IO.MemoryStream(IO.File.ReadAllBytes("sample.jpg"))
Using MyImage As Image = Image.FromStream(InputStream, False, False)
' ここで画像が使える
MessageBox.Show(MyImage.Width.ToString())
End Using ' 画像だけ破棄。InputStream は開いたまま
If InputStream.CanRead Then
MessageBox.Show("ストリームはまだ開いています")
End If
' 後片付け(必要になった時点で)
InputStream.Close()
InputStream.Dispose()
② 画像をクローンして、ストリーム依存を完全に断ち切る
画像を長期間保持する/別スレッドに渡す/ファイルロックをすぐ解放したい――そんなときは クローンが有効です。
Dim bmp As Bitmap
Using fs As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
Using img As Image = Image.FromStream(fs, False, False)
' クローンして GDI+ のストリーム依存を切る
bmp = New Bitmap(img)
End Using ' img の破棄。fs はまだ Using 内
End Using ' fs をここで閉じても bmp は利用可能
' 以降、bmp は元ストリーム不要(自由に使用・保存可能)
bmp.Save("out.png", Imaging.ImageFormat.Png)
bmp.Dispose()
ポイント: New Bitmap(img) はピクセルデータを新しい Bitmap にコピーするため、元のストリームや img のライフサイクルから独立します。
③ 「両方」Using に入れるネスト構文(片付け順序を明確に)
ストリームも一緒に Using に入れておけば、画像 → ストリーム の順に破棄されるため、後片付けの順序ミスを防げます。
Using InputStream As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
Using MyImage As Image = Image.FromStream(InputStream, False, False)
' 画像処理
End Using ' 先に画像を破棄
End Using ' 次にストリームを破棄
④ よくある失敗例(ファイルをすぐ閉じてしまう)
' これは避ける:画像がまだ生きているのにファイルを閉じる
Dim fs As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read)
Dim img As Image = Image.FromStream(fs)
fs.Close() ' <= ここで閉じると、後の操作で例外(GDI+ 一般エラー等)が起きうる
' img を使う処理...
img.Dispose()
「動くこともある」ため見逃されがちですが、環境や画像フォーマット、後続操作(Save/RotateFlip/サムネイル生成など)によって失敗します。再現性のある安全策=クローンを採用しましょう。
検証に使える最小サンプル(WinForms)
以下は「Using を抜けても InputStream は開いている」ことを確認できる実用的サンプルです。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim input As New IO.MemoryStream(IO.File.ReadAllBytes("sample.jpg"))
Using img As Image = Image.FromStream(input, False, False)
Me.Text = $"幅={img.Width}, 高さ={img.Height}"
End Using ' ← ここで解放されるのは img だけ
' まだ開いているので再度読み取れる
If input.CanSeek Then input.Seek(0, IO.SeekOrigin.Begin)
Dim head As Byte() = New Byte(7) {}
input.Read(head, 0, head.Length)
MessageBox.Show($"先頭 8 バイト: {BitConverter.ToString(head)}")
' 明示的に片付け
input.Dispose()
End Sub
「ファイルロック」「GDI+ 一般エラー」の正体と対策
- ファイルロック:
FileStreamを開いたまま画像を保持すると、元ファイルがロックされ、上書き・削除で失敗します。
対策: 早期にクローン(New Bitmap(img))→ 元ストリームを閉じる。 - GDI+ 一般エラー: 典型的には「保存先が同じファイルでロック中」「元ストリームを閉じた/破棄した」「保存先フォルダへの権限不足」。
対策: 別ファイル名で保存する、クローンしてから保存する、権限を確認する。
パフォーマンス/メモリ設計の勘所
- クローンはメモリを消費: 1920×1080・32bpp の場合、ピクセルだけで約 7.9MB。複数枚同時にクローンする設計ではメモリ監視が必須。
- 一時的に
MemoryStreamに載せ替えるコスト:File.ReadAllBytesはファイルを丸ごとメモリに読み込むため、大容量画像や連続処理では GC プレッシャーが増えます。
可能ならFileStream直読み+クローン→即クローズが安定。 - サムネイルだけ欲しい場合: クローン後に
GetThumbnailImageやDrawImageで縮小する。元ファイルを早く解放できる。
ストリーム種別ごとの注意点
| ストリーム種別 | 主な用途 | 注意点 | 推奨パターン |
|---|---|---|---|
FileStream | ディスク上の画像 | ファイルロックが発生。上書き保存や削除に影響 | 読み込んだら即クローン → すぐ閉じる |
MemoryStream | バイト配列からの読み込み | メモリ消費が大きくなりがち | 必要な箇所でのみ生成。長生きさせない |
NetworkStream 等 | ソケット/HTTP 等のストリーミング | 遅延読み込みでブロックし得る | 一度 MemoryStream にバッファしてから FromStream |
安全に保存するためのテンプレート
「読み込んだそのファイルと同名で保存したい」場合の定石です。ダブルバッファリングでロックや失敗を回避します。
Dim tempPath = IO.Path.GetTempFileName()
Using fs As New IO.FileStream("sample.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
Using img As Image = Image.FromStream(fs, False, True)
Using bmp As New Bitmap(img) ' 依存を切る
bmp.Save(tempPath, Imaging.ImageFormat.Jpeg)
End Using
End Using
End Using
' 置換(Windows のみ・必要ならバックアップ名を備える)
IO.File.Copy(tempPath, "sample.jpg", True)
IO.File.Delete(tempPath)
ユースケース別・実装クイックガイド
| ユースケース | ベストプラクティス | 備考 |
|---|---|---|
| 単にサイズを読みたい | Using img ... End Using で済ませる。ストリームは必要なら後でクローズ | 高速・低メモリ |
| 画像を UI に表示し続けたい | クローンしてから PictureBox.Image = bmp | ファイル解放・スレッド安全性向上 |
| 読み込んだファイルへ上書き保存 | 必ずクローン→元ストリームを閉じる→別名に保存→置換 | GDI+ 一般エラー回避 |
| 連続大量処理 | 1 枚ずつ Using と Dispose を徹底。大きい画像は逐次的に解放 | ワーキングセット抑制 |
テスト観点とデバッグのコツ
- 例外の明示化: 例外を握りつぶさず、キャッチしたら
ex.ToString()をログへ。 - ロック検知: 同じファイルの
FileShare.Noneで開いてみて例外が出るか確認。 - リーク検知: 長時間動作のツールで、処理件数に比例してメモリが増えないかを監視。
- 破損画像の試験:
validateImageData:=True版もテストに含め、入力バリデーションを強化。
FAQ
- Q:
validateImageData:=Trueにすればストリームを閉じても平気?
A: 検証が強くなるだけで、「ストリーム非依存」になる保証はありません。クローンが確実。 - Q:
New Bitmap(Stream)なら最初からストリーム非依存?
A:Bitmapのストリームオーバーロードも原則は同じです。クローン(New Bitmap(img))で切り離すのが定石。 - Q:
PictureBoxに直接Image.FromFileを渡しているが大丈夫?
A:Image.FromFileは内部でファイルをロックする挙動があり、上書き保存でトラブルの原因に。FromStream + クローンが安全です。 - Q: 画像表示中にファイルを削除できない。
A: どこかでファイルハンドルが生きています。表示用のImageをクローンしてから、元ストリーム・元画像を破棄してください。
実運用で使えるチェックリスト
- 画像は
Usingで確実に破棄しているか。 - ファイルを長時間ロックしていないか(必要なら即クローンして解放)。
- 不正画像や壊れたファイルを想定した例外処理があるか。
- 保存は別名一時ファイル → 置換の手順にしているか。
- 処理後にストリームを
Close/Disposeしているか。
まとめ
Image.FromStream は渡されたストリームを自動では閉じません。Using で破棄されるのは画像オブジェクトだけです。画像がストリームに依存する可能性があるため、画像の生存中に元ストリームを閉じるのは危険。ストリームを早く解放したいなら、画像をクローンして依存を断ち切るのが最良手です。最後は自分でストリームを確実に後片付け――この 3 点を守れば、ファイルロックや GDI+ エラーに悩まされることは大きく減ります。
付録:コピペで使えるスニペット集
ファイル → 表示用画像(ロックしない)
Dim displayImage As Bitmap
Using fs As New IO.FileStream(path:="sample.jpg", mode:=IO.FileMode.Open, access:=IO.FileAccess.Read, share:=IO.FileShare.Read)
Using img As Image = Image.FromStream(fs, useEmbeddedColorManagement:=False, validateImageData:=True)
displayImage = New Bitmap(img) ' ロックを残さない
End Using
End Using
PictureBox1.Image = displayImage
メモリ上の JPEG バイト列 → PNG へ変換保存
Dim jpegBytes As Byte() = IO.File.ReadAllBytes("in.jpg")
Using ms As New IO.MemoryStream(jpegBytes, writable:=False)
Using img As Image = Image.FromStream(ms, False, True)
Using bmp As New Bitmap(img)
bmp.Save("out.png", Imaging.ImageFormat.Png)
End Using
End Using
End Using
壊れた画像を早期検出(バリデーション ON)
Try
Using fs As New IO.FileStream("maybe_broken.jpg", IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Read)
Using img As Image = Image.FromStream(fs, False, True) ' validateImageData:=True
' OK 時の処理
End Using
End Using
Catch ex As Exception
MessageBox.Show("画像が不正です。" & Environment.NewLine & ex.Message)
End Try
キーメッセージ(おさらい)
- 自動クローズはされない:
FromStreamはストリームを閉じない。 Usingは自分だけ: 破棄されるのはImage自体。- 生存中は閉じない: 画像がストリームへ依存しうる。
- 早期解放はクローン:
New Bitmap(img)で依存を断つ。 - 最後は手で閉じる:
InputStream.Close/Disposeを忘れない。

コメント