-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
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.