Commit 9e269139 authored by James Welle's avatar James Welle Committed by Eric Domke
Browse files

Gradient Improvements

- Added support for the "gradientTransform" attribute on both linear and
  radial gradients. The matrix in this attribute needs to be applied to
  attributes with coordinate values on the gradient element in order to
  transform them into the correct coordinate space.

- Added support for a value of "pad" for the "spreadMode" attribute on
  both linear and radial gradients. This is the default value but was not
  implemented correctly. In order to implement, we examine the properties
  of the gradient along with the element to which the gradient is being
  applied to determine if we need to expand the bounds of the gradient to
  fill the element. If so, we do so and adjust the color stops and
  positions so they are correct for the new gradient bounds.

- Divided ISvgStylable into ISvgBoundable and ISvgStylable. The
  SvgUnit.ToDeviceValue method just needs bounds so it can take
  ISvgBoundable. Moved SvgDocument.GetDimensions() to SvgFragment and made
  SvgFragment ISvgBoundable.

- Fixed a bug in SvgFragment.PushTransforms where it was calling the
  SvgUnit.ToDeviceValue overload that takes no parameters. This overload
  doesn't work if the value being converted is a percentage. (The overload
  should probably be removed entirely, but we didn't take that on in this
  commit.)

- Fixed an issue in SvgGroup.Bounds where a child with empty bounds would
  cause the group's bounds to be reported as empty.

- Fixed broken build by adding missing SvgMarker.MarkerUnits property.

- Converted files that we touched with mixed tabs and spaces to spaces.
  Also removed unused usings from files we touched.

- Converted SvgLinearGradientServer to use properties without backing
  fields for X1, Y1, etc. in order to match SvgRadialGradientServer.

- Moved default value assignments into constructors for consistency.
parent 4f3df864
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