Home > Blockchain >  How do I bind a huge amount of images from the local disk at runtime?
How do I bind a huge amount of images from the local disk at runtime?

Time:01-20

I have an UWP application that is supposed to display images from a disk in a grid. The image paths are being fetched from a database at runtime. It's not guaranteed that the user knows where the images to be displayed are, so using a folder picker won't work.

I already gave the app the broadFileSystemAccess capability. Binding with the absolute path still doesn't seem to work. The only kind of working solution I have for now is to create BitmapImages with a stream as the Source right after I'm getting the list of paths. However this comes with a serious memory problem. Does anyone has a solution for me?

relevant part of the ImageGalleryPage.xaml:


<GridView
    Grid.Row="4"
    Grid.Column="0"
    Grid.ColumnSpan="5"
    Padding="{StaticResource MediumLeftRightMargin}"
    animations:Connected.ListItemElementName="thumbnailImage"
    animations:Connected.ListItemKey="galleryAnimationKey"
    IsItemClickEnabled="True"
    ItemsSource="{x:Bind ViewModel.Source, Mode=OneWay}"
    SelectionMode="None">
    <i:Interaction.Behaviors>
        <ic:EventTriggerBehavior EventName="ItemClick">
            <ic:InvokeCommandAction Command="{x:Bind ViewModel.ItemSelectedCommand}" />
        </ic:EventTriggerBehavior>
    </i:Interaction.Behaviors>
    <GridView.ItemTemplate>
        <DataTemplate x:DataType="models:GalleryImage">
            <Image
                x:Name="thumbnailImage"
                AutomationProperties.Name="{Binding Name}"
                ToolTipService.ToolTip="{Binding Name}"
                Source="{x:Bind Mode=OneWay, Path=ImgBitmapImage}"
                Style="{StaticResource ThumbnailImageStyle}" />
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

relevant part of the ImageGalleryPage.xaml.cs:

public ImageGalleryPage()
{
    InitializeComponent();
    Loaded  = ImageGalleryPage_Loaded;
}

private async void ImageGalleryPage_Loaded(object sender, RoutedEventArgs e)
{
    Combo_Labels.ItemsSource = await SqlServerClassnamesService.FetchClassnames();
    await ViewModel.LoadDataAsync();
}

ImageGalleryViewModel.cs:

public class ImageGalleryViewModel : ObservableObject
{
    public ObservableCollection<GalleryImage> Source { get; } = new ObservableCollection<GalleryImage>();


    public async Task LoadDataAsync()
    {
        Source.Clear();

        var data = await SqlServerDataService.GetCollection();

        foreach (var item in data)
        {
             await HandleImages(item);
        }
    }

    private async Task HandleImages(GalleryImage img)
    {
        StorageFile file = await StorageFile.GetFileFromPathAsync(img.Path);
        var stream = await file.OpenAsync(FileAccessMode.Read);
        var bitmapImage = new BitmapImage();
        await bitmapImage.SetSourceAsync(stream);
        img.Image = bitmapImage;
        Source.Add(img);
    }
}

GalleryImage.cs:

public class GalleryImage
{
    public string ID { get; set; }

    public string Name { get; set; }

    public string Path { get; set; }

    public BitmapImage Image { get; set; }

    public BitmapImage ImgBitmapImage
    {
        get => Image != null ? Image : new BitmapImage(new Uri(Path));
    }
}

CodePudding user response:

I assume the intent is to show some type of grid of thumbnails. But much of this should also apply if you have some other kind of image layout. This will also be general recommendations rather than actual code examples.

First of all you should probably also avoiding keeping all of the loaded images in memory at full resolution. You can instead downscale images, say to about 100x100 pixels for about 40kb per thumbnail. That way you should be able to fit plenty of them in memory. See how to resize image.

You can create the UI layout as soon as you have the list of images, and update the actual thumbnail asynchronously. At least if images are placed in some type of regular grid. A related idea is UI virtualization, i.e. delay creation of UI objects until they are actually visible. Some controls have builtin support for this, others do not. See Optimizing performance: Controls

You should try to do as much as possible of the compute intensive work of reading, decoding, and resizing on one or more background threads to avoid blocking the UI thread. This might require some familiarity with images, and how to convert images between different types of representations. As well as some familiarity with multi threading and thread safety.

You might also want to build a system that tries to be smart with what images it loads. I.e. prioritize loading images that the user can see. You might also want to keep a cache of full resolution images that the user is likely to view soon.

CodePudding user response:

I think @JonasH has provide a very great answer. In addition to his suggestions. I'd like to share a tool which might be helpful to you.

If you are not showing all the image at one time, you could take a look at the Incremental Loading Collection Helpers from the Community Toolkit. This helper could help you to implement incrementally loading when user scrolls the GridView. So you could only load parts of the images at the begining and load more when users scroll the view.

The document contains a sample demo about how to use it. What you need to do is implement the IIncrementalSource interface for your source and load more items when you need.

  • Related