// --------------------------------------------------------------------------------------------------------------------
//
// 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;
}
}
}