Skip to content

Instantly share code, notes, and snippets.

@ja72
Created May 5, 2023 19:12
Show Gist options
  • Save ja72/8faac41da525c5750ee08c6fd15c04ef to your computer and use it in GitHub Desktop.
Save ja72/8faac41da525c5750ee08c6fd15c04ef to your computer and use it in GitHub Desktop.
public static class Gdi
{
public static Pen Stroke { get; set; } = new Pen(Color.Black, 0);
public static SolidBrush Fill { get; set; } = new SolidBrush(Color.Black);
public static Font Font { get; set; } = SystemFonts.CaptionFont;
/// <summary>
/// Adds an arrow to the start of a <see cref="Pen"/> object.
/// </summary>
/// <remarks>Intended to be used with <see cref="Stroke"/></remarks>
/// <param name="pen">The pen to modify.</param>
/// <param name="arrowSize">Size of the arrow in pixels.</param>
public static void AddStartArrow(this Pen pen, float arrowSize)
{
pen.CustomStartCap = new AdjustableArrowCap(arrowSize/2, arrowSize);
}
/// <summary>
/// Removes the start arrow from a <see cref="Pen"/> object.
/// </summary>
/// <remarks>Intended to be used with <see cref="Stroke"/></remarks>
/// <param name="pen">The pen object to modify.</param>
public static void RemoveStartArrow(this Pen pen)
{
pen.StartCap = LineCap.NoAnchor;
}
/// <summary>
/// Adds an arrow to the end of a <see cref="Pen"/> object.
/// </summary>
/// <remarks>Intended to be used with <see cref="Stroke"/></remarks>
/// <param name="pen">The pen to modify.</param>
/// <param name="arrowSize">Size of the arrow in pixels.</param>
public static void AddEndArrow(this Pen pen, float arrowSize)
{
pen.CustomEndCap = new AdjustableArrowCap(arrowSize/2, arrowSize);
}
/// <summary>
/// Removes the end arrow from a <see cref="Pen"/> object.
/// </summary>
/// <remarks>Intended to be used with <see cref="Stroke"/></remarks>
/// <param name="pen">The pen object to modify.</param>
public static void RemoveEndArrow(this Pen pen)
{
pen.EndCap = LineCap.NoAnchor;
}
/// <summary>
/// Draws two axis along the x and y directions using arrowed lines and a dot at the origin.
/// </summary>
/// <param name="graphics">The graphics.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="arrowSize">The size of arrows and dot in pixels.</param>
public static void DrawAxes(this Graphics graphics, float scale, float arrowSize=1f)
{
float arrowTip = Math.Max(scale/5, arrowSize/2);
AddEndArrow(Stroke, arrowTip);
DrawLine(graphics, scale, 0, 0, arrowSize, 0d);
DrawLine(graphics, scale, 0, 0, 0d, arrowSize);
FillPoint(graphics, scale, 0d, 0d, arrowTip);
RemoveEndArrow(Stroke);
}
/// <summary>
/// Draws a small circle representing a point on the screen.
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="point">The point location.</param>
/// <param name="size">The size of the circle.</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawPoint(this Graphics g, float scale, double pointX, double pointY, float size = 6f)
{
float x = scale * (float)pointX, y = -scale * (float)pointY;
g.DrawEllipse(Stroke, x - size / 2, y - size / 2, size, size);
}
/// <summary>
/// Fills a small circle representing a point on the screen.
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="point">The point location.</param>
/// <param name="size">The size of the circle.</param>
/// <remarks>Uses <see cref="Fill"/> to fill the shape.</remarks>
public static void FillPoint(this Graphics g, float scale, double pointX, double pointY, float size = 6f)
{
float x = scale * (float)pointX, y = -scale * (float)pointY;
g.FillEllipse(Fill, x - size / 2, y - size / 2, size, size);
}
/// <summary>
/// Draws a set of points (nodes)
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the points</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawPoints(this Graphics g, float scale, double[,] nodes, float size = 6)
{
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
DrawPoint(g, scale, nodes[i, 0], nodes[i, 1],size);
}
}
/// <summary>
/// Draws a set of points (nodes)
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the points</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void FillPoints(this Graphics g, float scale, double[,] nodes, float size = 6)
{
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
FillPoint(g, scale, nodes[i, 0], nodes[i, 1],size);
}
}
/// <summary>
/// Draws consecutive lines defined by their end points (nodes)
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the nodes of the (consecutive) lines</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawLines(this Graphics g, float scale, double[,] nodes)
{
if (nodes.GetLength(0) <= 1) return;
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
points[i] = new PointF(scale * (float)nodes[i,0], -scale * (float)nodes[i,1]);
}
g.DrawLines(Stroke, points);
}
/// <summary>
/// Draws a curve through a set of points (nodes)
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the nodes of the (consecutive) lines</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawCurve(this Graphics g, float scale, double[,] nodes)
{
if (nodes.GetLength(0) <= 1) return;
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
points[i] = new PointF(scale * (float)nodes[i,0], -scale * (float)nodes[i,1]);
}
g.DrawCurve(Stroke, points);
}
/// <summary>
/// Draws a closed shape with lines defined by the end points (nodes).
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the nodes of the (consecutive) lines</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawPolygon(this Graphics g, float scale, double[,] nodes)
{
if (nodes.GetLength(0) <= 1) return;
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
points[i] = new PointF(scale * (float)nodes[i,0], -scale * (float)nodes[i,1]);
}
g.DrawPolygon(Stroke, points);
}
/// <summary>
/// Draws a closed curve though several points (nodes).
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the nodes of the (consecutive) lines</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawClosedCurve(this Graphics g, float scale, double[,] nodes)
{
if (nodes.GetLength(0) <= 1) return;
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
points[i] = new PointF(scale * (float)nodes[i,0], -scale * (float)nodes[i,1]);
}
g.DrawClosedCurve(Stroke, points);
}
/// <summary>
/// Fills a closed shape with lines defined by the end points (nodes).
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the nodes of the (consecutive) lines</param>
/// <remarks>Uses <see cref="Fill"/> to fill the shape.</remarks>
public static void FillPolygon(this Graphics g, float scale, double[,] nodes)
{
if (nodes.GetLength(0) <= 1) return;
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
points[i] = new PointF(scale * (float)nodes[i,0], -scale * (float)nodes[i,1]);
}
g.FillPolygon(Fill, points);
}
/// <summary>
/// Fills a closed curve though several points (nodes).
/// </summary>
/// <remarks>There must be more than one node to draw anything.</remarks>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="nodes">The array of locations defining the nodes of the (consecutive) lines</param>
/// <remarks>Uses <see cref="Fill"/> to fill the shape.</remarks>
public static void FillClosedCurve(this Graphics g, float scale, double[,] nodes)
{
if (nodes.GetLength(0) <= 1) return;
PointF[] points = new PointF[nodes.GetLength(0)];
for (int i = 0; i < points.Length; i++)
{
points[i] = new PointF(scale * (float)nodes[i,0], -scale * (float)nodes[i,1]);
}
g.FillClosedCurve(Fill, points);
}
/// <summary>
/// Draw a line between two points.
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="fromPosition">The start position.</param>
/// <param name="toPosition">The end position.</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawLine(this Graphics g, float scale, double fromPositionX, double fromPositionY, double toPositionX, double toPositionY)
{
float x1 = scale * (float)fromPositionX, y1 = -scale * (float)fromPositionY;
float x2 = scale * (float)toPositionX, y2 = -scale * (float)toPositionY;
g.DrawLine(Stroke, x1 ,y1, x2, y2);
}
/// <summary>
/// Draw a line from a point, to a certain offset using pixel coordinates.
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="fromPosition">The start position.</param>
/// <param name="xPixelOffset">The horizontal offset.</param>
/// <param name="yPixelOffset">The vertical offset.</param>
/// <remarks>Uses <see cref="Stroke"/> to draw the shape.</remarks>
public static void DrawLine(this Graphics g, float scale, double fromPositionX, double fromPositionY, float xPixelOffset, int yPixelOffset)
{
float x = scale * (float)fromPositionX, y = -scale * (float)fromPositionY;
g.DrawLine(Stroke, x, y, xPixelOffset, -yPixelOffset);
}
/// <summary>
/// Draws an ellipse
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="center">The center of the ellipse.</param>
/// <param name="semiMajor">The semi-major axis.</param>
/// <param name="semiMinor">The semi-minor axis.</param>
/// <param name="tiltAngleDegrees">The tilt angle degrees CCW from horizontal.</param>
/// <remarks>Uses <see cref="Stroke"/> to draw lines.</remarks>
public static void DrawEllipse(this Graphics g, float scale, double centerX, double centerY, float semiMajor, float semiMinor, float tiltAngleDegrees = 0)
{
float x = scale * (float)centerX, y = scale * (float)centerY;
float dx = scale * semiMajor, dy = scale * semiMinor;
var save = g.Save();
g.RotateTransform(-tiltAngleDegrees);
g.DrawEllipse(Stroke, x - dx, y - dy, 2 * dx, 2 * dy);
g.Restore(save);
}
/// <summary>
/// Fills an ellipse
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="center">The center of the ellipse.</param>
/// <param name="semiMajor">The semi-major axis.</param>
/// <param name="semiMinor">The semi-minor axis.</param>
/// <param name="tiltAngleDegrees">The tilt angle degrees CCW from horizontal.</param>
/// <remarks>Uses <see cref="Fill"/> to fill shapes.</remarks>
public static void FillEllipse(this Graphics g, float scale, double centerX, double centerY, float semiMajor, float semiMinor, float tiltAngleDegrees = 0)
{
float x = scale * (float)centerX, y = scale * (float)centerY;
float dx = scale * semiMajor, dy = scale * semiMinor;
var save = g.Save();
g.RotateTransform(-tiltAngleDegrees);
g.FillEllipse(Fill, x - dx, y - dy, 2 * dx, 2 * dy);
g.Restore(save);
}
/// <summary>
/// Draws an arc of an ellipse (partial ellipse)
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="center">The center of the ellipse.</param>
/// <param name="semiMajor">The semi-major axis.</param>
/// <param name="semiMinor">The semi-minor axis.</param>
/// <param name="startAngleDegrees">The start angle in degrees CCW from 3 o'clock.</param>
/// <param name="sweepAngleDegrees">The sweep angle in degrees CCW.</param>
/// <param name="tiltAngleDegrees">The tilt angle degrees CCW from horizontal.</param>
/// <remarks>Uses <see cref="Stroke"/> to draw lines.</remarks>
public static void DrawEllipseArc(this Graphics g, float scale, double centerX, double centerY, float semiMajor, float semiMinor, float startAngleDegrees, float sweepAngleDegrees, float tiltAngleDegrees = 0)
{
float x = scale * (float)centerX, y = scale * (float)centerY;
float dx = scale * semiMajor, dy = scale * semiMinor;
//float θ =
var save = g.Save();
g.RotateTransform(-tiltAngleDegrees);
g.DrawArc(Stroke, x - dx, y - dy, 2 * dx, 2 * dy, 360 - startAngleDegrees, -sweepAngleDegrees);
g.Restore(save);
}
/// <summary>
/// Draws the text.
/// </summary>
/// <param name="g">The graphics object to draw on.</param>
/// <param name="scale">The drawing scale converting (x, y) to <see cref="PointF"/>.</param>
/// <param name="text">The text to draw.</param>
/// <param name="anchor">The anchor point.</param>
/// <param name="alignment">The alignment of the text relative to the anchor point.</param>
/// <param name="padding">The padding in pixels to space from anchor point.</param>
/// <exception cref="System.NotSupportedException">For invalid <paramref name="alignment"/>.</exception>
/// <remarks>Uses <see cref="Stroke"/> to draw text.</remarks>
public static void DrawText(this Graphics g, float scale, string text, double anchorX, double anchorY, ContentAlignment alignment, int padding=4)
{
SizeF size = g.MeasureString(text, Font);
float x = scale * (float)anchorX, y = -scale * (float)anchorY;
switch (alignment)
{
case ContentAlignment.TopLeft:
x = x - size.Width - padding;
y = y - size.Height - padding;
break;
case ContentAlignment.TopCenter:
x = x - size.Width/2;
y = y - size.Height - padding;
break;
case ContentAlignment.TopRight:
x = x + padding;
y = y - size.Height - padding;
break;
case ContentAlignment.MiddleLeft:
x = x - size.Width - padding;
y = y - size.Height/2;
break;
case ContentAlignment.MiddleCenter:
x = x - size.Width/2;
y = y - size.Height/2;
break;
case ContentAlignment.MiddleRight:
x = x + padding;
y = y - size.Height/2;
break;
case ContentAlignment.BottomLeft:
x = x - size.Width - padding;
y = y + padding;
break;
case ContentAlignment.BottomCenter:
x = x - size.Width/2;
y = y + padding;
break;
case ContentAlignment.BottomRight:
x = x + padding;
y = y + padding;
break;
default:
throw new NotSupportedException();
}
g.DrawString(text, Font, Fill, x, y);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment