// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) 2014 OxyPlot contributors // // // The graphics render context. // // -------------------------------------------------------------------------------------------------------------------- #if OXYPLOT_COREDRAWING namespace OxyPlot.Core.Drawing #else namespace OxyPlot.WindowsForms #endif { using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Drawing.Text; using System.IO; using System.Linq; using OxyPlot; /// /// The graphics render context. /// public class GraphicsRenderContext : ClippingRenderContext, IDisposable { /// /// The font size factor. /// private const float FontsizeFactor = 0.8f; /// /// The images in use /// private readonly HashSet imagesInUse = new HashSet(); /// /// The image cache /// private readonly Dictionary imageCache = new Dictionary(); /// /// The brush cache. /// private readonly Dictionary brushes = new Dictionary(); /// /// The pen cache. /// private readonly Dictionary pens = new Dictionary(); /// /// The string format. /// private readonly StringFormat stringFormat; /// /// The GDI+ drawing surface. /// private Graphics g; /// /// Initializes a new instance of the class. /// /// The drawing surface. public GraphicsRenderContext(Graphics graphics = null) { this.g = graphics; if (this.g != null) { this.g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; } this.stringFormat = StringFormat.GenericTypographic; } /// /// Sets the graphics target. /// /// The graphics surface. public void SetGraphicsTarget(Graphics graphics) { this.g = graphics; this.g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit; } /// public override void DrawEllipse(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode) { var isStroked = stroke.IsVisible() && thickness > 0; this.SetSmoothingMode(this.ShouldUseAntiAliasingForEllipse(edgeRenderingMode)); if (fill.IsVisible()) { this.g.FillEllipse(this.GetCachedBrush(fill), (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height); } if (!isStroked) { return; } var pen = this.GetCachedPen(stroke, thickness); this.g.DrawEllipse(pen, (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height); } /// public override void DrawLine( IList points, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode, double[] dashArray, OxyPlot.LineJoin lineJoin) { if (stroke.IsInvisible() || thickness <= 0 || points.Count < 2) { return; } this.SetSmoothingMode(this.ShouldUseAntiAliasingForLine(edgeRenderingMode, points)); var pen = this.GetCachedPen(stroke, thickness, dashArray, lineJoin); this.g.DrawLines(pen, this.ToPoints(points)); } /// public override void DrawPolygon( IList points, OxyColor fill, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode, double[] dashArray, OxyPlot.LineJoin lineJoin) { if (points.Count < 2) { return; } this.SetSmoothingMode(this.ShouldUseAntiAliasingForLine(edgeRenderingMode, points)); var pts = this.ToPoints(points); if (fill.IsVisible()) { this.g.FillPolygon(this.GetCachedBrush(fill), pts); } if (stroke.IsInvisible() || thickness <= 0) { return; } var pen = this.GetCachedPen(stroke, thickness, dashArray, lineJoin); this.g.DrawPolygon(pen, pts); } /// public override void DrawRectangle(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness, EdgeRenderingMode edgeRenderingMode) { this.SetSmoothingMode(this.ShouldUseAntiAliasingForRect(edgeRenderingMode)); if (fill.IsVisible()) { this.g.FillRectangle( this.GetCachedBrush(fill), (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height); } if (stroke.IsInvisible() || thickness <= 0) { return; } var pen = this.GetCachedPen(stroke, thickness); this.g.DrawRectangle(pen, (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height); } /// /// Draws the text. /// /// The p. /// The text. /// The fill color. /// The font family. /// Size of the font. /// The font weight. /// The rotation angle. /// The horizontal alignment. /// The vertical alignment. /// The maximum size of the text. public override void DrawText( ScreenPoint p, string text, OxyColor fill, string fontFamily, double fontSize, double fontWeight, double rotate, HorizontalAlignment halign, VerticalAlignment valign, OxySize? maxSize) { if (text == null) { return; } var fontStyle = fontWeight < 700 ? FontStyle.Regular : FontStyle.Bold; using (var font = CreateFont(fontFamily, fontSize, fontStyle)) { this.stringFormat.Alignment = StringAlignment.Near; this.stringFormat.LineAlignment = StringAlignment.Near; var size = Ceiling(this.g.MeasureString(text, font, int.MaxValue, this.stringFormat)); if (maxSize != null) { if (size.Width > maxSize.Value.Width) { size.Width = (float)maxSize.Value.Width; } if (size.Height > maxSize.Value.Height) { size.Height = (float)maxSize.Value.Height; } } float dx = 0; if (halign == HorizontalAlignment.Center) { dx = -size.Width / 2; } if (halign == HorizontalAlignment.Right) { dx = -size.Width; } float dy = 0; this.stringFormat.LineAlignment = StringAlignment.Near; if (valign == VerticalAlignment.Middle) { dy = -size.Height / 2; } if (valign == VerticalAlignment.Bottom) { dy = -size.Height; } var graphicsState = this.g.Save(); this.g.TranslateTransform((float)p.X, (float)p.Y); var layoutRectangle = new RectangleF(0, 0, size.Width, size.Height); if (Math.Abs(rotate) > double.Epsilon) { this.g.RotateTransform((float)rotate); layoutRectangle.Height += (float)(fontSize / 18.0); } this.g.TranslateTransform(dx, dy); this.g.DrawString(text, font, this.GetCachedBrush(fill), layoutRectangle, this.stringFormat); this.g.Restore(graphicsState); } } /// /// Measures the text. /// /// The text. /// The font family. /// Size of the font. /// The font weight. /// The text size. public override OxySize MeasureText(string text, string fontFamily, double fontSize, double fontWeight) { if (text == null) { return OxySize.Empty; } var fontStyle = fontWeight < 700 ? FontStyle.Regular : FontStyle.Bold; using (var font = CreateFont(fontFamily, fontSize, fontStyle)) { this.stringFormat.Alignment = StringAlignment.Near; this.stringFormat.LineAlignment = StringAlignment.Near; var size = Ceiling(this.g.MeasureString(text, font, int.MaxValue, this.stringFormat)); return new OxySize(size.Width, size.Height); } } /// /// Cleans up resources not in use. /// /// This method is called at the end of each rendering. public override void CleanUp() { var imagesToRelease = this.imageCache.Keys.Where(i => !this.imagesInUse.Contains(i)).ToList(); foreach (var i in imagesToRelease) { var image = this.GetImage(i); image.Dispose(); this.imageCache.Remove(i); } this.imagesInUse.Clear(); } /// /// Draws the image. /// /// The source. /// The source executable. /// The source asynchronous. /// Width of the source. /// Height of the source. /// The executable. /// The asynchronous. /// The forward. /// The authentication. /// The opacity. /// if set to true [interpolate]. public override void DrawImage(OxyImage source, double srcX, double srcY, double srcWidth, double srcHeight, double x, double y, double w, double h, double opacity, bool interpolate) { var image = this.GetImage(source); if (image != null) { ImageAttributes ia = null; if (opacity < 1) { var cm = new ColorMatrix { Matrix00 = 1f, Matrix11 = 1f, Matrix22 = 1f, Matrix33 = (float)opacity, Matrix44 = 1f }; ia = new ImageAttributes(); ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); } this.g.InterpolationMode = interpolate ? InterpolationMode.HighQualityBicubic : InterpolationMode.NearestNeighbor; int sx = (int)Math.Floor(x); int sy = (int)Math.Floor(y); int sw = (int)Math.Ceiling(x + w) - sx; int sh = (int)Math.Ceiling(y + h) - sy; var destRect = new Rectangle(sx, sy, sw, sh); this.g.DrawImage(image, destRect, (float)srcX - 0.5f, (float)srcY - 0.5f, (float)srcWidth, (float)srcHeight, GraphicsUnit.Pixel, ia); } } /// protected override void SetClip(OxyRect rect) { this.g.SetClip(rect.ToRect(false)); } /// protected override void ResetClip() { this.g.ResetClip(); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { // dispose images foreach (var i in this.imageCache) { i.Value.Dispose(); } this.imageCache.Clear(); // dispose pens, brushes etc. this.stringFormat.Dispose(); foreach (var brush in this.brushes.Values) { brush.Dispose(); } this.brushes.Clear(); foreach (var pen in this.pens.Values) { pen.Dispose(); } this.pens.Clear(); } /// /// Creates a font. /// /// The font family. /// Size of the font. /// The font style. /// A font private static Font CreateFont(string fontFamily, double fontSize, FontStyle fontStyle) { return new Font(fontFamily, (float)fontSize * FontsizeFactor, fontStyle); } /// /// Returns the ceiling of the given as a . /// /// The size. /// A . private static SizeF Ceiling(SizeF size) { var ceiling = Size.Ceiling(size); return new SizeF(ceiling.Width, ceiling.Height); } /// /// Loads the image from the specified source. /// /// The image source. /// A . private Image GetImage(OxyImage source) { if (source == null) { return null; } if (!this.imagesInUse.Contains(source)) { this.imagesInUse.Add(source); } Image src; if (this.imageCache.TryGetValue(source, out src)) { return src; } Image btm; using (var ms = new MemoryStream(source.GetData())) { btm = Image.FromStream(ms); } this.imageCache.Add(source, btm); return btm; } /// /// Gets the cached brush. /// /// The fill color. /// A . private Brush GetCachedBrush(OxyColor fill) { Brush brush; if (this.brushes.TryGetValue(fill, out brush)) { return brush; } return this.brushes[fill] = fill.ToBrush(); } /// /// Gets the cached pen. /// /// The stroke color. /// The thickness. /// The dash array. /// The line join. /// A . private Pen GetCachedPen(OxyColor stroke, double thickness, double[] dashArray = null, OxyPlot.LineJoin lineJoin = OxyPlot.LineJoin.Miter) { GraphicsPenDescription description = new GraphicsPenDescription(stroke, thickness, dashArray, lineJoin); Pen pen; if (this.pens.TryGetValue(description, out pen)) { return pen; } return this.pens[description] = CreatePen(stroke, thickness, dashArray, lineJoin); } /// /// Creates a pen. /// /// The stroke. /// The thickness. /// The dash array. /// The line join. /// A . private Pen CreatePen(OxyColor stroke, double thickness, double[] dashArray = null, OxyPlot.LineJoin lineJoin = OxyPlot.LineJoin.Miter) { var pen = new Pen(stroke.ToColor(), (float)thickness); if (dashArray != null) { pen.DashPattern = this.ToFloatArray(dashArray); } switch (lineJoin) { case OxyPlot.LineJoin.Round: pen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round; break; case OxyPlot.LineJoin.Bevel: pen.LineJoin = System.Drawing.Drawing2D.LineJoin.Bevel; break; // The default LineJoin is Miter } return pen; } /// /// Sets the smoothing mode. /// /// A value indicating whether to use Anti-Aliasing. private void SetSmoothingMode(bool useAntiAliasing) { this.g.SmoothingMode = useAntiAliasing ? SmoothingMode.HighQuality : SmoothingMode.None; } /// /// Converts a double array to a float array. /// /// The a. /// The float array. private float[] ToFloatArray(double[] a) { if (a == null) { return null; } var r = new float[a.Length]; for (int i = 0; i < a.Length; i++) { r[i] = (float)a[i]; } return r; } /// /// Converts a list of point to an array of PointF. /// /// The points. /// An array of points. private PointF[] ToPoints(IList points) { if (points == null) { return null; } var r = new PointF[points.Count()]; int i = 0; foreach (ScreenPoint p in points) { r[i++] = new PointF((float)p.X, (float)p.Y); } return r; } } }