From 414d475085d4389829231cf743443e4df60e24a5 Mon Sep 17 00:00:00 2001
From: morpheusxx <morpheus_xx@team-mediaportal.com>
Date: Mon, 2 Jan 2012 13:02:25 +0100
Subject: [PATCH 1/2] Added support for different PenLineJoins for Shapes and Borders (Miter and Bevel (default) supported, Round not supported)

---
 .../UI/SkinEngine/Controls/Visuals/Border.cs       |   24 +++++-
 .../SkinEngine/Controls/Visuals/Shapes/Ellipse.cs  |    2 +-
 .../UI/SkinEngine/Controls/Visuals/Shapes/Path.cs  |    2 +-
 .../SkinEngine/Controls/Visuals/Shapes/Polygon.cs  |    2 +-
 .../Controls/Visuals/Shapes/Rectangle.cs           |    2 +-
 .../UI/SkinEngine/Controls/Visuals/Shapes/Shape.cs |   42 ++++++++
 .../DirectX/Triangulate/TriangulateHelper.cs       |  103 +++++++++++--------
 7 files changed, 129 insertions(+), 48 deletions(-)

diff --git a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Border.cs b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Border.cs
index cbabffe..7dc1b47 100644
--- a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Border.cs
+++ b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Border.cs
@@ -27,6 +27,7 @@ using System.Collections.Generic;
 using System.Drawing.Drawing2D;
 using MediaPortal.Common.General;
 using MediaPortal.UI.SkinEngine.Controls.Brushes;
+using MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes;
 using MediaPortal.UI.SkinEngine.DirectX.Triangulate;
 using MediaPortal.UI.SkinEngine.MpfElements;
 using SlimDX.Direct3D9;
@@ -47,6 +48,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
     protected AbstractProperty _backgroundProperty;
     protected AbstractProperty _borderBrushProperty;
     protected AbstractProperty _borderThicknessProperty;
+    protected AbstractProperty _borderLineJoinProperty;
     protected AbstractProperty _cornerRadiusProperty;
     protected AbstractProperty _contentProperty;
     protected int _verticesCountBorder;
@@ -71,6 +73,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
       _borderBrushProperty = new SProperty(typeof(Brush), null);
       _backgroundProperty = new SProperty(typeof(Brush), null);
       _borderThicknessProperty = new SProperty(typeof(double), 1.0);
+      _borderLineJoinProperty = new SProperty(typeof(PenLineJoin), PenLineJoin.Bevel);
       _cornerRadiusProperty = new SProperty(typeof(double), 0.0);
       _contentProperty = new SProperty(typeof(FrameworkElement), null);
     }
@@ -80,6 +83,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
       _borderBrushProperty.Attach(OnBorderBrushPropertyChanged);
       _backgroundProperty.Attach(OnBackgroundBrushPropertyChanged);
       _borderThicknessProperty.Attach(OnLayoutPropertyChanged);
+      _borderLineJoinProperty.Attach(OnBorderLineJoinChanged);
       _cornerRadiusProperty.Attach(OnLayoutPropertyChanged);
       _contentProperty.Attach(OnContentChanged);
     }
@@ -89,6 +93,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
       _borderBrushProperty.Detach(OnBorderBrushPropertyChanged);
       _backgroundProperty.Detach(OnBackgroundBrushPropertyChanged);
       _borderThicknessProperty.Detach(OnLayoutPropertyChanged);
+      _borderLineJoinProperty.Detach(OnBorderLineJoinChanged);
       _cornerRadiusProperty.Detach(OnLayoutPropertyChanged);
       _contentProperty.Detach(OnContentChanged);
     }
@@ -101,6 +106,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
       BorderBrush = copyManager.GetCopy(b.BorderBrush);
       Background = copyManager.GetCopy(b.Background);
       BorderThickness = b.BorderThickness;
+      BorderLineJoin = b.BorderLineJoin;
       CornerRadius = b.CornerRadius;
       Content = copyManager.GetCopy(b.Content);
       _initializedContent = copyManager.GetCopy(b._initializedContent);
