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;
//...
}
Related
I draw text on top of a image using Path.Addstring and filling it with a color and it works perfectly. Now I would like to split(bisect) the text vertically and have 2 different colors or textures. For eg. the top half of the text with a solid brush and the bottom half with hatch brush. I'd like to know if this is possible and which way should I implement it.
Reference image created using paint.net software. I drew a line to split the text and filled the bottom part with a different texture.
*I don't want the line to be visible in the final output.
Possible.
Fill the path with the solid brush.
Get the rectangle that bounds the path through the GraphicsPath.GetBounds method.
Call the Graphics.SetClip method to exclude the top half of the rectangle.
Fill the path with a TextureBrush or HatchBrush.
An example that uses a HatchBrush to fill the second vertical half of the path.
private void SomeControl_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
var r = (sender as Control).ClientRectangle;
using (var gp = new GraphicsPath())
using (var sf = new StringFormat())
using (var fnt = new Font("Blackoak Std", 72))
using (var hbr = new HatchBrush(HatchStyle.Percent25, Color.White, Color.Red))
{
sf.Alignment = sf.LineAlignment = StringAlignment.Center;
gp.AddString("RED", fnt.FontFamily, (int)fnt.Style, GetEmFontSize(fnt), r, sf);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillPath(Brushes.Red, gp);
var rf = gp.GetBounds();
rf.Height /= 2f;
g.SetClip(rf, CombineMode.Exclude);
g.FillPath(hbr, gp);
g.ResetClip();
g.SmoothingMode = SmoothingMode.None;
}
}
private float GetEmFontSize(Font fnt) =>
fnt.SizeInPoints * (fnt.FontFamily.GetCellAscent(fnt.Style) +
fnt.FontFamily.GetCellDescent(fnt.Style)) / fnt.FontFamily.GetEmHeight(fnt.Style);
See also the other HatchStyle values.
I'm using GMaps for using google maps on c#. I write latitude and langitude values and press the load button. Then the code put a marker(like arrow) that point. I want to rotate that marker for any degree like google maps'. I don't have any sensors so I can write the degree in a textbox and press a rotate button. How can i do that? This code shows how I create markers and put them into my map. I know bitmap a little but not too much and sorry for my bad english. I hope you'll understand what I want.
`double lat = Convert.ToDouble(txtLat.Text);
double lng = Convert.ToDouble(txtLong.Text);
map.Position = new PointLatLng(lat, lng);
//custom marker
Bitmap bmpMarker = (Bitmap)Image.FromFile("img/arrow.png");
PointLatLng point = new PointLatLng(lat, lng);
GMap.NET.WindowsForms.GMapMarker marker = new GMarkerGoogle(point, bmpMarker);
//1. Create a Overlay
GMapOverlay markers = new GMapOverlay("markers");
map.ZoomAndCenterMarkers("markers");
//2. Add all available markers to that Overlay
markers.Markers.Add(marker);
//3. Cover map with Overlay
map.Overlays.Add(markers);
//RotateImage(bmpMarker, 180.0f);
marker.ToolTipText = map.Position.ToString();`
Try to set the Bitmap image again with the rotation because it is not a reference type:
GMap.NET.WindowsForms.GMapMarker marker = new GMarkerGoogle(point, RotateImg(bmpMarker,45));
Suggestion to rotate the bitmap:
public Bitmap RotateImg(Bitmap bmpimage, float angle)
{
int w = bmpimage.Width;
int h = bmpimage.Height;
PixelFormat pf;
pf = bmpimage.PixelFormat;
Bitmap tempImg = new Bitmap(w, h, pf);
Graphics g = Graphics.FromImage(tempImg);
g.DrawImageUnscaled(bmpimage, 1, 1);
g.Dispose();
GraphicsPath path = new GraphicsPath();
path.AddRectangle(new RectangleF(0.0F, 0.0F, w, h));
Matrix mtrx = new Matrix();
mtrx.Rotate(angle);
RectangleF rct = path.GetBounds(mtrx);
Bitmap newImg = new Bitmap(Convert.ToInt32(rct.Width), Convert.ToInt32(rct.Height), pf);
g = Graphics.FromImage(newImg);
g.TranslateTransform(-rct.X, -rct.Y);
g.RotateTransform(angle);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(tempImg, 0, 0);
g.Dispose();
tempImg.Dispose();
return newImg;
}
Back here. Is there any way to improve the quality of the Arc?
I'm using e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
This is the piece of code that creates the arc:
using (GraphicsPath gp = new GraphicsPath())
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
gp.Reset();
gp.AddPie(_OuterRectangle, (float)_Properties.Origin, (float)_Properties.GaugeType);
gp.Reverse();
gp.AddPie(_InnerRectangle, (float)_Properties.Origin, (float)_Properties.GaugeType);
gp.Reverse();
pArea.SetClip(gp);
using (Pen oPen = new Pen(this.ForeColor, 2f))
{
e.Graphics.DrawPath(oPen, gp);
}
e.Graphics.SetClip(ClientRectangle);
}
Thanks in advance.
EDIT:
I've did what LarsTech proposed and now the quality is perfect, but I'm not having the figure I need:
OuterRectangle: is the ClientRectangle area, that I'm manipulating it to make Width and Height the same lenght;
InnerRectangle: is 2/3ths of the ClientRectangle area, ergo, of the OuterRectangle;
Properties.Origin: is the angle where the arc starts. I have it in an enumerator as Cardinal Points, where North is 270, East is 0,
and so. In case of the figure, is SouthWest, 135 degrees;
Properties.GaugeType: is another enumerator that says if is Complete = 360, Half = 180, Quarter = 90, so with that I can determine the sweep angle. In case of the figure is ThreeQuarter, 270 degrees.
The problem:
When clipping a region of the current Graphics (Graphics.SetClip method), the resulting drawing loses quality, because the antialiasing effect generated by Graphics.SmoothingMode = SmoothingMode.AntiAlias is lost.
A possible solution is to avoid clipping the region defined by the GraphicsPath used to design the arcs (GraphicsPath.AddPie method); this, however, leaves the lines of the Pie visible, compromising the shape.
Another solution is to draw an ellipsis in the center of the arcs using the background color of the Canvas. Since the arcs are drawn using two rectangles, we can use the inner rectagle, inflate it (Rectangle.Inflate method) as needed (a fraction - Pen.Width / 2 - of the Pen size used for the ouline, usually).
This allows to delete the artifacts generated by the GraphicsPath shapes and to draw some other graphics content in the center of the shapes.
For example, using different Brushes:
LinearGradientBrush HatchBrush TextureBrush
Of course there are other methods to achieve the same result. We could draw the Arcs using the GraphicsPath.AddArc method, extract or calculate the first and last points of the Arcs and use them to draw two lines (GraphicsPath.AddLine) that will close the figures.
But, since we want to draw different graphics objects in the center of the arcs, these objects will cover the center area anyway.
How to use this code:
In a Form, add a TrackBar (named tbarSpeed, here)
Add a PictureBox (named Canvas), with Size (200, 200).
Wire up the TrackBar tbarSpeed_Scroll event and the Panel Canvas_Paint event.
using System.Drawing;
using System.Drawing.Drawing2D;
float GaugeValue = 88.0f;
float GaugeSweepAngle = 270.0f;
float GaugeStartAngle = 135.0F;
private void Canvas_Paint(object sender, PaintEventArgs e)
{
var canvas = sender as Control;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var outerRectangle = new Rectangle(10, 10, 180, 180);
var innerRectangle = new Rectangle(30, 30, 140, 140);
var blendRectangle = new Rectangle(10, 10, 180, 160);
var innerCenter = new PointF(outerRectangle.Left + (outerRectangle.Width / 2),
outerRectangle.Top + (outerRectangle.Height / 2));
float gaugeLength = (outerRectangle.Width / 2) - 2;
using (var path = new GraphicsPath())
{
path.AddPie(outerRectangle, GaugeStartAngle, GaugeSweepAngle);
path.AddPie(innerRectangle, GaugeStartAngle, GaugeSweepAngle);
innerRectangle.Inflate(-1, -1);
using (var pen = new Pen(Color.White, 3f))
using (var backgroundbrush = new SolidBrush(canvas.BackColor))
using (var gradientBrush = new LinearGradientBrush(blendRectangle,
Color.Green, Color.Red, LinearGradientMode.ForwardDiagonal))
{
var blend = new Blend()
{
Factors = new[] { 0.0f, 0.0f, 0.1f, 0.3f, 0.7f, 1.0f },
Positions = new[] { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f }
};
gradientBrush.Blend = blend;
e.Graphics.FillPath(gradientBrush, path);
e.Graphics.DrawPath(pen, path);
e.Graphics.FillEllipse(backgroundbrush, innerRectangle);
using (var format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
innerRectangle.Location = new Point(innerRectangle.X, innerRectangle.Y + canvas.Font.Height);
e.Graphics.DrawString(GaugeValue.ToString() + "%", canvas.Font, Brushes.White, innerRectangle, format);
}
using (var mx = new Matrix())
{
mx.RotateAt(GaugeStartAngle + 90 + (GaugeValue * (GaugeSweepAngle / 100)), innerCenter);
e.Graphics.Transform = mx;
e.Graphics.DrawLine(pen, innerCenter, new PointF(innerCenter.X, innerCenter.Y - gaugeLength));
e.Graphics.ResetTransform();
}
}
}
}
private void tbarSpeed_Scroll(object sender, EventArgs e)
{
GaugeValue = tbarSpeed.Value;
Canvas.Invalidate();
}
Sample code on PasteBin
I am attempting to retrieve the pixels under a polygon; however, I seem to be unable to correctly get all the pixels accurately. Below is the code I have:
private void DrawPolygon()
{
PointF[] pts = new PointF[]{new PointF(5.196057f, 4.13434839f), new PointF(5.528517f, 4.621298f), new PointF(7.073008f, 6.5661006f), new PointF(5.28491259f, 9.206118f), new PointF(4.80768776f, 6.595068f), new PointF(5.196057f, 4.13434839f)};
GraphicsPath gp = new GraphicsPath(FillMode.Alternate);
gp.AddPolygon(pts);
Bitmap bmp = Bitmap.FromFile("...");
Graphics g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.None;
g.PixelOffsetMode = PixelOffsetMode.Half;
using (Brush b = new SolidBrush(Color.White))
{
g.FillPath(b, gp);
}
bmp.Save("...");
}
In the image below (left) you can see that several pixels under the polygon are excluded. Image on the right was the original with all pixels having a value. I feel that I am missing a setting in the 'Graphics', but I cannot figure out what.
I have following problem. I want to make some graphics in c# windows form.
I want to read bitmap to my program and after it write some text on this bitmap. In the end I want this picture load to pictureBox. And it's my question. How can I do it?
example, how must it work:
Bitmap a = new Bitmap(#"path\picture.bmp");
a.makeTransparent();
// ? a.writeText("some text", positionX, positionY);
pictuteBox1.Image = a;
Is it possible do to?
Bitmap bmp = new Bitmap("filename.bmp");
RectangleF rectf = new RectangleF(70, 90, 90, 50);
Graphics g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawString("yourText", new Font("Tahoma",8), Brushes.Black, rectf);
g.Flush();
image.Image=bmp;
Very old question, but just had to build this for an app today and found the settings shown in other answers do not result in a clean image (possibly as new options were added in later .Net versions).
Assuming you want the text in the centre of the bitmap, you can do this:
// Load the original image
Bitmap bmp = new Bitmap("filename.bmp");
// Create a rectangle for the entire bitmap
RectangleF rectf = new RectangleF(0, 0, bmp.Width, bmp.Height);
// Create graphic object that will draw onto the bitmap
Graphics g = Graphics.FromImage(bmp);
// ------------------------------------------
// Ensure the best possible quality rendering
// ------------------------------------------
// The smoothing mode specifies whether lines, curves, and the edges of filled areas use smoothing (also called antialiasing).
// One exception is that path gradient brushes do not obey the smoothing mode.
// Areas filled using a PathGradientBrush are rendered the same way (aliased) regardless of the SmoothingMode property.
g.SmoothingMode = SmoothingMode.AntiAlias;
// The interpolation mode determines how intermediate values between two endpoints are calculated.
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Use this property to specify either higher quality, slower rendering, or lower quality, faster rendering of the contents of this Graphics object.
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
// This one is important
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
// Create string formatting options (used for alignment)
StringFormat format = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
// Draw the text onto the image
g.DrawString("yourText", new Font("Tahoma",8), Brushes.Black, rectf, format);
// Flush all graphics changes to the bitmap
g.Flush();
// Now save or use the bitmap
image.Image = bmp;
References
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.smoothingmode(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.interpolationmode(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.pixeloffsetmode(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.graphics.textrenderinghint(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.drawing.stringformat(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/21kdfbzs(v=vs.110).aspx
You need to use the Graphics class in order to write on the bitmap.
Specifically, one of the DrawString methods.
Bitmap a = new Bitmap(#"path\picture.bmp");
using(Graphics g = Graphics.FromImage(a))
{
g.DrawString(....); // requires font, brush etc
}
pictuteBox1.Image = a;
var bmp = new Bitmap(#"path\picture.bmp");
using( Graphics g = Graphics.FromImage( bmp ) )
{
g.DrawString( ... );
}
picturebox1.Image = bmp;
If you want wrap your text, then you should draw your text in a rectangle:
RectangleF rectF1 = new RectangleF(30, 10, 100, 122);
e.Graphics.DrawString(text1, font1, Brushes.Blue, rectF1);
See: https://msdn.microsoft.com/en-us/library/baw6k39s(v=vs.110).aspx