Commit 2462bf13 authored by Tebjan Halm's avatar Tebjan Halm
Browse files

Merge pull request #85 from articulate/GradientTransform

Gradient Improvements
parents e71c9d97 3987f281
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml.Serialization;
using System.ComponentModel;
using System.Diagnostics;
namespace Svg
{
/// <summary>
/// The class that all SVG elements should derive from when they are to be rendered.
/// </summary>
public abstract partial class SvgVisualElement : SvgElement, ISvgStylable, ISvgClipable
public abstract partial class SvgVisualElement : SvgElement, ISvgBoundable, ISvgStylable, ISvgClipable
{
private bool _dirty;
private bool _requiresSmoothRendering;
......@@ -23,6 +17,23 @@ namespace Svg
/// Gets the <see cref="GraphicsPath"/> for this element.
/// </summary>
public abstract GraphicsPath Path { get; protected set; }
PointF ISvgBoundable.Location
{
get
{
return Bounds.Location;
}
}
SizeF ISvgBoundable.Size
{
get
{
return Bounds.Size;
}
}
/// <summary>
/// Gets the bounds of the element.
/// </summary>
......
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Globalization;
namespace Svg
......@@ -73,16 +70,16 @@ namespace Svg
/// Converts the current unit to one that can be used at render time.
/// </summary>
/// <returns>The representation of the current unit in a device value (usually pixels).</returns>
public float ToDeviceValue(ISvgStylable styleOwner)
public float ToDeviceValue(ISvgBoundable boundable)
{
return this.ToDeviceValue(styleOwner, false);
return this.ToDeviceValue(boundable, false);
}
/// <summary>
/// Converts the current unit to one that can be used at render time.
/// </summary>
/// <returns>The representation of the current unit in a device value (usually pixels).</returns>
public float ToDeviceValue(ISvgStylable styleOwner, bool vertical)
public float ToDeviceValue(ISvgBoundable boundable, bool vertical)
{
// If it's already been calculated
if (this._deviceValue.HasValue)
......@@ -131,14 +128,14 @@ namespace Svg
break;
case SvgUnitType.Percentage:
// Can't calculate if there is no style owner
if (styleOwner == null)
if (boundable == null)
{
_deviceValue = this.Value;
break;
}
// TODO : Support height percentages
System.Drawing.RectangleF size = styleOwner.Bounds;
System.Drawing.SizeF size = boundable.Bounds.Size;
_deviceValue = (((vertical) ? size.Height : size.Width) / 100) * this.Value;
break;
default:
......
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Drawing2D;
namespace Svg
{
......@@ -12,13 +8,37 @@ namespace Svg
/// An <see cref="SvgFragment"/> represents an SVG fragment that can be the root element or an embedded fragment of an SVG document.
/// </summary>
[SvgElement("svg")]
public class SvgFragment : SvgElement, ISvgViewPort
public class SvgFragment : SvgElement, ISvgViewPort, ISvgBoundable
{
/// <summary>
/// Gets the SVG namespace string.
/// </summary>
public static readonly Uri Namespace = new Uri("http://www.w3.org/2000/svg");
PointF ISvgBoundable.Location
{
get
{
return PointF.Empty;
}
}
SizeF ISvgBoundable.Size
{
get
{
return GetDimensions();
}
}
RectangleF ISvgBoundable.Bounds
{
get
{
return new RectangleF(((ISvgBoundable)this).Location, ((ISvgBoundable)this).Size);
}
}
private SvgUnit _x;
private SvgUnit _y;
......@@ -117,8 +137,8 @@ namespace Svg
if (!this.ViewBox.Equals(SvgViewBox.Empty))
{
float fScaleX = this.Width.ToDeviceValue() / this.ViewBox.Width;
float fScaleY = this.Height.ToDeviceValue() / this.ViewBox.Height;
float fScaleX = this.Width.ToDeviceValue(this, false) / this.ViewBox.Width;
float fScaleY = this.Height.ToDeviceValue(this, true) / this.ViewBox.Height;
float fMinX = -this.ViewBox.MinX;
float fMinY = -this.ViewBox.MinY;
......@@ -136,8 +156,8 @@ namespace Svg
}
float fViewMidX = (this.ViewBox.Width / 2) * fScaleX;
float fViewMidY = (this.ViewBox.Height / 2) * fScaleY;
float fMidX = this.Width.ToDeviceValue() / 2;
float fMidY = this.Height.ToDeviceValue() / 2;
float fMidX = this.Width.ToDeviceValue(this, false) / 2;
float fMidY = this.Height.ToDeviceValue(this, true) / 2;
switch (AspectRatio.Align)
{
......@@ -147,7 +167,7 @@ namespace Svg
fMinX += (fMidX - fViewMidX) / fScaleX;
break;
case SvgPreserveAspectRatio.xMaxYMin:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width;
fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
break;
case SvgPreserveAspectRatio.xMinYMid:
fMinY += (fMidY - fViewMidY) / fScaleY;
......@@ -157,19 +177,19 @@ namespace Svg
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMaxYMid:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width;
fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
fMinY += (fMidY - fViewMidY) / fScaleY;
break;
case SvgPreserveAspectRatio.xMinYMax:
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height;
fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break;
case SvgPreserveAspectRatio.xMidYMax:
fMinX += (fMidX - fViewMidX) / fScaleX;
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height;
fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break;
case SvgPreserveAspectRatio.xMaxYMax:
fMinX += (this.Width.ToDeviceValue() / fScaleX) - this.ViewBox.Width;
fMinY += (this.Height.ToDeviceValue() / fScaleY) - this.ViewBox.Height;
fMinX += (this.Width.ToDeviceValue(this, false) / fScaleX) - this.ViewBox.Width;
fMinY += (this.Height.ToDeviceValue(this, true) / fScaleY) - this.ViewBox.Height;
break;
default:
break;
......@@ -223,6 +243,24 @@ namespace Svg
this.AspectRatio = new SvgAspectRatio(SvgPreserveAspectRatio.xMidYMid);
}
public SizeF GetDimensions()
{
var w = Width.ToDeviceValue();
var h = Height.ToDeviceValue();
RectangleF bounds = new RectangleF();
var isWidthperc = Width.Type == SvgUnitType.Percentage;
var isHeightperc = Height.Type == SvgUnitType.Percentage;
if (isWidthperc || isHeightperc)
{
bounds = this.Bounds; //do just one call to the recursive bounds property
if (isWidthperc) w = (bounds.Width + bounds.X) * (w * 0.01f);
if (isHeightperc) h = (bounds.Height + bounds.Y) * (h * 0.01f);
}
return new SizeF(w, h);
}
public override SvgElement DeepCopy()
{
......@@ -239,5 +277,7 @@ namespace Svg
newObj.AspectRatio = this.AspectRatio;
return newObj;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using Svg.Transforms;
namespace Svg
{
......@@ -52,9 +46,17 @@ namespace Svg
// First it should check if rectangle is empty or it will return the wrong Bounds.
// This is because when the Rectangle is Empty, the Union method adds as if the first values where X=0, Y=0
if (r.IsEmpty)
{
r = ((SvgVisualElement)c).Bounds;
}
else
r = RectangleF.Union(r, ((SvgVisualElement)c).Bounds);
{
var childBounds = ((SvgVisualElement)c).Bounds;
if (!childBounds.IsEmpty)
{
r = RectangleF.Union(r, childBounds);
}
}
}
}
......
using System.Drawing;
namespace Svg
{
public interface ISvgBoundable
{
PointF Location
{
get;
}
SizeF Size
{
get;
}
RectangleF Bounds
{
get;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace Svg
......@@ -24,6 +20,5 @@ namespace Svg
SvgUnitCollection StrokeDashArray { get; set; }
SvgUnit StrokeDashOffset { get; set; }
GraphicsPath Path { get; }
RectangleF Bounds { get; }
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using Svg.Transforms;
namespace Svg
{
......@@ -10,7 +12,7 @@ namespace Svg
public abstract class SvgGradientServer : SvgPaintServer
{
private SvgCoordinateUnits _gradientUnits;
private SvgGradientSpreadMethod _spreadMethod = SvgGradientSpreadMethod.Pad;
private SvgGradientSpreadMethod _spreadMethod;
private SvgGradientServer _inheritGradient;
private List<SvgGradientStop> _stops;
......@@ -20,6 +22,7 @@ namespace Svg
internal SvgGradientServer()
{
this.GradientUnits = SvgCoordinateUnits.ObjectBoundingBox;
this.SpreadMethod = SvgGradientSpreadMethod.Pad;
this._stops = new List<SvgGradientStop>();
}
......@@ -96,12 +99,39 @@ namespace Svg
}
}
[SvgAttribute("gradientTransform")]
public SvgTransformCollection GradientTransform
{
get
{
return (this.Attributes.GetAttribute<SvgTransformCollection>("gradientTransform"));
}
set
{
this.Attributes["gradientTransform"] = value;
}
}
private Matrix EffectiveGradientTransform
{
get
{
var transform = new Matrix();
if (GradientTransform != null)
{
transform.Multiply(GradientTransform.GetMatrix());
}
return transform;
}
}
/// <summary>
/// Gets a <see cref="ColorBlend"/> representing the <see cref="SvgGradientServer"/>'s gradient stops.
/// </summary>
/// <param name="owner">The parent <see cref="SvgVisualElement"/>.</param>
/// <param name="opacity">The opacity of the colour blend.</param>
protected ColorBlend GetColourBlend(SvgVisualElement owner, float opacity, bool radial)
protected ColorBlend GetColorBlend(SvgVisualElement owner, float opacity, bool radial)
{
int colourBlends = this.Stops.Count;
bool insertStart = false;
......@@ -201,6 +231,38 @@ namespace Svg
}
}
protected ISvgBoundable CalculateBoundable(SvgVisualElement renderingElement)
{
return (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? (ISvgBoundable)renderingElement : renderingElement.OwnerDocument;
}
protected PointF TransformPoint(PointF originalPoint)
{
var newPoint = new[] { originalPoint };
EffectiveGradientTransform.TransformPoints(newPoint);
return newPoint[0];
}
protected PointF TransformVector(PointF originalVector)
{
var newVector = new[] { originalVector };
EffectiveGradientTransform.TransformVectors(newVector);
return newVector[0];
}
protected static double CalculateDistance(PointF first, PointF second)
{
return Math.Sqrt(Math.Pow(first.X - second.X, 2) + Math.Pow(first.Y - second.Y, 2));
}
protected static float CalculateLength(PointF vector)
{
return (float)Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}
public override SvgElement DeepCopy<T>()
{
......@@ -209,6 +271,7 @@ namespace Svg
newObj.SpreadMethod = this.SpreadMethod;
newObj.GradientUnits = this.GradientUnits;
newObj.InheritGradient = this.InheritGradient;
newObj.GradientTransform = this.GradientTransform;
return newObj;
}
......
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing.Drawing2D;
using System.Diagnostics;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Drawing2D;
using System.Linq;
namespace Svg
{
[SvgElement("linearGradient")]
public sealed class SvgLinearGradientServer : SvgGradientServer
{
private SvgUnit _x1;
private SvgUnit _y1;
private SvgUnit _x2;
private SvgUnit _y2;
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("x1")]
[SvgAttribute("x1")]
public SvgUnit X1
{
get { return this._x1; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("x1");
}
set
{
this._x1 = value;
Attributes["x1"] = value;
}
}
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("y1")]
[SvgAttribute("y1")]
public SvgUnit Y1
{
get { return this._y1; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("y1");
}
set
{
this._y1 = value;
this.Attributes["y1"] = value;
}
}
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("x2")]
[SvgAttribute("x2")]
public SvgUnit X2
{
get { return this._x2; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("x2");
}
set
{
this._x2 = value;
Attributes["x2"] = value;
}
}
[DefaultValue(typeof(SvgUnit), "0"), SvgAttribute("y2")]
[SvgAttribute("y2")]
public SvgUnit Y2
{
get { return this._y2; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("y2");
}
set
{
this._y2 = value;
this.Attributes["y2"] = value;
}
}
private bool IsInvalid
{
get
{
// Need at least 2 colours to do the gradient fill
return this.Stops.Count < 2;
}
}
public SvgLinearGradientServer()
{
this._x1 = new SvgUnit(0.0f);
this._y1 = new SvgUnit(0.0f);
this._x2 = new SvgUnit(0.0f);
this._y2 = new SvgUnit(0.0f);
X1 = new SvgUnit(SvgUnitType.Percentage, 0F);
Y1 = new SvgUnit(SvgUnitType.Percentage, 0F);
X2 = new SvgUnit(SvgUnitType.Percentage, 100F);
Y2 = new SvgUnit(SvgUnitType.Percentage, 0F);
}
public SvgPoint Start
public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{
get { return new SvgPoint(this.X1, this.Y1); }
if (IsInvalid)
{
return null;
}
public SvgPoint End
var boundable = CalculateBoundable(renderingElement);
var specifiedStart = CalculateStart(boundable);
var specifiedEnd = CalculateEnd(boundable);
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
if (NeedToExpandGradient(renderingElement, specifiedStart, specifiedEnd))
{
get { return new SvgPoint(this.X2, this.Y2); }
var expansion = ExpandGradient(renderingElement, specifiedStart, specifiedEnd);
effectiveStart = expansion.Item1;
effectiveEnd = expansion.Item2;
}
public override Brush GetBrush(SvgVisualElement owner, float opacity)
return new LinearGradientBrush(effectiveStart, effectiveEnd, Color.Transparent, Color.Transparent)
{
// Need at least 2 colours to do the gradient fill
if (this.Stops.Count < 2)
InterpolationColors = CalculateColorBlend(renderingElement, opacity, specifiedStart, effectiveStart, specifiedEnd, effectiveEnd),
WrapMode = WrapMode.TileFlipX
};
}
private PointF CalculateStart(ISvgBoundable boundable)
{
return null;
return TransformPoint(new PointF(this.X1.ToDeviceValue(boundable), this.Y1.ToDeviceValue(boundable, true)));
}
PointF start;
PointF end;
RectangleF bounds = (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? owner.Bounds : owner.OwnerDocument.GetDimensions();
private PointF CalculateEnd(ISvgBoundable boundable)
{
return TransformPoint(new PointF(this.X2.ToDeviceValue(boundable), this.Y2.ToDeviceValue(boundable, true)));
}
// Have start/end points been set? If not the gradient is horizontal
if (!this.End.IsEmpty())
private bool NeedToExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
// Get the points to work out an angle
if (this.Start.IsEmpty())
return SpreadMethod == SvgGradientSpreadMethod.Pad && (boundable.Bounds.Contains(specifiedStart) || boundable.Bounds.Contains(specifiedEnd));
}
private Tuple<PointF, PointF> ExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
{
if (!NeedToExpandGradient(boundable, specifiedStart, specifiedEnd))
{
Debug.Fail("Unexpectedly expanding gradient when not needed!");
return new Tuple<PointF, PointF>(specifiedStart, specifiedEnd);
}
var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
var effectiveStart = specifiedStart;
var effectiveEnd = specifiedEnd;
var elementDiagonal = (float)CalculateDistance(new PointF(boundable.Bounds.Left, boundable.Bounds.Top), new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom));
var expandedStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -elementDiagonal);
var expandedEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, elementDiagonal);
var intersectionPoints = new LineF(expandedStart.X, expandedStart.Y, expandedEnd.X, expandedEnd.Y).Intersection(boundable.Bounds);
if (boundable.Bounds.Contains(specifiedStart))
{
effectiveStart = CalculateClosestIntersectionPoint(expandedStart, intersectionPoints);
effectiveStart = MovePointAlongVector(effectiveStart, specifiedUnitVector, -1);
}
if (boundable.Bounds.Contains(specifiedEnd))
{
start = bounds.Location;
effectiveEnd = CalculateClosestIntersectionPoint(effectiveEnd, intersectionPoints);
effectiveEnd = MovePointAlongVector(effectiveEnd, specifiedUnitVector, 1);
}
return new Tuple<PointF, PointF>(effectiveStart, effectiveEnd);
}
else
private ColorBlend CalculateColorBlend(SvgVisualElement owner, float opacity, PointF specifiedStart, PointF effectiveStart, PointF specifiedEnd, PointF effectiveEnd)
{
var colorBlend = GetColorBlend(owner, opacity, false);
var startDelta = CalculateDistance(specifiedStart, effectiveStart);
var endDelta = CalculateDistance(specifiedEnd, effectiveEnd);
if (!(startDelta > 0) && !(endDelta > 0))
{
start = new PointF(this.Start.X.ToDeviceValue(owner), this.Start.Y.ToDeviceValue(owner, true));
return colorBlend;
}
float x = (this.End.X.IsEmpty) ? start.X : this.End.X.ToDeviceValue(owner);
end = new PointF(x, this.End.Y.ToDeviceValue(owner, true));
var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
var effectiveLength = CalculateDistance(effectiveStart, effectiveEnd);
for (var i = 0; i < colorBlend.Positions.Length; i++)
{
var originalPoint = MovePointAlongVector(specifiedStart, specifiedUnitVector, (float) specifiedLength * colorBlend.Positions[i]);
var distanceFromEffectiveStart = CalculateDistance(effectiveStart, originalPoint);
colorBlend.Positions[i] = (float) Math.Max(0F, Math.Min((distanceFromEffectiveStart / effectiveLength), 1.0F));
}
else
if (startDelta > 0)
{
// Default: horizontal
start = bounds.Location;
end = new PointF(bounds.Right, bounds.Top);
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
}
LinearGradientBrush gradient = new LinearGradientBrush(start, end, Color.Transparent, Color.Transparent);
gradient.InterpolationColors = base.GetColourBlend(owner, opacity, false);
if (endDelta > 0)
{
colorBlend.Positions = colorBlend.Positions.Concat(new[] { 1F }).ToArray();
colorBlend.Colors = colorBlend.Colors.Concat(new[] { colorBlend.Colors.Last() }).ToArray();
}
// Needed to fix an issue where the gradient was being wrapped when though it had the correct bounds
gradient.WrapMode = WrapMode.TileFlipX;
return gradient;
return colorBlend;
}
private static PointF CalculateClosestIntersectionPoint(PointF sourcePoint, IList<PointF> targetPoints)
{
Debug.Assert(targetPoints.Count == 2, "Unexpected number of intersection points!");
return CalculateDistance(sourcePoint, targetPoints[0]) < CalculateDistance(sourcePoint, targetPoints[1]) ? targetPoints[0] : targetPoints[1];
}
private static PointF MovePointAlongVector(PointF start, PointF unitVector, float distance)
{
return start + new SizeF(unitVector.X * distance, unitVector.Y * distance);
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgLinearGradientServer>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgLinearGradientServer;
......@@ -134,5 +229,106 @@ namespace Svg
return newObj;
}
private sealed class LineF
{
private float X1
{
get;
set;
}
private float Y1
{
get;
set;
}
private float X2
{
get;
set;
}
private float Y2
{
get;
set;
}
public LineF(float x1, float y1, float x2, float y2)
{
X1 = x1;
Y1 = y1;
X2 = x2;
Y2 = y2;
}
public List<PointF> Intersection(RectangleF rectangle)
{
var result = new List<PointF>();
AddIfIntersect(this, new LineF(rectangle.X, rectangle.Y, rectangle.Right, rectangle.Y), result);
AddIfIntersect(this, new LineF(rectangle.Right, rectangle.Y, rectangle.Right, rectangle.Bottom), result);
AddIfIntersect(this, new LineF(rectangle.Right, rectangle.Bottom, rectangle.X, rectangle.Bottom), result);
AddIfIntersect(this, new LineF(rectangle.X, rectangle.Bottom, rectangle.X, rectangle.Y), result);
return result;
}
private PointF? Intersection(LineF other)
{
var a1 = Y2 - Y1;
var b1 = X1 - X2;
var c1 = X2 * Y1 - X1 * Y2;
var r3 = a1 * other.X1 + b1 * other.Y1 + c1;
var r4 = a1 * other.X2 + b1 * other.Y2 + c1;
if (r3 != 0 && r4 != 0 && Math.Sign(r3) == Math.Sign(r4))
{
return null;
}
var a2 = other.Y2 - other.Y1;
var b2 = other.X1 - other.X2;
var c2 = other.X2 * other.Y1 - other.X1 * other.Y2;
var r1 = a2 * X1 + b2 * Y1 + c2;
var r2 = a2 * X2 + b2 * Y2 + c2;
if (r1 != 0 && r2 != 0 && Math.Sign(r1) == Math.Sign(r2))
{
return (null);
}
var denom = a1 * b2 - a2 * b1;
if (denom == 0)
{
return null;
}
var offset = denom < 0 ? -denom / 2 : denom / 2;
var num = b1 * c2 - b2 * c1;
var x = (num < 0 ? num - offset : num + offset) / denom;
num = a2 * c1 - a1 * c2;
var y = (num < 0 ? num - offset : num + offset) / denom;
return new PointF(x, y);
}
private static void AddIfIntersect(LineF first, LineF second, ICollection<PointF> result)
{
var intersection = first.Intersection(second);
if (intersection != null)
{
result.Add(intersection.Value);
}
}
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.Serialization;
using System.Drawing.Drawing2D;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using Svg.DataTypes;
namespace Svg
......@@ -77,6 +72,13 @@ namespace Svg
set { this.Attributes["markerHeight"] = value; }
}
[SvgAttribute("markerUnits")]
public virtual SvgMarkerUnits MarkerUnits
{
get { return this.Attributes.GetAttribute<SvgMarkerUnits>("markerUnits"); }
set { this.Attributes["markerUnits"] = value; }
}
public SvgMarker()
{
MarkerUnits = SvgMarkerUnits.strokeWidth;
......
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
namespace Svg
{
......@@ -9,22 +12,40 @@ namespace Svg
[SvgAttribute("cx")]
public SvgUnit CenterX
{
get { return this.Attributes.GetAttribute<SvgUnit>("cx"); }
set { this.Attributes["cx"] = value; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("cx");
}
set
{
this.Attributes["cx"] = value;
}
}
[SvgAttribute("cy")]
public SvgUnit CenterY
{
get { return this.Attributes.GetAttribute<SvgUnit>("cy"); }
set { this.Attributes["cy"] = value; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("cy");
}
set
{
this.Attributes["cy"] = value;
}
}
[SvgAttribute("r")]
public SvgUnit Radius
{
get { return this.Attributes.GetAttribute<SvgUnit>("r"); }
set { this.Attributes["r"] = value; }
get
{
return this.Attributes.GetAttribute<SvgUnit>("r");
}
set
{
this.Attributes["r"] = value;
}
}
[SvgAttribute("fx")]
......@@ -41,8 +62,10 @@ namespace Svg
return value;
}
set { this.Attributes["fx"] = value; }
set
{
this.Attributes["fx"] = value;
}
}
[SvgAttribute("fy")]
......@@ -59,61 +82,139 @@ namespace Svg
return value;
}
set { this.Attributes["fy"] = value; }
set
{
this.Attributes["fy"] = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SvgRadialGradientServer"/> class.
/// </summary>
public SvgRadialGradientServer()
{
//Apply default values of 50% to cX,cY and r
CenterX = new SvgUnit(SvgUnitType.Percentage, 50);
CenterY = new SvgUnit(SvgUnitType.Percentage, 50);
Radius = new SvgUnit(SvgUnitType.Percentage, 50);
CenterX = new SvgUnit(SvgUnitType.Percentage, 50F);
CenterY = new SvgUnit(SvgUnitType.Percentage, 50F);
Radius = new SvgUnit(SvgUnitType.Percentage, 50F);
}
public override Brush GetBrush(SvgVisualElement renderingElement, float opacity)
{
float radius = this.Radius.ToDeviceValue(renderingElement);
var origin = CalculateOrigin(renderingElement);
if (radius <= 0)
var centerPoint = CalculateCenterPoint(renderingElement, origin);
var focalPoint = CalculateFocalPoint(renderingElement, origin);
var specifiedRadius = CalculateRadius(renderingElement);
var effectiveRadius = CalculateEffectiveRadius(renderingElement, centerPoint, specifiedRadius);
var brush = new PathGradientBrush(CreateGraphicsPath(origin, centerPoint, effectiveRadius))
{
return null;
InterpolationColors = CalculateColorBlend(renderingElement, opacity, specifiedRadius, effectiveRadius),
CenterPoint = focalPoint
};
Debug.Assert(brush.Rectangle.Contains(renderingElement.Bounds), "Brush rectangle does not contain rendering element bounds!");
return brush;
}
GraphicsPath path = new GraphicsPath();
float left = this.CenterX.ToDeviceValue(renderingElement);
float top = this.CenterY.ToDeviceValue(renderingElement, true);
private PointF CalculateOrigin(SvgVisualElement renderingElement)
{
return CalculateBoundable(renderingElement).Location;
}
private PointF CalculateCenterPoint(ISvgBoundable boundable, PointF origin)
{
var deviceCenterX = origin.X + CenterX.ToDeviceValue(boundable);
var deviceCenterY = origin.Y + CenterY.ToDeviceValue(boundable, true);
var transformedCenterPoint = TransformPoint(new PointF(deviceCenterX, deviceCenterY));
return transformedCenterPoint;
}
private PointF CalculateFocalPoint(ISvgBoundable boundable, PointF origin)
{
var deviceFocalX = origin.X + FocalX.ToDeviceValue(boundable);
var deviceFocalY = origin.Y + FocalY.ToDeviceValue(boundable, true);
var transformedFocalPoint = TransformPoint(new PointF(deviceFocalX, deviceFocalY));
return transformedFocalPoint;
}
RectangleF boundingBox = (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) ? renderingElement.Bounds : renderingElement.OwnerDocument.GetDimensions();
private float CalculateRadius(ISvgBoundable boundable)
{
var radius = Radius.ToDeviceValue(boundable);
var transformRadiusVector = TransformVector(new PointF(radius, 0));
var transformedRadius = CalculateLength(transformRadiusVector);
return transformedRadius;
}
private float CalculateEffectiveRadius(ISvgBoundable boundable, PointF centerPoint, float specifiedRadius)
{
if (SpreadMethod != SvgGradientSpreadMethod.Pad)
{
return specifiedRadius;
}
var topLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Top);
var topRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Top);
var bottomRight = new PointF(boundable.Bounds.Right, boundable.Bounds.Bottom);
var bottomLeft = new PointF(boundable.Bounds.Left, boundable.Bounds.Bottom);
var effectiveRadius = (float)Math.Ceiling(
Math.Max(
Math.Max(
CalculateDistance(centerPoint, topLeft),
CalculateDistance(centerPoint, topRight)
),
Math.Max(
CalculateDistance(centerPoint, bottomRight),
CalculateDistance(centerPoint, bottomLeft)
)
)
);
effectiveRadius = Math.Max(effectiveRadius, specifiedRadius);
return effectiveRadius;
}
private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius)
{
var path = new GraphicsPath();
path.AddEllipse(
boundingBox.Left + left - radius,
boundingBox.Top + top - radius,
radius * 2,
radius * 2);
origin.X + centerPoint.X - effectiveRadius,
origin.Y + centerPoint.Y - effectiveRadius,
effectiveRadius * 2,
effectiveRadius * 2
);
PathGradientBrush brush = new PathGradientBrush(path);
ColorBlend blend = base.GetColourBlend(renderingElement, opacity, true);
return path;
}
brush.InterpolationColors = blend;
brush.CenterPoint =
new PointF(
boundingBox.Left + this.FocalX.ToDeviceValue(renderingElement),
boundingBox.Top + this.FocalY.ToDeviceValue(renderingElement, true));
private ColorBlend CalculateColorBlend(SvgVisualElement renderingElement, float opacity, float specifiedRadius, float effectiveRadius)
{
var colorBlend = GetColorBlend(renderingElement, opacity, true);
return brush;
if (specifiedRadius >= effectiveRadius)
{
return colorBlend;
}
for (var i = 0; i < colorBlend.Positions.Length - 1; i++)
{
colorBlend.Positions[i] = 1 - (specifiedRadius / effectiveRadius) * (1 - colorBlend.Positions[i]);
}
colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
return colorBlend;
}
public override SvgElement DeepCopy()
{
return DeepCopy<SvgRadialGradientServer>();
}
public override SvgElement DeepCopy<T>()
{
var newObj = base.DeepCopy<T>() as SvgRadialGradientServer;
......
......@@ -114,6 +114,7 @@
<Compile Include="DataTypes\SvgViewBox.cs" />
<Compile Include="Document Structure\SvgTitle.cs" />
<Compile Include="Document Structure\SvgDocumentMetadata.cs" />
<Compile Include="Painting\ISvgBoundable.cs" />
<Compile Include="Painting\SvgMarker.cs" />
<Compile Include="Document Structure\SvgDefinitionList.cs" />
<Compile Include="Document Structure\SvgDescription.cs" />
......
......@@ -19,8 +19,6 @@ namespace Svg
public static readonly int PointsPerInch = 96;
private SvgElementIdManager _idManager;
/// <summary>
/// Initializes a new instance of the <see cref="SvgDocument"/> class.
/// </summary>
......@@ -293,25 +291,6 @@ namespace Svg
return null;
}
public RectangleF GetDimensions()
{
var w = Width.ToDeviceValue();
var h = Height.ToDeviceValue();
RectangleF bounds = new RectangleF();
var isWidthperc = Width.Type == SvgUnitType.Percentage;
var isHeightperc = Height.Type == SvgUnitType.Percentage;
if(isWidthperc || isHeightperc)
{
bounds = this.Bounds; //do just one call to the recursive bounds property
if(isWidthperc) w = (bounds.Width + bounds.X) * (w * 0.01f);
if(isHeightperc) h = (bounds.Height + bounds.Y) * (h * 0.01f);
}
return new RectangleF(0, 0, w, h);
}
/// <summary>
/// Renders the <see cref="SvgDocument"/> to the specified <see cref="SvgRenderer"/>.
/// </summary>
......@@ -413,6 +392,5 @@ namespace Svg
this.Write(fs);
}
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment