如何将调色板图像从另一个线程生成到 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#) 我无法从另一个线程获取更改背景的按钮
如何在不同线程的 C++/CLI 中将图像数据从 BitmapSource (WPF) 复制到 cv::Mat (OpenCV)?
在具有 ui/非 ui 线程差异的 WPF 中使用 PInvoke