I created a form in C# where there is a image which is being converted into a boolean array. I am trying to generate a thread with a timer where the image is converted 4 times a second.
When I debug it, it works, I can trace the ticks from the timer. But when the form is running the application quits without giving a bug.
This is the initialize script:
form = new LoadForm();
form.Show();
form.BringToFront();
timer = new System.Threading.Timer(new TimerCallback(camTick), null, 0, 250);
This is the tick that works:
private void camTick(Object myObject)
{
if (form.isRunning)
{
bool[,] ar = form.getBoolBitmap(100);
}
}
This is the function that loads and saves the bitmap. In form1.cs
public bool[,] getBoolBitmap(uint threshold) {
unsafe {
Bitmap b = getBitmap();
BitmapData originalData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
bool[,] ar = new bool[b.Width, b.Height];
for (int y = 0; y < b.Height; y++) {
byte* Row = (byte*)originalData.Scan0 + (y * originalData.Stride);
for (int x = 0; x < b.Width; x++) {
if ((byte)Row[x * 3] < threshold) {
ar[x, y] = false;
} else {
ar[x, y] = true;
}
}
}
b.Dispose();
return ar;
}
}
public Bitmap getBitmap() {
if (!panelVideoPreview.IsDisposed) {
Bitmap b = new Bitmap(panelVideoPreview.Width, panelVideoPreview.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(b)) {
Rectangle rectanglePanelVideoPreview = panelVideoPreview.Bounds;
Point sourcePoints = panelVideoPreview.PointToScreen(new Point(panelVideoPreview.ClientRectangle.X, panelVideoPreview.ClientRectangle.Y));
g.CopyFromScreen(sourcePoints, Point.Empty, rectanglePanelVideoPreview.Size);
}
return b;
} else {
Bitmap b = new Bitmap(panelVideoPreview.Width, panelVideoPreview.Height);
return b;
}
}
Is this image being stored in a Control like PictureBox? If so, make sure you're only accessing it on the control's thread. Check out Control.Invoke().
The callback from System.Threading.Timer runs on a ThreadPool thread. In that callback you are accessing a Form instance. You simply cannot do this. Well, you can, but it will not work correctly. It will fail unpredictable and sometimes spectacularly.
Use System.Windows.Forms.Timer instead. Have it tick every 4 seconds. In the Tick event get whatever data you need from the form and then pass it off to another thread for further processing.
In the following code I get the Bitmap object by calling DrawToBitmap on the UI thread. I then pass the Bitmap off to a Task where it can be converted into a bool[] in the background. Optionally, you can return that bool[] from the Task and then call ContinueWith to transfer it back to the UI thread if necessary (sounds like you probably do not need to do that though).
private void YourWindowsFormsTimer_Tick(object sender, EventArgs args)
{
// Get the bitmap object while still on the UI thread.
var bm = new Bitmap(panelVideoPreview.ClientSize.Width, panelVideoPreview.ClientSize.Height, PixelFormat.Format24bppRgb);
panelVideoPreview.DrawToBitmap(bm, panelVideoPreview.ClientRectangle);
Task.Factory
.StartNew(() =>
{
// It is not safe to access the UI here.
bool[,] ar = ConvertBitmapToByteArray(bm);
DoSomethingWithTheArrayHereIfNecessary(ar);
// Optionally return the byte array if you need to transfer it to the UI thread.
return ar;
})
.ContinueWith((task) =>
{
// It is safe to access the UI here.
// Get the returned byte array.
bool[,] ar = task.Result;
UpdateSomeControlIfNecessaryHere(ar);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
And ConvertBitmapToByteArray would look like this now.
public unsafe bool[,] ConvertBitmapToBoolArray(Bitmap b, uint threshold)
{
BitmapData originalData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
bool[,] ar = new bool[b.Width, b.Height];
for (int y = 0; y < b.Height; y++)
{
byte* Row = (byte*)originalData.Scan0 + (y * originalData.Stride);
for (int x = 0; x < b.Width; x++)
{
if ((byte)Row[x * 3] < treshold)
{
ar[x, y] = false;
} else
{
ar[x, y] = true;
}
}
}
b.Dispose();
return ar;
}
After some searching on the internet I found this out, I needed to make another function to return the bool[,], this is what I came up with:
public bool[,] invokeBitmap(uint treshold) {
if (this.InvokeRequired) {
return (bool[,])this.Invoke((Func<bool[,]>)delegate {
return getBoolBitmap(treshold);
});
} else {
return getBoolBitmap(treshold);
}
}
Related
I'm receiving two errors:
'Bitmap ImageManipulation.LockBits(object)' has the wrong return type
'Bitmap ImageManipulation.GetSet(object)' has the wrong return type
And they point me to the problem as shown:
How can I change public Bitmap LockBits and GetSet to have the correct return type?
Form1.cs
Bitmap newFile;
Bitmap[,] imgArray;
Bitmap[,] modimgArray;
ImageManipulation modifyRGB = new ImageManipulation();
FileOperations getFile = new FileOperations();
private void btnLockBits_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(new ParameterizedThreadStart(modifyRGB.LockBits));
t1.Start(newFile);
}
private void btnGetSet_Click(object sender, EventArgs e)
{
#region Call ImageManipulation Class
// Set up thread(s) to run "Manipulatie" method
//Use parameterized thread start
//**********************************************
// NOTE: method "manipulate" needs to accept type "object", not Bitmap in order
// for it to work with parameterized thread start
// **********************************************
Thread t1 = new Thread(new ParameterizedThreadStart(modifyRGB.GetSet));
t1.Start(newFile);
#endregion
//modifyRGB.Manipulate(newFile);
}
ImageManipulation.cs
public class ImageEventArgs : EventArgs
{
public Bitmap bmap { get; set; }
}
public Bitmap GetSet(object bmp)
{
Bitmap bmap = (Bitmap)bmp;
Color theColor = new Color();
for (int i = 0; i < bmap.Width; i++)
{
for (int j = 0; j < bmap.Height; j++)
{
// Get the color of the pixel at (i, j)
theColor = bmap.GetPixel(i, j);
// Change the color at that pixel; DivideByZeroException out the green and blue
Color newColor = Color.FromArgb(theColor.R, theColor.G, 0);
// Set the new color of the pixel
bmap.SetPixel(i, j, newColor);
}
}
OnImageFinished(bmap);
return bmap;
}
public Bitmap LockBits(object bmp)
{
Bitmap bmap = (Bitmap)bmp;
// Use "unsafe" becausae c# doesnt support pointer arithmetic by default
unsafe
{
// Lock the bitmap into system memory
// "Pixelformat" can be "Fomat24bppRgb", "Format32bppArgb", etc.
BitmapData bitmapData =
bmap.LockBits(new Rectangle(0, 0, bmap.Width,
bmap.Height), ImageLockMode.ReadWrite, bmap.PixelFormat);
//Define variables for bytes per pixel, as well as Image Width & Height
int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(bmap.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
//Define a pointer to the first pixel in the locked image
// Scan0 gets or sets the address of the first pixel data in the bitmap
// This can also be thought of as the first scan line in the bitmap.
byte* PtrFirstPixel = (byte*)bitmapData.Scan0;
// Step through each pixel in the image using pointers
// Parallel.For executes a 'for' loop in which iterations
// may run in parralel
Parallel.For(0, heightInPixels, y =>
{
// USe the 'Stride' (scanline width) proerty to step line by line thru the image
byte* currentLine = PtrFirstPixel + (y * bitmapData.Stride);
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
{
// GET: each pixel color (R, G, & B)
int oldBlue = currentLine[x];
int oldGreen = currentLine[x + 1];
int oldRed = currentLine[x + 2];
// SET: Zero out the Blue, copy Green and Red unchanged
currentLine[x] = 0;
currentLine[x + 1] = (byte)oldGreen;
currentLine[x + 2] = (byte)oldRed;
}
});
bmap.UnlockBits(bitmapData);
}
OnImageFinished(bmap);
return bmap;
}
You can not return anything from a thread start (only void)
public Bitmap GetSet(object bmp) ...
public Bitmap LockBits(object bmp) ...
These methods can not be used for a ParameterizedThreadStart
You can Invoke Methods on The Main Thread
Put something like this in your Method
Invoke((MethodInvoker)(() => THIS WILL RUN ON MAIN THREAD));
I am attempting to draw about 3600 points on a form, it is pretty slow using one thread so I decided I want to use 4 threads for it.
In my code I divide the 3600 points to the 4 threads and they are supposed to draw it. however for some reason an ArgumentOutOfRangeException is being thrown.
I tried to debug my code but I couldn't find the mistake.
here is the code :
(Ignore the class _3DPoint, it is just a point that has x,y,z values. when I draw them I only use the x,y values.)
code for drawing the points :
public Graphics g; //g = this.CreateGraphics() in form1.Load()
public void drawrectangle(_3DPoint)
float xCord = float.Parse(p.x.ToString());
float yCord = float.Parse(p.y.ToString());
Brush b = new SolidBrush(Color.White);
xCord = lsize * xCord + center.X;
yCord = lsize * yCord + 10 + center.Y;
g.FillRectangle(b, xCord, yCord, 2, 2);
}
lsize, center are just variables for aligning the points as I want them.
All of the multithread action code:
public List<_3DPoint[]> multiThreadsdata = new List<_3DPoint[]>();
public void handlemultithread(_3DPoint[] P)
{
g.Clear(Color.Black);
for (int i = 0; i < multiThreads.Length; i++)
{
multiThreadsdata.Add(new _3DPoint[P.Length / multiThreads.Length]);
}
for (int i = 0; i < multiThreads.Length; i++)
{
for (int j = (P.Length / multiThreads.Length) * (i); j < (P.Length / multiThreads.Length) * (i + 1); j++)
{
multiThreadsdata[i][j - ((P.Length / multiThreads.Length) * i)] = new _3DPoint(P[j]);
}
}
for (int i = 0; i < multiThreads.Length; i++)
{
multiThreads[i] = new Thread(() => drawPoints(multiThreadsdata[i]));
multiThreads[i].Start();
}
}
delegate void SetCallBackPoint(_3DPoint location);
public void drawPoints(_3DPoint[] locations)
{
for (int i = 0; i < locations.Length; i++)
{
if (this.InvokeRequired)
{
SetCallBackPoint e = new SetCallBackPoint(drawrectangle);
this.Invoke(e, new object[] { locations[i] });
}
else
{
drawrectangle(locations[i]);
}
}
}
P is a _3DPoint array that contains all the 3600 points.
mutliThreads is a Thread[] containing 4 threads.
I get the exception in handlemultithread method. in the third line of this for loop :
for (int i = 0; i < multiThreads.Length; i++)
{
multiThreads[i] = new Thread(() => drawPoints(multiThreadsdata[i])); // <- here.
multiThreads[i].Start();
}
I don't know what is the problem, my guess is that there is some problem with the multithreading because I'm just a beginner with multithreading.
Thanks a bunch.
It is entirely possible to Draw 3600 rectangles quickly on a form when you apply the suggestions in the comments.
If that doesn't give you enough time you can consider creating Images on a single background thread, store them in some sort of buffer until they are needed to e painted on the Graphics object of the Paint event of the form. That is only feasible if you can know upfront what needs to be painted on the next frame.
This example uses a simple Background worker to fill an ConcurrentQueue with images. The comments in the code explain what is going on.
public partial class Form1 : Form
{
static ConcurrentQueue<Image> buffer = new ConcurrentQueue<Image>();
static Random r = new Random();
public Form1()
{
InitializeComponent();
backgroundWorker1.RunWorkerAsync();
// this is already a great performance win ...
DoubleBuffered = true;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Image img =null;
// get from buffer ..
if (!buffer.TryDequeue(out img))
{
// nothing available
// direct random
for (var x = 0; x < e.ClipRectangle.Width; x++)
{
for (var y = 0; y < e.ClipRectangle.Height; y++)
{
using (var pen = new Pen(new SolidBrush(Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)))))
{
e.Graphics.DrawRectangle(pen, x, y, 1, 1);
}
}
}
}
else
{
// otherwise Draw the prepared image
e.Graphics.DrawImage(img,0,0);
Trace.WriteLine(buffer.Count);
img.Dispose();
}
}
private void button1_Click(object sender, EventArgs e)
{
// force a repaint of the Form
Invalidate();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// as long as the form is not disposed
while (!IsDisposed)
{
// we keep 60 images in memory
if (buffer.Count < 60)
{
// bitmap
var bmp = new Bitmap(this.Width, this.Height);
var img = Graphics.FromImage(bmp);
// draw
for (int i = 0; i < 3600; i++)
{
using (var pen = new Pen(new SolidBrush(Color.FromArgb(r.Next(255), r.Next(255), r.Next(255)))))
{
img.DrawRectangle(pen, r.Next(Width),r.Next(Height), r.Next(Width), r.Next(Height));
}
}
// store the drawing in the buffer
buffer.Enqueue(bmp);
}
else
{
// simple and naive way to give other threads a bit of room
Thread.Sleep(0);
}
}
}
}
Keep in mind that when you have a CPU heavy process adding more threads will not by magic make your methods run quicker. You might even make it worse: more threads compete for time on the CPU.
I'm trying to slide through a few images basically you have 2 buttons forward and backwards. They're function is to scroll through a list images. Once one of them reaches the end it must go back to the other side of the list. Here's what I have
private List<Bitmap> RotatePacks = new List<Bitmap> { new Bitmap(#"Assets\All_Cards\All_Royal\All_Royal.png"),
new Bitmap(#"Assets\All_Cards\All_Classic\All_Classic.jpg")};
private void bNext_Click(object sender, EventArgs e)
{
Bitmap currentImage = (Bitmap)pickCards.Image;
for (int i = 0; i < RotatePacks.Count; i++)
{
if (AreEqual(currentImage, RotatePacks[i]))
{
try
{
pickCards.Image = RotatePacks[i + 1];
}
catch (Exception)
{
bPrevious_Click(sender, e);
pickCards.Image = RotatePacks[i - 1];
}
}
}
}
private void bPrevious_Click(object sender, EventArgs e)
{
Bitmap currentImage = (Bitmap)pickCards.Image;
for (int i = 0; i < RotatePacks.Count; i++)
{
if (AreEqual(currentImage, RotatePacks[i]))
{
try
{
pickCards.Image = RotatePacks[i - 1];
}
catch (Exception)
{
bNext_Click(sender, e);
}
}
}
}
Those are the 2 buttons. Here I'm trying to compare the image of the pictureBox that's holding the images with the list RotatePacks. Like this im getting the current image that is being shown. Here's the AreEqual method :
public unsafe static bool AreEqual(Bitmap b1, Bitmap b2) // copy pasted
{
if (b1.Size != b2.Size)
{
return false;
}
if (b1.PixelFormat != b2.PixelFormat)
{
return false;
}
/*if (b1.PixelFormat != PixelFormat.Format32bppArgb)
{
return false;
}*/
Rectangle rect = new Rectangle(0, 0, b1.Width, b1.Height);
BitmapData data1
= b1.LockBits(rect, ImageLockMode.ReadOnly, b1.PixelFormat);
BitmapData data2
= b2.LockBits(rect, ImageLockMode.ReadOnly, b1.PixelFormat);
int* p1 = (int*)data1.Scan0;
int* p2 = (int*)data2.Scan0;
int byteCount = b1.Height * data1.Stride / 4; //only Format32bppArgb
bool result = true;
for (int i = 0; i < byteCount; ++i)
{
if (*p1++ != *p2++)
{
result = false;
break;
}
}
b1.UnlockBits(data1);
b2.UnlockBits(data2);
return result;
}
So now back to my problem the buttons are working just the way i want them but they work just once. If i press button next and than button previous or i press the next button twice the program will crash. It gives me the exception here
BitmapData data2
= b2.LockBits(rect, ImageLockMode.ReadOnly, b1.PixelFormat);
Here's some screenshots of the actual exception :
http://prntscr.com/9ug3nl
http://prntscr.com/9ug3vv
P.S Im using this comparing method but i haven't program it. I copied the code from another StackOverflow question
There seems to be a couple of issues here:
It is worth to have an early check
if (b1 == b2) //put this
return true;
//do something else
Rectangle rect = new Rectangle(0, 0, b1.Width, b1.Height);
//and so on
Likely b1 == b2 will cause that problem
Seems like your LockBits refer to the same exact items (same rect, same size, some mode, same pixel format):
Rectangle rect = new Rectangle(0, 0, b1.Width, b1.Height);
BitmapData data1
= b1.LockBits(rect, ImageLockMode.ReadOnly, b1.PixelFormat);
BitmapData data2
= b2.LockBits(rect, ImageLockMode.ReadOnly, b1.PixelFormat);
This could be another cause of the issue...
Like Ian pointed out you have to check if the images you are trying to compare in AreEqual() method (the current image and the one in Rotatepacks[i]) are identical AND belong to the same instance or not? IF they belong to the same instance it'd obviously generate that exception. Use the .Equals() method to check if the images belong to the same instance.
Consider an image instance
Bitmap img;
The image in img is loaded into the picture box as well as in the RotatePacks. In your code you are trying to lock that same instance twice so it generates an exception
The bitmap lines Ian pointed are ok tho.., 2 different images can have the same size and pixel format.
Also if that really was the case, i don't think you need to compare pixel by pixel. I suppose you have Bitmap instances in Rotatepacks. Just compare your image instance in picture box with the one in Rotatepacks using .Equals()
I have a method in my C# WinForms program, which checks if X column of an image has black pixels or not.
static Boolean GetColumnState(Bitmap bmp, int x, int col)
{
BitmapData pixelData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),ImageLockMode.ReadOnly,PixelFormat.Format32bppArgb);
Boolean state = false;
unsafe
{
int* pData = (int*)pixelData.Scan0.ToPointer();
pData += x;
for (int i = 0; i < bmp.Height; ++i)
{
pData += bmp.Width;
if (Color.FromArgb(*pData) == Color.FromArgb(255, col, col, col)) // error here
{ state = true; break; }
}
}
bmp.UnlockBits(pixelData);
return state;
}
Unfortunatelly I'm getting
"Attempted to read or write protected memory" error
at "(Color.FromArgb(*pData) == Color.FromArgb(255, col, col, col)) " line
Here is how i define "col" from different method:
if (GetColumnState(b1, 0, 255) == false)
{ col = 255; }
else {col = 0;}
// more code + loops
GetColumnState(b1, i, col)
Strange thing is: I'm getting errors only if pixel color is defined as 255 (i.e. black)..
How do you explain this? Please note, that I'm writing an OCR program, for that reason I load multiple dictionaries with different key values. I crop images a lot during OCR.
My lucky guess is that threads are messing up one another.
Now, I've found a way to fix this problem, but the price is + ~150-200ms to total script execution time (which is unnecessary price, I think).
Normally I load dictionaries like this:
Dictionary<string, Bitmap> lookup = new Dictionary<string, Bitmap>();
Bitmap l0 = new Bitmap(#"C:\xxx\0.bmp", true);
//+15 more
lookup.Add("0", l0);
//+15 more
Dictionary<string, Bitmap> lookup2 = new Dictionary<string, Bitmap>();
Bitmap nAa = new Bitmap(#"C:\yyy\Aa.bmp", true);
//+15 more
lookup2.Add("A", nAa);
//+15 more
To fix this problem I have to create "voids" for each dictionary and load them in different threads, like this:
void loadNumbers1()
{
lookup4 = new Dictionary<string, Bitmap>();
Bitmap sd = new Bitmap(#"C:\xxxxx\a.bmp", true);
//+15 more
lookup4.Add("0", s0);
//+15 more
}
void loadNumbers2()// 4, 5, 6,
{
//repeat
}
Now we launch threads:
Thread tNum2= new Thread(new ThreadStart(loadNumbers2));
tNum2.Start();
Thread tNum3= new Thread(new ThreadStart(loadNumbers3));
tNum3.Start();
The last step (without this step program works faster, but error occurs more often):
tNum3.Join();
That's it, now i don't have any problems, but execution time is longer.. Any ideas on how to solve this problem in an easier way, without using multiple threads? In my case, without join() I'm getting 5-100ms Dictionary load time, With join() - up to 300ms. Without threads - up to 180 (in case if error doesn't occur).
Sorry for long post
EDIT: (permanent fix)
static unsafe Boolean GetColumnState(Bitmap bmp, int x, int col)
{
BitmapData pixelData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),ImageLockMode.ReadOnly,PixelFormat.Format32bppArgb);
Boolean state = false;
unsafe
{
byte[] buffer = new byte[pixelData.Height * pixelData.Stride];
Marshal.Copy(pixelData.Scan0, buffer, 0, buffer.Length);
for (int i = 0; i < pixelData.Height - 1; ++i)
{
byte red = buffer[i * pixelData.Stride + 4 * x + 2];
if (red == col)
{ state = true; break; }
}
}
bmp.UnlockBits(pixelData);
return state;
}
I don't understand what was so wrong about pointers, but bytes work great. #tia thanks for pointing out to my problem.
Does anybody know why multithreading slows down dictionary load time instead of speeding it up?
If I understand the code correctly, the loop
for (int i = 0; i < bmp.Height; ++i)
{
pData += bmp.Width;
if (Color.FromArgb(*pData) == Color.FromArgb(255, col, col, col)) // error here
{ state = true; break; }
}
is not correct. The pointer should be incremented at the end of iteration, or else you will skip the first scan line and overflow reading the bitmap buffer.
So I've been sharing some thoughts on the above topic title on my website about fast, unsafe pixel access. A gentlemen gave me a rough example of how he'd do it in C++, but that doesn't help me in C# unless I can interop it, and the interop is fast as well. I had found a class in the internet that was written using MSDN help, to unsafely access pixels. The class is exceptionally fast, but it's not fast enough. Here's the class:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace DCOMProductions.Desktop.ScreenViewer {
public unsafe class UnsafeBitmap {
Bitmap bitmap;
// three elements used for MakeGreyUnsafe
int width;
BitmapData bitmapData = null;
Byte* pBase = null;
public UnsafeBitmap(Bitmap bitmap) {
this.bitmap = new Bitmap(bitmap);
}
public UnsafeBitmap(int width, int height) {
this.bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
}
public void Dispose() {
bitmap.Dispose();
}
public Bitmap Bitmap {
get {
return (bitmap);
}
}
private Point PixelSize {
get {
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF bounds = bitmap.GetBounds(ref unit);
return new Point((int)bounds.Width, (int)bounds.Height);
}
}
public void LockBitmap() {
GraphicsUnit unit = GraphicsUnit.Pixel;
RectangleF boundsF = bitmap.GetBounds(ref unit);
Rectangle bounds = new Rectangle((int)boundsF.X,
(int)boundsF.Y,
(int)boundsF.Width,
(int)boundsF.Height);
// Figure out the number of bytes in a row
// This is rounded up to be a multiple of 4
// bytes, since a scan line in an image must always be a multiple of 4 bytes
// in length.
width = (int)boundsF.Width * sizeof(Pixel);
if (width % 4 != 0) {
width = 4 * (width / 4 + 1);
}
bitmapData =
bitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
pBase = (Byte*)bitmapData.Scan0.ToPointer();
}
public Pixel GetPixel(int x, int y) {
Pixel returnValue = *PixelAt(x, y);
return returnValue;
}
public void SetPixel(int x, int y, Pixel colour) {
Pixel* pixel = PixelAt(x, y);
*pixel = colour;
}
public void UnlockBitmap() {
bitmap.UnlockBits(bitmapData);
bitmapData = null;
pBase = null;
}
public Pixel* PixelAt(int x, int y) {
return (Pixel*)(pBase + y * width + x * sizeof(Pixel));
}
}
}
Basically what I am doing is copying the entire screen and comparing each pixel to and old copy. On a 1680x1050 bitmap, this takes approximately 300 milliseconds using the following code.
private Bitmap GetInvalidFrame(Bitmap frame) {
Stopwatch sp = new Stopwatch();
sp.Start();
if (m_FrameBackBuffer == null) {
return frame;
}
Int32 pixelsToRead = frame.Width * frame.Height;
Int32 x = 0, y = 0;
UnsafeBitmap unsafeBitmap = new UnsafeBitmap(frame);
UnsafeBitmap unsafeBuffBitmap = new UnsafeBitmap(m_FrameBackBuffer);
UnsafeBitmap retVal = new UnsafeBitmap(frame.Width, frame.Height);
unsafeBitmap.LockBitmap();
unsafeBuffBitmap.LockBitmap();
retVal.LockBitmap();
do {
for (x = 0; x < frame.Width; x++) {
Pixel newPixel = unsafeBitmap.GetPixel(x, y);
Pixel oldPixel = unsafeBuffBitmap.GetPixel(x, y);
if (newPixel.Alpha != oldPixel.Alpha || newPixel.Red != oldPixel.Red || newPixel.Green != oldPixel.Green || newPixel.Blue != oldPixel.Blue) {
retVal.SetPixel(x, y, newPixel);
}
else {
// Skip pixel
}
}
y++;
} while (y != frame.Height);
unsafeBitmap.UnlockBitmap();
unsafeBuffBitmap.UnlockBitmap();
retVal.UnlockBitmap();
sp.Stop();
System.Diagnostics.Debug.WriteLine(sp.Elapsed.Milliseconds.ToString());
sp.Reset();
return retVal.Bitmap;
}
Is there any possible method/means/approach that I could speed this up to about 30ms? I can copy the screen in about 30ms using Graphics.CopyFromScreen(), so that produces approximately 30 frames each second. However, a program only runs as fast as its slower counterpart, so the 300ms delay in GetInvalidFrame, slows this down to about 1 - 3 frames each second. This isn't good for a meeting software.
Any advice, approaches, pointers in the right direction would be absolutely wonderful! Also, the code that is used to draw the bitmap on the client-side is below as well.
To comment on Dmitriy's answer/comment:
#region RootWorkItem
private ScreenClient m_RootWorkItem;
/// <summary>
/// Gets the RootWorkItem
/// </summary>
public ScreenClient RootWorkItem {
get {
if (m_RootWorkItem == null) {
m_RootWorkItem = new ScreenClient();
m_RootWorkItem.FrameRead += new EventHandler<FrameEventArgs>(RootWorkItem_FrameRead);
}
return m_RootWorkItem;
}
}
#endregion
private void RootWorkItem_FrameRead(Object sender, FrameEventArgs e) {
if (e.Frame != null) {
if (uxSurface.Image != null) {
Bitmap frame = (Bitmap)uxSurface.Image;
Graphics g = Graphics.FromImage(frame);
g.DrawImage(e.Frame, 0, 0); // Draw only updated pixels
uxSurface.Image = frame;
}
else {
uxSurface.Image = e.Frame; // Draw initial, full image
}
}
else {
uxSurface.Image = null;
}
}
Unsafe approach with usage of integers instead of Pixels and single loop:
private static Bitmap GetInvalidFrame(Bitmap oldFrame, Bitmap newFrame)
{
if (oldFrame.Size != newFrame.Size)
{
throw new ArgumentException();
}
Bitmap result = new Bitmap(oldFrame.Width, oldFrame.Height, oldFrame.PixelFormat);
Rectangle lockArea = new Rectangle(Point.Empty, oldFrame.Size);
PixelFormat format = PixelFormat.Format32bppArgb;
BitmapData oldData = oldFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
BitmapData newData = newFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
BitmapData resultData = result.LockBits(lockArea, ImageLockMode.WriteOnly, format);
int len = resultData.Height * Math.Abs(resultData.Stride) / 4;
unsafe
{
int* pOld = (int*)oldData.Scan0;
int* pNew = (int*)newData.Scan0;
int* pResult = (int*)resultData.Scan0;
for (int i = 0; i < len; i++)
{
int oldValue = *pOld++;
int newValue = *pNew++;
*pResult++ = oldValue != newValue ? newValue : 0 /* replace with 0xff << 24 if you need non-transparent black pixel */;
// *pResult++ = *pOld++ ^ *pNew++; // if you can use XORs.
}
}
oldFrame.UnlockBits(oldData);
newFrame.UnlockBits(newData);
result.UnlockBits(resultData);
return result;
}
I think you really can use XORed frames here and I hope that this can have better performance on both sides.
private static void XorFrames(Bitmap leftFrame, Bitmap rightFrame)
{
if (leftFrame.Size != rightFrame.Size)
{
throw new ArgumentException();
}
Rectangle lockArea = new Rectangle(Point.Empty, leftFrame.Size);
PixelFormat format = PixelFormat.Format32bppArgb;
BitmapData leftData = leftFrame.LockBits(lockArea, ImageLockMode.ReadWrite, format);
BitmapData rightData = rightFrame.LockBits(lockArea, ImageLockMode.ReadOnly, format);
int len = leftData.Height * Math.Abs(rightData.Stride) / 4;
unsafe
{
int* pLeft = (int*)leftData.Scan0;
int* pRight = (int*)rightData.Scan0;
for (int i = 0; i < len; i++)
{
*pLeft++ ^= *pRight++;
}
}
leftFrame.UnlockBits(leftData);
rightFrame.UnlockBits(rightData);
}
You can use this procedure on both sides in following way:
On server side you need to evaluate difference between old and new frame, send it to client and replace old frame by new. The server code should look something like this:
XorFrames(oldFrame, newFrame); // oldFrame ^= newFrame
Send(oldFrame); // send XOR of two frames
oldFrame = newFrame;
On client side you need to update your current frame with xor frame recieved from server:
XorFrames((Bitmap)uxSurface.Image, e.Frame);
Here: Utilizing the GPU with c# there are mentioned some librarys for using the GPU from C#.
Yes, you can do so by using unsafe code.
BitmapData d = l.LockBits(new Rectangle(0, 0, l.Width, l.Height), ImageLockMode.ReadOnly,l.PixelFormat);
IntPtr scan = d.Scan0;
unsafe
{
byte* p = (byte*)(void*)scan;
//dostuff
}
Check out http://www.codeproject.com/KB/GDI-plus/csharpgraphicfilters11.aspx for some basic examples of this kind of stuff. My code is based on that.
Note:
One of the reasons this will be much faster than yours is that you are separately comparing each channel instead of just comparing the entire byte using one operation. Similarly, changing PixelAt to give you a byte to facilitate this would probably give you an improvement.
Instead of checking each and every pixel, you can just perform a basic memory compare of the 2 bitmaps. In C, something like memcmp().
This would give you a much quicker test to let you know that the images are the same or not. Only when you know they are different do you need to resort to the more expensive code that will help you determine where they are different (if you even need to know that).
I am not a C# person though, so I don't know how easy it is to get access to the raw memory.
Was able to slice off about 60ms. I think this is going to require the GPU. I'm not seeing any solution to this utilizing the CPU, even by comparing more than one byte/pixel at a time, unless someone can whip up a code sample to show me otherwise. Still sits at about 200-260ms, far too slow for 30fps.
private static BitmapData m_OldData;
private static BitmapData m_NewData;
private static unsafe Byte* m_OldPBase;
private static unsafe Byte* m_NewPBase;
private static unsafe Pixel* m_OldPixel;
private static unsafe Pixel* m_NewPixel;
private static Int32 m_X;
private static Int32 m_Y;
private static Stopwatch m_Watch = new Stopwatch();
private static GraphicsUnit m_GraphicsUnit = GraphicsUnit.Pixel;
private static RectangleF m_OldBoundsF;
private static RectangleF m_NewBoundsF;
private static Rectangle m_OldBounds;
private static Rectangle m_NewBounds;
private static Pixel m_TransparentPixel = new Pixel() { Alpha = 0x00, Red = 0, Green = 0, Blue = 0 };
private Bitmap GetInvalidFrame(Bitmap frame) {
if (m_FrameBackBuffer == null) {
return frame;
}
m_Watch.Start();
unsafe {
m_OldBoundsF = m_FrameBackBuffer.GetBounds(ref m_GraphicsUnit);
m_OldBounds = new Rectangle((Int32)m_OldBoundsF.X, (Int32)m_OldBoundsF.Y, (Int32)m_OldBoundsF.Width, (Int32)m_OldBoundsF.Height);
m_OldData = m_FrameBackBuffer.LockBits(m_OldBounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
m_NewBoundsF = m_FrameBackBuffer.GetBounds(ref m_GraphicsUnit);
m_NewBounds = new Rectangle((Int32)m_NewBoundsF.X, (Int32)m_NewBoundsF.Y, (Int32)m_NewBoundsF.Width, (Int32)m_NewBoundsF.Height);
m_NewData = frame.LockBits(m_NewBounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
m_OldPBase = (Byte*)m_OldData.Scan0.ToPointer();
m_NewPBase = (Byte*)m_NewData.Scan0.ToPointer();
do {
for (m_X = 0; m_X < frame.Width; m_X++) {
m_OldPixel = (Pixel*)(m_OldPBase + m_Y * m_OldData.Stride + 1 + m_X * sizeof(Pixel));
m_NewPixel = (Pixel*)(m_NewPBase + m_Y * m_NewData.Stride + 1 + m_X * sizeof(Pixel));
if (m_OldPixel->Alpha == m_NewPixel->Alpha // AccessViolationException accessing Property in get {}
|| m_OldPixel->Red == m_NewPixel->Red
|| m_OldPixel->Green == m_NewPixel->Green
|| m_OldPixel->Blue == m_NewPixel->Blue) {
// Set the transparent pixel
*m_NewPixel = m_TransparentPixel;
}
}
m_Y++; //Debug.WriteLine(String.Format("X: {0}, Y: {1}", m_X, m_Y));
} while (m_Y < frame.Height);
}
m_Y = 0;
m_Watch.Stop();
Debug.WriteLine("Time elapsed: " + m_Watch.ElapsedMilliseconds.ToString());
m_Watch.Reset();
return frame;
}