dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.04k stars 1.17k forks source link

Cannot freeze color palette based BitmapSource image #4393

Open SetTrend opened 3 years ago

SetTrend commented 3 years ago

Problem description:

I'm not able to freeze a color palette based BitmapSource image.

So, it's impossible to create a color palette based BitmapSource image asynchronously and yield to WPF UI thread. (See this issue on StackOverflow.)

Actual behavior:

BitmapSource.CanFreeze always yields false when the image assigned is using a color palette.

Expected behavior:

BitmapSource.CanFreeze should return true, even with bitmaps using a color palette.

A color palette is a DispatcherObject that cannot be transferred between threads, cloned or anything else. So, there is no way to workaround this situation.

Minimal repro:

internal static Task<BitmapSource> GetImageAsync()
{
  return Task.Run<BitmapSource>(() =>
  {
    BitmapImage bi = new BitmapImage();

    bi.BeginInit();
    bi.UriSource = new Uri(@"test.jpg");
    bi.DecodePixelWidth = 16;
    bi.EndInit();

    FormatConvertedBitmap fcb = new FormatConvertedBitmap(bi, PixelFormats.Indexed2, new BitmapPalette(bi, 4), 1);

    // Required for the UI thread to be able to use the bitmap.
    // However, fcb.CanFreeze is false, though.
    fcb.Freeze();

    return fcb;
  });
}


WPF tracing returns the following warning message:

System.Windows.Freezable Warning:
  2 : CanFreeze is returning false because a DependencyProperty
      on the Freezable has a value that is a DispatcherObject
      with thread affinity
SetTrend commented 3 years ago

I found a workaround now (although I'm not sure why this works):

If I create a new BitmapFrame from the FormatConvertedBitmap, I seem to be able to freeze that new frame, even though it has been created by the same thread the FormatConvertedBitmap has been created with:

internal static Task<BitmapSource> GetImageAsync()
{
  return Task.Run<BitmapSource>(() =>
  {
    BitmapImage bi = new BitmapImage();

    bi.BeginInit();
    bi.UriSource = new Uri(@"test.jpg");
    bi.DecodePixelWidth = 16;
    bi.EndInit();

    FormatConvertedBitmap fcb = new FormatConvertedBitmap(bi, PixelFormats.Indexed2, new BitmapPalette(bi, 4), 1);

    // Required for the UI thread to be able to use the bitmap.
    BitmapSource bf = BitmapFrame.Create(fcb);
    bf.Freeze();

    return bf;
  });
}
SetTrend commented 1 year ago

Did anyone have the time to investigate on this issue?

miloush commented 1 year ago

The warning tells you why the workaround works, the palette is a dependency property on FormatConvertedBitmap but not on BitmapSource. So what you cannot freeze is in fact specifically FormatConvertedBitmap.

I guess we could make BitmapPalette freezable.