External/OxyPlot/OxyPlot.WindowsForms/GraphicsRenderContext.cs
author moel.mich
Sat, 08 Jun 2013 16:53:22 +0000
changeset 391 5be8f2773237
permissions -rw-r--r--
Added the source code of OxyPlot as of commit d190d7748a73 (6.5.2013).
     1 // --------------------------------------------------------------------------------------------------------------------
     2 // <copyright file="GraphicsRenderContext.cs" company="OxyPlot">
     3 //   The MIT License (MIT)
     4 //
     5 //   Copyright (c) 2012 Oystein Bjorke
     6 //
     7 //   Permission is hereby granted, free of charge, to any person obtaining a
     8 //   copy of this software and associated documentation files (the
     9 //   "Software"), to deal in the Software without restriction, including
    10 //   without limitation the rights to use, copy, modify, merge, publish,
    11 //   distribute, sublicense, and/or sell copies of the Software, and to
    12 //   permit persons to whom the Software is furnished to do so, subject to
    13 //   the following conditions:
    14 //
    15 //   The above copyright notice and this permission notice shall be included
    16 //   in all copies or substantial portions of the Software.
    17 //
    18 //   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    19 //   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    20 //   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    21 //   IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    22 //   CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    23 //   TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    24 //   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    25 // </copyright>
    26 // <summary>
    27 //   The graphics render context.
    28 // </summary>
    29 // --------------------------------------------------------------------------------------------------------------------
    30 namespace OxyPlot.WindowsForms
    31 {
    32     using System;
    33     using System.Collections.Generic;
    34     using System.Drawing;
    35     using System.Drawing.Drawing2D;
    36     using System.Drawing.Imaging;
    37     using System.IO;
    38     using System.Linq;
    39 
    40     using OxyPlot;
    41 
    42     /// <summary>
    43     /// The graphics render context.
    44     /// </summary>
    45     internal class GraphicsRenderContext : RenderContextBase
    46     {
    47         /// <summary>
    48         /// The font size factor.
    49         /// </summary>
    50         private const float FontsizeFactor = 0.8f;
    51 
    52         /// <summary>
    53         /// The GDI+ drawing surface.
    54         /// </summary>
    55         private Graphics g;
    56 
    57         /// <summary>
    58         /// Initializes a new instance of the <see cref="GraphicsRenderContext"/> class.
    59         /// </summary>
    60         public GraphicsRenderContext()
    61         {
    62         }
    63 
    64         /// <summary>
    65         /// Sets the graphics target.
    66         /// </summary>
    67         /// <param name="graphics">The graphics surface.</param>
    68         public void SetGraphicsTarget(Graphics graphics)
    69         {
    70             this.g = graphics;
    71         }
    72 
    73         /// <summary>
    74         /// Draws the ellipse.
    75         /// </summary>
    76         /// <param name="rect">The rect.</param>
    77         /// <param name="fill">The fill.</param>
    78         /// <param name="stroke">The stroke.</param>
    79         /// <param name="thickness">The thickness.</param>
    80         public override void DrawEllipse(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness)
    81         {
    82             if (fill != null)
    83             {
    84                 this.g.FillEllipse(
    85                     this.ToBrush(fill), (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
    86             }
    87 
    88             if (stroke == null || thickness <= 0)
    89             {
    90                 return;
    91             }
    92 
    93             using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
    94             {
    95                 this.g.SmoothingMode = SmoothingMode.HighQuality;
    96                 this.g.DrawEllipse(pen, (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
    97             }
    98         }
    99 
   100         /// <summary>
   101         /// Draws the line.
   102         /// </summary>
   103         /// <param name="points">The points.</param>
   104         /// <param name="stroke">The stroke.</param>
   105         /// <param name="thickness">The thickness.</param>
   106         /// <param name="dashArray">The dash array.</param>
   107         /// <param name="lineJoin">The line join.</param>
   108         /// <param name="aliased">if set to <c>true</c> [aliased].</param>
   109         public override void DrawLine(
   110             IList<ScreenPoint> points,
   111             OxyColor stroke,
   112             double thickness,
   113             double[] dashArray,
   114             OxyPenLineJoin lineJoin,
   115             bool aliased)
   116         {
   117             if (stroke == null || thickness <= 0 || points.Count < 2)
   118             {
   119                 return;
   120             }
   121 
   122             this.g.SmoothingMode = aliased ? SmoothingMode.None : SmoothingMode.HighQuality;
   123             using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
   124             {
   125 
   126                 if (dashArray != null)
   127                 {
   128                     pen.DashPattern = this.ToFloatArray(dashArray);
   129                 }
   130 
   131                 switch (lineJoin)
   132                 {
   133                     case OxyPenLineJoin.Round:
   134                         pen.LineJoin = LineJoin.Round;
   135                         break;
   136                     case OxyPenLineJoin.Bevel:
   137                         pen.LineJoin = LineJoin.Bevel;
   138                         break;
   139 
   140                     // The default LineJoin is Miter
   141                 }
   142 
   143                 this.g.DrawLines(pen, this.ToPoints(points));
   144             }
   145         }
   146 
   147         /// <summary>
   148         /// Draws the polygon.
   149         /// </summary>
   150         /// <param name="points">The points.</param>
   151         /// <param name="fill">The fill.</param>
   152         /// <param name="stroke">The stroke.</param>
   153         /// <param name="thickness">The thickness.</param>
   154         /// <param name="dashArray">The dash array.</param>
   155         /// <param name="lineJoin">The line join.</param>
   156         /// <param name="aliased">if set to <c>true</c> [aliased].</param>
   157         public override void DrawPolygon(
   158             IList<ScreenPoint> points,
   159             OxyColor fill,
   160             OxyColor stroke,
   161             double thickness,
   162             double[] dashArray,
   163             OxyPenLineJoin lineJoin,
   164             bool aliased)
   165         {
   166             if (points.Count < 2)
   167             {
   168                 return;
   169             }
   170 
   171             this.g.SmoothingMode = aliased ? SmoothingMode.None : SmoothingMode.HighQuality;
   172 
   173             PointF[] pts = this.ToPoints(points);
   174             if (fill != null)
   175             {
   176                 this.g.FillPolygon(this.ToBrush(fill), pts);
   177             }
   178 
   179             if (stroke != null && thickness > 0)
   180             {
   181                 using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
   182                 {
   183 
   184                     if (dashArray != null)
   185                     {
   186                         pen.DashPattern = this.ToFloatArray(dashArray);
   187                     }
   188 
   189                     switch (lineJoin)
   190                     {
   191                         case OxyPenLineJoin.Round:
   192                             pen.LineJoin = LineJoin.Round;
   193                             break;
   194                         case OxyPenLineJoin.Bevel:
   195                             pen.LineJoin = LineJoin.Bevel;
   196                             break;
   197 
   198                         // The default LineJoin is Miter
   199                     }
   200 
   201                     this.g.DrawPolygon(pen, pts);
   202                 }
   203             }
   204         }
   205 
   206         /// <summary>
   207         /// The draw rectangle.
   208         /// </summary>
   209         /// <param name="rect">
   210         /// The rect.
   211         /// </param>
   212         /// <param name="fill">
   213         /// The fill.
   214         /// </param>
   215         /// <param name="stroke">
   216         /// The stroke.
   217         /// </param>
   218         /// <param name="thickness">
   219         /// The thickness.
   220         /// </param>
   221         public override void DrawRectangle(OxyRect rect, OxyColor fill, OxyColor stroke, double thickness)
   222         {
   223             if (fill != null)
   224             {
   225                 this.g.FillRectangle(
   226                     this.ToBrush(fill), (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
   227             }
   228 
   229             if (stroke == null || thickness <= 0)
   230             {
   231                 return;
   232             }
   233 
   234             using (var pen = new Pen(this.ToColor(stroke), (float)thickness))
   235             {
   236                 this.g.DrawRectangle(pen, (float)rect.Left, (float)rect.Top, (float)rect.Width, (float)rect.Height);
   237             }
   238         }
   239 
   240         /// <summary>
   241         /// The draw text.
   242         /// </summary>
   243         /// <param name="p">
   244         /// The p.
   245         /// </param>
   246         /// <param name="text">
   247         /// The text.
   248         /// </param>
   249         /// <param name="fill">
   250         /// The fill.
   251         /// </param>
   252         /// <param name="fontFamily">
   253         /// The font family.
   254         /// </param>
   255         /// <param name="fontSize">
   256         /// The font size.
   257         /// </param>
   258         /// <param name="fontWeight">
   259         /// The font weight.
   260         /// </param>
   261         /// <param name="rotate">
   262         /// The rotate.
   263         /// </param>
   264         /// <param name="halign">
   265         /// The halign.
   266         /// </param>
   267         /// <param name="valign">
   268         /// The valign.
   269         /// </param>
   270         /// <param name="maxSize">
   271         /// The maximum size of the text.
   272         /// </param>
   273         public override void DrawText(
   274             ScreenPoint p,
   275             string text,
   276             OxyColor fill,
   277             string fontFamily,
   278             double fontSize,
   279             double fontWeight,
   280             double rotate,
   281             HorizontalAlignment halign,
   282             VerticalAlignment valign,
   283             OxySize? maxSize)
   284         {
   285             var fs = FontStyle.Regular;
   286             if (fontWeight >= 700)
   287             {
   288                 fs = FontStyle.Bold;
   289             }
   290 
   291             using (var font = new Font(fontFamily, (float)fontSize * FontsizeFactor, fs))
   292             {
   293                 using (var sf = new StringFormat { Alignment = StringAlignment.Near })
   294                 {
   295                     var size = this.g.MeasureString(text, font);
   296                     if (maxSize != null)
   297                     {
   298                         if (size.Width > maxSize.Value.Width)
   299                         {
   300                             size.Width = (float)maxSize.Value.Width;
   301                         }
   302 
   303                         if (size.Height > maxSize.Value.Height)
   304                         {
   305                             size.Height = (float)maxSize.Value.Height;
   306                         }
   307                     }
   308 
   309                     float dx = 0;
   310                     if (halign == HorizontalAlignment.Center)
   311                     {
   312                         dx = -size.Width / 2;
   313                     }
   314 
   315                     if (halign == HorizontalAlignment.Right)
   316                     {
   317                         dx = -size.Width;
   318                     }
   319 
   320                     float dy = 0;
   321                     sf.LineAlignment = StringAlignment.Near;
   322                     if (valign == VerticalAlignment.Middle)
   323                     {
   324                         dy = -size.Height / 2;
   325                     }
   326 
   327                     if (valign == VerticalAlignment.Bottom)
   328                     {
   329                         dy = -size.Height;
   330                     }
   331 
   332                     this.g.TranslateTransform((float)p.X, (float)p.Y);
   333                     if (Math.Abs(rotate) > double.Epsilon)
   334                     {
   335                         this.g.RotateTransform((float)rotate);
   336                     }
   337 
   338                     this.g.TranslateTransform(dx, dy);
   339 
   340                     var layoutRectangle = new RectangleF(0, 0, size.Width, size.Height);
   341                     this.g.DrawString(text, font, this.ToBrush(fill), layoutRectangle, sf);
   342 
   343                     this.g.ResetTransform();
   344                 }
   345             }
   346         }
   347 
   348         /// <summary>
   349         /// The measure text.
   350         /// </summary>
   351         /// <param name="text">The text.</param>
   352         /// <param name="fontFamily">The font family.</param>
   353         /// <param name="fontSize">The font size.</param>
   354         /// <param name="fontWeight">The font weight.</param>
   355         /// <returns>The size of the text.</returns>
   356         public override OxySize MeasureText(string text, string fontFamily, double fontSize, double fontWeight)
   357         {
   358             if (text == null)
   359             {
   360                 return OxySize.Empty;
   361             }
   362 
   363             var fs = FontStyle.Regular;
   364             if (fontWeight >= 700)
   365             {
   366                 fs = FontStyle.Bold;
   367             }
   368 
   369             using (var font = new Font(fontFamily, (float)fontSize * FontsizeFactor, fs))
   370             {
   371                 var size = this.g.MeasureString(text, font);
   372                 return new OxySize(size.Width, size.Height);
   373             }
   374         }
   375 
   376         /// <summary>
   377         /// Converts a fill color to a System.Drawing.Brush.
   378         /// </summary>
   379         /// <param name="fill">
   380         /// The fill color.
   381         /// </param>
   382         /// <returns>
   383         /// The brush.
   384         /// </returns>
   385         private Brush ToBrush(OxyColor fill)
   386         {
   387             if (fill != null)
   388             {
   389                 return new SolidBrush(this.ToColor(fill));
   390             }
   391 
   392             return null;
   393         }
   394 
   395         /// <summary>
   396         /// Converts a color to a System.Drawing.Color.
   397         /// </summary>
   398         /// <param name="c">
   399         /// The color.
   400         /// </param>
   401         /// <returns>
   402         /// The System.Drawing.Color.
   403         /// </returns>
   404         private Color ToColor(OxyColor c)
   405         {
   406             return Color.FromArgb(c.A, c.R, c.G, c.B);
   407         }
   408 
   409         /// <summary>
   410         /// Converts a double array to a float array.
   411         /// </summary>
   412         /// <param name="a">
   413         /// The a.
   414         /// </param>
   415         /// <returns>
   416         /// The float array.
   417         /// </returns>
   418         private float[] ToFloatArray(double[] a)
   419         {
   420             if (a == null)
   421             {
   422                 return null;
   423             }
   424 
   425             var r = new float[a.Length];
   426             for (int i = 0; i < a.Length; i++)
   427             {
   428                 r[i] = (float)a[i];
   429             }
   430 
   431             return r;
   432         }
   433 
   434         /// <summary>
   435         /// Converts a list of point to an array of PointF.
   436         /// </summary>
   437         /// <param name="points">
   438         /// The points.
   439         /// </param>
   440         /// <returns>
   441         /// An array of points.
   442         /// </returns>
   443         private PointF[] ToPoints(IList<ScreenPoint> points)
   444         {
   445             if (points == null)
   446             {
   447                 return null;
   448             }
   449 
   450             var r = new PointF[points.Count()];
   451             int i = 0;
   452             foreach (ScreenPoint p in points)
   453             {
   454                 r[i++] = new PointF((float)p.X, (float)p.Y);
   455             }
   456 
   457             return r;
   458         }
   459 
   460         public override void CleanUp()
   461         {
   462             var imagesToRelease = imageCache.Keys.Where(i => !imagesInUse.Contains(i));
   463             foreach (var i in imagesToRelease)
   464             {
   465                 var image = this.GetImage(i);
   466                 image.Dispose();
   467                 imageCache.Remove(i);
   468             }
   469 
   470             imagesInUse.Clear();
   471         }
   472 
   473         public override OxyImageInfo GetImageInfo(OxyImage source)
   474         {
   475             var image = this.GetImage(source);
   476             return image == null ? null : new OxyImageInfo { Width = (uint)image.Width, Height = (uint)image.Height, DpiX = image.HorizontalResolution, DpiY = image.VerticalResolution };
   477         }
   478 
   479         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)
   480         {
   481             var image = this.GetImage(source);
   482             if (image != null)
   483             {
   484                 ImageAttributes ia = null;
   485                 if (opacity < 1)
   486                 {
   487                     var cm = new ColorMatrix
   488                                  {
   489                                      Matrix00 = 1f,
   490                                      Matrix11 = 1f,
   491                                      Matrix22 = 1f,
   492                                      Matrix33 = 1f,
   493                                      Matrix44 = (float)opacity
   494                                  };
   495 
   496                     ia = new ImageAttributes();
   497                     ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
   498                 }
   499 
   500                 g.InterpolationMode = interpolate ? InterpolationMode.HighQualityBicubic : InterpolationMode.NearestNeighbor;
   501                 int sx = (int)Math.Round(x);
   502                 int sy = (int)Math.Round(y);
   503                 int sw = (int)Math.Round(x + w) - sx;
   504                 int sh = (int)Math.Round(y + h) - sy;
   505                 g.DrawImage(image, new Rectangle(sx, sy, sw, sh), srcX, srcY, srcWidth, srcHeight, GraphicsUnit.Pixel, ia);
   506             }
   507         }
   508 
   509         private HashSet<OxyImage> imagesInUse = new HashSet<OxyImage>();
   510 
   511         private Dictionary<OxyImage, Image> imageCache = new Dictionary<OxyImage, Image>();
   512 
   513 
   514         private Image GetImage(OxyImage source)
   515         {
   516             if (source == null)
   517             {
   518                 return null;
   519             }
   520 
   521             if (!this.imagesInUse.Contains(source))
   522             {
   523                 this.imagesInUse.Add(source);
   524             }
   525 
   526             Image src;
   527             if (this.imageCache.TryGetValue(source, out src))
   528             {
   529                 return src;
   530             }
   531 
   532             if (source != null)
   533             {
   534                 Image btm;
   535                 using (var ms = new MemoryStream(source.GetData()))
   536                 {
   537                     btm = Image.FromStream(ms);
   538                 }
   539 
   540                 this.imageCache.Add(source, btm);
   541                 return btm;
   542             }
   543 
   544             return null;
   545         }
   546 
   547         public override bool SetClip(OxyRect rect)
   548         {
   549             this.g.SetClip(rect.ToRect(false));
   550             return true;
   551         }
   552 
   553         public override void ResetClip()
   554         {
   555             this.g.ResetClip();
   556         }
   557     }
   558 }