Trying to match pixel colour then click [C#] - c#

I need help getting my program to match the "stored" colour with the current one in the same location then click the mouse if it's the same. The grabbing of the colour works great so far in my code just unsure how to match a colour and a point, etc.
Also a start/stop button for the loop would be nice.
My code so far:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pixel_detection_test_3
{
public partial class PixelDetectionForm : Form
{
private const UInt32 MOUSEEVENTF_LEFTDOWN = 0x0002;
private const UInt32 MOUSEEVENTF_LEFTUP = 0x0004;
[DllImport("user32.dll")]
private static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, uint dwExtraInf);
private int pixelY;
private int pixelX;
private Point pixelYX;
private static Color currentColour;
private static Color storedColour;
public PixelDetectionForm()
{
InitializeComponent();
}
static Color GetPixel(Point position)
{
using (var bitmap = new Bitmap(1, 1))
{
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.CopyFromScreen(position, new Point(0, 0), new Size(1, 1));
}
return bitmap.GetPixel(0, 0);
}
}
private void PixelDetectionForm_KeyDown(object sender, KeyEventArgs e)
{
// Get Cursor Pixel Position
if (e.KeyCode == Keys.F1 || e.KeyCode == Keys.F2)
{
pixelY = Cursor.Position.Y;
pixelX = Cursor.Position.X;
pixelYX = Cursor.Position;
textBoxYPos.Text = pixelY.ToString();
textBoxXPos.Text = pixelX.ToString();
e.Handled = true;
}
// Get Cursor Pixel Colour
if (e.KeyCode == Keys.F1 || e.KeyCode == Keys.F3)
{
storedColour = GetPixel(Cursor.Position);
textBoxColour.Text = storedColour.ToString().Remove(0, 14).TrimEnd(']');
panelColourDisplay.BackColor = storedColour;
e.Handled = true;
}
}
// Not working, need help with this
private async void buttonStart_Click(object sender, EventArgs e)
{
while (true)
{
GetPixel(pixelYX);
// Should get position of 'pixelY' and 'pixelX'
panelColourDisplay2.BackColor = GetPixel(Cursor.Position);
if (pixelYX == storedColour)
{
MousePress();
}
// Need this to prevent not responding
await Task.Delay(3);
}
}
private void MousePress()
{
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
}
private void PixelDetectionForm_Click(object sender, EventArgs e)
{
ActiveControl = null;
}
private void PixelDetectionForm_Activated(object sender, EventArgs e)
{
ActiveControl = null;
}
}
}
Thanks

Well, an alternative to the while..loop is using a Timer to achieve that.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pixel_detection_test_3
{
public partial class PixelDetectionForm : Form
{
private readonly Timer Tmr;
private Point lastPoint;
//Assign this from your input code.
private Color targetColor;
public PixelDetectionForm()
{
Tmr = new Timer { Interval = 50 };
Tmr.Tick += (s, e) => FindMatches(Cursor.Position);
}
//...
In the timer's Tick event, the FindMatches(..) method is called to check the current Cursor.Position and add the distinct matches into a ListBox. You can replace the last part with what you really need to do when you find a match. Like calling the MousePress() method in your code:
//...
private void FindMatches(Point p)
{
//To avoid the redundant calls..
if (p.Equals(lastPoint)) return;
lastPoint = p;
using (var b = new Bitmap(1, 1))
using (var g = Graphics.FromImage(b))
{
g.CopyFromScreen(p, Point.Empty, b.Size);
var c = b.GetPixel(0, 0);
if (c.ToArgb().Equals(targetColor.ToArgb()) &&
!listBox1.Items.Cast<Point>().Contains(p))
{
listBox1.Items.Add(p);
listBox1.SelectedItem = p;
}
}
}
private void PixelDetectionForm_FormClosing(object sender, FormClosingEventArgs e)
{
Tmr.Dispose();
}
}
}
Start and stop the timer in the click events of the Start and Stop buttons.
Here's a demo:
Another alternative is to use the Global Mouse and Keyboard Hooks. Check this, this, and this for more details.
Edit 2/11/2020
If you just want to check whether a given color at a given point exists in a given image, then you can do:
private void buttonStart_Click(object sender, EventArgs e)
{
var targetColor = ...; //target color.
var targetPoint = ...; //target point.
var sz = Screen.PrimaryScreen.Bounds.Size;
using (var b = new Bitmap(sz.Width, sz.Height, PixelFormat.Format32bppArgb))
using (var g = Graphics.FromImage(b))
{
g.CopyFromScreen(Point.Empty, Point.Empty, b.Size, CopyPixelOperation.SourceCopy);
var bmpData = b.LockBits(new Rectangle(Point.Empty, sz), ImageLockMode.ReadOnly, b.PixelFormat);
var pixBuff = new byte[bmpData.Stride * bmpData.Height];
Marshal.Copy(bmpData.Scan0, pixBuff, 0, pixBuff.Length);
b.UnlockBits(bmpData);
for (var y = 0; y < b.Height; y++)
for(var x = 0; x < b.Width; x++)
{
var pos = (y * bmpData.Stride) + (x * 4);
var blue = pixBuff[pos];
var green = pixBuff[pos + 1];
var red = pixBuff[pos + 2];
var alpha = pixBuff[pos + 3];
if (Color.FromArgb(alpha, red, green, blue).ToArgb().Equals(targetColor.ToArgb()) &&
new Point(x, y).Equals(targetPoint))
{
//execute you code here..
MessageBox.Show("The given color exists at the given point.");
return;
}
}
}
MessageBox.Show("The given color doesn't exist at the given point.");
}
If you want to get a list of all the positions of a given color, then create a new List<Point>() and change the check condition to:
//...
var points = new List<Point>();
if (Color.FromArgb(alpha, red, green, blue).ToArgb().Equals(targetColor.ToArgb()))
{
points.Add(new Point(x, y));
}

