Updating picturebox causes performance issues - c#

I have an app that contains a picture box which updates images from a live camera every time a new image gets loaded into the camera buffer. My problem is that whenever I'm getting this live feed, the whole app becomes very slow and (sometimes) unresponsive. I have a separate thread which basically does all the imaging steps and then puts the new image in the picture box. I'm kind of stuck on how to fix the issue and I'm wondering if anyone has any ideas? I'm not sure what kind of code you need but here is the ImageUpdated event that gets the image and sticks it on the PictureBox. Thanks for any help!
void CurrentCamera_ImageUpdated(object sender, EventArgs e)
{
try
{
lock (CurrentCamera.image)
{
if (CurrentCamera != null && CurrentCamera.image != null && !changeCam)
{
videoImage = CurrentCamera.videoImage;
if (CurrentCamera.videoImage != null && this.IsHandleCreated)
{
Bitmap tmp = new Bitmap(CurrentCamera.image.Width, CurrentCamera.image.Height);
//Creates a crosshair on the image
using (Graphics g = Graphics.FromImage(tmp))
{
g.DrawImage(CurrentCamera.image, new Point(0, 0));
g.DrawLine(crosshairPen, new Point(CurrentCamera.image.Width / 2, 0), new Point(CurrentCamera.image.Width / 2, (CurrentCamera.image.Height)));
g.DrawLine(crosshairPen, new Point(0, CurrentCamera.image.Height / 2), new Point((CurrentCamera.image.Width), CurrentCamera.image.Height / 2));
g.DrawEllipse(crosshairPen, (CurrentCamera.image.Width / 2) - crosshairRadius, (CurrentCamera.image.Height / 2) - crosshairRadius, crosshairRadius * 2, crosshairRadius * 2);
}
pictureBox1.BeginInvoke((MethodInvoker)delegate
{
pictureBox1.Image = tmp;
});
}
}
}
}
catch { }
}

In addition to the comment, I think that this might speed up a little bit. First, if the image is't set on the pictureBox1, then don't set another. Second, because of using pictureBox, it is optimized not to have flicker so you can draw the crosshair in your PaintEvent method handler for the pictureBox1. This is a little bit improved code that avoids double Bitmap drawing, then you should speed up as much as you can CurrentCamera_ImageUpdated method execution because new images come very fast, C# code can't handle that much drawing even on a high performance machine. Start from here, and keep on improving.
public Form1()
{
InitializeComponent();
pictureBox1.Paint += new PaintEventHandler(pictureBox1_Paint);
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (pictureBox1.Image != null)
{
int width = pictureBox1.Image.Width;
int height = pictureBox1.Image.Height;
e.Graphics.DrawLine(crosshairPen, new Point(width / 2, 0), new Point(width / 2, height));
e.Graphics.DrawLine(crosshairPen, new Point(0, pictureBox1.Image.Height / 2), new Point(width, height / 2));
e.Graphics.DrawEllipse(crosshairPen, (width / 2) - crosshairRadius, (height / 2) - crosshairRadius, crosshairRadius * 2, crosshairRadius * 2);
}
}
IAsyncResult result = null;
void CurrentCamera_ImageUpdated(object sender, EventArgs e)
{
try
{
lock (CurrentCamera.image)
{
if (CurrentCamera != null && CurrentCamera.image != null && !changeCam)
{
videoImage = CurrentCamera.videoImage;
if (CurrentCamera.videoImage != null && this.IsHandleCreated)
{
if (result != null && result.IsCompleted)
{
result = pictureBox1.BeginInvoke((MethodInvoker)delegate
{
pictureBox1.Image = CurrentCamera.videoImage;
});
}
}
}
}
}
catch { }
}

Related

Accessing control from another thread safely

