I'm developing a screen-sharing app which runs a loop constantly and receives small frames from a socket. The next step is to draw them into the PictureBox.
Of course, I use thread because I don't want to freeze the ui.
This is my code:
Bitmap frame = byteArrayToImage(buff) as Bitmap;//a praticular bitmap im getting from a socket.
Bitmap current = (Bitmap)pictureBox1.Image;
var graphics = Graphics.FromImage(current);
graphics.DrawImage(frame, left, top);//left and top are two int variables of course.
pictureBox1.Image = current;
But now I'm getting an error:
Object is already in use elsewhere.
in this line var graphics = Graphics.FromImage(current);
Tried to Clone it, Create a New Bitmap(current). still no success.
Invalidate() your PictureBox so it redraws itself:
Bitmap frame = byteArrayToImage(buff) as Bitmap;
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawImage(frame, left, top);
}
pictureBox1.Invalidate();
If you need it to be thread safe, then:
pictureBox1.Invoke((MethodInvoker)delegate {
Bitmap frame = byteArrayToImage(buff) as Bitmap;
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawImage(frame, left, top);
}
pictureBox1.Invalidate();
});
Related
I am trying to write a simple C# Windows App to display streaming video from a local USB webcam. I am using AForge.NET, FilterInfoCollection, VideoCaptureDevice etc to put video stream into a standard PictureBox. As is, it works fine. However, I need to downscale the videostream. When I try that, FPS drops significantly and becomes even lower as the app continues to run. My rescale routine is as follows (it aims to maintain aspect ratio and zoom in by preserving height):
public Image ResizeImage(Image img, int target_width, int height)
{
int width = (int)(height * img.Width / (float)img.Height);
Bitmap b = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(img, (target_width - width)/2, 0, width, height);
}
return b;
}
And it is called by:
private void video_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
Invoke(new Action(() =>
{
picVideo.Image = ResizeImage((Bitmap)eventArgs.Frame.Clone(), picVideo.Width, picVideo.Height); // rescaled stream
//picVideo.Image = (Bitmap)eventArgs.Frame.Clone(); // unscaled stream
}));
}
Setup code is this:
filterInfoCollection = new FilterInfoCollection(FilterCategory.VideoInputDevice);
videoCaptureDevice = new VideoCaptureDevice(filterInfoCollection[frmMain.Camera].MonikerString);
videoCaptureDevice.NewFrame += video_NewFrame;
videoCaptureDevice.Start();
However, I have this old 3rd party EXE that runs fine and is able to rescale the same webcam videostream as I resize its window. So it looks like this rescaling routine is too slow.
So, is there a faster rescaling method? I basically need to achieve downscaling from FullHD to 1024*768. I don't really need this to be high-quality.
Problem completely solved by adding
g.InterpolationMode = InterpolationMode.NearestNeighbor;
before g.DrawImage()
I am drawing images in a C# Winforms panel with:
private void DrawPanel_Paint(object sender, PaintEventArgs e)
{
DrawOnPanel(e.Graphics);
}
The called method takes an existing image from my resources (myImage), gives it to another method which resizes the image and returns the resized image so it can be drawn.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = ResizeImage(Resources.myImage);
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
The resize image function is:
public Bitmap ResizeImage(Bitmap image)
{
int scale = 3;
var destImage= new Bitmap(image.Width * scale, image.Height * scale);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
graphics.SmoothingMode = SmoothingMode.HighSpeed;
graphics.PixelOffsetMode = PixelOffsetMode.None;
using var wrapMode = new ImageAttributes();
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
return destImage;
}
The program keeps calling DrawPanel.Invalidate() in its loop.
I am detecting a memory leak each time DrawPanel.Invalidate() is called. The memory consumption is rising steadily until the GC takes care of it. While this isn't a game breaking problem, I'm still wondering where and how should I dispose of my objects in the above code.
I tried using using var _resizedImage = ResizeImage(Resources.myImage); in the above DrawOnPanel method but the program returns an error System.ArgumentException: 'Parameter is not valid.'. If I remove using there is no error.
Every time that ResizeImage is called, you create a new Bitmap. This Bitmap should be Disposed as soon as you know that it is not needed anymore. It seems that you don't need the resized image after you've drawn it on the Graphics. Therefore I suggest the following change:
public static void DrawOnPanel(Graphics g)
{
using (Image_resizedImage = ResizeImage(Resources.myImage))
{
g.DrawImage(_resizedImage, destX, destY);
}
// resizedImage is Disposed
// ... several other images resized & manipulated then drawn
}
Room for improvement
Your current design will create a new Resized Bitmap every time DrawOnPanel is called. It seems to me that most of time that Resources.myImage is resized the resized Bitmap will be the same. Why don't you just remember it until the parameters that influence the resized Bitmap are changed?
If I look at the code it seems that you always create the same resized image from an original image. So you could even put all your resized images into one Dictionary for fast lookup:
// get all Image resources that you will resize and put them in a Dictionary
// Key original Bitmap, Value: resized Bitmap
private Dictionary<Bitmap, BitMap> images = this.FetchImages()
.ToDictionary(
// Parameter keySelector: key is original image
image => image,
// Parameter valueSelector: value is the resized image
imgage => ResizeImage(original));
Displaying the resized images will now be much faster: only a Lookup.
public static void DrawOnPanel(Graphics g)
{
var _resizedImage = this.Images[Resources.myImage];
g.DrawImage(_resizedImage, destX, destY);
// ... several other images resized & manipulated then drawn
}
Don't forget to Dispose the bitmaps when your Form is Closed, or at its latest when your Form is Disposed:
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var resizedImage in images.Values)
resizedImage.Dispose();
}
}
I have a grid already drawn to a picturebox and I wish to draw a rectangle in the cell the mouse is clicked in. I'm trying to do this by drawing a rectangle in the top left corner of the cell and having it fill the entire cell.
However when I click the grid disappears.
How do I make it so the grid stays?
and is there an obvious better method for doing this?
The only way to redraw the grid is by pressing the button again but I want it to stay there.
Thanks.
You need to create a Graphics
Image bm;// Or Bitmap
using (var graphics = Graphics.FromImage(bm))
{
Draw(rects, graphics);
}
private static void Draw(List<Rectangle> rectangles, Graphics graphics)
{
Pen redPen = new Pen(Color.Red, 1);
foreach (var rect in rectangles)
{
graphics.DrawRectangle(redPen, rect);
}
}
At every mouse click in the mouse_click event at first you dispose the image of the picture box and then again assign the bitmap to the picturebox image otherwise you might face out of memory exception if you click on the grid too many times
private void InitializePictureBoxImage(PictureBox pictureBox)
{
if(pictureBox.Image != null)
{
var image = pictureBox.Image; // cache it to dispose
pictureBox.Image = null; // don't dispose an used object
image.Dispose(); // and dispose of it
}
Bitmap bitmap = new Bitmap(pictureBox.Width, pictureBox.Height);
pictureBox.Image = bitmap; // assign bitmap to image
}
Then call the method in the mouse_click event which you used to draw the picture box when you press the Generate Grid button in your form. Afterwards use graphics inside the mouse_click event to colour the grid which you pressed taking the X-Y coordinate from MouseEventArgs.
Altogether I think it will be something like this
private void Mouse_ClickEvent(object sender, MouseEventArgs e)
{
InitializePictureBoxImage(pictureBox);
DrawThePictureBoxImage(pictureBox);
using (Graphics graphics = Graphics.FromImage(pictureBox.Image))
{
Color color = Color.Blue;
color = Color.FromArgb(150, color.R, color.G, color.B); // lower the opacity so the writing in the grid is visible
var brush = new SolidBrush(color);
// Calculate the starting X-Y coordinate and Width,Height of the grid to color
graphics.FillRectangle(brush, startX, startY,
width, height);
}
Suppose i have an PictureBox with image assigned to it, and i just want to draw a smaller bitmap above it.
This is my code:
Bitmap a = Getimage();//just a small function generates a new image.
Bitmap f = (Bitmap) pictureBox1.Image;//getting the picturebox image.
Graphics g = Graphics.FromImage(f);
g.DrawImage(a,150,200);//draws a on f at (150,200).
PictureBox1.Image=f;
However, in my program it runs on a loop in a seperated thread, so im getting an Error:
Object is in use elsewhere
Is there any way to draw directly to the Picturebox itself? instead of getting it's bitmap and draw, and assign again? Or at least how may i solve the above exception?
Thanks.
this will help you
public Form1()
{
InitializeComponent();
new Thread(MyDraw).Start();
}
private void MyDraw()
{
while(true)
{
Invoke(new Action(DrawItem));
Thread.Sleep(1000);
}
}
private void DrawItem()
{
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawString("Hello", new Font("Arial", 20), Brushes.Yellow, PointF.Empty);
}
}
in Winform application, i have a user control; on which shapes like rectangle,circle etc. are drawn.
i am trying to take snapshot of the same by using DrawToBitmap() method.
i have a Bitmap of fixed size(300 x 300) and user control is of size (600 x 800)
so the taken snapshot contains only part of the user control.
how to get the snapshot of entire user control in the bitmap ?
thanks in advance.
You can use the following approach:
static void DrawControlToImage(Control ctrl, Image img) {
Rectangle sourceRect = ctrl.ClientRectangle;
Size targetSize = new Size(img.Width, img.Height);
using(Bitmap tmp = new Bitmap(sourceRect.Width, sourceRect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb)) {
ctrl.DrawToBitmap(tmp, sourceRect);
using(Graphics g = Graphics.FromImage(img)) {
g.DrawImage(tmp, new Rectangle(Point.Empty, targetSize));
}
}
}