Related

c# Drawing with gdi32.dll vs System.Drawing.Graphics

So I have some code that creates a highlight effect on top of a picture box with gdi32.dll and I am wondering if there is a simpler way to do it with System.Drawing.Graphics? Basically with the gdi32.dll, I have to capture a screen shot after drawing, post that to my picturebox and then I am able to draw more stuff and change the color of the pen that I use. If I just try to change the pen thickness and color and draw on the screen again, if changes what I already drew.
Now I have a version of this that uses System.Drawing.Graphics and lots of math with FillPolygon but if I draw over an area I already drew on, it just makes the area I drew on darker. It does not do this with gdi32.dll which justr shades as long as you have not already shaded the area with the mouse. Any suggestions?
public partial class Form9 : Form
{
private bool is_mouse_down { get; set; } // Will check if the mouse is down or not.
private Color Pen_Color = new Color();
private int Pen_Type { get; set; }
private int Thickness { get; set; }
private bool Start { get; set; }
List<Point> Points = new List<Point>();
public Form9()
{
InitializeComponent();
pictureBox1.Dock = DockStyle.Fill;
Pen_Color = Color.Blue;
Pen_Type = 13; // Type = 9 for highlighter, Type = 13 for solid.
Thickness = 2;
Start = false;
pictureBox1.MouseDown += pictureBox1_MouseDown;
pictureBox1.MouseUp += pictureBox1_MouseUp;
pictureBox1.MouseMove += pictureBox1_MouseMove;
pictureBox1.Paint += pictureBox1_OnPaint;
}
private void DrawHighlight(Graphics g, Point[] usePoints, int brushSize, int penType, Color brushColor)
{
int useColor = System.Drawing.ColorTranslator.ToWin32(brushColor);
IntPtr pen = GetImage.GDI32.CreatePen(GetImage.GDI32.PS_SOLID, brushSize, (uint)useColor);
IntPtr hDC = g.GetHdc();
IntPtr xDC = GetImage.GDI32.SelectObject(hDC, pen);
GetImage.GDI32.SetROP2(hDC, penType);//GetImage.GDI32.R2_MASKPEN);
for (int i = 1; i <= usePoints.Length - 1; i++)
{
Point p1 = usePoints[i - 1];
Point p2 = usePoints[i];
GetImage.GDI32.MoveToEx(hDC, p1.X, p1.Y, IntPtr.Zero);
GetImage.GDI32.LineTo(hDC, p2.X, p2.Y);
}
GetImage.GDI32.SetROP2(hDC, GetImage.GDI32.R2_COPYPEN);
GetImage.GDI32.SelectObject(hDC, xDC);
GetImage.GDI32.DeleteObject(pen);
g.ReleaseHdc(hDC);
}
private void pictureBox1_OnPaint(object sender, PaintEventArgs e)
{
if (Start)
{
base.OnPaint(e);
if (is_mouse_down)
{
DrawHighlight(e.Graphics, Points.ToArray(), Thickness, Pen_Type, Pen_Color);
}
}
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
Points.Clear();
Start = true;
is_mouse_down = true;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
is_mouse_down = false;
using (Image img = CaptureScreen())
{
try
{
if (System.IO.File.Exists(Program.ProgramPath + #"\Temp\marked.bmp"))
{
System.IO.File.Delete(Program.ProgramPath + #"\Temp\marked.bmp");
}
}
catch (Exception Ex)
{
MessageBox.Show("File Delete Error" + Environment.NewLine + Convert.ToString(Ex));
}
try
{
img.Save(Program.ProgramPath + #"\Temp\marked.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
}
catch (Exception Ex)
{
MessageBox.Show("Unable to save Screenshot" + Environment.NewLine + Convert.ToString(Ex));
}
}
if (System.IO.File.Exists(Program.ProgramPath + #"\Temp\marked.bmp"))
{
using (FileStream fs = new System.IO.FileStream(Program.ProgramPath + #"\Temp\marked.bmp", System.IO.FileMode.Open, System.IO.FileAccess.Read, FileShare.Read))
{
pictureBox1.Image = Image.FromStream(fs);
}
}
pictureBox1.Invalidate(); // Refreshes picturebox image.
}
public Image CaptureScreen()
{
GetImage gi = new GetImage();
return gi.CaptureWindow(GetImage.User32.GetDesktopWindow());
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (is_mouse_down == true) // Check to see if the mouse button is down while moving over the form.
{
Points.Add(new Point(e.X, e.Y));
pictureBox1.Invalidate(); // Refreshes picturebox image.
}
}
Here are a couple photos of what I am talking about:
Using System.Drawing.Graphics:
Using gdi32.dll:
UPDATE
After testing some of your code...I got a some strange stuff.
This is a way to draw multiple independent stroke of semi-transparent color without piling up the alpha:
It uses tow lists, one for the strokes and one for the current stroke:
List<List<Point>> strokes = new List<List<Point>>();
List<Point> currentStroke = new List<Point>();
They are filled in the usual way
private void canvas_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
currentStroke.Add(e.Location);
if (currentStroke.Count == 1)
currentStroke.Add(new Point(currentStroke[0].X + 1,
currentStroke[0].Y));
canvasInvalidate();
}
}
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button.HasFlag(MouseButtons.Left))
{
currentStroke.Add(e.Location);
canvas.Invalidate();
}
}
private void canvas_MouseUp(object sender, MouseEventArgs e)
{
if (currentStroke.Count > 1)
{
strokes.Add(currentStroke.ToList());
currentStroke.Clear();
}
canvas.Invalidate();
}
In this version we avoid overlay effects of overlapping strokes by drawing all pixels in only one call. All the pixels are painted by creating a GraphicsPath from the strokes and and filling it:
private void canvas_Paint(object sender, PaintEventArgs e)
{
if (strokes.Count > 0 || currentStroke.Count > 0)
{
GraphicsPath gp = new GraphicsPath();
gp.FillMode = FillMode.Winding;
if (currentStroke.Count > 0)
{
gp.AddCurve(currentStroke.ToArray());
gp.CloseFigure();
}
foreach (var stroke in strokes)
{
gp.AddCurve(stroke.ToArray());
gp.CloseFigure();
}
using (SolidBrush b = new SolidBrush(Color.FromArgb(77, 177, 99, 22)))
{
e.Graphics.FillPath(b, gp);
}
}
}
Note that you should take care not to move back onto the current stroke while drawing it or else the croosing path parts will create holes!
A Clear or Save Button are simple, the former Clears the two list and invalidates, the latter would use DrawToBitmap to save the control..
Note: To avoid flicker do make sure the canval Panel is DoubleBuffered!
Update:
Here is another way that uses a Pen to draw the overlay. To avoid the piling up of alpha and changed color values (depending on the PixelFormat) it uses a fast function to modify all set pixels in the overlay to have the same overlay color:
The stroke collection code is the same. The Paint is reduced to calling a function to create an overlay bitmap and drawing it:
private void canvas_Paint(object sender, PaintEventArgs e)
{
using (Bitmap bmp = new Bitmap(canvas.ClientSize.Width,
canvas.ClientSize.Height, PixelFormat.Format32bppPArgb))
{
PaintToBitmap(bmp);
e.Graphics.DrawImage(bmp, 0, 0);
}
The first function does the drawing, pretty much like before, but with simple pen strokes:
private void PaintToBitmap(Bitmap bmp)
{
Color overlayColor = Color.FromArgb(77, 22, 99, 99);
using (Graphics g = Graphics.FromImage(bmp))
using (Pen p = new Pen(overlayColor, 15f))
{
p.MiterLimit = p.Width / 2;
p.EndCap = LineCap.Round;
p.StartCap = LineCap.Round;
p.LineJoin = LineJoin.Round;
g.SmoothingMode = SmoothingMode.AntiAlias;
if (currentStroke.Count > 0)
{
g.DrawCurve(p, currentStroke.ToArray());
}
foreach (var stroke in strokes)
g.DrawCurve(p, stroke.ToArray());
}
SetAlphaOverlay(bmp, overlayColor);
}
It also calls the function that 'flattens' all set pixels to the overlay color:
void SetAlphaOverlay(Bitmap bmp, Color col)
{
Size s = bmp.Size;
PixelFormat fmt = bmp.PixelFormat;
Rectangle rect = new Rectangle(Point.Empty, s);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, fmt);
int size1 = bmpData.Stride * bmpData.Height;
byte[] data = new byte[size1];
System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, data, 0, size1);
for (int y = 0; y < s.Height; y++)
{
for (int x = 0; x < s.Width; x++)
{
int index = y * bmpData.Stride + x * 4;
if (data[index + 0] + data[index + 1] + data[index + 2] > 0)
{
data[index + 0] = col.B;
data[index + 1] = col.G;
data[index + 2] = col.R;
data[index + 3] = col.A;
}
}
}
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
bmp.UnlockBits(bmpData);
}
It uses LockBits, so it is pretty fast..
Here it is in action:
Update 2:
Just for the fun of it here is an extension of only a few lines that adds the option of drawing filled curves:
The fill mode is stored in a cheapo hack by having the 1st element twice. These are the changes:
In the MouseDown:
currentStroke.Add(e.Location);
if (cbx_Fill.Checked)
currentStroke.Add(e.Location);
And in the PaintToBitmap:
g.SmoothingMode = SmoothingMode.AntiAlias;
if (currentStroke.Count > 0)
{
if (cbx_Fill.Checked)
g.FillClosedCurve(b, currentStroke.ToArray());
else
g.DrawCurve(p, currentStroke.ToArray());
}
foreach (var stroke in strokes)
if (stroke[0]==stroke[1])
g.FillClosedCurve(b, stroke.ToArray());
else
g.DrawCurve(p, stroke.ToArray());
And one more demo:

Replacing old values when drawing

I am using Windows Form application and using one button to generate random number and draw on form. when button is clicked, It is adding a random number using Graphics.Drawing method. Problem is when I hit the button first time it works fine and add a random number i.e 11111. When I hit button again it will add a new random number (on next position) but it will also change previous numbers to new generated random number.
Updated: (Added Complete Code)
Edit: I have moved Random outside of scoop so now it does not generate same number but still its changing old random numbers to other ones.
Main Class:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DrawingText
{
public partial class Form1 : Form
{
private Point mouseDownPosition = new Point(0, 0);
private Point mouseMovePosition = new Point(0, 0);
private int mousePressdDown;
private ArrayList drawnItemsList;
Random rnd;
public Form1()
{
InitializeComponent();
drawnItemsList = new ArrayList();
this.rnd = new Random();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
mouseMovePosition = e.Location;
if (e.Button == MouseButtons.Left)
mousePressdDown = 1;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
mouseDownPosition = e.Location;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (mousePressdDown == 1)
{
label1.Text = "X: " + mouseMovePosition.X.ToString();
label2.Text = "Y: " + mouseMovePosition.Y.ToString();
this.Invalidate();
}
DrawingData a = new DrawingData(mouseMovePosition, mouseDownPosition);
drawnItemsList.Add(a);
mousePressdDown = 0;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
foreach (DrawingData a in drawnItemsList)
{
draw(e.Graphics, a.old, a.cur);
}
draw(e.Graphics, mouseDownPosition, mouseMovePosition);
}
private void draw(Graphics e, Point mold, Point mcur)
{
Pen p = new Pen(Color.Black, 2);
using (Font useFont = new Font("Gotham Medium", 28, FontStyle.Bold))
{
string header2 = rnd.Next().ToString();
RectangleF header2Rect = new RectangleF();
int moldX = mold.X - 5;
int moldY = mold.Y;
header2Rect.Location = new Point(moldX, moldY);
header2Rect.Size = new Size(600, ((int)e.MeasureString(header2, useFont, 600, StringFormat.GenericTypographic).Height));
e.DrawString(header2, useFont, Brushes.Black, header2Rect);
}
}
private void button1_Click(object sender, EventArgs e)
{
}
}
}
Drawing Data Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace DrawingText
{
[Serializable]
class DrawingData
{
private Point mold; // mouseDown position
private Point mcur; // mouseUp poslition
public DrawingData()
{
mold = new Point(0, 0);
mcur = new Point(0, 0);
}
public DrawingData(Point old, Point cur)
{
mold = old;
mcur = cur;
}
public Point old
{
get
{
return mold;
}
set
{
mold = value;
}
}
public Point cur
{
get
{
return mcur;
}
set
{
mcur = value;
}
}
}
}
3 times button clicked and it replaced old value with new one:
You need to store the random value with the point values in the DrawingData class, like this:
Main Class:
namespace DrawingText
{
public partial class Form1 : Form
{
private Point mouseDownPosition = new Point(0, 0);
private Point mouseMovePosition = new Point(0, 0);
private int mousePressdDown;
private ArrayList drawnItemsList;
Random rnd;
public Form1()
{
InitializeComponent();
drawnItemsList = new ArrayList();
this.rnd = new Random();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (mousePressdDown == 1)
{
label1.Text = "X: " + mouseMovePosition.X.ToString();
label2.Text = "Y: " + mouseMovePosition.Y.ToString();
this.Invalidate();
}
DrawingData a = new DrawingData(mouseMovePosition, mouseDownPosition, rnd.Next().ToString());
drawnItemsList.Add(a);
mousePressdDown = 0;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
foreach (DrawingData a in drawnItemsList)
{
draw(e.Graphics, a);
}
draw(e.Graphics, mouseDownPosition, mouseMovePosition);
}
private void draw(Graphics e, DrawingData a)
{
Pen p = new Pen(Color.Black, 2);
using (Font useFont = new Font("Gotham Medium", 28, FontStyle.Bold))
{
RectangleF header2Rect = new RectangleF();
int moldX = a.old.X - 5;
int moldY = a.old.Y;
header2Rect.Location = new Point(moldX, moldY);
header2Rect.Size = new Size(600, ((int)e.MeasureString(header2, useFont, 600, StringFormat.GenericTypographic).Height));
e.DrawString(a.Rand, useFont, Brushes.Black, header2Rect);
}
}
}
}
Drawing Data Class:
namespace DrawingText
{
[Serializable]
public class DrawingData
{
private Point mold; // mouseDown position
private Point mcur; // mouseUp poslition
private string randValue; // random data value
public DrawingData()
{
mold = new Point(0, 0);
mcur = new Point(0, 0);
randValue = String.Empty;
}
public DrawingData(Point old, Point cur, string rand)
{
mold = old;
mcur = cur;
randValue = rand;
}
public Point old
{
get
{
return mold;
}
set
{
mold = value;
}
}
public Point cur
{
get
{
return mcur;
}
set
{
mcur = value;
}
}
public sting Rand
{
get
{
return randValue;
}
set
{
randValue = value;
}
}
}
You are recreating your random each time in the loop which will cause it to have the same seed, and the same first number. That's why all your numbers are the same. You should.
Move your random outside of the method and loop, and use it instead. Change the line Random rnd = new Random() to rnd = new Random(). You already have a variable in the class to hold the random.
If you want the previous random numbers to remain the same as the last time, you need to store them in a list somewhere and draw them on paint. You are currently creating a new set of random numbers each time.
This is made on the fly using graphics path:
GraphicsPath gp;
int moldX = 10;
int moldY = 10;
public Form1()
{
InitializeComponent();
gp = new GraphicsPath();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillPath(Brushes.Black, gp);
// if you want the numbers outlined do e.Graphics.DrawPath
}
private void button1_Click(object sender, EventArgs e)
{
AddToPath();
Invalidate();
}
private void AddToPath()
{
using (Font useFont = new Font("Gotham Medium", 28, FontStyle.Bold))
{
Random rnd = new Random();
string header2 = rnd.Next().ToString();
int strsize = TextRenderer.MeasureText(header2, useFont).Height;
StringFormat format = StringFormat.GenericDefault;
gp.AddString(header2, useFont.FontFamily, 1, 28, new Point(moldX, moldY), format);
moldX += 5;
moldY += strsize;
}
}

How do I show the new form at the current position of the mouse cursor?

This is the code:
private void hsMagnfier_OnMouseDown(object sender)
{
int x = mLastCursorPosition.X;
int y = mLastCursorPosition.Y;
MagnifierForm magnifier = new MagnifierForm(mConfiguration, System.Windows.Forms.Cursor.Position);//mLastCursorPosition);
magnifier.Show();
}
This code above is in a Form which I can drag over the screen.
Then when I click on an icon it's doing the magnifier.Show(); and the magnifier form is shown up where the mouse current position is.
But if I click on it again so now the position of the new form the magnifier is in my Form1 center. And not where the mouse current position as in the first time.
This is the MagnifierForm code maybe first time it's in the current mouse position but in the next time/s it's in the center of Form1 ?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.IO;
using System.Drawing.Imaging;
namespace ScreenVideoRecorder
{
public partial class MagnifierForm : Form
{
public MagnifierForm(Configuration configuration, Point startPoint)
{
InitializeComponent();
//--- My Init ---
mConfiguration = configuration;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = mConfiguration.ShowInTaskbar;
TopMost = mConfiguration.TopMostWindow;
Width = mConfiguration.MagnifierWidth;
Height = mConfiguration.MagnifierHeight;
// Make the window (the form) circular
GraphicsPath gp = new GraphicsPath();
gp.AddEllipse(ClientRectangle);
Region = new Region(gp);
mImageMagnifier = Properties.Resources.magnifierGlass;
mTimer = new Timer();
mTimer.Enabled = true;
mTimer.Interval = 20;
mTimer.Tick += new EventHandler(HandleTimer);
mScreenImage = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height);
mStartPoint = startPoint;
mTargetPoint = startPoint;
if (mConfiguration.ShowInTaskbar)
ShowInTaskbar = true;
else
ShowInTaskbar = false;
}
protected override void OnShown(EventArgs e)
{
RepositionAndShow();
}
private delegate void RepositionAndShowDelegate();
private void RepositionAndShow()
{
if (InvokeRequired)
{
Invoke(new RepositionAndShowDelegate(RepositionAndShow));
}
else
{
// Capture the screen image now!
Graphics g = Graphics.FromImage(mScreenImage);
g.CopyFromScreen(0, 0, 0, 0, new Size(mScreenImage.Width, mScreenImage.Height));
g.Dispose();
if (mConfiguration.HideMouseCursor)
Cursor.Hide();
else
Cursor = Cursors.Cross;
Capture = true;
if (mConfiguration.RememberLastPoint)
{
mCurrentPoint = mLastMagnifierPosition;
Cursor.Position = mLastMagnifierPosition;
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
}
else
{
mCurrentPoint = Cursor.Position;
}
Show();
}
}
void HandleTimer(object sender, EventArgs e)
{
float dx = mConfiguration.SpeedFactor * (mTargetPoint.X - mCurrentPoint.X);
float dy = mConfiguration.SpeedFactor * (mTargetPoint.Y - mCurrentPoint.Y);
if (mFirstTime)
{
mFirstTime = false;
mCurrentPoint.X = mTargetPoint.X;
mCurrentPoint.Y = mTargetPoint.Y;
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
return;
}
mCurrentPoint.X += dx;
mCurrentPoint.Y += dy;
if (Math.Abs(dx) < 1 && Math.Abs(dy) < 1)
{
mTimer.Enabled = false;
}
else
{
// Update location
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
mLastMagnifierPosition = new Point((int)mCurrentPoint.X, (int)mCurrentPoint.Y);
}
Refresh();
}
protected override void OnMouseDown(MouseEventArgs e)
{
mOffset = new Point(Width / 2 - e.X, Height / 2 - e.Y);
mCurrentPoint = PointToScreen(new Point(e.X + mOffset.X, e.Y + mOffset.Y));
mTargetPoint = mCurrentPoint;
mTimer.Enabled = true;
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (mConfiguration.CloseOnMouseUp)
{
Close();
mScreenImage.Dispose();
}
Cursor.Show();
Cursor.Position = mStartPoint;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
mTargetPoint = PointToScreen(new Point(e.X + mOffset.X, e.Y + mOffset.Y));
mTimer.Enabled = true;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
if (mConfiguration.DoubleBuffered)
{
// Do not paint background (required for double buffering)!
}
else
{
base.OnPaintBackground(e);
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (mBufferImage == null)
{
mBufferImage = new Bitmap(Width, Height);
}
Graphics bufferGrf = Graphics.FromImage(mBufferImage);
Graphics g;
if (mConfiguration.DoubleBuffered)
{
g = bufferGrf;
}
else
{
g = e.Graphics;
}
if (mScreenImage != null)
{
Rectangle dest = new Rectangle(0, 0, Width, Height);
int w = (int)(Width / mConfiguration.ZoomFactor);
int h = (int)(Height / mConfiguration.ZoomFactor);
int x = Left - w / 2 + Width / 2;
int y = Top - h / 2 + Height / 2;
g.DrawImage(
mScreenImage,
dest,
x, y,
w, h,
GraphicsUnit.Pixel);
}
if (mImageMagnifier != null)
{
g.DrawImage(mImageMagnifier, 0, 0, Width, Height);
}
if (mConfiguration.DoubleBuffered)
{
e.Graphics.DrawImage(mBufferImage, 0, 0, Width, Height);
}
}
//--- Data Members ---
#region Data Members
private Timer mTimer;
private Configuration mConfiguration;
private Image mImageMagnifier;
private Image mBufferImage = null;
private Image mScreenImage = null;
private Point mStartPoint;
private PointF mTargetPoint;
private PointF mCurrentPoint;
private Point mOffset;
private bool mFirstTime = true;
private static Point mLastMagnifierPosition = Cursor.Position;
#endregion
}
}
The first time the new Form the magnifier is shown up where my mouse cursour is.
The next time i click on it's showing the magnifier form in the center of Form1 and not where the mouse cursour is.
Why is that ? When i clikc on the icon again it's still doing the
System.Windows.Forms.Cursor.Position
Again. Strange.
Consider you have two forms - Master and Child
If you are calling Child from Master on MouseUp event(for example), write the code in MouseUp event of Master form
ChildForm obj=new ChildForm();
obj.pntLocation = new Point(Cursor.Position.X, Cursor.Position.Y);
obj.ShowDialog();
Declare a variable inside the Child for location
public Point pntLocation;
Now set location inside the Form_Load of Child
this.Location = pntLocation;
Ok found that the part that doing it is here in the Magnifier form:
mConfiguration.RememberLastPoint = false;
if (mConfiguration.RememberLastPoint)
{
mCurrentPoint = mLastMagnifierPosition;
Cursor.Position = mLastMagnifierPosition;
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
}
else
{
mCurrentPoint = Cursor.Position;
}
So I added for now the line: mConfiguration.RememberLastPoint = false; which did the job for now.

Manipulating an image and updating the pictureBox has some issues

I could not fit exactly what I wanted to say in the title, it would be too long. Okay this is a multi-threaded app. What my app does is looks at a picture, find the edges of the picture, and finds the shape of that object from the edges. While it finds the shape, it constantly updates the image so we can get some sort of visual representation. I have created a very short (40 seconds) video demonstrating the issue: http://phstudios.com/projects/Programming/MultiThreadIssue/
As you can see, everything is working fine until the minute I move the window. This is always the case. I ran the program several times without moving the window and it ran fine. However, the minute I move the window, it will come up with that error. As you see, I am locking the specific image I would like to work with. Is the forms OnPaint overriding that somehow? Is there any way I can fix that?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Imaging;
namespace LineRecognition
{
public enum Shape
{
Unknown,
Quadrilateral,
Circle,
Triangle
}
public partial class Form1 : Form
{
Bitmap image, original, shapes;
List<Point> outlines;
Shape shape;
ShapeDetection detector;
public Form1()
{
InitializeComponent();
edgeDetection.WorkerReportsProgress = true;
shapeDetection.WorkerReportsProgress = true;
shape = Shape.Unknown;
}
private void Form1_Load(object sender, EventArgs e)
{
original = new Bitmap("photo1.png");
image = new Bitmap("photo1.png");
shapes = new Bitmap(image.Width, image.Height);
pictureBox1.Image = (Image)original;
}
private void findLines_Click(object sender, EventArgs e)
{
if (edgeDetection.IsBusy != true)
{
lblStatus.Text = "Finding Edges";
edgeDetection.RunWorkerAsync();
}
}
private void justTheOutlines(Bitmap image, List<Point> pixels, BackgroundWorker worker)
{
lock (image)
{
for (int i = 0; i < pixels.Count; i++)
{
image.SetPixel(pixels[i].X, pixels[i].Y, Color.Red);
worker.ReportProgress((int)((float)i * 100 / (float)pixels.Count));
}
}
}
private List<Point> outlineLines(Bitmap image, BackgroundWorker worker)
{
int w = image.Width;
int h = image.Height;
int alpha = 800000;
List<Point> changes = new List<Point>();
lock (image)
{
for (int i = 0; i < w; i++)
{
for (int j = 0; j < h; j++)
{
Color selected = image.GetPixel(i, j);
Color nextRight = selected;
Color nextDown = selected;
if (i < w - 1)
nextRight = image.GetPixel(i + 1, j);
if (j < h - 1)
nextDown = image.GetPixel(i, j + 1);
int iSelected = selected.ToArgb();
int iNextRight = nextRight.ToArgb();
int iNextDown = nextDown.ToArgb();
if (Math.Abs(iSelected - iNextRight) > alpha)
{
if (iSelected < iNextRight)
{
Point p = new Point(i, j);
if(!ContainsPoint(changes, p)) changes.Add(p);
}
else
{
Point p = new Point(i + 1, j);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
}
if (Math.Abs(iSelected - iNextDown) > alpha)
{
if (iSelected < iNextDown)
{
Point p = new Point(i, j);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
else
{
Point p = new Point(i, j + 1);
if (!ContainsPoint(changes, p)) changes.Add(p);
}
}
image.SetPixel(i, j, Color.White);
}
worker.ReportProgress((int)(((float)i / (float)w) * 100));
}
}
return changes;
}
private bool ContainsPoint(List<Point> changes, Point p)
{
foreach (Point n in changes)
{
if (n.Equals(p)) return true;
}
return false;
}
private void edgeDetection_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
outlines = outlineLines(image, worker);
justTheOutlines(image, outlines, worker);
pictureBox2.Image = (Image)image;
Thread.Sleep(100);
image.Save("photo-lines.jpg");
}
private void edgeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
algorithmProgress.Value = e.ProgressPercentage;
}
private void edgeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
algorithmProgress.Value = 0;
findLines.Enabled = false;
determineShape.Enabled = true;
lblStatus.Text = "";
}
private void determineShape_Click(object sender, EventArgs e)
{
if (shapeDetection.IsBusy != true)
{
pictureBox1.Image = (Image)image;
lblStatus.Text = "Running Shape Detection: Circle -> Quadrilateral";
shapeDetection.RunWorkerAsync();
}
}
private void ShapeDetection_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
detector = new ShapeDetection(outlines, 40, 10);
detector.Worker = worker;
detector.circleChange += new ShapeDetection.CircleChangeEventHandler(circleChange);
if (detector.IsCircle())
{
MessageBox.Show("Object is a circle");
shape = Shape.Circle;
}
else if (detector.IsQuadrilateral())
{
MessageBox.Show("Object is a quadrilateral", "Number of edges: " + detector.Summits);
shape = Shape.Quadrilateral;
}
else
{
int sides = detector.Summits.Count;
if (sides == 3)
{
MessageBox.Show("Object is a triangle");
shape = Shape.Triangle;
}
else
{
MessageBox.Show("Number of edges: " + detector.Summits.Count, "Unknown");
}
}
BitmapDrawing.DrawLines(detector.Summits, shapes);
BitmapDrawing.DrawSummits(detector.Summits, shapes);
pictureBox2.Image = (Image)shapes;
Thread.Sleep(100);
}
private void ShapeDetection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (detector != null)
{
lblSummits.Text += detector.Summits.Count;
lblType.Text += shape.ToString();
determineShape.Enabled = false;
lblStatus.Text = "";
}
}
void circleChange(object sender, CircleChangeEventArgs e)
{
lock (shapes)
{
Point p = detector.visited[detector.visited.Count - 1];
shapes.SetPixel(p.X, p.Y, Color.Blue);
pictureBox2.Image = (Image)shapes;
Thread.Sleep(10);
detector.Worker.ReportProgress((int)(e.percent * 100));
}
}
private void shapeDetection_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
algorithmProgress.Value = e.ProgressPercentage;
}
}
}
Update
What Nick said before worked fine. I added that to my CircleChange event and it works. Can somebody explain why the invoke makes it work instead of setting the picturebox2.Image to the shapes image? I mean I call it after I call setpixel, so I should be done modifying the image right?
void circleChange(object sender, CircleChangeEventArgs e)
{
Point p = detector.visited[detector.visited.Count - 1];
shapes.SetPixel(p.X, p.Y, Color.Blue);
Image copyForPictureBox = shapes.Clone() as Image;
BeginInvoke(new Action(() => pictureBox2.Image = copyForPictureBox));
Thread.Sleep(15);
detector.Worker.ReportProgress((int)(e.percent * 100));
}
It appears that you're operating on the shapes bitmap on a thread separate to the GUI thread. When you move the window the OnPaint routine will run which will also access the image.
What you need to do to solve this is operate on a separate bitmap in your worker thread, and then pass a copy of that to the GUI thread using Invoke on the form. That way you're guaranteed only to have one thread accessing the picture box image at a time.
Edit:
void MyThreadFunction( )
{
Bitmap localThreadImage;
...
Image copyForPictureBox = localThreadImage.Clone( ) as Image;
BeginInvoke( new Action( () => pictureBox.Image = copyForPictureBox ) );
....
}
So the idea is that you create a Bitmap on the thread which is only ever accessed by that thread (i.e. your background worker thread). And when you get to a point when you want to update the image in the PictureBox you invoke onto the the GUI thread using BeginInvoke (which doesn't block your worker thread) passing a copy of the Bitmap to the PictureBox.
Locking the shapes object at only one point in your application does not accomplish anything. You also use this bitmap to draw to the window, and my guess is that you are not locking it for drawing. You can either lock it in OnPaint as well, or use a different bitmap for manipulation and display.

generated Terrain wouldn't display in directX (C# )

Greetings
I genereated a terrain with this equation:
H(x,y) = (abs(sin(x * y)) + abs(sin(0,2 * x) + sin(0,4 * y)) + abs(cos(0,12 * x) + cos(0,47 * y))) * e^(0.005*(x+y))
Now, this gives me a mix of featuresize, and a nice slope. This works fine, when I plot it using scilab.
I tried to import this in a c# application.
The terrain is created in this code:
using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX;
using System.Collections.Generic;
namespace Kamera_eins
{
public partial class Terrain : Form
{
public double[,] DTM;
string response ;
public Terrain()
{
InitializeComponent();
response = "";
DTM = new double[2048/4,2048/4];
}
public void BoxTheSky()
{
}
public void BoxTheLand()
{
mesh();
surf();
}
public void begin()
{
}
public void mesh()
{
response = "";
int i = new int();
int j = new int();
i = 0;
j = 0;
for (i=0;i<2048/4 ;i++ ) {
for (j=0;j<2048/4 ;j++ ) {
DTM[i,j] = Math.Abs (Math.Sin (j*i)) + Math.Abs(Math.Sin(0.2*i) * Math.Sin(0.4*j) ) + Math.Abs(Math.Cos(0.12* i) * Math.Cos(0.47*j));
DTM[i,j] = Math.Pow(Math.E, (0.012* (i + j)));
}
}
response = "DTM mesh ready";
}
public void surf()
{
}
}
}
This is kept in a file called terrain.cs, and i make this a winform, because i plan to add a simple textbox, where i can later make some sort of realtime log of the process.
Now, there is another file, and in that file, intend to display this terrain. this second file goes as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Data;
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;
namespace Kamera_eins
{
public class viewport : Form
{
public Microsoft.DirectX.Direct3D.Device device = null;
public PresentParameters presentparameter = new PresentParameters();
public bool device_exists;
public bool show;
public int HEIGHT;
public int WIDTH;
public string paintDesc;
private float angle ;
private CustomVertex.PositionColored[] vertices;
public double[,] heightData;
private int[] indices;
private IndexBuffer ib;
private VertexBuffer vb;
private Microsoft.DirectX.DirectInput.Device keyb;
//public
public viewport()
{
this.ClientSize = new System.Drawing.Size(600, 600);
this.Text = "Terrain viewport";
WIDTH = 2048 / 4;
HEIGHT = 2048 / 4;
heightData = new double[HEIGHT,WIDTH];
keyb = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
keyb.SetCooperativeLevel(this, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);
keyb.Acquire();
presentparameter.Windowed = true;
presentparameter.SwapEffect = SwapEffect.Discard;
presentparameter.AutoDepthStencilFormat = DepthFormat.D16;
presentparameter.EnableAutoDepthStencil = true;
this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
try {
device = new Microsoft.DirectX.Direct3D.Device(0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentparameter);
device.DeviceLost += new EventHandler(this.InvalidateDeviceObjects);
device.DeviceReset += new EventHandler(this.RestoreDeviceObjects);
device.Disposing += new EventHandler(this.DeleteDeviceObjects);
device.DeviceResizing += new CancelEventHandler(this.EnvironmentResizing);
device_exists = true;
} catch (Exception DirectException) {
device_exists = false;
}
}
private void setcamera()
{
device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1f, 50f);
device.Transform.View = Matrix.LookAtLH (new Vector3(0, 0, 100), new Vector3(0, 0, 0) , new Vector3(0,0,1) );
device.RenderState.Lighting = false;
device.RenderState.FillMode = FillMode.WireFrame;
device.RenderState.CullMode = Cull.None;
}
public void declareVertex()
{
vb = new VertexBuffer(typeof(CustomVertex.PositionColored), HEIGHT*WIDTH, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default);
vertices = new CustomVertex.PositionColored[HEIGHT*WIDTH];
for (int x=0;x< WIDTH;x++) {
for (int y=0; y< HEIGHT;y++) {
vertices[x + y * WIDTH].Position = new Vector3(x, y, (float)heightData[x,y]);
int r = Convert.ToInt32(205 * heightData[x,y] / 200 );
if(r>254)
r = 254;
vertices[x + y * WIDTH].Color = Color.FromArgb( r , 120 , 120).ToArgb();
}
}
vb.SetData(vertices, 0, LockFlags.None);
}
public void declareIndex()
{
ib = new IndexBuffer(typeof(int), (WIDTH-1)*(HEIGHT-1)*6, device, Usage.WriteOnly, Pool.Default);
indices = new int[(WIDTH-1)*(HEIGHT-1)*6];
for (int x=0;x< WIDTH-1;x++) {
for (int y=0; y< HEIGHT-1;y++) {
indices[(x+y*(WIDTH-1))*6] = (x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*6+1] = (x+1)+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+2] = x+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+3] = (x+1)+(y+1)*WIDTH;
indices[(x+y*(WIDTH-1))*6+4] = x+y*WIDTH;
indices[(x+y*(WIDTH-1))*6+5] = x+(y+1)*WIDTH;
}
}
ib.SetData(indices, 0, LockFlags.None);
}
protected override void Dispose (bool disposing)
{
base.Dispose(disposing);
MessageBox.Show("");
}
protected virtual void InvalidateDeviceObjects(object sender, EventArgs e)
{
}
protected virtual void RestoreDeviceObjects(object sender, EventArgs e)
{
}
protected virtual void DeleteDeviceObjects(object sender, EventArgs e)
{
}
protected virtual void EnvironmentResizing(object sender, CancelEventArgs e)
{
}
public void run()
{
while(this.Created)
{
render();
setcamera();
// optional: loading the height using functional call:
// loadheight();
Application.DoEvents();
}
}
public void render()
{
if (device != null)
{
device.Clear(ClearFlags.Target, Color.Black, 1.0f, 0);
device.BeginScene();
//display terrain
device.VertexFormat = CustomVertex.PositionColored.Format;
device.SetStreamSource(0, vb, 0);
device.Indices = ib;
device.Transform.World = Matrix.Translation(-HEIGHT/2, -WIDTH/2, 0)*Matrix.RotationZ(angle) ;
device.DrawIndexedPrimitives(PrimitiveType.TriangleFan, 0, 0, WIDTH*HEIGHT, 0, indices.Length/3);
//turn off lights now
device.EndScene();
device.Present();
this.Invalidate();
readkeyboard();
}
}
void readkeyboard()
{
KeyboardState keys = keyb.GetCurrentKeyboardState();
if (keys[Key.Delete])
{
angle+=0.03f;
}
if (keys[Key.Next])
{
angle-=0.03f;
}
}
public void openport()
{
}
protected override void OnPaint(PaintEventArgs e)
{
render();
setcamera();
}
}
}
Now, yet a third file calls the world creation and display:
void MainFormLoad(object sender, EventArgs e)
{
world = new World();
world.setterrain();
}
the surf and box-somthing functions do not yet do anything.
All what i get now, is just a black window (the device.clear(... ) part) - i tried to adjust the camera .. no success
please help, i want to show the terrain in the window ....

Categories