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

Posted

技术标签:

【中文标题】如何将调色板图像从另一个线程生成到 WPF UI 线程?【英文标题】:How can I yield a palette image from another thread to WPF UI thread? 【发布时间】:2021-07-05 06:48:39 【问题描述】:

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

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

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

这是示例代码:

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(请参阅上面的评论)。

【问题讨论】:

似乎无法使用非空 DestinationPalette 冻结 FormatConvertedBitmap。你在这里的实际目标是什么,只是减少颜色的数量? 我正在写一个图像识别程序。这只是能够重现问题的最小样本。我在 BitmapPalette 构造函数中提供了专用颜色,以便能够在图像中搜索这些颜色。 【参考方案1】:

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

为简单起见,它使用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();

【讨论】:

您确定您指的是BitmapSource.Create() 而不是BitmapFrame.Create()?这实际上也是我想出的解决方法。不过,我省略了复制。 不幸的是,我无法在评论中添加代码示例,所以我将我的版本添加到上面的问题中。 啊,是的,对...我对您的解决方案有这个问题:使用您的这个解决方案,我需要深入了解图像格式。但是,我需要异步创建任意图像,然后将结果复制到Image 控件。我相信您的解决方案需要太多开销才能普遍适用于各种图像格式。 请原谅我一开始就没有明确我的问题的一般方面。 我不这么认为。使用调色位图是您的想法,因此您可以控制第一种中间格式,即 Indexed8。第二个 Rgb24 或 Brg24 是固定的,即始终相同。两者都与原始图像的像素格式无关。

以上是关于如何将调色板图像从另一个线程生成到 WPF UI 线程?的主要内容,如果未能解决你的问题,请参考以下文章

.NET WPF (C#) 我无法从另一个线程获取更改背景的按钮

Gtkmm:如何从另一个线程更新 UI?连续不断

如何在不同线程的 C++/CLI 中将图像数据从 BitmapSource (WPF) 复制到 cv::Mat (OpenCV)?

在具有 ui/非 ui 线程差异的 WPF 中使用 PInvoke

Java:如何从另一个线程启动 UI 对话框,例如对于身份验证器

如何从另一个类调用viewmodel方法