Search code examples
c#wpfzipbitmapimage

Read BitmapImage from Zip archive


For a WPF project I need to read a BitmapImage from a Zip file. The original fileformat is .png I know how to do this directly from a file system and that is working fine. From a Zip file, unfortunately the image is not shown, though an image seems to be read.

I created a simple test project:

public partial class MainWindow : Window
    {

    public BitmapImage RouteImage { get; set; }

    public MainWindow()
        {
        InitializeComponent();
        LoadBitmap();
        DataContext = RouteImage;
        }

    public void LoadBitmap()
        {
        RouteImage = new BitmapImage();
        var PackedFile = @"D:\Temp\MainContent.ap";
        try
            {
                {
                using (ZipArchive archive = ZipFile.OpenRead(PackedFile))
                    {
                    var file = archive.GetEntry("RouteInformation/image.png");
                    if (file != null)
                        {
                        using (var zipEntryStream = file.Open())
                            {
                            RouteImage.BeginInit();
                            RouteImage.CacheOption = BitmapCacheOption.OnLoad;
                            RouteImage.StreamSource = zipEntryStream;
                            RouteImage.EndInit();
                            return;
                            }
                        }
                    }
                }
            }
        catch (Exception e)
            {
            var s = "Exception: " + e.Message;
            }
        }
    }
}

The XAML code looks like this:

<Image Height="128" Width="256"  Source="{Binding BitmapImage}"/>

In the debugger it looks like the stream is created and bound to the BitmapImage, but the width and height are set to 1. so I think something is wrong withe the reading of the data in the zip file.


Solution

  • Not sure about the exact reason, but it seems necessary to copy the zip stream to an intermediate MemoryStream and read the BitmapImage from there.

    You should also write a view model class with property change notification:

    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        private BitmapImage routeImage;
    
        public BitmapImage RouteImage
        {
            get { return routeImage; }
            set
            {
                routeImage = value;
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(RouteImage)));
            }
        }
    
        public void LoadImage(string archiveName, string entryName)
        {
            using (var archive = ZipFile.OpenRead(archiveName))
            {
                var entry = archive.GetEntry(entryName);
                if (entry != null)
                {
                    using (var zipStream = entry.Open())
                    using (var memoryStream = new MemoryStream())
                    {
                        zipStream.CopyTo(memoryStream); // here
                        memoryStream.Position = 0;
    
                        var bitmap = new BitmapImage();
                        bitmap.BeginInit();
                        bitmap.CacheOption = BitmapCacheOption.OnLoad;
                        bitmap.StreamSource = memoryStream;
                        bitmap.EndInit();
    
                        RouteImage = bitmap;
                    }
                }
            }
        }
    }
    

    Assign an instance of the view model to the DataContext of your Window:

    public MainWindow()
    {
        InitializeComponent();
    
        var viewModel = new ViewModel();
        DataContext = viewModel;
    
        viewModel.LoadImage(
            @"D:\Games\steamapps\common\RailWorks\Content\Routes\00000036-0000-0000-0000-000000002012\MainContent.ap",
            "RouteInformation/image.png");
    }
    

    and bind the RouteImage property like this:

    <Image Source="{Binding RouteImage}"/>
    

    If you are intending to load large image file from the zip archive, I'd recommend to call the view model code in an async method:

    public async Task LoadImageAsync(string archiveName, string entryName)
    {
        RouteImage = await Task.Run(() => LoadImage(archiveName, entryName));
    }
    
    private BitmapImage LoadImage(string archiveName, string entryName)
    {
        BitmapImage bitmap = null;
    
        using (var archive = ZipFile.OpenRead(archiveName))
        {
            var entry = archive.GetEntry(entryName);
            if (entry != null)
            {
                using (var zipStream = entry.Open())
                using (var memoryStream = new MemoryStream())
                {
                    zipStream.CopyTo(memoryStream);
                    memoryStream.Position = 0;
    
                    bitmap = new BitmapImage();
                    bitmap.BeginInit();
                    bitmap.CacheOption = BitmapCacheOption.OnLoad;
                    bitmap.StreamSource = memoryStream;
                    bitmap.EndInit();
                    bitmap.Freeze(); // necessary when loaded in non-UI thread
                }
            }
        }
    
        return bitmap;
    }
    

    Call the method from an async Loaded event handler in your MainWindow:

    Loaded += async (s, e) => await viewModel.LoadImageAsync
        @"D:\Games\steamapps\common\RailWorks\Content\Routes\00000036-0000-0000-0000-000000002012\MainContent.ap",
        "RouteInformation/image.png");