首页 > 解决方案 > 如何将调色板图像从另一个线程生成到 WPF UI 线程?

问题描述

我想异步创建/操作基于调色板的图像并将该图像生成到 WPF UI 线程。

为了从另一个线程向 UI 线程产生一个可冻结的对象,需要冻结该对象。

但是,当它基于调色板时,我无法冻结图像。BitmapPalette派生自,DispatcherObject所以我不能冻结它。

如何将调色板图像从另一个线程生成到 WPF UI 线程?


这是示例代码:

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;
  });
}

...这是我得到的警告(错误):

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

@克莱门斯:

这是我想出的解决方法。它非常类似于您的解决方案。不过我省略了复制。

private void CopyBitmapSourceToUi(BitmapSource image)
{
  BitmapSource uiSource;

  uiSource = BitmapFrame.Create(image);
  uiSource.Freeze();  // locks bitmap and enables access by UI thread

  Dispatcher.Invoke(() => Source = uiSource);
  Thread.Sleep(10);   // WPF requires a short while to render the picture. During that period, you cannot create a WritableBitmap from the source image. So I added a minor delay.
}

但是,使用我的解决方案,当 WPF 呈现图像时,我似乎无法从源创建 WritableBitmap(请参阅上面的评论)。

标签: c#wpfmultithreading

解决方案


您可以创建调色板位图的副本,如下所示。

为简单起见,它使用Indexed8代替Indexed2,以便源像素缓冲区中的每个字节都包含一个调色板索引。

目标位图使用该Rgb24格式,因此源缓冲区的每个字节都映射到目标缓冲区的 3 个字节。您也可以使用Bgr24,但您必须更改 for 循环中的索引。

var palette = new BitmapPalette(bi, 4);
var fcb = new FormatConvertedBitmap(bi, PixelFormats.Indexed8, palette, 1);

var sourceBuffer = new byte[fcb.PixelWidth * fcb.PixelHeight];
var targetBuffer = new byte[3 * sourceBuffer.Length];

fcb.CopyPixels(sourceBuffer, fcb.PixelWidth, 0);

for (int i = 0; i < sourceBuffer.Length; i++)
{
    var color = palette.Colors[sourceBuffer[i]];

    targetBuffer[3 * i + 0] = color.R;
    targetBuffer[3 * i + 1] = color.G;
    targetBuffer[3 * i + 2] = color.B;
}

var bitmap = BitmapSource.Create(
    fcb.PixelWidth, fcb.PixelHeight,
    fcb.DpiX, fcb.DpiY,
    PixelFormats.Rgb24, null,
    targetBuffer, 3 * fcb.PixelWidth);

bitmap.Freeze();

推荐阅读