@@ -141,6 +147,11 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
       OnBorderBrushChanged(brush);
     }
 
+    void OnBorderLineJoinChanged(AbstractProperty property, object oldValue)
+    {
+      _performLayout = true;
+    }
+
     void OnBackgroundBrushChanged(IObservable observable)
     {
       _performLayout = true;
@@ -224,6 +235,17 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
       set { _borderThicknessProperty.SetValue(value); }
     }
 
+    public AbstractProperty BorderLineJoinProperty
+    {
+      get { return _borderLineJoinProperty; }
+    }
+
+    public PenLineJoin BorderLineJoin
+    {
+      get { return (PenLineJoin) _borderLineJoinProperty.GetValue(); }
+      set { _borderLineJoinProperty.SetValue(value); }
+    }
+
     public AbstractProperty CornerRadiusProperty
     {
       get { return _cornerRadiusProperty; }
@@ -364,7 +386,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals
                 bool isClosed;
                 gpi.NextSubpath(subPath, out isClosed);
                 PointF[] pathPoints = subPath.PathPoints;
-                TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) BorderThickness, isClosed, 1,
+                TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) BorderThickness, isClosed, 1, BorderLineJoin,
                     out subPathVerts[i]);
               }
             }
diff --git a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Ellipse.cs b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Ellipse.cs
index 948dc35..9c03b74 100644
--- a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Ellipse.cs
+++ b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Ellipse.cs
@@ -59,7 +59,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
 
           if (Stroke != null && StrokeThickness > 0)
           {
-            TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, true, 1, out verts);
+            TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, true, 1, StrokeLineJoin, out verts);
             Stroke.SetupBrush(this, ref verts, context.ZOrder, true);
             PrimitiveBuffer.SetPrimitiveBuffer(ref _strokeContext, ref verts, PrimitiveType.TriangleList);
           }
diff --git a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Path.cs b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Path.cs
index 9a16975..9470d56 100644
--- a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Path.cs
+++ b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Path.cs
@@ -126,7 +126,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
                   bool isClosed;
                   gpi.NextSubpath(subPath, out isClosed);
                   PointF[] pathPoints = subPath.PathPoints;
-                  TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, isClosed, 1,
+                  TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, isClosed, 1, StrokeLineJoin,
                       out subPathVerts[i]);
                 }
               }
diff --git a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Polygon.cs b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Polygon.cs
index cb7cd20..9a4aeee 100644
--- a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Polygon.cs
+++ b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Polygon.cs
@@ -105,7 +105,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
 
           if (Stroke != null && StrokeThickness > 0)
           {
-            TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, true, 1, out verts);
+            TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, true, 1, StrokeLineJoin, out verts);
             Stroke.SetupBrush(this, ref verts, context.ZOrder, true);
             PrimitiveBuffer.SetPrimitiveBuffer(ref _strokeContext, ref verts, PrimitiveType.TriangleList);
           }
diff --git a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Rectangle.cs b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Rectangle.cs
index 82b87f2..224832d 100644
--- a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Rectangle.cs
+++ b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Rectangle.cs
@@ -134,7 +134,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
 
           if (Stroke != null && StrokeThickness > 0)
           {
-            TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, true, 1, out verts);
+            TriangulateHelper.TriangulateStroke_TriangleList(pathPoints, (float) StrokeThickness, true, 1, StrokeLineJoin, out verts);
             Stroke.SetupBrush(this, ref verts, context.ZOrder, true);
             PrimitiveBuffer.SetPrimitiveBuffer(ref _strokeContext, ref verts, PrimitiveType.TriangleList);
           }
