I'm using a picture box in my C# code with a given image.
i do all the paint actions in the Pain event (see code below).
when the image needs to be updated (ading dots on it) i call Invalidate to make it repaint. however after some time the image and the dots dissapear and are replaced by e big red X in a red border filling up the picture box.
note that the image is saved in the save folder as the exe file. and is appearing ok at first (same with the dots).
what am i doing wrong?
Crossthread problem?
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
setImage = Image.FromFile("IMG_1612.png");
Pen p = new Pen(Color.Red);
var g = Graphics.FromImage(setImage);//e.Graphics;
g.DrawImage(setImage,0,0);
foreach (Circles element in _circles)
{
g.FillEllipse(new SolidBrush(element.color), element.Punt.X, element.Punt.Y, _CIRCLESIZE, _CIRCLESIZE);
}
this.pictureBox1.Image = setImage;
g.Dispose();
}
delegate void PicturBoxUpdate(Control control);
private void UpdatePictureBox(Control control)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (control.InvokeRequired)
{
PicturBoxUpdate d = new PicturBoxUpdate(UpdatePictureBox);
this.Invoke(d, new object[] { control});
}
else
{
control.Invalidate();
control.Update();
}
}
private void DataProcessing(string data)
{
data = data.Replace('<', ' ').Replace('>',' ').Trim();
string[] processingdata = data.Split(';');
Circles tempCircle;
for (int i = 0; i < processingdata.Length-2;i++)
{
_data[i] = Convert.ToByte(processingdata[i], 16);
BitArray localdata = new BitArray(BitConverter.GetBytes(_data[i]).ToArray());
switch(i)
{
case 0:
if (_data[i] == 0xAA)
{
tempCircle = _circles[5];
tempCircle.color = Color.Green;
_circles[5] = tempCircle;
tempCircle = _circles[4];
tempCircle.color = Color.Green;
_circles[4] = tempCircle;
}
else
{
if (localdata.Get(1) & !localdata.Get(2))
{
tempCircle = _circles[5];
tempCircle.color = Color.Orange;
_circles[5] = tempCircle;
tempCircle = _circles[4];
tempCircle.color = Color.Orange;
_circles[4] = tempCircle;
}
if (localdata.Get(3) & !localdata.Get(0))
{
tempCircle = _circles[5];
tempCircle.color = Color.Blue;
_circles[5] = tempCircle;
}
if (localdata.Get(5) & !localdata.Get(4))
{
tempCircle = _circles[4];
tempCircle.color = Color.Blue;
_circles[4] = tempCircle;
}
if (localdata.Get(7) & !localdata.Get(6))
{
tempCircle = _circles[4];
tempCircle.color = Color.Purple;
_circles[4] = tempCircle;
tempCircle = _circles[5];
tempCircle.color = Color.Purple;
_circles[5] = tempCircle;
}
}
break;
case 1:
break;
default:
break;
}
}
UpdatePictureBox(pictureBox1);
GC.Collect();
}
You definitely shouldn't be using Image.FromFile() in the Paint() event like that; set it once and simple draw your circles using the supplied Graphics in the Paint() event:
private Image setImage;
private void Form1_Load(object sender, EventArgs e)
{
setImage = Image.FromFile("IMG_1612.png");
pictureBox1.Image = setImage;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
foreach (Circles element in _circles)
{
using (SolidBrush B = new SolidBrush(element.Color))
{
g.FillEllipse(B, element.Punt.X, element.Punt.Y, _CIRCLESIZE, _CIRCLESIZE);
}
}
}
Related
Me and my buddy have been working on this magnifier application and we cannot make it work the way we want it.
The way we would like it to work:
Open app.
Move mouse to area you want magnified.
Hit enter.
Magnifying window moves to (offset) location of mouse and keeps updating that window for that specific location.
Hit enter again to move window to new cursor location.
Right now once i hit enter, the window follows the mouse because it goes into a for loop where it grabs "Cursor.Position". I've tried to save the Cursor.Position value at the "OnkeyDown" event and use it inside the timer loop but that won't work since it "does not exist in current context".
Can anyone see how i can do this?
Thanks in advance!
/Morten
/* O-button zooms out
* I-button zooms in
* Esc-button exits app
* Enter moves magnifying window to new location (doesn't work)
*/
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Magnifier
{
public partial class Form1 : Form
{
Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
PictureBox pictureBox1 = new PictureBox();
int zoom = 3; //zoom level
public bool NewZoomLocation = false;
public Form1()
{
{
InitializeComponent();
pictureBox1.Dock = DockStyle.Fill;
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
Controls.Add(pictureBox1);
FormBorderStyle = FormBorderStyle.None;
Timer timer = new Timer();
timer.Interval = 100;
timer.Tick += timer_Tick;
timer.Start();
}
void timer_Tick(object sender, EventArgs e)
{ var position = Cursor.Position;
int xlocation = position.X;
int ylocation = position.Y;
{
try
{
var graphics = Graphics.FromImage(printscreen as Image);
graphics.CopyFromScreen(0, 0, 0, 0, printscreen.Size);
GC.Collect(); // Force the garbage collector (deals with memory leak)
if (NewZoomLocation == true)
{
var lensbmp = new Bitmap(50, 50); //Bitmap for Zoom window
var i = 0;
var j = 0;
for (int row = xlocation - 25; row < xlocation + 25; row++)
{
j = 0;
for (int column = ylocation - 25; column < ylocation + 25; column++)
{
lensbmp.SetPixel(i, j, printscreen.GetPixel(row, column));
j++;
}
i++;
}
this.pictureBox1.Image = new Bitmap(lensbmp, lensbmp.Width * zoom, lensbmp.Height * zoom);
Size = pictureBox1.Image.Size;
Left = xlocation - 45 * (zoom); //Horisontal position of final zoom window
Top = ylocation + 30; //Vertical position of final zoom window
TopMost = true;
}
}
catch //(Exception ex)
{
//MessageBox.Show(ex.Message);
}
}
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyValue == 73) // I-button to zoom in
zoom++;
else if (e.KeyValue == 79) // O-button to zoom in
zoom--;
else if (e.KeyValue == 27) // Esc-button to exit
{
Close();
Dispose();
}
else if (e.KeyValue == 13) // Enter-button to choose zoon area
{
NewZoomLocation = true;
}
base.OnKeyDown(e);
}
}
}
I'm not really sure what you want to achieve here, however this should get you in a better place.
First thing first. The use of GC.Collect its because you are trying to plug a memory leak, if you ever create an image, dispose of it.
Given some globals
private readonly PictureBox pictureBox1 = new PictureBox();
private Bitmap _lastBmp = new Bitmap(300, 300);
private Point _position;
public bool NewZoomLocation;
private int zoom = 3; //zoom level
Constructor
public Form1()
{
InitializeComponent();
pictureBox1.Dock = DockStyle.Fill;
pictureBox1.BorderStyle = BorderStyle.FixedSingle;
Controls.Add(pictureBox1);
FormBorderStyle = FormBorderStyle.None;
KeyPreview = true;
Size = _lastBmp.Size;
TopMost = true;
var timer = new Timer();
timer.Interval = 100;
timer.Tick += timer_Tick;
timer.Start();
}
Cleanup
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_lastBmp.Dispose();
_lastBmp = null;
}
Keydown
protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
base.OnPreviewKeyDown(e);
switch (e.KeyCode)
{
case Keys.Enter:
NewZoomLocation = true;
_position = Cursor.Position;
break;
case Keys.Up:
zoom++;
break;
case Keys.Down:
zoom--;
break;
case Keys.Escape:
Close();
break;
}
}
Timer
private void timer_Tick(object sender, EventArgs e)
{
if (NewZoomLocation)
{
var w = _lastBmp.Size.Width / zoom;
var h = _lastBmp.Size.Height / zoom;
var x = _position.X - w / 2;
var y = _position.Y - h / 2;
var size = new Size(w, h);
using (var screen = new Bitmap(size.Width, size.Height))
{
using (var g = Graphics.FromImage(screen))
{
g.CopyFromScreen(new Point(x, y), Point.Empty, size);
}
// resize
using (var g = Graphics.FromImage(_lastBmp))
{
g.DrawImage(screen, new Rectangle(new Point(), _lastBmp.Size), new Rectangle(0, 0, w, h), GraphicsUnit.Pixel);
}
}
pictureBox1.Image = _lastBmp;
}
}
There is a lot more that can be done with this, however it should get you started. There is no memory leak anymore, it only grabs a screen shot of what it needs so will be faster.
Good luck
I have been tasked with creating a somewhat hierarchical datagridview for my company. I heavily modified one from Syed Shanu that is posted here https://www.codeproject.com/Articles/848637/Nested-DataGridView-in-windows-forms-csharp. I'm almost done (data loads properly, etc.), however I cannot for the life of me figure out how to get the detail grid to move when I scroll. It's a drawn on rectangle and I'm looking for a way to somehow bind it to the master grid so it scrolls up and down with the regular grid. Any help would be appreciated. Here is the code that draws the rectangle:
private void masterDGVs_CellContentClick_Event(object sender, DataGridViewCellEventArgs e)
{
DataGridViewImageColumn cols = (DataGridViewImageColumn)MasterDGVs.Columns[0];
MasterDGVs.Rows[e.RowIndex].Cells[0].Value = Image.FromFile(#"expand.png");
if (e.ColumnIndex == gridColumnIndex)
{
if (ImageName == #"expand.png")
{
DetailDGVs.Visible = true;
ImageName = #"toggle.png";
MasterDGVs.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = Image.FromFile(ImageName);
String FilterExpression = MasterDGVs.Rows[e.RowIndex].Cells[FilterColumnName].Value.ToString();
MasterDGVs.Controls.Add(DetailDGVs);
Rectangle DGVRectangle = MasterDGVs.GetCellDisplayRectangle(1, e.RowIndex, true);
DetailDGVs.Size = new Size(MasterDGVs.Width - 48, DetailDGVs.PreferredSize.Height - 16);
DetailDGVs.Location = new Point(DGVRectangle.X, DGVRectangle.Y + 20);
DataView detailView = new DataView(DetailGridDT);
detailView.RowFilter = FilterColumnName + " = '" + FilterExpression + "'";
foreach (DataGridViewRow row in DetailDGVs.Rows)
{
if (row.Cells[5].Value.ToString() == "Error")
{
row.Cells[5].Style.ForeColor = Color.DarkRed;
}
else if (row.Cells[5].Value.ToString() == "Processed and Complete")
{
row.Cells[5].Style.ForeColor = Color.Green;
}
else
{
row.Cells[5].Style.ForeColor = Color.Yellow;
}
}
}
else
{
ImageName = #"expand.png";
MasterDGVs.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = Image.FromFile(ImageName);
DetailDGVs.Visible = false;
}
}
else
{
DetailDGVs.Visible = false;
}
}
I have sort of working by adding:
MasterDGVs.MouseWheel += new MouseEventHandler(DetailDGV_Scroll);
DetailDGVs.MouseWheel += new MouseEventHandler(MasterDGV_Scroll);
and
private void DetailDGV_Scroll(object sender, MouseEventArgs e)
{
int scale = e.Delta * SystemInformation.MouseWheelScrollLines / 5;
DetailDGVs.Top = DetailDGVs.Top + scale;
}
private void MasterDGV_Scroll(object sender, MouseEventArgs e)
{
int scale = e.Delta * SystemInformation.MouseWheelScrollDelta / 5;
MasterDGVs.Top = MasterDGVs.Top - scale;
}
public static Boolean TextBoxValidation(TextBox txt, String AdditionalMsg)
{
if (txt.Text.Trim() == "")
{
MessageBox.Show("Please Enter " + AdditionalMsg);
return false;
}
return true;
}
This is my code; when the user does not fill some entry then a message is shown. I want something more creative: when the user does not fill some entry into the textbox, a red border blinks around my textbox and a message is shown to user just like a tooltip.
Refer to the picture I have uploaded:
I kind of wanted something like that for a while and this is what I come up with:
Since you can't set border color for TextBox, I made a UserControl with textbox inside:
public partial class UCTextBoxCustomcs : UserControl
{
private ToolTip _errorToolTip;
// keep original background color so you can change it when txet value is OK
private Color _orgBgColor;
public new Color BackColor
{
get { return _orgBgColor; }
set
{
base.BackColor = value;
_orgBgColor = value;
}
}
public new string Text
{
get { return this.txbContent.Text; }
set { this.txbContent.Text = value; }
}
public Color InvalidBgColor { get; set; }
private bool _IsValid;
public bool IsValid
{
get { return _IsValid; }
set
{
_IsValid = value;
if (value)
{
base.BackColor = _orgBgColor;
_errorToolTip.SetToolTip(this.txbContent, "");
_errorToolTip.ShowAlways = false;
_errorToolTip.Hide(this.txbContent);
}
else
{
base.BackColor = InvalidBgColor;
_errorToolTip.ShowAlways = true;
this._errorToolTip.BackColor = InvalidBgColor;
_errorToolTip.Show(this.ErrorText, this.txbContent,this.txbContent.Width +3 ,0);
}
}
}
private string _ErrorText;
public string ErrorText
{
get
{
return _ErrorText;
}
set
{
_ErrorText = value;
if (value == null || value.Length == 0) IsValid = true;
else IsValid = false;
}
}
public UCTextBoxCustomcs()
{
this._errorToolTip = new ToolTip();
// BackColor in ToolTip is ignored, so if you want to change it,
// you have to draw it yourself
this._errorToolTip.OwnerDraw = true;
_errorToolTip.Draw += new DrawToolTipEventHandler(_errorToolTip_Draw);
_errorToolTip.Popup += new PopupEventHandler(_errorToolTip_Popup);
// white background so it looks like TextBox
this.BackColor = Color.White;
InitializeComponent();
this.txbContent.BorderStyle = BorderStyle.None;
// Intelisense tells you this property isn't there, but it is
// you have to set it to false so TextBox height can be changed
// when MultiLine is set to false
this.txbContent.AutoSize = false;
this.txbContent.Multiline = false;
// Leave 1 pixel around TextBox for pseudo-border
this.Padding = new Padding(1);
this.txbContent.Dock = DockStyle.Fill;
this.InvalidBgColor = Color.Red;
this.IsValid = true;
}
void _errorToolTip_Popup(object sender, PopupEventArgs e)
{
using (Font f = new Font("Calibri", 9))
{
Size ttSize = TextRenderer.MeasureText(
_errorToolTip.GetToolTip(e.AssociatedControl), f);
e.ToolTipSize = new Size(ttSize.Width + 6, ttSize.Height + 6);
}
}
void _errorToolTip_Draw(object sender, DrawToolTipEventArgs e)
{
// In this case a simple rectangle is drawn, but you can draw whatever you want
// Draw the custom background.
e.Graphics.FillRectangle(new SolidBrush(this.InvalidBgColor), e.Bounds);
// Draw the standard border.
e.DrawBorder();
// Draw the custom text.
// The using block will dispose the StringFormat automatically.
using (StringFormat sf = new StringFormat())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.None;
sf.FormatFlags = StringFormatFlags.NoWrap;
using (Font f = new Font("Calibri", 9))
{
Rectangle textBounds = new Rectangle(
e.Bounds.Left+3,
e.Bounds.Top+3,
e.Bounds.Width-6,
e.Bounds.Height-6);
e.Graphics.DrawString(e.ToolTipText, f,
SystemBrushes.ActiveCaptionText, e.Bounds, sf);
}
}
}
protected override void OnValidating(CancelEventArgs e)
{
this.ValidateChildren();
base.OnValidating(e);
}
}
How to use:
private void ucTextBoxCustomcs1_Validating(object sender, CancelEventArgs e)
{
if (ucTextBoxCustomcs1.Text.Length == 0)
{
ucTextBoxCustomcs1.ErrorText = "Cant be empty";
}
else ucTextBoxCustomcs1.ErrorText = null;
}
It looks like this:
I am doing OCR application. I have this error when I run the system which the system will save the picturebox3.image into a folder.
//When user is selecting, RegionSelect = true
private bool RegionSelect = false;
private int x0, x1, y0, y1;
private Bitmap bmpImage;
private void loadImageBT_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog open = new OpenFileDialog();
open.InitialDirectory = #"C:\Users\Shen\Desktop";
open.Filter = "Image Files(*.jpg; *.jpeg)|*.jpg; *.jpeg";
if (open.ShowDialog() == DialogResult.OK)
{
singleFileInfo = new FileInfo(open.FileName);
string dirName = System.IO.Path.GetDirectoryName(open.FileName);
loadTB.Text = open.FileName;
pictureBox1.Image = new Bitmap(open.FileName);
bmpImage = new Bitmap(pictureBox1.Image);
}
}
catch (Exception)
{
throw new ApplicationException("Failed loading image");
}
}
//User image selection Start Point
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
RegionSelect = true;
//Save the start point.
x0 = e.X;
y0 = e.Y;
}
//User select image progress
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
//Do nothing it we're not selecting an area.
if (!RegionSelect) return;
//Save the new point.
x1 = e.X;
y1 = e.Y;
//Make a Bitmap to display the selection rectangle.
Bitmap bm = new Bitmap(bmpImage);
//Draw the rectangle in the image.
using (Graphics g = Graphics.FromImage(bm))
{
g.DrawRectangle(Pens.Red, Math.Min(x0, x1), Math.Min(y0, y1), Math.Abs(x1 - x0), Math.Abs(y1 - y0));
}
//Temporary display the image.
pictureBox1.Image = bm;
}
//Image Selection End Point
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
// Do nothing it we're not selecting an area.
if (!RegionSelect) return;
RegionSelect = false;
//Display the original image.
pictureBox1.Image = bmpImage;
// Copy the selected part of the image.
int wid = Math.Abs(x0 - x1);
int hgt = Math.Abs(y0 - y1);
if ((wid < 1) || (hgt < 1)) return;
Bitmap area = new Bitmap(wid, hgt);
using (Graphics g = Graphics.FromImage(area))
{
Rectangle source_rectangle = new Rectangle(Math.Min(x0, x1), Math.Min(y0, y1), wid, hgt);
Rectangle dest_rectangle = new Rectangle(0, 0, wid, hgt);
g.DrawImage(bmpImage, dest_rectangle, source_rectangle, GraphicsUnit.Pixel);
}
// Display the result.
pictureBox3.Image = area;
** ERROR occuer here!!!!!**
area.Save(#"C:\Users\Shen\Desktop\LenzOCR\TempFolder\tempPic.jpg"); // error line occcur
singleFileInfo = new FileInfo("C:\\Users\\Shen\\Desktop\\LenzOCR\\TempFolder\\tempPic.jpg");
}
private void ScanBT_Click(object sender, EventArgs e)
{
var folder = #"C:\Users\Shen\Desktop\LenzOCR\LenzOCR\WindowsFormsApplication1\ImageFile";
DirectoryInfo directoryInfo;
FileInfo[] files;
directoryInfo = new DirectoryInfo(folder);
files = directoryInfo.GetFiles("*.jpg", SearchOption.AllDirectories);
var processImagesDelegate = new ProcessImagesDelegate(ProcessImages2);
processImagesDelegate.BeginInvoke(files, null, null);
//BackgroundWorker bw = new BackgroundWorker();
//bw.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
//bw.RunWorkerAsync(bw);
//bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}
private void ProcessImages2(FileInfo[] files)
{
var comparableImages = new List<ComparableImage>();
var index = 0x0;
foreach (var file in files)
{
if (exit)
{
return;
}
var comparableImage = new ComparableImage(file);
comparableImages.Add(comparableImage);
index++;
}
index = 0;
similarityImagesSorted = new List<SimilarityImages>();
var fileImage = new ComparableImage(singleFileInfo);
for (var i = 0; i < comparableImages.Count; i++)
{
if (exit)
return;
var destination = comparableImages[i];
var similarity = fileImage.CalculateSimilarity(destination);
var sim = new SimilarityImages(fileImage, destination, similarity);
similarityImagesSorted.Add(sim);
index++;
}
similarityImagesSorted.Sort();
similarityImagesSorted.Reverse();
similarityImages = new BindingList<SimilarityImages>(similarityImagesSorted);
var buttons =
new List<Button>
{
ScanBT
};
if (similarityImages[0].Similarity > 70)
{
con = new System.Data.SqlClient.SqlConnection();
con.ConnectionString = "Data Source=SHEN-PC\\SQLEXPRESS;Initial Catalog=CharacterImage;Integrated Security=True";
con.Open();
String getFile = "SELECT ImageName, Character FROM CharacterImage WHERE ImageName='" + similarityImages[0].Destination.ToString() + "'";
SqlCommand cmd2 = new SqlCommand(getFile, con);
SqlDataReader rd2 = cmd2.ExecuteReader();
while (rd2.Read())
{
for (int i = 0; i < 1; i++)
{
string getText = rd2["Character"].ToString();
Action showText = () => ocrTB.AppendText(getText);
ocrTB.Invoke(showText);
}
}
con.Close();
}
else
{
MessageBox.Show("No character found!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
Since it has been a while, I'm hoping you found your answer, but I'm going to guess that you needed to set the file format when you're saving a jpeg:
area.Save(#"C:\Users\Shen\Desktop\LenzOCR\TempFolder\tempPic.jpg",System.Drawing.Imaging.ImageFormat.Jpeg);
Past that, I can't remember if the picturebox control is double buffered or not which could be the problem (if it's not, you might not be able to access it for saving purposes while it is being rendered, but if you make a copy of area before setting the picturebox3.Image property that would fix that issue):
Bitmap SavingObject=new Bitmap(area);
picturebox3.Image=area;
SavingObject.Save(#"C:\Users\Shen\Desktop\LenzOCR\TempFolder\tempPic.jpg",System.Drawing.Imaging.ImageFormat.Jpeg);
Anyway, I hope you ended up finding your solution (considering it's been a couple months since this was posted).
This looks like a copy of this question:
c# A generic error occurred in GDI+
Same code, same error, same author.
Can't see any difference. But maybe I'm missing something.
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.