WPF学習中「BitmapImageのソースをFileStreamで読み込む – zipファイル編」

C# コンピュータ
C#

BitmapImageのStreamSourceにZipファイル内にある画像ファイルのFileStreamを割り当てて画像を表示します。

プロジェクトの作成

mkdir プロジェクト名
cd プロジェクト名
dotnet new wpf
code .

ソースコード

ファイル名:MainWindow.xaml

<Window x:Class="Sample53ReadZipImageFile.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Sample53ReadZipImageFile"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <Image Name="Image1">
            </Image>
        </StackPanel>
    </Grid>
</Window>

ファイル名:MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using System.IO;
using System.IO.Compression;

namespace Sample53ReadZipImageFile
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var location = @"C:\Users\asagao\Pictures\202104081936.zip";
            var filename = @"202104081936.PNG";

            // ビットマップイメージを作成
            var bi = new BitmapImage();

            // zipファイルを開く
            using (var zip = ZipFile.OpenRead(location))
            {
                // zipファイル内のファイルのストリームを取得
                var entry = zip.GetEntry(filename);
                using(Stream fs = entry.Open())
                {
                    // メモリストリームを開く
                    using (var ms = new MemoryStream())
                    {
                        // ファイルストリームをメモリストリームにコピー
                        fs.CopyTo(ms);

                        // メモリストリームの先頭へ読み込み位置を移動
                        ms.Seek(0, SeekOrigin.Begin);

                        // 初期化開始
                        bi.BeginInit();

                        // 読み込み時にイメージ全体をメモリにキャッシュ
                        bi.CacheOption = BitmapCacheOption.OnLoad;

                        // ビットマップのソースにファイルストリームを割り当て
                        bi.StreamSource = ms;

                        // 初期化終了
                        bi.EndInit();
                    }                        
                }
            }
            bi.Freeze();

            // イメージコントロールのソースにビットマップイメージを割り当て
            Image1.Source = bi;
        }
    }
}

感想

成功すると画像が表示されます。

ZipArchiveEntry.Open()の戻り値の型がSystem.IO.StreamでBitmapImage.StreamSourceの型もSystem.IO.Streamなのでそのまま割り当てれば動きそうな気がしますが、それだとなぜか画像が表示されません。
特別例外なども発生していないので、自分の情報収集能力では解決方法を見つけることが出来ませんでした。
ただ、出来上がったBitmapImageはWidthとHeightが1となっており、なんとなくソースに割り当てたStreamから読み込みがされていないように感じました。強制的に読み込む方法も見当たらないので、とりあえずStreamをMemoryStreamにコピーしMemoryStreamをBitmapImageに割り当てたところ画像が表示されました。
なんとなく目的が達成されたのでこれで良しとします。
あと、なぜかPNG形式のファイルはうまく表示されるのにJPEG形式のファイルだと例外が発生していましたが、MemoryStreamの位置を先頭にSeekしたところ解消されました。
これも原因をつかめていませんが、解決したので良しとします。

 

追記:20240429
Zipファイル内のファイルのストリームオブジェクトが取得できるので、同じストリームであればローカルファイルと同じ取扱いが出来ると言うことだと受け取り、コードを書いてみました。ビルドエラーは出ませんが実行時例外が発生して結構悩みました。
まぁ、HDDのローカルファイルとZIPファイル内のアーカイブされたファイル、メモリ上のメモリストリームが同じストリームオブジェクトして動作するようにすると、かなりパフォーマンスが落ちそうな予感がします。個人的にzipファイルが一時ディレクトリに展開されたローカルファイルなのか、それともメモリ上に展開されたメモリストリームなのかが知りたいところです。ファイルサイズがメモリに収まらない程巨大なケースを考慮すると一時ディレクトリに展開されたローカルファイルだと思われますが、パフォーマンスを考えるとメモリに収まるのであれば、メモリーストリームの方がアーカイブファイルの展開の工数分パフォーマンスが向上すると思われます。展開後のファイルサイズはzipファイル内にセットされていますので其方を参照して展開するしないのロジックを組めそうな感じがしますが、ローカルファイルのストリームとメモリストリームの振る舞いの違いを見ると、そのような作りなっていないと思われます。

コメント