diff --git a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Shape.cs b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Shape.cs
index 3e01510..49cb801 100644
--- a/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Shape.cs
+++ b/MediaPortal/Source/UI/SkinEngine/Controls/Visuals/Shapes/Shape.cs
@@ -30,6 +30,25 @@ using MediaPortal.Utilities.DeepCopy;
 
 namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
 {
+  /// <summary>
+  /// Describes the shape that joins two lines or segments.
+  /// </summary>
+  public enum PenLineJoin
+  {
+    /// <summary>
+    /// Line joins use regular angular vertices.
+    /// </summary>
+    Miter,
+    /// <summary>
+    /// Line joins use beveled vertices. This is the default behaviour in MPF.
+    /// </summary>
+    Bevel,
+    /// <summary>
+    /// Line joins use rounded vertices.
+    /// </summary>
+    Round
+  }
+
   public class Shape : FrameworkElement
   {
     #region Protected fields
@@ -38,6 +57,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
     protected AbstractProperty _fillProperty;
     protected AbstractProperty _strokeProperty;
     protected AbstractProperty _strokeThicknessProperty;
+    protected AbstractProperty _strokeLineJoinProperty;
 
     protected volatile bool _performLayout;
     protected PrimitiveBuffer _fillContext;
@@ -66,6 +86,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
       _fillProperty = new SProperty(typeof(Brush), null);
       _strokeProperty = new SProperty(typeof(Brush), null);
       _strokeThicknessProperty = new SProperty(typeof(double), 1.0);
+      _strokeLineJoinProperty = new SProperty(typeof(PenLineJoin), PenLineJoin.Bevel);
       _stretchProperty = new SProperty(typeof(Stretch), Stretch.None);
     }
 
@@ -74,6 +95,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
       _fillProperty.Attach(OnFillBrushPropertyChanged);
       _strokeProperty.Attach(OnStrokeBrushPropertyChanged);
       _strokeThicknessProperty.Attach(OnStrokeThicknessChanged);
+      _strokeLineJoinProperty.Attach(OnStrokeLineJoinChanged);
     }
 
     void Detach()
@@ -81,6 +103,7 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
       _fillProperty.Detach(OnFillBrushPropertyChanged);
       _strokeProperty.Detach(OnStrokeBrushPropertyChanged);
       _strokeThicknessProperty.Detach(OnStrokeThicknessChanged);
+      _strokeLineJoinProperty.Detach(OnStrokeLineJoinChanged);
     }
 
     public override void DeepCopy(IDeepCopyable source, ICopyManager copyManager)
@@ -114,6 +137,11 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
       _performLayout = true;
     }
 
+    void OnStrokeLineJoinChanged(AbstractProperty property, object oldValue)
+    {
+      _performLayout = true;
+    }
+
     void OnFillBrushPropertyChanged(AbstractProperty property, object oldValue)
     {
       if (oldValue is Brush)
@@ -175,6 +203,20 @@ namespace MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes
       get { return (double) _strokeThicknessProperty.GetValue(); }
       set { _strokeThicknessProperty.SetValue(value); }
     }
