我在 asm 中过滤图像,并使用 WPF C# 作为 GUI。当我有过滤数据并且想将其转换为位图时,我在这行 Marshal.Copy(imageBytesPtr, imageBytes, 0, imageWidth * imageHeight * 4); 中遇到内存访问错误最奇怪的事情是随机发生的,有时我在 1,2,3,4,5 个线程上过滤图像,它会压垮 6 个线程,但是当我再次运行程序并在第 6 个线程上启动应用程序时,一切都很好
这是完整的代码:
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Diagnostics;
using System.Threading;
using System.Windows.Ink;
using System.Security.Permissions;
using System.Windows.Markup;
using System.ComponentModel;
using System.Reflection;
using System.Linq;
/*
* POMYSLY
* -narzie zmienic tak zeby asm sie dodawal ten offset i zobaczymy co sie stanie
*/
namespace LaplaceFilter
{
public partial class MainWindow : Window
{
public CroppedBitmap finalBitmap;
public List<byte> processedImage;
public int imageWidth;
public int imageHeight;
private List<WriteableBitmap> processedImages = new List<WriteableBitmap>();
private BitmapImage sourceImage;
private int parts;
private string selectedFilePath;
private Thread[] ThreadsData;
private byte[] finalImage;
public WriteableBitmap resultBitmap;
public Int32Rect rect;
private static int counter;
private int offset = 0;
[DllImport("C:\\Users\\jakub\\source\\repos\\LaplaceFilter\\x64\\Debug\\laplaceCpp.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void FiltrImage(IntPtr arrayPtr, int size, int width, int height, int bytesPerPixel,int start, IntPtr resultArray);
[DllImport("C:\\Users\\jakub\\source\\repos\\LaplaceFilter\\x64\\Debug\\LaplaceAsm.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern void LaplaceApply(IntPtr arrayPtr, int width, int height, IntPtr resultArray,int shift);
public MainWindow()
{
InitializeComponent();
}
private void SelectFile_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog
{
Title = "Wybierz plik graficzny",
Filter = "Pliki graficzne (*.png, *.jpg)|*.png;*.jpg",
};
if (openFileDialog.ShowDialog() == true)
{
selectedFilePath = openFileDialog.FileName;
sourceImage = new BitmapImage(new Uri(selectedFilePath));
EntryImage.Source = sourceImage;
filtr.IsEnabled = true;
ExecutingTime.Content = "Czas wykonywania algorytmu: ";
}
}
private void Filtr_Click(object sender, RoutedEventArgs e)
{
parts = (int)MySlider.Value;
ThreadsData = new Thread[parts];
splitData(sourceImage, parts);
counter = 0;
filtr.IsEnabled = false;
}
private void splitData(BitmapImage sourceImage, int parts)
{
int sourceWidth = (int)sourceImage.PixelWidth;
int sourceHeight = (int)sourceImage.PixelHeight;
int partHeight = sourceHeight / parts;
imageHeight = sourceHeight;
imageWidth = sourceWidth;
byte[] bitmapData = new byte[sourceHeight * sourceWidth * 4];
byte[] AsmResult = new byte[sourceHeight * sourceWidth * 4];
IntPtr AsmResultPtr = Marshal.UnsafeAddrOfPinnedArrayElement(AsmResult, 0);
CroppedBitmap croppedBitmap = new CroppedBitmap(sourceImage, new Int32Rect(0, 0, sourceWidth, sourceHeight));
int stride = croppedBitmap.PixelWidth * 4; // 4 bajty na piksel (RGBA)
croppedBitmap.CopyPixels(bitmapData, stride, 0);
IntPtr bitmapDataPtr = Marshal.UnsafeAddrOfPinnedArrayElement(bitmapData, 0);
int[] partsArray = new int[parts];
for (int i = 0; i < parts; i++)
{
if (i == parts - 1)
{
partsArray[i] = sourceHeight - (i * (int)(sourceHeight / parts));
}
else
{
partsArray[i] = (int)(sourceHeight / parts);
}
}
int startY = 0;
for (int i = 0; i < parts; i++)
{
int tempPartHeight = partsArray[i];
if (parts == 1)
{
tempPartHeight = partsArray[i];
}
if (i == 0)
{
startY = 0;
}
else if (i == parts - 1)
{
startY = sourceHeight - tempPartHeight;
}
else
{
startY += partsArray[i];
}
int o = startY * imageWidth * 4 ;
int index = i;
if (CPlusPlusRadioButton.IsChecked == true)
{
ThreadsData[index] = new Thread(() => ProcessImagesLaPlaceCpp(bitmapDataPtr, sourceWidth, tempPartHeight, o, AsmResultPtr, i, parts));
}
else if (ASMRadioButton.IsChecked == true)
{
ThreadsData[index] = new Thread(() => ProcessImagesLaPlace(bitmapDataPtr, sourceWidth, tempPartHeight, o, AsmResultPtr, i, parts));
}
else
{
if (i == 0)
{
MessageBox.Show("Wybierz filtr");
}
ThreadsData[index] = new Thread(() => Thread.Sleep(1));
}
int a = 1;
}
for (int i = 0; i < parts; i++)
{
int index = i;
ThreadsData[index].Start();
//Thread.Sleep(100);
}
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < parts; i++)
{
int index = i;
ThreadsData[index].Join();
}
stopwatch.Stop();
long elapsedTicks = stopwatch.ElapsedTicks;
double elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
TimeSpan elapsed = stopwatch.Elapsed;
ExecutingTime.Content = $"Czas wykonywania algorytmu: {elapsedTicks} ";
}
private readonly object lockObject = new object();
private void ProcessImagesLaPlace(IntPtr bitmapDataPtr, int width, int height, int startY, IntPtr AsmResultPtr, int i, int parts)
{
if (width > 0 && height > 0)
{
// int stride = width * 4;
lock (lockObject)
{
Dispatcher.InvokeAsync(() =>
{
height--;
counter++;
LaplaceApply(bitmapDataPtr, width * 4, height, AsmResultPtr, startY);
if (counter == parts)
{
DisplayLastProcessedImage(AsmResultPtr);
}
filtr.IsEnabled = true;
}, DispatcherPriority.Background);
}
}
else
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("Obrazek jest pusty.");
});
}
}
private void ProcessImagesLaPlaceCpp(IntPtr bitmapDataPtr, int width, int height, int startY, IntPtr AsmResultPtr, int i, int parts)
{
if (width > 0 && height > 0)
{
// int stride = width * 4;
Dispatcher.InvokeAsync(() =>
{
counter++;
FiltrImage(bitmapDataPtr, width * height, width * 4, height, 3, startY, AsmResultPtr);
if (counter == parts)
{
DisplayLastProcessedImage(AsmResultPtr);
}
filtr.IsEnabled = true;
}, DispatcherPriority.Background);
}
else
{
Dispatcher.Invoke(() =>
{
MessageBox.Show("Obrazek jest pusty.");
});
}
}
private void DisplayLastProcessedImage(IntPtr imageBytesPtr)
{
Dispatcher.InvokeAsync(() =>
{
resultBitmap = new WriteableBitmap(imageWidth, imageHeight, 96, 96, PixelFormats.Bgr32, null);
rect = new Int32Rect(0, 0, imageWidth, imageHeight);
byte[] imageBytes = new byte[imageWidth * imageHeight * 4];
Marshal.Copy(imageBytesPtr, imageBytes, 0, imageWidth * imageHeight * 4);
resultBitmap.WritePixels(rect, imageBytes, imageWidth * 4, 0);
FinalImage.Source = resultBitmap;
}, DispatcherPriority.Background);
}
}
}
我尝试放入原始位图(未过滤),但出现了一些问题。我尝试在线程上运行它,但是当我单击多次来过滤图像时,它又发生了
尚不清楚到底是什么导致了您所观察到的错误。
但是,您的代码似乎太复杂了。根据我对这个问题的评论,我想展示一个简单的程序,它可以异步更新 WriteableBitmap 的
BackBuffer
部分,而无需显式使用线程或制作原始图像数据的许多副本。
代码运行一定数量的
Task
,每个BackBuffer
中的像素变亮或变暗。您可以用拉普拉斯滤波器方法替换 WritePart
方法的实现。
public MainWindow()
{
InitializeComponent();
Loaded += OnWindowLoaded;
}
private async void OnWindowLoaded(object sender, RoutedEventArgs e)
{
var source = new BitmapImage(new Uri(...)); // you source image
var bitmap = new WriteableBitmap(source);
bitmap.Lock();
var buffer = bitmap.BackBuffer;
var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var parts = 6;
var partHeight = height / parts;
var tasks = new Task[parts];
for (var i = 0; i < parts; i++)
{
var y = i * partHeight;
if (i == parts - 1)
{
partHeight = height - y;
}
tasks[i] = Task.Run(() => WritePart(buffer, width, y, partHeight));
}
await Task.WhenAll(tasks);
bitmap.AddDirtyRect(new Int32Rect(0, 0, width, height));
bitmap.Unlock();
image.Source = bitmap; // <Image x:Name="image"/> in XAML
}
private static readonly Random random = new Random();
private static void WritePart(IntPtr buffer, int width, int y, int height)
{
var f = 0.5 + random.NextDouble(); // 0.5 .. 1.5
unsafe
{
var stride = 4 * width;
var buf = (byte*)buffer.ToPointer() + stride * y;
for (var i = 0; i < stride * height; i += 4)
{
buf[i] = Math.Min((byte)0xFF, (byte)(f * buf[i]));
buf[i + 1] = Math.Min((byte)0xFF, (byte)(f * buf[i + 1]));
buf[i + 2] = Math.Min((byte)0xFF, (byte)(f * buf[i + 2]));
}
}
}