I'm creating a program that draws points in a bitmap in memory (using system.drawing) and displays it in a PictureBox control, this way :
private void button_startGen_Click(object sender, EventArgs e) {
continueTask_generate = true;
Task task = Task.Run(() => {
while(continueTask_generate) {
for (int i = 0; i < Iterations; i++) {
gm.GenerateNextPoint();
}
UpdateBitmap();
Thread.Sleep(Time);
}
});
}
private void UpdateBitmap() {
pictureBox_bitmap.Image = gm.bitmap;
}
The way this works is that, when you press the button "start generation", it'll start generating points, and you'll see that generation in the PictureBox. It generates i points, and then updates the pictureBox, waits for t milliseconds, and does the process again, until you press a "stop" button.
Now, as I'm updating the pictureBox control from a different thread than the main one, I'll get those pesky "InvalidOperationException". I'm a noob in threading. I've tried both solutions in this page but none of them worked for me.
(For reference, this is my 'updated' UpdateBitmap() method for the first solution, which it didn't work:
private delegate void UpdateBitmapDelegate();
private void UpdateBitmap() {
if(pictureBox_bitmap.InvokeRequired) {
UpdateBitmapDelegate deg = new UpdateBitmapDelegate(UpdateBitmap);
pictureBox_bitmap.Invoke(deg);
}
else {
pictureBox_bitmap.Image = gm.bitmap;
}
}
Any help would be appreciated, as I'm making this program mainly to learn this kind of thing.
Edit:
I'm not sure if the content of gm.GenerateNextPoint() is relevant, but I'll post it too. GenerateNextPoint() calls this method:
private void DrawPoint(Point point, int radius, Color color) {
SolidBrush brush = new SolidBrush(color);
using (Graphics g = Graphics.FromImage(bitmap)) {
if (radius > 0 && radius < 3) {
g.FillRectangle(brush, point.X, point.Y, radius, radius);
}
else if (radius > 1) {
int radiusOffset = (int)(radius / 2f);
g.FillEllipse(brush, point.X - radiusOffset, point.Y - radiusOffset, radius, radius);
}
}
}
This method also interacts with gm.bitmap, as does UpdateBitmap().
Pls try this:
Add this to your function on the other thread-
if (InvokeRequired)
{
this.Invoke(new MethodInvoker(delegate
{ //your working code here }));
}
else
{
//the same code here
}

Xamarin MasterDetailPage memory leak with custom renderer

I am using a MasterDetailPage in Xamarin.Forms/Android with each item in the hamburger page representing a page. I change the detail page by calling:
Detail = new NavigationPage(new Pages.HomePage());
IsPresented = false;
What I have noticed is that the allocation for this page is somewhat large (it's requesting like 22mb). Not only that, but if I were to repeatedly press the sidebar's home button, I get an OutOfMemoryException after about 5 clicks. So something isn't clicking with the Garbage Collection.
The home page has a custom renderer that essentially composites an image using three png files. This works with minimal slowdown on my phone, but the emulator lags significantly when it is on screen, leading me to believe that it is causing performance issues.
public class GoalJarViewRenderer : ImageRenderer
{
Bitmap fillBmp = null;
Bitmap jarHud = null;
Paint font;
Paint fontBold;
string goalString = "";
string progString = "";
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
this.SetWillNotDraw(false);
base.OnElementChanged(e);
Element.PropertyChanged += SignalUpdate;
//Cache
if (fillBmp == null) fillBmp = BitmapFactory.DecodeResource(this.Context.Resources, Resource.Drawable.jar_fill);
if (jarHud == null) jarHud = BitmapFactory.DecodeResource(this.Context.Resources, Resource.Drawable.jar_hud);
if (font == null)
{
font = new Paint();
font.AntiAlias = true;
font.Color = Android.Graphics.Color.White;
font.TextSize = 50;
font.TextAlign = Paint.Align.Center;
}
if (fontBold == null)
{
fontBold = new Paint(font);
fontBold.FakeBoldText = true;
fontBold.TextSize = 80;
}
}
private void SignalUpdate(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
GoalJarView handle = (GoalJarView)this.Element;
goalString = String.Format("${0:#0.0#} / ${1:##.##}", handle.CurrentValue, handle.GoalValue);
progString = String.Format("{0:##0.00}%", (handle.CurrentValue / handle.GoalValue) * 100);
this.Invalidate();
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
//Draw the fill
double LID_FIX = 0.91;
GoalJarView handle = (GoalJarView)this.Element;
double fillRatio = Math.Min(1,((handle.CurrentValue * LID_FIX) / handle.GoalValue));
int srcStartY = (int)(fillBmp.Height - Math.Floor(fillBmp.Height * fillRatio));
int destStartY = (int)(canvas.Height - Math.Floor(canvas.Height * fillRatio));
Rect fillSrc = new Rect(0, srcStartY, fillBmp.Width, fillBmp.Height);
RectF fillDest = new RectF(0, destStartY, canvas.Width, canvas.Height);
canvas.DrawBitmap(fillBmp, fillSrc, fillDest, null);
//Draw the text container
canvas.DrawBitmap(jarHud, null, new Rect(0,0,canvas.Width,canvas.Height), null);
//Draw the Text
canvas.DrawText(progString, canvas.Width / 2, (canvas.Height / 2) - 20, fontBold);
canvas.DrawText(goalString, canvas.Width / 2, (canvas.Height / 2) + 30, font);
}
}
I was told that BitmapFactory.DecodeResource was incredibly taxing, so I moved that into the OnElementChanged rather than the draw function. I don't see anything else immediately taxing inside of Draw(), but I am new to Xamarin/Android and assume the problem is glaringly obvious to a seasoned developer.
I have tried creating local variables on the MasterDetailPage that stores pages so they don't need to be created every time an item is pressed, but this led to crashing when the page reopened, as the actual control class that stores the data bindings was null.
What is causing this memory leak, and how can I correct it?

Drawing lines on a panel

I try to make a graphics.
When I click on my label, I want to draw a line. It works, it draw my line but at the last point there is another line going at the left top corner.. I don't know why.
(It's useless, but it's for another project, I try to understand how works the drawing)
Here's my code :
public partial class Form1 : Form
{
Pen myPen = new Pen(Color.Blue);
Graphics g = null;
int start_x = 0, start_y;
Point[] points = new Point[1000];
int test = 0;
public Form1()
{
InitializeComponent();
start_y = canvas.Height / 2;
points[0] = new Point (start_x,start_y);
myPen.Width = 1;
}
private void drawLine()
{
g.DrawLines(myPen, points);
}
private void incrementation_Click(object sender, EventArgs e)
{
test = test + 1;
incrementation.Text = test.ToString();
if(test == 1)
{
points[1] = new Point(100, start_y);
}
if (test == 2)
{
points[test] = new Point(200, 90),new Point(220, 10);
}
if (test == 3)
{
points[test] = new Point(220, 10);
drawLine();
}
}
private void canvas_Paint(object sender, PaintEventArgs e)
{
g = canvas.CreateGraphics();
}
}
A couple of issues.
You don't assign any values to points after points[3].
Point is a structure and will have a value of [0,0] at all further elements
so your lines go there.. (all 996 of them ;-)
There is more you should change:
Do the drawing in the Paint event or trigger it from there.
Do not store the Paint e.Grahpics object. You can pass it out to use it, but don't try to hold on to it.
After adding or changing the points, write canvas.Invalidate() to trigger the Paint event. This will make your drawing persistent.
To learn about persistent drawing minimize & restore the form!
Also you should use a List<Point> instead of an array. This lets you add Points without having to decide on the number of Points you want to support..
To create a new Point you write something like this:
points.Add(new Point(100, start_y) );
To draw you then use this format in the Paint event::
e.Graphics.DrawLines(myPen, points.toArray());
In the constructor you're filling first point as
points[0] = new Point (start_x,start_y);
At this moment, start_x = 0 (since you're not assigned anything else to it after declaration int start_x = 0).
Then in incrementation_Click you're assigning points[1], points[2] and points[3], but you don't changing anywhere in your code points[0].
So when you calling g.DrawLines - first point will always be (0, canvas.Height / 2)
Aside from this:
You don't need to create graphics explicitly in _Paint event handler since it can accessed as e.Graphics.
It's better to move all paintings into canvas_Paint like:
private void canvas_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLines(myPen, points);
}
and in your _Click handler instead of calling drawLine you should only call canvas.Refresh()

Drawstring on a panel change when other panel move over

I draw a rectangle panel with every button click. After that add a line on the edge of the rectangle and text on the center. But when I drag a panel move over other panel. The panel string will change. Please advice. I can't upload an image. How can I upload an image like that can show my problem more clearly.
This link http://i3.photobucket.com/albums/y53/ctkhai/1-4.png show the gui of my software. The upper left and lower left are picturebox. User add a "box" when click on button once and the "box" will show on upper left. User can drag the box to lower right and arrange all the box.
now the problem is when user drag the new added "box" and move over some other "box", the text i draw on previous box will change. like this http://i3.photobucket.com/albums/y53/ctkhai/2-4.png.
Update: I try to create a class for the tag. but it won't work, the number change again. It is the way I create class for the tag and read is wrong? code like below
Product _box = new Product();
List<Panel>product = new List<Panel>();
public class Product
{
public float X { set; get; } //box coordinate
public float Y { set; get; } //box coordinate
public int rotate { set; get; }
public int entryP { set; get; }
public int boxName { set; get; }
}
private void button_RecAdd_Click(object sender, EventArgs e)
{
locX = pictureBox_conveyor.Left + (pictureBox_conveyor.Width / 2 - box_y / 2);
locY = pictureBox_conveyor.Top + (pictureBox_conveyor.Height / 2 - box_x / 2);
_box.boxName = panelBoxNo;
_box.entryP = 1;
_box.rotate = 0;
_box.X = locX;
_box.Y = locY;
Panel box = new Panel();
box.Location = new Point(locX, locY);
box.Name = "box" + panelBoxNo;
box.Tag = _box;
box.Size = new Size(box_y, box_x);
box.BackColor = boxColor;
pbW = box.Width;
pbH = box.Height;
box.MouseDown += panelBox_MouseDown;
box.MouseMove += panelBox_MouseMove;
box.Paint += new PaintEventHandler((s, m) =>
{
Graphics g = m.Graphics;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;]
Product b = box.Tag as Product;
string text = b.boxName.ToString();
SizeF textSize = m.Graphics.MeasureString(text, Font);
PointF locationToDraw = new PointF();
locationToDraw.X = (pbW / 2) - (textSize.Width / 2);
locationToDraw.Y = (pbH / 2) - (textSize.Height / 2);
g.DrawString(text, Font, Brushes.Black, locationToDraw);
g.DrawRectangle(new Pen(Color.Black), 0, 0, pbW - 1, pbH - 1);
g.DrawLine(drawLine, 0, 0, 0, pbH);
});
product.Add(box);
panel_pelletLayout.Controls.Add(box);
box.BringToFront();
label_boxNo.Text = panelBoxNo.ToString();
panelBoxNo++;
}
private void panelBox_MouseDown(object sender, MouseEventArgs e)
{
Panel p = sender as Panel;
if (e.Button == MouseButtons.Left)
{
xPos = e.X;
yPos = e.Y;
if (p != null)
{
activePnlBox = p.Name;
textBox_selectedName.Text = p.Name;
textBox_selectedX.Text = p.Left.ToString();
textBox_selectedY.Text = p.Top.ToString();
}
}
}
private void panelBox_MouseMove(object sender, MouseEventArgs e)
{
Panel p = sender as Panel;
if (p != null)
{
if (e.Button == MouseButtons.Left)
{
p.Left = ((e.X + p.Left - (p.Width / 2)) / gripGap) * gripGap;
p.Top = ((e.Y + p.Top - (p.Height / 2)) / gripGap) * gripGap;
textBox_selectedX.Text = p.Left.ToString();
textBox_selectedY.Text = p.Top.ToString();
}
}
}
Your Paint event handler has to be responsible for drawing everything each time. Your code looks like it only draws one object.
When you drag something over your box, the box becomes invalid and needs to be painted from scratch which means it erases everything and calls your Paint handler. Your Paint event handler then just draws one object.
I suspect that what you want to do is keep a data structure of each item you draw and then have a loop in your Paint event handler that will draw all the objects you add.
Don't use variables that you defined outside a loop, in the paint event. That might be your problem.
Try to paint ((Panel)s).Name. Does this work properly?
Your title has got all of us confused.. You don't draw Rectangles, you create new Panels on each ButtonClick, right?
The code for the Paint event is not quite right, though. Just like in any Paint event you should use the built-in Graphics object. And as Hans has noted, you should not destroy/dispose things you didn't create.
The main problem you describe seems to be that your boxes have to paint themselves without referring to their real numbers. You should store their numbers e.g. in their Tags..
(Or you could extract it from their Names, like you do it in the MouseDown!)
box[panelBoxNo].Name = "box" + panelBoxNo;
box[panelBoxNo].Tag = panelBoxNo; // < === !!
//..
box[panelBoxNo].Paint += new PaintEventHandler((s, m) =>
{
Graphics g = m.Graphics; // < === !!
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
string text = box[panelBoxNo].Tag.ToString(); // < ===
SizeF textSize = g.MeasureString(text, Font);
PointF locationToDraw = new PointF();
locationToDraw.X = (pbW / 2) - (textSize.Width / 2);
locationToDraw.Y = (pbH / 2) - (textSize.Height / 2);
g.DrawString(text, Font, Brushes.Black, locationToDraw);
g.DrawRectangle(new Pen(Color.Black), 0, 0, pbW - 1, pbH - 1);
g.DrawLine(drawLine, 0, 0, 0, pbH);
// g.Dispose(); // < === !!
// m.Graphics.DrawImageUnscaled(drawBox, new Point(0, 0)); // < === !!
// m.Dispose(); // < === !!
});
And, as I have noted you should only use arrays if you (or the code) really knows the number of elements. In your case a List<Panel> will be flexible to hold any number of elements without changing any other part of the code, except the adding. You can access the List just like an Array. (Which it is behind the scenes..)
Update: The way I see it now, your problems all are about scope in one way or another.
Scope in its most direct meaning is the part of your code where a variable is known and accessible. In a slightly broader meaning it is also about the time when it has the value your need.
Your original problem was of the latter kind: You accessed the partNo in thr Paint event of the Panels, when it had long changed to a new, probably higher value.
Your current problem is to understand the scope of the variables in the ButtonClick event.
Usually this is no problem; looking at the pairs of braces, the scope is obvious. But: Here we have a dynamically created Lambda event and this is completely out of the scope of the Click event!! Behind the scenes this Paint event is removed from the Click code, placed in a new event and replaced by a line that simply adds a delegate to the Paint handler just like any regular event.
So: nothing you declare in the ButtonClick is known in the Paint code!
To access these data you must place them in the Panel properties, in our case in the Tag and access them via casting from the Sender parameter s!
So you need to change this line in the Paint event
Product b = box.Tag as Product;
to something like this:
Product b = ( (Panel) s ).Tag as Product;

Stuck trying to get my c# text scroller to run smoothly

I'm doing a small project where my vc# application needs to include a text scroller / news ticker. Application will be running on 30+ screens showing off internal ad production at my workplace.
I've been googling and testing for a couple of months now but has yet to find / create a good solution where the movement is smooth and not choppy.
So my question is: is it possible to create perfect smooth scroll motion in c# or do I need to go about it some other way?
The code I'm using at the moment, part of a sample I edited, is running almost smooth except it seems to lag every 100 ms or so.
Here is the code I'm using:
namespace ScrollDemo1
{
public partial class NewsTicker : Panel
{
private Timer mScroller;
private int mOffset;
private string mText;
private Size mPixels;
private Bitmap mBuffer;
public NewsTicker()
{
mScroller = new Timer();
mScroller.Interval = 1;
mScroller.Enabled = false;
mScroller.Tick += DoScroll;
}
[Browsable(true)]
public override string Text
{
get { return mText; }
set
{
mText = value;
mScroller.Enabled = mText.Length > 0;
mPixels = TextRenderer.MeasureText(mText, this.Font);
mOffset = this.Width;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
}
private void DoScroll(object sender, EventArgs e)
{
mOffset -= 1;
if (mOffset < -mPixels.Width) mOffset = this.Width;
Invalidate();
Update();
}
protected override void OnPaint(PaintEventArgs e)
{
if (mBuffer == null || mBuffer.Width != this.Width || mBuffer.Height != this.Height)
mBuffer = new Bitmap(this.Width, this.Height);
Graphics gr = Graphics.FromImage(mBuffer);
Brush bbr = new SolidBrush(this.BackColor);
Brush fbr = new SolidBrush(this.ForeColor);
Bitmap bmp = global::ScrollDemo1.Properties.Resources.text_bg1;
TextureBrush tb = new TextureBrush(bmp);
int iLoc = (this.Height / 2) - (mPixels.Height / 2);
//Console.WriteLine(iLoc.ToString());
//gr.FillRectangle(bbr, new Rectangle(0, 0, this.Width, this.Height));
gr.FillRectangle(tb, new Rectangle(0, 0, this.Width, this.Height));
gr.DrawString(mText, this.Font, fbr, mOffset, iLoc);
e.Graphics.DrawImage(mBuffer, 0, 0);
bbr.Dispose();
fbr.Dispose();
gr.Dispose();
}
}
}
I'd suggest you go with WPF. It has rich and easy to use support for animations and tends to be be very smooth since it is Direct3D based.

Categories