我有一个简单的方法,应该使用本机代码模糊图像:
internal override void Apply(BitmapBuffer framebuffer,
BitmapBuffer backBuffer,
BitmapBuffer frontBuffer,
BitmapBufferRepository repository)
{
var data = framebuffer.Bitmap.LockBits(
new System.Drawing.Rectangle(0, 0, framebuffer.Bitmap.Width, framebuffer.Bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format32bppArgb);
ImageProcessing.GaussianBlur(data.Scan0,
data.Stride,
data.Width,
data.Height,
Radius);
framebuffer.Bitmap.UnlockBits(data);
}
GaussianBlur方法定义如下:
[DllImport("Animator.Engine.Native.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void GaussianBlur(IntPtr bitmapData,
int stride,
int width,
int height,
int radius);
在 C++ 中:
extern "C" void __cdecl GaussianBlur(unsigned char* bitmapData,
int stride,
int width,
int height,
int radius)
{
// Gaussian kernel
int diameter = 2 * radius + 1;
std::shared_ptr<float[]> kernel = generateGaussKernel(diameter);
// Blur
auto copy = std::shared_ptr<unsigned char[]>(new unsigned char[height * stride]);
for (int y = 0; y < height; y++)
memcpy(copy.get() + y * stride, bitmapData + y * stride, width * BYTES_PER_PIXEL);
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
FloatColor sum;
float weight = 0.0f;
int count = 0;
int xStart = x - radius;
int xEnd = x + radius;
int yStart = y - radius;
int yEnd = y + radius;
for (int x1 = xStart; x1 <= xEnd; x1++)
for (int y1 = yStart; y1 <= yEnd; y1++)
{
// Find weight
int kernelX = x1 - xStart;
int kernelY = y1 - yStart;
float kernelValue = kernel[kernelY * diameter + kernelX];
// Premultiply alpha
FloatColor color;
if (x1 >= 0 && x1 < width && y1 >= 0 && y1 < height)
color = getFloatColor(copy.get(), stride, x1, y1);
else
color = FloatColor(0);
sum.R += (float)(color.R * color.A) * kernelValue;
sum.G += (float)(color.G * color.A) * kernelValue;
sum.B += (float)(color.B * color.A) * kernelValue;
sum.A += color.A * kernelValue;
weight += kernelValue;
count++;
}
if (count > 0)
{
FloatColor result;
result.A = sum.A / weight;
if (result.A > 0)
{
result.R = ((sum.R / weight) / result.A);
result.G = ((sum.G / weight) / result.A);
result.B = ((sum.B / weight) / result.A);
}
setFloatColor(bitmapData, stride, x, y, result);
}
}
}
当我的应用程序在多线程环境中运行时(
Parallel.For
),尝试调用GaussianBlur
会导致“调试断言失败”错误:
---------------------------
Microsoft Visual C++ Runtime Library
---------------------------
Debug Assertion Failed!
Program: ...nimator\Animator\bin\x64\Debug\net6.0-windows7.0\Animator.exe
File: minkernel\crts\ucrt\src\appcrt\heap\debug_heap.cpp
Line: 904
Expression: _CrtIsValidHeapPointer(block)
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
(Press Retry to debug the application)
---------------------------
Przerwij Ponów próbę Ignoruj
---------------------------
事实证明,在 P/Invoke 实际完成之前断言就失败了(本机函数中第一行的断点从未命中)。
我做错了什么?
编辑:我刚刚检查过,当第二个线程尝试调用该函数时,断言失败(有时)会发生。
顺便说一句,我的 C++ 代码中的所有函数都是线程安全实现的,特别是除了通过参数传递的资源之外,它们都没有使用任何外部资源。
事实证明,我对线程安全实现的函数撒了谎。导致问题的两个函数都使用了函数“generateGaussKernel”,该函数不是线程安全的。我用以下方法修复了它:
std::shared_ptr<float[]> generateGaussKernel(int diameter)
{
// *** HERE ***
EnterCriticalSection(&gaussKernelCriticalSection);
while (gaussKernels.size() <= diameter)
gaussKernels.push_back(std::shared_ptr<float[]>(nullptr));
if (gaussKernels[diameter].get() == nullptr)
{
float sigma = diameter / 4.0f;
std::shared_ptr<float[]> kernel(new float[diameter * diameter]);
int mean = diameter / 2;
float sum = 0.0; // For accumulating the kernel values
for (int x = 0; x < diameter; ++x)
for (int y = 0; y < diameter; ++y) {
kernel[y * diameter + x] = (float)(exp(-0.5 * (pow((x - mean) / sigma, 2.0) + pow((y - mean) / sigma, 2.0))) / (2 * M_PI * sigma * sigma));
// Accumulate the kernel values
sum += kernel[y * diameter + x];
}
// Normalize the kernel
for (int x = 0; x < diameter; ++x)
for (int y = 0; y < diameter; ++y)
kernel[y * diameter + x] /= sum;
gaussKernels[diameter] = std::shared_ptr<float[]>(kernel);
}
// *** AND HERE ***
LeaveCriticalSection(&gaussKernelCriticalSection);
return gaussKernels[diameter];
}
显然还需要额外调用
InitializeCriticalSection()
和 DeleteCriticalSection()
。
现在错误已经消失了。