+    
+    public AbstractProperty StrokeLineJoinProperty
+    {
+      get { return _strokeLineJoinProperty; }
+    }
+
+    /// <summary>
+    /// Gets or sets a PenLineJoin enumeration value that specifies the type of join that is used at the vertices of a Shape.
+    /// </summary>
+    public PenLineJoin StrokeLineJoin
+    {
+      get { return (PenLineJoin) _strokeLineJoinProperty.GetValue(); }
+      set { _strokeLineJoinProperty.SetValue(value); }
+    }
 
     protected void PerformLayout(RenderContext context)
     {
diff --git a/MediaPortal/Source/UI/SkinEngine/DirectX/Triangulate/TriangulateHelper.cs b/MediaPortal/Source/UI/SkinEngine/DirectX/Triangulate/TriangulateHelper.cs
index 4eb2408..5a0c563 100644
--- a/MediaPortal/Source/UI/SkinEngine/DirectX/Triangulate/TriangulateHelper.cs
+++ b/MediaPortal/Source/UI/SkinEngine/DirectX/Triangulate/TriangulateHelper.cs
@@ -25,9 +25,10 @@
 using System;
 using System.Collections.Generic;
 using System.Drawing;
+using MediaPortal.UI.SkinEngine.Controls.Visuals.Shapes;
 using SlimDX;
 using SlimDX.Direct3D9;
-using Matrix=SlimDX.Matrix;
+using Matrix = SlimDX.Matrix;
 
 namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
 {
@@ -158,7 +159,7 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
     /// <summary>
     /// Converts the graphics path to an array of vertices using TriangleList.
     /// </summary>
-    public static void TriangulateStroke_TriangleList(PointF[] points, float thickness, bool close, float zCoord,
+    public static void TriangulateStroke_TriangleList(PointF[] points, float thickness, bool close, float zCoord, PenLineJoin lineJoin,
         out PositionColoredTextured[] verts)
     {
       PointF[] pathPoints = AdjustPoints(points);
@@ -169,7 +170,7 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
         cpoints[i] = new CPoint2D(pt.X, pt.Y);
       }
       PolygonDirection direction = CPolygon.GetPointsDirection(cpoints);
-      TriangulateStroke_TriangleList(pathPoints, thickness, close, direction, zCoord, out verts);
+      TriangulateStroke_TriangleList(pathPoints, thickness, close, direction, zCoord, lineJoin, out verts);
     }
 
     /// <summary>
@@ -180,9 +181,10 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
     /// <param name="close">True if we should connect the first and last point.</param>
     /// <param name="direction">The polygon direction.</param>
     /// <param name="zCoord">Z coordinate of the returned vertices.</param>
+    /// <param name="lineJoin">The PenLineJoin to use.</param>
     /// <param name="verts">The generated verts.</param>
     public static void TriangulateStroke_TriangleList(PointF[] points, float thickness, bool close,
-        PolygonDirection direction, float zCoord, out PositionColoredTextured[] verts)
+        PolygonDirection direction, float zCoord, PenLineJoin lineJoin, out PositionColoredTextured[] verts)
     {
       verts = null;
       PointF[] pathPoints = AdjustPoints(points);
@@ -197,7 +199,8 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
         pointCount = pathPoints.Length - 1;
 
       int pointsLength = pathPoints.Length;
-      verts = new PositionColoredTextured[pointCount * 3 * 3 - (close ? 0 : 3)];
+      int trianglesPerSegment = lineJoin == PenLineJoin.Miter ? 4 : 3;
+      verts = new PositionColoredTextured[pointCount * trianglesPerSegment * 3 - (close ? 0 : 3)];
 
       float insetX;
       float insetY;
@@ -217,22 +220,36 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
         if (lastInset.HasValue)
         {
           // If we wanted to have different StrokeLineJoin implementations, this should be done here. At the moment, the join is quite trivial.
-          verts[offset].Position = new Vector3(pathPoints[i].X, pathPoints[i].Y, zCoord);
-          verts[offset + 1].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, zCoord);
-          verts[offset + 2].Position = new Vector3(pathPoints[i].X + lastInset.Value.X, pathPoints[i].Y + lastInset.Value.Y, zCoord);
-          offset += 3;
+          switch (lineJoin)
+          {
+            case PenLineJoin.Miter:
+              verts[offset++].Position = new Vector3(pathPoints[i].X, pathPoints[i].Y, zCoord);
+              verts[offset++].Position = new Vector3(pathPoints[i].X + lastInset.Value.X, pathPoints[i].Y + lastInset.Value.Y, zCoord);
+              verts[offset++].Position = new Vector3(pathPoints[i].X + insetX + lastInset.Value.X, pathPoints[i].Y + insetY + lastInset.Value.Y, zCoord);
+
+              verts[offset++].Position = new Vector3(pathPoints[i].X + insetX + lastInset.Value.X, pathPoints[i].Y + insetY + lastInset.Value.Y, zCoord);
+              verts[offset++].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, zCoord);
+              verts[offset++].Position = new Vector3(pathPoints[i].X, pathPoints[i].Y, zCoord);
+              break;
+              // Currently Round is not supported and will be rendered as Bevel.
+            case PenLineJoin.Round:
+            case PenLineJoin.Bevel:
+              verts[offset++].Position = new Vector3(pathPoints[i].X, pathPoints[i].Y, zCoord);
+              verts[offset++].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, zCoord);
+              verts[offset++].Position = new Vector3(pathPoints[i].X + lastInset.Value.X, pathPoints[i].Y + lastInset.Value.Y, zCoord);
+              break;
+          }
         }
 
-        verts[offset].Position = new Vector3(pathPoints[i].X, pathPoints[i].Y, zCoord);
-        verts[offset + 1].Position = new Vector3(nextPoint.X, nextPoint.Y, zCoord);
-        verts[offset + 2].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, 1);
+        verts[offset++].Position = new Vector3(pathPoints[i].X, pathPoints[i].Y, zCoord);
+        verts[offset++].Position = new Vector3(nextPoint.X, nextPoint.Y, zCoord);
+        verts[offset++].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, zCoord);
 
-        verts[offset + 3].Position = new Vector3(nextPoint.X, nextPoint.Y, zCoord);
-        verts[offset + 4].Position = new Vector3(nextPoint.X + insetX, nextPoint.Y + insetY, zCoord);
-        verts[offset + 5].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, zCoord);
+        verts[offset++].Position = new Vector3(nextPoint.X, nextPoint.Y, zCoord);
+        verts[offset++].Position = new Vector3(nextPoint.X + insetX, nextPoint.Y + insetY, zCoord);
+        verts[offset++].Position = new Vector3(pathPoints[i].X + insetX, pathPoints[i].Y + insetY, zCoord);
 
         lastInset = new PointF(insetX, insetY);
-        offset += 6;
       }
     }
 
@@ -272,31 +289,31 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
       }
     }
 
-  /// <summary>
-  /// Describes how points should be painted in a LineStrip relative to its center.
-  /// </summary>
-  /// <remarks>
-  /// The behavior of the <see cref="LeftHanded"/> and <see cref="RightHanded"/> modes depends on the order the points are
-  /// listed in. <see cref="LeftHanded"/> will draw the line on the outside of a clockwise curve and on the
-  /// inside of a counterclockwise curve; <see cref="RightHanded"/> is the opposite.
-  /// </remarks>
-  public enum WidthMode
-  {
-    /// <summary>
-    /// Centers the width on the line.
-    /// </summary>
-    Centered,
-
-    /// <summary>
-    /// Places the width on the left-hand side of the line.
-    /// </summary>
-    LeftHanded,
-
     /// <summary>
-    /// Places the width on the right-hand side of the line.
+    /// Describes how points should be painted in a LineStrip relative to its center.
     /// </summary>
-    RightHanded
-  }
+    /// <remarks>
+    /// The behavior of the <see cref="LeftHanded"/> and <see cref="RightHanded"/> modes depends on the order the points are
+    /// listed in. <see cref="LeftHanded"/> will draw the line on the outside of a clockwise curve and on the
+    /// inside of a counterclockwise curve; <see cref="RightHanded"/> is the opposite.
+    /// </remarks>
+    public enum WidthMode
+    {
+      /// <summary>
+      /// Centers the width on the line.
+      /// </summary>
+      Centered,
+
+      /// <summary>
+      /// Places the width on the left-hand side of the line.
+      /// </summary>
+      LeftHanded,
+
+      /// <summary>
+      /// Places the width on the right-hand side of the line.
+      /// </summary>
+      RightHanded
+    }
 
     /// <summary>
     /// Generates the vertices of a thickened line strip.
@@ -420,12 +437,12 @@ namespace MediaPortal.UI.SkinEngine.DirectX.Triangulate
         v2 = pathPoints[index];
         ZCross(ref v1, ref v2, out temp);
         area += temp;
-        centroid.X += (float)((v1.X + v2.X) * temp);
-        centroid.Y += (float)((v1.Y + v2.Y) * temp);
+        centroid.X += (float) ((v1.X + v2.X) * temp);
+        centroid.Y += (float) ((v1.Y + v2.Y) * temp);
       }
       temp = 1 / (Math.Abs(area) * 3);
-      centroid.X *= (float)temp;
-      centroid.Y *= (float)temp;
+      centroid.X *= (float) temp;
+      centroid.Y *= (float) temp;
 
       cx = Math.Abs(centroid.X);
       cy = Math.Abs(centroid.Y);
-- 
1.7.3.1.msysgit.0

