// --------------------------------------------------------------------------------------------------------------------
//
// The MIT License (MIT)
//
// Copyright (c) 2012 Oystein Bjorke
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//
// The graphics render context.
//
// --------------------------------------------------------------------------------------------------------------------
namespace OxyPlot.WindowsForms
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using OxyPlot;
///
/// The graphics render context.
///
internal class GraphicsRenderContext : RenderContextBase
{
///
/// The font size factor.
///
private const float FontsizeFactor = 0.8f;
///
/// The GDI+ drawing surface.
///
private Graphics g;
///
/// Initializes a new instance of the class.
///
public GraphicsRenderContext()
{
}
///
/// Sets the graphics target.
///
/// The graphics surface.
public void SetGraphicsTarget(Graphics graphics)
{
this.g = graphics;
}
///
/// Draws the ellipse.
///
/// The rect.
/// The fill.
/// The stroke.
/// The thickness.
public override void DrawEllipse(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness)
{
if (fill != null)
{
this.g.FillEllipse(
this.ToBrush(fill), (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
}
if (stroke == null || thickness <= 0)
{
return;
}
using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
{
this.g.SmoothingMode = SmoothingMode.HighQuality;
this.g.DrawEllipse(pen, (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
}
}
///
/// Draws the line.
///
/// The points.
/// The stroke.
/// The thickness.
/// The dash array.
/// The line join.
/// if set to true [aliased].
public override void DrawLine(
IList points,
OxyColor stroke,
double thickness,
double[] dashArray,
OxyPenLineJoin lineJoin,
bool aliased)
{
if (stroke == null || thickness <= 0 || points.Count < 2)
{
return;
}
this.g.SmoothingMode = aliased ? SmoothingMode.None : SmoothingMode.HighQuality;
using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
{
if (dashArray != null)
{
pen.DashPattern = this.ToFloatArray(dashArray);
}
switch (lineJoin)
{
case OxyPenLineJoin.Round:
pen.LineJoin = LineJoin.Round;
break;
case OxyPenLineJoin.Bevel:
pen.LineJoin = LineJoin.Bevel;
break;
// The default LineJoin is Miter
}
this.g.DrawLines(pen, this.ToPoints(points));
}
}
///
/// Draws the polygon.
///
/// The points.
/// The fill.
/// The stroke.
/// The thickness.
/// The dash array.
/// The line join.
/// if set to true [aliased].
public override void DrawPolygon(
IList points,
OxyColor fill,
OxyColor stroke,
double thickness,
double[] dashArray,
OxyPenLineJoin lineJoin,
bool aliased)
{
if (points.Count < 2)
{
return;
}
this.g.SmoothingMode = aliased ? SmoothingMode.None : SmoothingMode.HighQuality;
PointF[] pts = this.ToPoints(points);
if (fill != null)
{
this.g.FillPolygon(this.ToBrush(fill), pts);
}
if (stroke != null && thickness > 0)
{
using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
{
if (dashArray != null)
{
pen.DashPattern = this.ToFloatArray(dashArray);
}
switch (lineJoin)
{
case OxyPenLineJoin.Round:
pen.LineJoin = LineJoin.Round;
break;
case OxyPenLineJoin.Bevel:
pen.LineJoin = LineJoin.Bevel;
break;
// The default LineJoin is Miter
}
this.g.DrawPolygon(pen, pts);
}
}
}
///
/// The draw rectangle.
///
///
/// The rect.
///
///
/// The fill.
///
///
/// The stroke.
///
///
/// The thickness.
///
public override void DrawRectangle(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness)
{
if (fill != null)
{
this.g.FillRectangle(
this.ToBrush(fill), (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
}
if (stroke == null || thickness <= 0)
{
return;
}
using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
{
this.g.DrawRectangle(pen, (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
}
}
///
/// The draw text.
///
///
/// The p.
///
///
/// The text.
///
///
/// The fill.
///
///
/// The font family.
///
///
/// The font size.
///
///
/// The font weight.
///
///
/// The rotate.
///
///
/// The halign.
///
///
/// The valign.
///
///
/// 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)
{
var fs = FontStyle.Regular;
if (fontWeight >= 700)
{
fs = FontStyle.Bold;
}
using (var font = new Font(fontFamily, (float)fontSize * FontsizeFactor, fs))
{
using (var sf = new StringFormat { Alignment = StringAlignment.Near })
{
var size = this.g.MeasureString(text, font);
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;
sf.LineAlignment = StringAlignment.Near;
if (valign == VerticalAlignment.Middle)
{
dy = -size.Height / 2;
}
if (valign == VerticalAlignment.Bottom)
{
dy = -size.Height;
}
this.g.TranslateTransform((float)p.X, (float)p.Y);
if (Math.Abs(rotate) > double.Epsilon)
{
this.g.RotateTransform((float)rotate);
}
this.g.TranslateTransform(dx, dy);
var layoutRectangle = new RectangleF(0, 0, size.Width, size.Height);
this.g.DrawString(text, font, this.ToBrush(fill), layoutRectangle, sf);
this.g.ResetTransform();
}
}
}
///
/// The measure text.
///
/// The text.
/// The font family.
/// The font size.
/// The font weight.
/// The size of the text.
public override OxySize MeasureText(string text, string fontFamily, double fontSize, double fontWeight)
{
if (text == null)
{
return OxySize.Empty;
}
var fs = FontStyle.Regular;
if (fontWeight >= 700)
{
fs = FontStyle.Bold;
}
using (var font = new Font(fontFamily, (float)fontSize * FontsizeFactor, fs))
{
var size = this.g.MeasureString(text, font);
return new OxySize(size.Width, size.Height);
}
}
///
/// Converts a fill color to a System.Drawing.Brush.
///
///
/// The fill color.
///
///
/// The brush.
///
private Brush ToBrush(OxyColor fill)
{
if (fill != null)
{
return new SolidBrush(this.ToColor(fill));
}
return null;
}
///
/// Converts a color to a System.Drawing.Color.
///
///
/// The color.
///
///
/// The System.Drawing.Color.
///
private Color ToColor(OxyColor c)
{
return Color.FromArgb(c.A, c.R, c.G, c.B);
}
///
/// 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;
}
public override void CleanUp()
{
var imagesToRelease = imageCache.Keys.Where(i => !imagesInUse.Contains(i));
foreach (var i in imagesToRelease)
{
var image = this.GetImage(i);
image.Dispose();
imageCache.Remove(i);
}
imagesInUse.Clear();
}
public override OxyImageInfo GetImageInfo(OxyImage source)
{
var image = this.GetImage(source);
return image == null ? null : new OxyImageInfo { Width = (uint)image.Width, Height = (uint)image.Height, DpiX = image.HorizontalResolution, DpiY = image.VerticalResolution };
}
public override void DrawImage(OxyImage source, uint srcX, uint srcY, uint srcWidth, uint 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 = 1f,
Matrix44 = (float)opacity
};
ia = new ImageAttributes();
ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
}
g.InterpolationMode = interpolate ? InterpolationMode.HighQualityBicubic : InterpolationMode.NearestNeighbor;
int sx = (int)Math.Round(x);
int sy = (int)Math.Round(y);
int sw = (int)Math.Round(x + w) - sx;
int sh = (int)Math.Round(y + h) - sy;
g.DrawImage(image, new Rectangle(sx, sy, sw, sh), srcX, srcY, srcWidth, srcHeight, GraphicsUnit.Pixel, ia);
}
}
private HashSet imagesInUse = new HashSet();
private Dictionary imageCache = new Dictionary();
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;
}
if (source != null)
{
Image btm;
using (var ms = new MemoryStream(source.GetData()))
{
btm = Image.FromStream(ms);
}
this.imageCache.Add(source, btm);
return btm;
}
return null;
}
public override bool SetClip(OxyRect rect)
{
this.g.SetClip(rect.ToRect(false));
return true;
}
public override void ResetClip()
{
this.g.ResetClip();
}
}
}