Skip to content

Allow BitmapSource to invalidate its image cache ptr #39

@dotMorten

Description

@dotMorten

The BitmapImage is rather nice, in that it doesn’t start downloading from the provided URI until it’s being rendering, which is great for efficiency.
The problem though is that if that URI requires some sort of specialized authentication, it doesn’t work.

So naturally I set out to create a more custom BitmapSource that mimics what BitmapImage does but uses a different mechanism to download the bytes. The problem however is that it isn’t working. There’s an internal Boolean ‘_needsUpdate’ inside BitmapSource that prevents the update after the first (empty) render, and no public method to flip the switch back. You can see this behavior in reference source where _convertedDUCEPtr doesn’t get cleared out:
https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Media/Imaging/BitmapSource.cs,1065
https://referencesource.microsoft.com/#PresentationCore/Core/CSharp/System/Windows/Media/Imaging/BitmapSource.cs,1701

So my proposal is to add a protected void Invalidate() method to BitmapSource that does exactly that.

Here’s an example of a reproducer image source that mimics updating the source at any point (in this case manually by calling Refresh() and generating random pixels):

    public class RandomizedImageSource : BitmapSource
    {
        private Random R = new Random();

        public RandomizedImageSource(int width, int height)
        {
            PixelWidth = width;
            PixelHeight = height;
        }

        public void Refresh()
        {
            //Flip the _needsUpdate flag to true.
            //If we don't do this, the cached bitmap would be used and the image won't update
            var field = typeof(BitmapSource).GetField("_needsUpdate", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            field?.SetValue(this, true);
            //Trigger a re-render by calling this which will trigger the "FireChanged" event on the freezable
            WritePostscript();
        }

        protected override Freezable CreateInstanceCore() => new RandomizedImageSource(PixelWidth, PixelHeight);        
        public override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset) => R.NextBytes((byte[])pixels);
        public override bool IsDownloading => false;
        public override PixelFormat Format => PixelFormats.Bgra32;
        public override int PixelWidth { get; }
        public override int PixelHeight { get; }
        public override double DpiX => 96;
        public override double DpiY => 96;
        public override BitmapPalette Palette => null;
        public override event EventHandler<ExceptionEventArgs> DecodeFailed;
    }

Note the reflection code - It just doesn’t work without flipping that flag.

An alternative solution would be to just flip the flag in the OnChanged method which gets called when calling WritePostscript() which in turn calls the internal FireChanged method:

        protected override void OnChanged()
        {
            var field = typeof(BitmapSource).GetField("_needsUpdate", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            field?.SetValue(this, true);
            base.OnChanged();
        }

The benefit of this approach would be no new API, but it could affect existing behavior, and anyone forgetting to call base.OnChanged would not get this benefit.

There’s at least a few other use-cases you could use this for, like for example auto-refreshing, placeholder image while downloading etc.

I'm up for providing the PR for this, but would appreciate some guidance which of the two proposed methods should be used (the second one doesn't require an API change, but on the other hand might be a behavior change)

In addition since DownloadProgress and DownloadFailed is overrideable, we need public constructors for DownloadProgressEventArgs and ExceptionEventArgs, or there's no point in overriding them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    API suggestionEarly API idea and discussion, it is NOT ready for implementation

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions