SvgRadialGradientServer.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. using System;
  2. using System.Diagnostics;
  3. using System.Drawing;
  4. using System.Collections.Generic;
  5. using System.Drawing.Drawing2D;
  6. using System.Linq;
  7. #pragma warning disable
  8. namespace Svg
  9. {
  10. [SvgElement("radialGradient")]
  11. public sealed class SvgRadialGradientServer : SvgGradientServer
  12. {
  13. [SvgAttribute("cx")]
  14. public SvgUnit CenterX
  15. {
  16. get
  17. {
  18. return this.Attributes.GetAttribute<SvgUnit>("cx");
  19. }
  20. set
  21. {
  22. this.Attributes["cx"] = value;
  23. }
  24. }
  25. [SvgAttribute("cy")]
  26. public SvgUnit CenterY
  27. {
  28. get
  29. {
  30. return this.Attributes.GetAttribute<SvgUnit>("cy");
  31. }
  32. set
  33. {
  34. this.Attributes["cy"] = value;
  35. }
  36. }
  37. [SvgAttribute("r")]
  38. public SvgUnit Radius
  39. {
  40. get
  41. {
  42. return this.Attributes.GetAttribute<SvgUnit>("r");
  43. }
  44. set
  45. {
  46. this.Attributes["r"] = value;
  47. }
  48. }
  49. [SvgAttribute("fx")]
  50. public SvgUnit FocalX
  51. {
  52. get
  53. {
  54. var value = this.Attributes.GetAttribute<SvgUnit>("fx");
  55. if (value.IsEmpty || value.IsNone)
  56. {
  57. value = this.CenterX;
  58. }
  59. return value;
  60. }
  61. set
  62. {
  63. this.Attributes["fx"] = value;
  64. }
  65. }
  66. [SvgAttribute("fy")]
  67. public SvgUnit FocalY
  68. {
  69. get
  70. {
  71. var value = this.Attributes.GetAttribute<SvgUnit>("fy");
  72. if (value.IsEmpty || value.IsNone)
  73. {
  74. value = this.CenterY;
  75. }
  76. return value;
  77. }
  78. set
  79. {
  80. this.Attributes["fy"] = value;
  81. }
  82. }
  83. public SvgRadialGradientServer()
  84. {
  85. CenterX = new SvgUnit(SvgUnitType.Percentage, 50F);
  86. CenterY = new SvgUnit(SvgUnitType.Percentage, 50F);
  87. Radius = new SvgUnit(SvgUnitType.Percentage, 50F);
  88. }
  89. private object _lockObj = new Object();
  90. private SvgUnit NormalizeUnit(SvgUnit orig)
  91. {
  92. return (orig.Type == SvgUnitType.Percentage && this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox ?
  93. new SvgUnit(SvgUnitType.User, orig.Value / 100) :
  94. orig);
  95. }
  96. public override Brush GetBrush(SvgVisualElement renderingElement, ISvgRenderer renderer, float opacity, bool forStroke = false)
  97. {
  98. LoadStops(renderingElement);
  99. try
  100. {
  101. if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.SetBoundable(renderingElement);
  102. // Calculate the path and transform it appropriately
  103. var center = new PointF(NormalizeUnit(CenterX).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
  104. NormalizeUnit(CenterY).ToDeviceValue(renderer, UnitRenderingType.Vertical, this));
  105. var focals = new PointF[] {new PointF(NormalizeUnit(FocalX).ToDeviceValue(renderer, UnitRenderingType.Horizontal, this),
  106. NormalizeUnit(FocalY).ToDeviceValue(renderer, UnitRenderingType.Vertical, this)) };
  107. var specifiedRadius = NormalizeUnit(Radius).ToDeviceValue(renderer, UnitRenderingType.Other, this);
  108. var path = new GraphicsPath();
  109. path.AddEllipse(
  110. center.X - specifiedRadius, center.Y - specifiedRadius,
  111. specifiedRadius * 2, specifiedRadius * 2
  112. );
  113. using (var transform = EffectiveGradientTransform)
  114. {
  115. var bounds = renderer.GetBoundable().Bounds;
  116. transform.Translate(bounds.X, bounds.Y, MatrixOrder.Prepend);
  117. if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox)
  118. {
  119. transform.Scale(bounds.Width, bounds.Height, MatrixOrder.Prepend);
  120. }
  121. path.Transform(transform);
  122. transform.TransformPoints(focals);
  123. }
  124. // Calculate any required scaling
  125. var scaleBounds = RectangleF.Inflate(renderingElement.Bounds, renderingElement.StrokeWidth, renderingElement.StrokeWidth);
  126. var scale = CalcScale(scaleBounds, path);
  127. // Not ideal, but this makes sure that the rest of the shape gets properly filled or drawn
  128. if (scale > 1.0f && SpreadMethod == SvgGradientSpreadMethod.Pad)
  129. {
  130. var stop = Stops.Last();
  131. var origColor = stop.GetColor(renderingElement);
  132. var renderColor = System.Drawing.Color.FromArgb((int)Math.Round(opacity * stop.Opacity * 255), origColor);
  133. var origClip = renderer.GetClip();
  134. try
  135. {
  136. using (var solidBrush = new SolidBrush(renderColor))
  137. {
  138. var newClip = origClip.Clone();
  139. newClip.Exclude(path);
  140. renderer.SetClip(newClip);
  141. var renderPath = (GraphicsPath)renderingElement.Path(renderer);
  142. if (forStroke)
  143. {
  144. using (var pen = new Pen(solidBrush, renderingElement.StrokeWidth.ToDeviceValue(renderer, UnitRenderingType.Other, renderingElement)))
  145. {
  146. renderer.DrawPath(pen, renderPath);
  147. }
  148. }
  149. else
  150. {
  151. renderer.FillPath(solidBrush, renderPath);
  152. }
  153. }
  154. }
  155. finally
  156. {
  157. renderer.SetClip(origClip);
  158. }
  159. }
  160. // Get the color blend and any tweak to the scaling
  161. var blend = CalculateColorBlend(renderer, opacity, scale, out scale);
  162. // Transform the path based on the scaling
  163. var gradBounds = path.GetBounds();
  164. var transCenter = new PointF(gradBounds.Left + gradBounds.Width / 2, gradBounds.Top + gradBounds.Height / 2);
  165. using (var scaleMat = new Matrix())
  166. {
  167. scaleMat.Translate(-1 * transCenter.X, -1 * transCenter.Y, MatrixOrder.Append);
  168. scaleMat.Scale(scale, scale, MatrixOrder.Append);
  169. scaleMat.Translate(transCenter.X, transCenter.Y, MatrixOrder.Append);
  170. path.Transform(scaleMat);
  171. }
  172. // calculate the brush
  173. var brush = new PathGradientBrush(path);
  174. brush.CenterPoint = focals[0];
  175. brush.InterpolationColors = blend;
  176. return brush;
  177. }
  178. finally
  179. {
  180. if (this.GradientUnits == SvgCoordinateUnits.ObjectBoundingBox) renderer.PopBoundable();
  181. }
  182. }
  183. /// <summary>
  184. /// Determine how much (approximately) the path must be scaled to contain the rectangle
  185. /// </summary>
  186. /// <param name="bounds">Bounds that the path must contain</param>
  187. /// <param name="path">Path of the gradient</param>
  188. /// <returns>Scale factor</returns>
  189. /// <remarks>
  190. /// This method continually transforms the rectangle (fewer points) until it is contained by the path
  191. /// and returns the result of the search. The scale factor is set to a constant 95%
  192. /// </remarks>
  193. private float CalcScale(RectangleF bounds, GraphicsPath path, Graphics graphics = null)
  194. {
  195. var points = new PointF[] {
  196. new PointF(bounds.Left, bounds.Top),
  197. new PointF(bounds.Right, bounds.Top),
  198. new PointF(bounds.Right, bounds.Bottom),
  199. new PointF(bounds.Left, bounds.Bottom)
  200. };
  201. var pathBounds = path.GetBounds();
  202. var pathCenter = new PointF(pathBounds.X + pathBounds.Width / 2, pathBounds.Y + pathBounds.Height / 2);
  203. using (var transform = new Matrix())
  204. {
  205. transform.Translate(-1 * pathCenter.X, -1 * pathCenter.Y, MatrixOrder.Append);
  206. transform.Scale(.95f, .95f, MatrixOrder.Append);
  207. transform.Translate(pathCenter.X, pathCenter.Y, MatrixOrder.Append);
  208. while (!(path.IsVisible(points[0]) && path.IsVisible(points[1]) &&
  209. path.IsVisible(points[2]) && path.IsVisible(points[3])))
  210. {
  211. var previousPoints = new PointF[]
  212. {
  213. new PointF(points[0].X, points[0].Y),
  214. new PointF(points[1].X, points[1].Y),
  215. new PointF(points[2].X, points[2].Y),
  216. new PointF(points[3].X, points[3].Y)
  217. };
  218. transform.TransformPoints(points);
  219. if (Enumerable.SequenceEqual(previousPoints, points))
  220. {
  221. break;
  222. }
  223. }
  224. }
  225. return bounds.Height / (points[2].Y - points[1].Y);
  226. }
  227. //New plan:
  228. // scale the outer rectangle to always encompass ellipse
  229. // cut the ellipse in half (either vertical or horizontal)
  230. // determine the region on each side of the ellipse
  231. private static IEnumerable<GraphicsPath> GetDifference(RectangleF subject, GraphicsPath clip)
  232. {
  233. var clipFlat = (GraphicsPath)clip.Clone();
  234. clipFlat.Flatten();
  235. var clipBounds = clipFlat.GetBounds();
  236. var bounds = RectangleF.Union(subject, clipBounds);
  237. bounds.Inflate(bounds.Width * .3f, bounds.Height * 0.3f);
  238. var clipMidPoint = new PointF((clipBounds.Left + clipBounds.Right) / 2, (clipBounds.Top + clipBounds.Bottom) / 2);
  239. var leftPoints = new List<PointF>();
  240. var rightPoints = new List<PointF>();
  241. foreach (var pt in clipFlat.PathPoints)
  242. {
  243. if (pt.X <= clipMidPoint.X)
  244. {
  245. leftPoints.Add(pt);
  246. }
  247. else
  248. {
  249. rightPoints.Add(pt);
  250. }
  251. }
  252. leftPoints.Sort((p, q) => p.Y.CompareTo(q.Y));
  253. rightPoints.Sort((p, q) => p.Y.CompareTo(q.Y));
  254. var point = new PointF((leftPoints.Last().X + rightPoints.Last().X) / 2,
  255. (leftPoints.Last().Y + rightPoints.Last().Y) / 2);
  256. leftPoints.Add(point);
  257. rightPoints.Add(point);
  258. point = new PointF(point.X, bounds.Bottom);
  259. leftPoints.Add(point);
  260. rightPoints.Add(point);
  261. leftPoints.Add(new PointF(bounds.Left, bounds.Bottom));
  262. leftPoints.Add(new PointF(bounds.Left, bounds.Top));
  263. rightPoints.Add(new PointF(bounds.Right, bounds.Bottom));
  264. rightPoints.Add(new PointF(bounds.Right, bounds.Top));
  265. point = new PointF((leftPoints.First().X + rightPoints.First().X) / 2, bounds.Top);
  266. leftPoints.Add(point);
  267. rightPoints.Add(point);
  268. point = new PointF(point.X, (leftPoints.First().Y + rightPoints.First().Y) / 2);
  269. leftPoints.Add(point);
  270. rightPoints.Add(point);
  271. var path = new GraphicsPath(FillMode.Winding);
  272. path.AddPolygon(leftPoints.ToArray());
  273. yield return path;
  274. path.Reset();
  275. path.AddPolygon(rightPoints.ToArray());
  276. yield return path;
  277. }
  278. private static GraphicsPath CreateGraphicsPath(PointF origin, PointF centerPoint, float effectiveRadius)
  279. {
  280. var path = new GraphicsPath();
  281. path.AddEllipse(
  282. origin.X + centerPoint.X - effectiveRadius,
  283. origin.Y + centerPoint.Y - effectiveRadius,
  284. effectiveRadius * 2,
  285. effectiveRadius * 2
  286. );
  287. return path;
  288. }
  289. private ColorBlend CalculateColorBlend(ISvgRenderer renderer, float opacity, float scale, out float outScale)
  290. {
  291. var colorBlend = GetColorBlend(renderer, opacity, true);
  292. float newScale;
  293. List<float> pos;
  294. List<Color> colors;
  295. outScale = scale;
  296. if (scale > 1)
  297. {
  298. switch (this.SpreadMethod)
  299. {
  300. case SvgGradientSpreadMethod.Reflect:
  301. newScale = (float)Math.Ceiling(scale);
  302. pos = (from p in colorBlend.Positions select 1 + (p - 1) / newScale).ToList();
  303. colors = colorBlend.Colors.ToList();
  304. for (var i = 1; i < newScale; i++)
  305. {
  306. if (i % 2 == 1)
  307. {
  308. for (int j = 1; j < colorBlend.Positions.Length; j++)
  309. {
  310. pos.Insert(0, (newScale - i - 1) / newScale + 1 - colorBlend.Positions[j]);
  311. colors.Insert(0, colorBlend.Colors[j]);
  312. }
  313. }
  314. else
  315. {
  316. for (int j = 0; j < colorBlend.Positions.Length - 1; j++)
  317. {
  318. pos.Insert(j, (newScale - i - 1) / newScale + colorBlend.Positions[j]);
  319. colors.Insert(j, colorBlend.Colors[j]);
  320. }
  321. }
  322. }
  323. colorBlend.Positions = pos.ToArray();
  324. colorBlend.Colors = colors.ToArray();
  325. outScale = newScale;
  326. break;
  327. case SvgGradientSpreadMethod.Repeat:
  328. newScale = (float)Math.Ceiling(scale);
  329. pos = (from p in colorBlend.Positions select p / newScale).ToList();
  330. colors = colorBlend.Colors.ToList();
  331. for (var i = 1; i < newScale; i++)
  332. {
  333. pos.AddRange(from p in colorBlend.Positions select (i + (p <= 0 ? 0.001f : p)) / newScale);
  334. colors.AddRange(colorBlend.Colors);
  335. }
  336. colorBlend.Positions = pos.ToArray();
  337. colorBlend.Colors = colors.ToArray();
  338. outScale = newScale;
  339. break;
  340. default:
  341. outScale = 1.0f;
  342. //for (var i = 0; i < colorBlend.Positions.Length - 1; i++)
  343. //{
  344. // colorBlend.Positions[i] = 1 - (1 - colorBlend.Positions[i]) / scale;
  345. //}
  346. //colorBlend.Positions = new[] { 0F }.Concat(colorBlend.Positions).ToArray();
  347. //colorBlend.Colors = new[] { colorBlend.Colors.First() }.Concat(colorBlend.Colors).ToArray();
  348. break;
  349. }
  350. }
  351. return colorBlend;
  352. }
  353. public override SvgElement DeepCopy()
  354. {
  355. return DeepCopy<SvgRadialGradientServer>();
  356. }
  357. public override SvgElement DeepCopy<T>()
  358. {
  359. var newObj = base.DeepCopy<T>() as SvgRadialGradientServer;
  360. newObj.CenterX = this.CenterX;
  361. newObj.CenterY = this.CenterY;
  362. newObj.Radius = this.Radius;
  363. newObj.FocalX = this.FocalX;
  364. newObj.FocalY = this.FocalY;
  365. return newObj;
  366. }
  367. }
  368. }
  369. #pragma warning restore