123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Linq;
- #pragma warning disable
- namespace Svg
- {
- [SvgElement("linearGradient")]
- public sealed class SvgLinearGradientServer : SvgGradientServer
- {
- [SvgAttribute("x1")]
- public SvgUnit X1
- {
- get
- {
- return this.Attributes.GetAttribute<SvgUnit>("x1");
- }
- set
- {
- Attributes["x1"] = value;
- }
- }
- [SvgAttribute("y1")]
- public SvgUnit Y1
- {
- get
- {
- return this.Attributes.GetAttribute<SvgUnit>("y1");
- }
- set
- {
- this.Attributes["y1"] = value;
- }
- }
- [SvgAttribute("x2")]
- public SvgUnit X2
- {
- get
- {
- return this.Attributes.GetAttribute<SvgUnit>("x2");
- }
- set
- {
- Attributes["x2"] = value;
- }
- }
- [SvgAttribute("y2")]
- public SvgUnit Y2
- {
- get
- {
- return this.Attributes.GetAttribute<SvgUnit>("y2");
- }
- set
- {
- 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()
- {
- 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 override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity, bool forStroke = false)
- {
- LoadStops(renderingElement);
- if (this.Stops.Count < 1) return null;
- if (this.Stops.Count == 1)
- {
- var stopColor = this.Stops[0].GetColor(renderingElement);
- int alpha = (int)Math.Round((opacity * (stopColor.A / 255.0f)) * 255);
- Color colour = System.Drawing.Color.FromArgb(alpha, stopColor);
- return new SolidBrush(colour);
- }
- try
- {
- if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement);
- var points = new PointF[] {
- SvgUnit.GetDevicePoint(NormalizeUnit(this.X1), NormalizeUnit(this.Y1), renderer, this),
- SvgUnit.GetDevicePoint(NormalizeUnit(this.X2), NormalizeUnit(this.Y2), renderer, this)
- };
- var bounds = renderer.GetBoundable().Bounds;
- if (bounds.Width <= 0 || bounds.Height <= 0 || ((points[0].X == points[1].X) && (points[0].Y == points[1].Y)))
- {
- if (this.GetCallback != null) return GetCallback().GetBrush(renderingElement, renderer, opacity, forStroke);
- return null;
- }
- using (var transform = EffectiveGradientTransform)
- {
- var midPoint = new PointF((points[0].X + points[1].X) / 2, (points[0].Y + points[1].Y) / 2);
- transform.Translate(bounds.X, bounds.Y, MatrixOrder.Prepend);
- if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox)
- {
- // Transform a normal (i.e. perpendicular line) according to the transform
- transform.Scale(bounds.Width, bounds.Height, MatrixOrder.Prepend);
- transform.RotateAt(-90.0f, midPoint, MatrixOrder.Prepend);
- }
- transform.TransformPoints(points);
- }
- if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox)
- {
- // Transform the normal line back to a line such that the gradient still starts in the correct corners, but
- // has the proper normal vector based on the transforms. If you work out the geometry, these formulas should work.
- var midPoint = new PointF((points[0].X + points[1].X) / 2, (points[0].Y + points[1].Y) / 2);
- var dy = (points[1].Y - points[0].Y);
- var dx = (points[1].X - points[0].X);
- var x2 = points[0].X;
- var y2 = points[1].Y;
- if (Math.Round(dx, 4) == 0)
- {
- points[0] = new PointF(midPoint.X + dy / 2 * bounds.Width / bounds.Height, midPoint.Y);
- points[1] = new PointF(midPoint.X - dy / 2 * bounds.Width / bounds.Height, midPoint.Y);
- }
- else if (Math.Round(dy, 4) == 0)
- {
- points[0] = new PointF(midPoint.X, midPoint.Y - dx / 2 * bounds.Height / bounds.Width);
- points[1] = new PointF(midPoint.X, midPoint.Y + dx / 2 * bounds.Height / bounds.Width); ;
- }
- else
- {
- var startX = (float)((dy * dx * (midPoint.Y - y2) + Math.Pow(dx, 2) * midPoint.X + Math.Pow(dy, 2) * x2) /
- (Math.Pow(dx, 2) + Math.Pow(dy, 2)));
- var startY = dy * (startX - x2) / dx + y2;
- points[0] = new PointF(startX, startY);
- points[1] = new PointF(midPoint.X + (midPoint.X - startX), midPoint.Y + (midPoint.Y - startY));
- }
- }
- var effectiveStart = points[0];
- var effectiveEnd = points[1];
- if (PointsToMove(renderingElement, points[0], points[1]) > LinePoints.None)
- {
- var expansion = ExpandGradient(renderingElement, points[0], points[1]);
- effectiveStart = expansion.StartPoint;
- effectiveEnd = expansion.EndPoint;
- }
- var result = new LinearGradientBrush(effectiveStart, effectiveEnd, System.Drawing.Color.Transparent, System.Drawing.Color.Transparent)
- {
- InterpolationColors = CalculateColorBlend(renderer, opacity, points[0], effectiveStart, points[1], effectiveEnd),
- WrapMode = WrapMode.TileFlipX
- };
- return result;
- }
- finally
- {
- if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.PopBoundable();
- }
- }
- private SvgUnit NormalizeUnit(SvgUnit orig)
- {
- return (orig.Type == SvgUnitType.Percentage && this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox ?
- new SvgUnit(SvgUnitType.User, orig.Value / 100) :
- orig);
- }
- [Flags]
- private enum LinePoints
- {
- None = 0,
- Start = 1,
- End = 2
- }
- private LinePoints PointsToMove(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
- {
- var bounds = boundable.Bounds;
- if (specifiedStart.X == specifiedEnd.X)
- {
- return (bounds.Top < specifiedStart.Y && specifiedStart.Y < bounds.Bottom ? LinePoints.Start : LinePoints.None) |
- (bounds.Top < specifiedEnd.Y && specifiedEnd.Y < bounds.Bottom ? LinePoints.End : LinePoints.None);
- }
- else if (specifiedStart.Y == specifiedEnd.Y)
- {
- return (bounds.Left < specifiedStart.X && specifiedStart.X < bounds.Right ? LinePoints.Start : LinePoints.None) |
- (bounds.Left < specifiedEnd.X && specifiedEnd.X < bounds.Right ? LinePoints.End : LinePoints.None);
- }
- return (boundable.Bounds.Contains(specifiedStart) ? LinePoints.Start : LinePoints.None) |
- (boundable.Bounds.Contains(specifiedEnd) ? LinePoints.End : LinePoints.None);
- }
- #if READONLY_STRUCTS
- public readonly struct GradientPoints
- #else
- public struct GradientPoints
- #endif
- {
- public readonly PointF StartPoint;
- public readonly PointF EndPoint;
- public GradientPoints(PointF startPoint, PointF endPoint)
- {
- this.StartPoint = startPoint;
- this.EndPoint = endPoint;
- }
- }
- private GradientPoints ExpandGradient(ISvgBoundable boundable, PointF specifiedStart, PointF specifiedEnd)
- {
- var pointsToMove = PointsToMove(boundable, specifiedStart, specifiedEnd);
- if (pointsToMove == LinePoints.None)
- {
- Debug.Fail("Unexpectedly expanding gradient when not needed!");
- return new GradientPoints(specifiedStart, specifiedEnd);
- }
- var bounds = boundable.Bounds;
- var effectiveStart = specifiedStart;
- var effectiveEnd = specifiedEnd;
- var intersectionPoints = CandidateIntersections(bounds, specifiedStart, specifiedEnd);
- Debug.Assert(intersectionPoints.Count == 2, "Unanticipated number of intersection points");
- if (!(Math.Sign(intersectionPoints[1].X - intersectionPoints[0].X) == Math.Sign(specifiedEnd.X - specifiedStart.X) &&
- Math.Sign(intersectionPoints[1].Y - intersectionPoints[0].Y) == Math.Sign(specifiedEnd.Y - specifiedStart.Y)))
- {
- intersectionPoints = intersectionPoints.Reverse().ToList();
- }
- if ((pointsToMove & LinePoints.Start) > 0) effectiveStart = intersectionPoints[0];
- if ((pointsToMove & LinePoints.End) > 0) effectiveEnd = intersectionPoints[1];
- switch (SpreadMethod)
- {
- case SvgGradientSpreadMethod.Reflect:
- case SvgGradientSpreadMethod.Repeat:
- var specifiedLength = CalculateDistance(specifiedStart, specifiedEnd);
- var specifiedUnitVector = new PointF((specifiedEnd.X - specifiedStart.X) / (float)specifiedLength, (specifiedEnd.Y - specifiedStart.Y) / (float)specifiedLength);
- var oppUnitVector = new PointF(-specifiedUnitVector.X, -specifiedUnitVector.Y);
- var startExtend = (float)(Math.Ceiling(CalculateDistance(effectiveStart, specifiedStart) / specifiedLength) * specifiedLength);
- effectiveStart = MovePointAlongVector(specifiedStart, oppUnitVector, startExtend);
- var endExtend = (float)(Math.Ceiling(CalculateDistance(effectiveEnd, specifiedEnd) / specifiedLength) * specifiedLength);
- effectiveEnd = MovePointAlongVector(specifiedEnd, specifiedUnitVector, endExtend);
- break;
- }
- return new GradientPoints(effectiveStart, effectiveEnd);
- }
- private IList<PointF> CandidateIntersections(RectangleF bounds, PointF p1, PointF p2)
- {
- var results = new List<PointF>();
- if (Math.Round(Math.Abs(p1.Y - p2.Y), 4) == 0)
- {
- results.Add(new PointF(bounds.Left, p1.Y));
- results.Add(new PointF(bounds.Right, p1.Y));
- }
- else if (Math.Round(Math.Abs(p1.X - p2.X), 4) == 0)
- {
- results.Add(new PointF(p1.X, bounds.Top));
- results.Add(new PointF(p1.X, bounds.Bottom));
- }
- else
- {
- PointF candidate;
- // Save some effort and duplication in the trivial case
- if ((p1.X == bounds.Left || p1.X == bounds.Right) && (p1.Y == bounds.Top || p1.Y == bounds.Bottom))
- {
- results.Add(p1);
- }
- else
- {
- candidate = new PointF(bounds.Left, (p2.Y - p1.Y) / (p2.X - p1.X) * (bounds.Left - p1.X) + p1.Y);
- if (bounds.Top <= candidate.Y && candidate.Y <= bounds.Bottom) results.Add(candidate);
- candidate = new PointF(bounds.Right, (p2.Y - p1.Y) / (p2.X - p1.X) * (bounds.Right - p1.X) + p1.Y);
- if (bounds.Top <= candidate.Y && candidate.Y <= bounds.Bottom) results.Add(candidate);
- }
- if ((p2.X == bounds.Left || p2.X == bounds.Right) && (p2.Y == bounds.Top || p2.Y == bounds.Bottom))
- {
- results.Add(p2);
- }
- else
- {
- candidate = new PointF((bounds.Top - p1.Y) / (p2.Y - p1.Y) * (p2.X - p1.X) + p1.X, bounds.Top);
- if (bounds.Left <= candidate.X && candidate.X <= bounds.Right) results.Add(candidate);
- candidate = new PointF((bounds.Bottom - p1.Y) / (p2.Y - p1.Y) * (p2.X - p1.X) + p1.X, bounds.Bottom);
- if (bounds.Left <= candidate.X && candidate.X <= bounds.Right) results.Add(candidate);
- }
- }
- return results;
- }
- private ColorBlend CalculateColorBlend(ISvgRenderer renderer, float opacity, PointF specifiedStart, PointF effectiveStart, PointF specifiedEnd, PointF effectiveEnd)
- {
- float startExtend;
- float endExtend;
- List<Color> colors;
- List<float> positions;
- var colorBlend = GetColorBlend(renderer, opacity, false);
- var startDelta = CalculateDistance(specifiedStart, effectiveStart);
- var endDelta = CalculateDistance(specifiedEnd, effectiveEnd);
- if (!(startDelta > 0) && !(endDelta > 0))
- {
- return colorBlend;
- }
- 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);
- switch (SpreadMethod)
- {
- case SvgGradientSpreadMethod.Reflect:
- startExtend = (float)(Math.Ceiling(CalculateDistance(effectiveStart, specifiedStart) / specifiedLength));
- endExtend = (float)(Math.Ceiling(CalculateDistance(effectiveEnd, specifiedEnd) / specifiedLength));
- colors = colorBlend.Colors.ToList();
- positions = (from p in colorBlend.Positions select p + startExtend).ToList();
- for (var i = 0; i < startExtend; i++)
- {
- if (i % 2 == 0)
- {
- for (var j = 1; j < colorBlend.Positions.Length; j++)
- {
- positions.Insert(0, (float)((startExtend - 1 - i) + 1 - colorBlend.Positions[j]));
- colors.Insert(0, colorBlend.Colors[j]);
- }
- }
- else
- {
- for (var j = 0; j < colorBlend.Positions.Length - 1; j++)
- {
- positions.Insert(j, (float)((startExtend - 1 - i) + colorBlend.Positions[j]));
- colors.Insert(j, colorBlend.Colors[j]);
- }
- }
- }
- int insertPos;
- for (var i = 0; i < endExtend; i++)
- {
- if (i % 2 == 0)
- {
- insertPos = positions.Count;
- for (var j = 0; j < colorBlend.Positions.Length - 1; j++)
- {
- positions.Insert(insertPos, (float)((startExtend + 1 + i) + 1 - colorBlend.Positions[j]));
- colors.Insert(insertPos, colorBlend.Colors[j]);
- }
- }
- else
- {
- for (var j = 1; j < colorBlend.Positions.Length; j++)
- {
- positions.Add((float)((startExtend + 1 + i) + colorBlend.Positions[j]));
- colors.Add(colorBlend.Colors[j]);
- }
- }
- }
- colorBlend.Colors = colors.ToArray();
- colorBlend.Positions = (from p in positions select p / (startExtend + 1 + endExtend)).ToArray();
- break;
- case SvgGradientSpreadMethod.Repeat:
- startExtend = (float)(Math.Ceiling(CalculateDistance(effectiveStart, specifiedStart) / specifiedLength));
- endExtend = (float)(Math.Ceiling(CalculateDistance(effectiveEnd, specifiedEnd) / specifiedLength));
- colors = new List<Color>();
- positions = new List<float>();
- for (int i = 0; i < startExtend + endExtend + 1; i++)
- {
- for (int j = 0; j < colorBlend.Positions.Length; j++)
- {
- positions.Add((i + colorBlend.Positions[j] * 0.9999f) / (startExtend + endExtend + 1));
- colors.Add(colorBlend.Colors[j]);
- }
- }
- positions[positions.Count - 1] = 1.0f;
- colorBlend.Colors = colors.ToArray();
- colorBlend.Positions = positions.ToArray();
- break;
- default:
- 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.Round(Math.Max(0F, Math.Min((distanceFromEffectiveStart / effectiveLength), 1.0F)), 5);
- }
- if (startDelta > 0)
- {
- colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
- colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
- }
- if (endDelta > 0)
- {
- colorBlend.Positions = colorBlend.Positions.Concat(new[] { 1F }).ToArray();
- colorBlend.Colors = colorBlend.Colors.Concat(new[] { colorBlend.Colors.Last() }).ToArray();
- }
- break;
- }
- 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;
- newObj.X1 = this.X1;
- newObj.Y1 = this.Y1;
- newObj.X2 = this.X2;
- newObj.Y2 = this.Y2;
- 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;
- }
- /// <remarks>http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2</remarks>
- private PointF? Intersection(LineF other)
- {
- const int precision = 8;
- var a1 = (double)Y2 - Y1;
- var b1 = (double)X1 - X2;
- var c1 = a1 * X1 + b1 * Y1;
- var a2 = (double)other.Y2 - other.Y1;
- var b2 = (double)other.X1 - other.X2;
- var c2 = a2 * other.X1 + b2 * other.Y1;
- var det = a1 * b2 - a2 * b1;
- if (det == 0)
- {
- return null;
- }
- else
- {
- var xi = (b2 * c1 - b1 * c2) / det;
- var yi = (a1 * c2 - a2 * c1) / det;
- if (Math.Round(Math.Min(X1, X2), precision) <= Math.Round(xi, precision) &&
- Math.Round(xi, precision) <= Math.Round(Math.Max(X1, X2), precision) &&
- Math.Round(Math.Min(Y1, Y2), precision) <= Math.Round(yi, precision) &&
- Math.Round(yi, precision) <= Math.Round(Math.Max(Y1, Y2), precision) &&
- Math.Round(Math.Min(other.X1, other.X2), precision) <= Math.Round(xi, precision) &&
- Math.Round(xi, precision) <= Math.Round(Math.Max(other.X1, other.X2), precision) &&
- Math.Round(Math.Min(other.Y1, other.Y2), precision) <= Math.Round(yi, precision) &&
- Math.Round(yi, precision) <= Math.Round(Math.Max(other.Y1, other.Y2), precision))
- {
- return new PointF((float)xi, (float)yi);
- }
- else
- {
- return null;
- }
- }
- }
- private static void AddIfIntersect(LineF first, LineF second, ICollection<PointF> result)
- {
- var intersection = first.Intersection(second);
- if (intersection != null)
- {
- result.Add(intersection.Value);
- }
- }
- }
- }
- }
- #pragma warning restore
|