I am attempting to rotate a gauge based on data that I am acquiring from a Serial Port. The Serial communications are working well and now I am having issues making the gauge rotate. I am now trying to make the image rotate with a slider bar and I am still having no luck. I currently have a timer implemented to trigger every 100 ms and run this code. However when I move the sliderBar nothing happens to the image on the screen. The reason I am using a timer is because that is what I will be using for my final implementation. Using a timer to trigger the UI update instead of the Serial event makes the application run much more smooth.
As always any help is greatly appreciated!
in the constructor...
public Form1()
{
InitializeComponent();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
imgpic = (Image)pictureBoxBase.Image.Clone(); // This is storing an image in a picture box...
foreach (int rate in baudRates)
{
brbox.Items.Add(rate);
}
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = 100;
timer.Enabled = true;
timer.Start();
com.DataReceived += new SerialDataReceivedEventHandler(OnReceived);
}
Then in the timer event...
void timer_Tick(object sender, EventArgs e) // Again it is initially drawing the picture, but it does not rotate with the statusBar
{
Point test = new Point(0, 0);
Image img = new Bitmap(400, 400);
pictureBox1.Image = img;
Graphics g = Graphics.FromImage(pictureBox1.Image);
Matrix mm1 = new Matrix();
mm1.RotateAt((trackBar1.Value),new Point( 0,0),MatrixOrder.Append);
GraphicsPath gp = new GraphicsPath();
gp.Transform(mm1);
gp.AddPolygon(new Point[] { new Point(0, 0), new Point(imgpic.Width, 0), new Point(0, imgpic.Height) });
PointF[] pts = gp.PathPoints;
g.DrawImage(imgpic, test);
pictureBox1.Refresh();
}
Key issues are:
Not actually drawing the path you produce
Not rotating the polys you add to the path (must apply transform after adding them)
Lots of potential for memory leaks here - objects with unmanaged components (Graphics, GraphicsPath, Image, and Matrix objects here) need to be disposed so that underlying Windows objects can be deleted nicely (.NET can't do this for you).
Fixed up code:
void timer_Tick(object sender, EventArgs e)
{
if (pictureBox1.Image != null)
pictureBox1.Image.Dispose(); // dispose old image (you might consider reusing it rather than making a new one each frame)
Point test = new Point(0, 0);
Image img = new Bitmap(400, 400);
pictureBox1.Image = img;
Graphics g = Graphics.FromImage(pictureBox1.Image);
Matrix mm1 = new Matrix();
mm1.RotateAt((trackBar1.Value), new Point( 0,0), MatrixOrder.Append); // note that the angle is in degrees, so make sure the trackbar value or input is suitably scaled
GraphicsPath gp = new GraphicsPath();
gp.AddPolygon(new Point[] { new Point(0, 0), new Point(imgpic.Width, 0), new Point(0, imgpic.Height) });
//PointF[] pts = gp.PathPoints; // not needed for this task
g.DrawPath(Pens.Black, gp); // draw the path with a simple black pen
g.Transform = mm1; // transform the graphics object so the image is rotated
g.DrawImage(imgpic, test); // if the image needs to be behind the path, draw it beforehand
mm1.Dispose();
gp.Dispose();
g.Disose(); // prevent possible memory leaks
pictureBox1.Refresh();
}
I think this ought to work, if it still has issue, comment here and I'll modify it if need be.
(Edit: looks like there is a lot of stuff to dispose that I didn't quite expect)
Related
I don't know how to repaint my picturebox. This is just a demonstration. The production code is too time consuming to put in the paint event. What I need is a way to capture the image that is drawn in the pucturebox with the graphics methods so I can quickly repaint it when needed.
Here's a demo:
public partial class Form1 : Form
{
Image Outout;
public Form1()
{
InitializeComponent();
button1.Click += Button1_Click;
}
private void Button1_Click(Object sender, EventArgs e)
{
PrintPageEventArgs eOutput;
Graphics g;
string OutputText;
Font PrintFont;
OutputText = "CERTIFICATION";
PrintFont = new Font("Arial", 16, FontStyle.Bold);
g = pictureBox1.CreateGraphics();
eOutput = new PrintPageEventArgs(g, new Rectangle(new Point(25, 25), new Size(new Point(825, 1075))), new Rectangle(new Point(0, 0), new Size(new Point(850, 1100))), new PageSettings());
eOutput.Graphics.DrawString(OutputText, PrintFont, Brushes.Black, 0, 0);
Outout = pictureBox1.Image;
pictureBox1.Paint += PictureBox1_Paint;
}
private void PictureBox1_Paint(object sender, PaintEventArgs e)
{
pictureBox1.Image = Outout;
}
}
Thanks TaW for pointing me in the right direction. The example I posted is from an ERP system and the actual problem uses over a dozen objects plus the graphics is from a multipage report. So drawing in the paint event will not work. For starters, keeping a list of things to draw is pointless, since EVERYTHING needs to be drawn when the part number changes. Also the report generator needs to run twice, the first time just to calculate the number of pages and the second time to actually draw the pages. Also I cant use the PrintPreview of the PrintDocument because of its limitations. But you were spot on about using Graphics g = Graphics.FromImage(bmp). That's what I needed.
#LarsTech, you are correct, that is a weird use of the PrintPageEventArgs. That is merely a side effect when an issue embedded in a couple of events and several objects needs to be reduced in form to present a representation of a problem. If its reduced too small, the proposed solutions do not scale, therefore, do not work. If it is not reduced enough, it can be difficult to understand the actual problem, as people will present solutions to different aspects, some of which are artificial due to the reduction of the issue.
ANSWER
Drawing on the graphics created by the picturebox was not persistent. Drawing on a bitmap, however, worked perfectly as suggested by TaW. Thank you for your assistance!
PrintPageEventArgs eOutput;
Graphics g;
string OutputText;
Font PrintFont;
Bitmap Output;
OutputText = "CERTIFICATION";
PrintFont = new Font("Times New Roman", 24, FontStyle.Regular);
Output = new Bitmap(850, 1100);
g = Graphics.FromImage(Output);
eOutput = new PrintPageEventArgs(g, new Rectangle(new Point(25, 25), new Size(new Point(825, 1075))), new Rectangle(new Point(0, 0), new Size(new Point(850, 1100))), new PageSettings());
eOutput.Graphics.DrawString(OutputText, PrintFont, Brushes.Black, 0, 0);
pictureBox1.Image = Output;
I'm creating a program that uses the flycapture camera. I've created a class that extends the pictureBox class in order to draw a crosshair, consisting of two lines, onto the screen. I want to be able to move the crosshair from the center to any other location on the screen.
The problem is when the form is resized, the crosshair moves to a different location as shown here. I want the crosshair to be pointing at the same part of the image as before it was resized (in the example it is no longer pointing at the grey mesh). I'm drawing the crosshair in relation to the height and width of the pictureBox. I want to be able to draw the lines on the image but the image height and width are always the same regardless of the image's size.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace FlyCapture2SimpleGUI_CSharp
{
class IMSPictureBox : PictureBox
{
private Color colorSetting = Color.Black;
private float width = 1.0f;
public IMSPictureBox()
{
this.Paint += IMSPictureBox_Paint;
}
private void IMSPictureBox_Paint(object sender, PaintEventArgs e)
{
//Draw if image has loaded
if (this.Image != null)
{
//Draw horizontal line
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
new Point(0, this.Size.Height / 2 + 100),
new Point(this.Size.Width, this.Size.Height / 2 + 100));
//Draw vertical line
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
new Point(this.Size.Width / 2 + 100, 0),
new Point(this.Size.Width / 2 + 100, this.Size.Height));
}
}
}
}
Edit:
As DiskJunky suggested, I'm now drawing on the image itself and not using the Paint function above.
Here is the image getting set:
private void UpdateUI(object sender, ProgressChangedEventArgs e)
{
UpdateStatusBar();
pictureBox1.SetImage = m_processedImage.bitmap;
pictureBox1.Invalidate();
}
Here are the lines drawing on the image:
public System.Drawing.Image SetImage
{
set
{
using (Graphics g = Graphics.FromImage(value))
{
g.DrawLine(new Pen(Color.Red, 3.0f), new Point(0, 0), new Point(value.Width, value.Height));
g.Dispose();
}
this.Image = value;
}
get
{
return this.Image;
}
}
I now have a line that scales with the image, but now it's constantly flickering.
This isn't exact but modifying to something like the following will keep the position static as the picture box resizes;
class IMSPictureBox : PictureBox
{
private Color colorSetting = Color.Black;
private float width = 1.0f;
private Tuple<Point, Point> _verticalLine;
private Tuple<Point, Point> _horizontalLine;
public IMSPictureBox()
{
this.Paint += IMSPictureBox_Paint;
}
private void IMSPictureBox_Paint(object sender, PaintEventArgs e)
{
//Draw if image has loaded
if (this.Image != null)
{
//Draw vertical line
if (_verticalLine == null)
{
_verticalLine = new Tuple<Point, Point>(new Point(100, 0), new Point(100, this.Size.Height);
}
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
_verticalLine.Item1,
_verticalLine.Item2);
//Draw horizontal line
if (_horizontalLine == null)
{
_horizontalLine = new Tuple<Point, Point>(new Point(0, 100), new Point(this.Size.Width, 100);
}
e.Graphics.DrawLine(
new Pen(this.colorSetting, this.width),
_horizontalLine.Item1,
_horizontalLine.Item2);
}
}
}
Edit
The above solution outlines the concept of preserving the line position. As per discussion in comments below, this developed into a more involved solution for the OP as additional requirements came out of investigating and testing the solution above for the original intent - to cleanly draw a a coordinate marker on an image.
To that end a manual double buffering mechanism is recommended in that scenario as the PictureBox control supports limited drawing capability itself. An example of manually implementing a double buffering solution can be found here; https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-manually-render-buffered-graphics
Rather than calling DrawEllipse() there'll be calls to DrawLine() to display the coordinate marker. One caveat is that if the image is still being displayed in the PictureBox then the PictureBoxSizeMode.Zoom value may still have to be accounted for.
I want to zoom picture box along with graphics.
this will Zoom the only image part not the graphics part.
public Image PictureBoxZoom(Image imgP, Size size)
{
Bitmap bm = new Bitmap(imgP, Convert.ToInt32(imgP.Width * size.Width), Convert.ToInt32(imgP.Height * size.Height));
Graphics grap = Graphics.FromImage(bm);
grap.InterpolationMode = InterpolationMode.HighQualityBicubic;
return bm;
}
private void zoomSlider_Scroll(object sender, EventArgs e)
{
if (zoomSlider.Value > 0 && img != null)
{
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = null;
pictureBox1.Image = PictureBoxZoom(img, new Size(zoomSlider.Value, zoomSlider.Value));
}
}
the source of image is:
img = Image.FromStream(openFileDialog1.OpenFile());
Picture is zooming but when we draw the rectangle outside image then it's not zooming along with image.
See image:
You can do this (rather) easily by scaling the e.Graphics object in the Paint event.. See here for an example!
So to work with you code you probably should add this to the Paint event:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.ScaleTransform(zoom, zoom);
// now do your drawing..
// here just a demo ellipse and a line..
Rectangle rect = panel1.ClientRectangle;
g.DrawEllipse(Pens.Firebrick, rect);
using (Pen pen = new Pen(Color.DarkBlue, 4f)) g.DrawLine(pen, 22, 22, 88, 88);
}
To calculate the zoom factor you need to do a little calculation.
Usually you would not create a zoomed Image but leave that job to the PictureBox with a SizeMode=Zoom.
Then you could write:
float zoom = 1f * pictureBox1.ClientSize.Width / pictureBox1.Image.Width;
To center the image one usually moves the PictureBox inside a Panel. And to allow scrolling one sets the Panel to Autocroll=true;
But since you are creating a zoomed Image you should keep track of the current zoom by some other means.
Note:
But as you use PictureBoxSizeMode.CenterImage maybe your problem is not with the zooming after all?? If your issue really is the placement, not the size of the drawn graphics you need to do a e.Graphics.TranslateTransform to move it to the right spot..
I am trying to rotate an image that is in side of a PictureBox in a C# WinForm. I rotate the image with every tick of the timer1 (Interval = 100). As I am rotating, however, the image becomes blurrier and blurrier. I am not sure if there is a way to fix this. I have enabled the Form to be double buffered.
Here is the code I am using:
public static Image RotateImage(Image img, float rotationAngle)
{
Bitmap bmp = new Bitmap(img.Width, img.Height);
bmp.SetResolution(img.HorizontalResolution, img.VerticalResolution);
Graphics gfx = Graphics.FromImage(bmp);
gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);
gfx.RotateTransform(-rotationAngle);
gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);
gfx.DrawImage(img, new Point(0, 0));
gfx.Dispose();
return bmp;
}
private void timer1_Tick(object sender, EventArgs e)
{
Image bmp = Form1.RotateImage(pictureBox1.Image, 10);
pictureBox1.Image = bmp;
}
Consider keeping your original image around, and each time you rotate, calculate the new image by applying the cumulative rotation to the original image.
What you're seeing is an unavoidable consequence of the rotation, which can only approximate the original image given the finite number of pixels it has to work with. You're seeing approximations of approximations, and they'll just get worse each time you rotate.
I'm trying to draw a series of connected segments, but the curved segments seem to produce an artifact, whereby the outer side of the curve is not smooth at all, but very jagged. This is part of a GIS program I am making.
For these lines, the line itself needs to be quite wide, as this represents the range of data that can be collected on this line for the GIS data. There also has to be an area directly under the line where no data is collected. This also can be wide, but not as wide as the main line.
I have done this using a graphics path, which I then widen and use as a clipping region to block the area directly under the line. I then draw the actual line. The sample code below does this, with made up values for ease of regenerating.
This works fine with straight lines, but with curved lines there are very irregular shapes on the outside of the curves. I have no idea why this happens.
Any ideas would be much appreciated, cheers,
Greg
I made this sample code using a basic form with a picturebox and a button on it, whereby when I clicked the button it would execute this method:
private void drawCurvedLine()
{
//initialise the plot area:
Bitmap image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.BackgroundImage = image;
Graphics g = Graphics.FromImage(image);
//the width of the pen represents the width of a sonar swathe:
Pen widePen = new Pen(new SolidBrush(Color.FromArgb(80, Color.Blue)), 50);
PointF[] points = new PointF[4];
//first straight:
points[0] = new PointF(287.284149F,21.236269F);
points[1] = new PointF(183.638443F,406.936249F);
//second straight:
points[2] = new PointF(130.842773F, 515.574036F);
points[3] = new PointF(-1950.91321F, 3491.868F);
//graphics path for the line:
GraphicsPath gPath = new GraphicsPath();
gPath.AddLine(points[0], points[1]);
gPath.AddArc(new RectangleF(-445.464447F,3.84924316F,640.067444F,640.067444F), -(90 - 105.0412369999982F), 10.8775282F);
gPath.AddArc(new RectangleF(-445.464417F, 3.84915161F, 640.067444F, 640.067444F), -(90 - 115.91811484539707F), 10.8775091F);
gPath.AddLine(points[2], points[3]);
//widen the line to the width equal to what the fish will not be able to see:
gPath.Widen(new Pen(Color.White, 10));
//now exclude that widened line from the main graphics:
g.ExcludeClip(new Region(gPath));
//draw the swathe line:
g.DrawPath(widePen, gPath);
//reset the clipping for the next line:
g.ResetClip();
}
Try to use a separate GraphicsPath for excluded region:
private void drawCurvedLine()
{
//initialise the plot area:
Bitmap image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.BackgroundImage = image;
using(Graphics g = Graphics.FromImage(image))
{
PointF[] points = new PointF[4];
//first straight:
points[0] = new PointF(287.284149F, 21.236269F);
points[1] = new PointF(183.638443F, 406.936249F);
//second straight:
points[2] = new PointF(130.842773F, 515.574036F);
points[3] = new PointF(-1950.91321F, 3491.868F);
//graphics path for the line:
using(GraphicsPath gPath = new GraphicsPath())
{
gPath.AddLine(points[0], points[1]);
gPath.AddArc(new RectangleF(-445.464447F, 3.84924316F, 640.067444F, 640.067444F), -(90 - 105.0412369999982F), 10.8775282F);
gPath.AddArc(new RectangleF(-445.464417F, 3.84915161F, 640.067444F, 640.067444F), -(90 - 115.91811484539707F), 10.8775091F);
gPath.AddLine(points[2], points[3]);
//widen the line to the width equal to what the fish will not be able to see:
using(GraphicsPath innerPath = (GraphicsPath)gPath.Clone())
{
using(Pen pen = new Pen(Color.White, 10))
{
innerPath.Widen(pen);
}
//now exclude that widened line from the main graphics:
using(Region reg = new Region(innerPath))
{
g.ExcludeClip(reg);
//draw the swathe line:
//the width of the pen represents the width of a sonar swathe:
using(Pen widePen = new Pen(new SolidBrush(Color.FromArgb(80, Color.Blue)), 50))
{
g.DrawPath(widePen, gPath);
}
//reset the clipping for the next line:
g.ResetClip();
}
}
}
}
}
Set the smoothing mode properly on your Graphics instance. Take a look here.
Try setting the CompositingQuality, the InterpolationMode and the SmoothingMode properties to increase the quality of your Graphics object:
using(Graphics g = Graphics.FromImage(image))
{
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
//...
}