Graphics2D.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. //----------------------------------------------------------------------------
  2. // Anti-Grain Geometry - Version 2.4
  3. // Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
  4. //
  5. // C# port by: Lars Brubaker
  6. // larsbrubaker@gmail.com
  7. // Copyright (C) 2007
  8. //
  9. // Permission to copy, use, modify, sell and distribute this software
  10. // is granted provided this copyright notice appears in all copies.
  11. // This software is provided "as is" without express or implied
  12. // warranty, and with no claim as to its suitability for any purpose.
  13. //
  14. //----------------------------------------------------------------------------
  15. // Contact: mcseem@antigrain.com
  16. // mcseemagg@yahoo.com
  17. // http://www.antigrain.com
  18. //----------------------------------------------------------------------------
  19. using MatterHackers.Agg.Font;
  20. using MatterHackers.Agg.Image;
  21. using MatterHackers.Agg.Platform;
  22. using MatterHackers.Agg.Transform;
  23. using MatterHackers.Agg.VertexSource;
  24. using MatterHackers.VectorMath;
  25. using System;
  26. using System.Collections.Generic;
  27. using Typography.OpenFont;
  28. namespace MatterHackers.Agg
  29. {
  30. public class ColoredVertexSource
  31. {
  32. public IVertexSource VertexSource { get; set; }
  33. public Color Color { get; set; }
  34. public ColoredVertexSource(IVertexSource vertexSource, Color color)
  35. {
  36. VertexSource = vertexSource;
  37. Color = color;
  38. }
  39. }
  40. public interface IStyleHandler
  41. {
  42. Color color(int style);
  43. void GenerateSpan(Color[] span, int spanIndex, int x, int y, int len, int style);
  44. bool IsSolid(int style);
  45. };
  46. public abstract class Graphics2D
  47. {
  48. protected Stack<Affine> affineTransformStack = new Stack<Affine>();
  49. protected IImageByte destImageByte;
  50. protected IImageFloat destImageFloat;
  51. protected ScanlineRasterizer rasterizer;
  52. protected Stroke StrockedText;
  53. private const int cover_full = 255;
  54. public Graphics2D()
  55. {
  56. affineTransformStack.Push(Affine.NewIdentity());
  57. }
  58. public Graphics2D(IImageByte destImage, ScanlineRasterizer rasterizer)
  59. : this()
  60. {
  61. Initialize(destImage, rasterizer);
  62. }
  63. public enum Alignment
  64. {
  65. Left,
  66. Center,
  67. Right
  68. }
  69. public enum TransformQuality
  70. {
  71. Fastest,
  72. Best
  73. }
  74. public IImageByte DestImage
  75. {
  76. get
  77. {
  78. return destImageByte;
  79. }
  80. }
  81. public IImageFloat DestImageFloat
  82. {
  83. get
  84. {
  85. return destImageFloat;
  86. }
  87. }
  88. public double DeviceScale { get; set; } = 1;
  89. public abstract int Height { get; }
  90. public TransformQuality ImageRenderQuality { get; set; } = TransformQuality.Fastest;
  91. public ScanlineRasterizer Rasterizer
  92. {
  93. get { return rasterizer; }
  94. }
  95. public abstract IScanlineCache ScanlineCache
  96. {
  97. get;
  98. set;
  99. }
  100. public int TransformStackCount
  101. {
  102. get { return affineTransformStack.Count; }
  103. }
  104. public abstract int Width { get; }
  105. public static void AssertDebugNotDefined()
  106. {
  107. #if DEBUG
  108. throw new Exception("DEBUG is defined and should not be!");
  109. #endif
  110. }
  111. public static double GetScallingBaseOnMaxSize(ImageBuffer image, Vector2 maxSize, out Vector2 size)
  112. {
  113. double ratio = 1;
  114. size = new Vector2(image.Width, image.Height);
  115. if (size.X > maxSize.X)
  116. {
  117. size.X = maxSize.X;
  118. ratio = size.X / image.Width;
  119. size.Y = image.Height * ratio;
  120. }
  121. if (size.Y > maxSize.Y)
  122. {
  123. size.Y = maxSize.Y;
  124. ratio = size.Y / image.Height;
  125. size.X = image.Width * ratio;
  126. }
  127. return ratio;
  128. }
  129. public void Circle(Vector2 origin, double radius, Color color)
  130. {
  131. Circle(origin.X, origin.Y, radius, color);
  132. }
  133. public void Circle(double x, double y, double radius, Color color)
  134. {
  135. Ellipse elipse = new Ellipse(x, y, radius, radius);
  136. Render(elipse, color);
  137. }
  138. public abstract void Clear(IColorType color);
  139. /// <summary>
  140. /// Draws an arc representing a portion of an ellipse specified by a Rectangle structure.
  141. /// </summary>
  142. /// <param name="color">The color to draw in.</param>
  143. /// <param name="rect">Structure that defines the boundaries of the ellipse.</param>
  144. /// <param name="startAngle">Angle in degrees measured clockwise from the x-axis to the starting point of the arc.</param>
  145. /// <param name="sweepAngle">Angle in degrees measured clockwise from the startAngle parameter to ending point of the arc.</param>
  146. public void DrawArc(Color color, RectangleDouble rect, int startAngle, int sweepAngle)
  147. {
  148. throw new NotImplementedException();
  149. }
  150. public void DrawLine(Color color, Vector2 start, Vector2 end)
  151. {
  152. Line(start, end, color);
  153. }
  154. public void DrawString(string text,
  155. Vector2 position,
  156. double pointSize = 12,
  157. Justification justification = Justification.Left,
  158. Baseline baseline = Baseline.Text,
  159. Color color = default,
  160. bool drawFromHintedCach = false,
  161. Color backgroundColor = default,
  162. bool bold = false,
  163. TypeFace typeface = null)
  164. {
  165. DrawString(text, position.X, position.Y, pointSize, justification, baseline, color, drawFromHintedCach, backgroundColor, bold,typeface);
  166. }
  167. /// <summary>
  168. /// Draws a string on a typeface printer object with various optional styling parameters.
  169. /// </summary>
  170. /// <param name="text">The string text to be drawn.</param>
  171. /// <param name="x">The x-coordinate where the string starts.</param>
  172. /// <param name="y">The y-coordinate where the string starts.</param>
  173. /// <param name="pointSize">The size of the point in pixels. Default is 12.</param>
  174. /// <param name="justification">Defines the justification of the string, i.e., the alignment of the text. It can be left, right, or center. Default is 'Left'.</param>
  175. /// <param name="baseline">Defines the baseline alignment of the text, i.e., the vertical alignment of the text. It can be 'Text', 'Ideographic', etc. Default is 'Text'.</param>
  176. /// <param name="color">Defines the color of the text. Default is 'Black' if not specified.</param>
  177. /// <param name="drawFromHintedCach">A boolean flag to indicate if the rendered string should be drawn from hinted cache. Default is 'false'.</param>
  178. /// <param name="backgroundColor">Defines the background color of the text. No background color is applied if not specified.</param>
  179. /// <param name="bold">A boolean flag to indicate if the text should be bold. Default is 'false'.</param>
  180. /// <returns>Returns a TypeFacePrinter object that holds the rendered string and drawing settings.</returns>
  181. /// <example>
  182. /// TypeFacePrinter printer = DrawString("Hello World", 50, 50, 14, Justification.Center, Baseline.Text, Color.Red, true, Color.White, true);
  183. /// </example>
  184. /// <remarks>
  185. /// If the 'color' parameter's alpha value is zero, the function will interpret it as the color black.
  186. /// If the 'backgroundColor' parameter's alpha value is not zero, a rectangle of that color will be drawn as a background behind the string.
  187. /// </remarks>
  188. public TypeFacePrinter DrawString(string text,
  189. double x,
  190. double y,
  191. double pointSize = 12,
  192. Justification justification = Justification.Left,
  193. Baseline baseline = Baseline.Text,
  194. Color color = default,
  195. bool drawFromHintedCach = false,
  196. Color backgroundColor = default,
  197. bool bold = false,
  198. TypeFace typeface = null)
  199. {
  200. TypeFacePrinter stringPrinter = typeface == null ? new TypeFacePrinter(text, pointSize, new Vector2(x, y), justification, baseline, bold):new TypeFacePrinter(text,new StyledTypeFace(typeface,pointSize),new Vector2(x,y),justification,baseline);
  201. if (color.Alpha0To255 == 0)
  202. {
  203. color = Color.Black;
  204. }
  205. if (backgroundColor.Alpha0To255 != 0)
  206. {
  207. FillRectangle(stringPrinter.LocalBounds, backgroundColor);
  208. }
  209. stringPrinter.DrawFromHintedCache = drawFromHintedCach;
  210. stringPrinter.Render(this, color);
  211. return stringPrinter;
  212. }
  213. public void DrawString(TypeFacePrinter stringPrinter,Color color = default,Color backgroundColor = default)
  214. {
  215. if(stringPrinter ==null) throw new ArgumentNullException(nameof(stringPrinter));
  216. if (color.Alpha0To255 == 0)
  217. {
  218. color = Color.Black;
  219. }
  220. if (backgroundColor.Alpha0To255 != 0)
  221. {
  222. FillRectangle(stringPrinter.LocalBounds, backgroundColor);
  223. }
  224. stringPrinter.Render(this, color);
  225. }
  226. public void FillRectangle(RectangleDouble rect, IColorType fillColor)
  227. {
  228. FillRectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, fillColor);
  229. }
  230. public void FillRectangle(RectangleInt rect, IColorType fillColor)
  231. {
  232. FillRectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, fillColor);
  233. }
  234. public void FillRectangle(Vector2 leftBottom, Vector2 rightTop, IColorType fillColor)
  235. {
  236. FillRectangle(leftBottom.X, leftBottom.Y, rightTop.X, rightTop.Y, fillColor);
  237. }
  238. public abstract void FillRectangle(double left, double bottom, double right, double top, IColorType fillColor);
  239. public abstract RectangleDouble GetClippingRect();
  240. public Affine GetTransform()
  241. {
  242. return affineTransformStack.Peek();
  243. }
  244. public void Initialize(IImageByte destImage, ScanlineRasterizer rasterizer)
  245. {
  246. destImageByte = destImage;
  247. destImageFloat = null;
  248. this.rasterizer = rasterizer;
  249. }
  250. public void Initialize(IImageFloat destImage, ScanlineRasterizer rasterizer)
  251. {
  252. destImageByte = null;
  253. destImageFloat = destImage;
  254. this.rasterizer = rasterizer;
  255. }
  256. /// <summary>
  257. /// Render a line
  258. /// </summary>
  259. /// <param name="start">start position</param>
  260. /// <param name="end">end position</param>
  261. /// <param name="color">line color</param>
  262. /// <param name="strokeWidth">The width in pixels, -1 will render 1 pixel scaled to device units</param>
  263. public void Line(Vector2 start, Vector2 end, Color color, double strokeWidth = -1)
  264. {
  265. if (strokeWidth == -1)
  266. {
  267. strokeWidth = 1 * DeviceScale;
  268. }
  269. Line(start.X, start.Y, end.X, end.Y, color, strokeWidth);
  270. }
  271. public IVertexSource GetLine(double x1, double y1, double x2, double y2, double strokeWidth = -1)
  272. {
  273. if (strokeWidth == -1)
  274. {
  275. strokeWidth = 1 * DeviceScale;
  276. }
  277. var lineToDraw = new VertexStorage();
  278. lineToDraw.Clear();
  279. lineToDraw.MoveTo(x1, y1);
  280. lineToDraw.LineTo(x2, y2);
  281. return new Stroke(lineToDraw, strokeWidth);
  282. }
  283. /// <summary>
  284. /// Render a line
  285. /// </summary>
  286. /// <param name="x1">x start</param>
  287. /// <param name="y1">y start</param>
  288. /// <param name="x2">x end</param>
  289. /// <param name="y2">y end</param>
  290. /// <param name="color">color of the line</param>
  291. /// <param name="strokeWidth">The width in pixels, -1 will render 1 pixel scaled to device units</param>
  292. public virtual void Line(double x1, double y1, double x2, double y2, Color color, double strokeWidth = -1)
  293. {
  294. this.Render(GetLine(x1, y1, x2, y2, strokeWidth), color);
  295. }
  296. public Affine PopTransform()
  297. {
  298. if (affineTransformStack.Count == 1)
  299. {
  300. throw new System.Exception("You cannot remove the last transform from the stack.");
  301. }
  302. return affineTransformStack.Pop();
  303. }
  304. public void PushTransform()
  305. {
  306. if (affineTransformStack.Count > 1000)
  307. {
  308. throw new System.Exception("You seem to be leaking transforms. You should be popping some of them at some point.");
  309. }
  310. affineTransformStack.Push(affineTransformStack.Peek());
  311. }
  312. public abstract void Rectangle(double left, double bottom, double right, double top, Color color, double strokeWidth = -1);
  313. public void Rectangle(RectangleDouble rect, Color color, double strokeWidth = -1)
  314. {
  315. if (strokeWidth == -1)
  316. {
  317. strokeWidth = 1 * DeviceScale;
  318. }
  319. Rectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, color, strokeWidth);
  320. }
  321. public void Rectangle(RectangleInt rect, Color color)
  322. {
  323. Rectangle(rect.Left, rect.Bottom, rect.Right, rect.Top, color);
  324. }
  325. public abstract void Render(IVertexSource vertexSource, IColorType colorType);
  326. public void Render(IImageByte imageSource, Point2D position)
  327. {
  328. Render(imageSource, position.x, position.y);
  329. }
  330. public void Render(IImageByte imageSource, Vector2 position)
  331. {
  332. Render(imageSource, position.X, position.Y);
  333. }
  334. public void Render(IImageByte imageSource, Vector2 position, double width, double height)
  335. {
  336. Render(imageSource, position.X, position.Y, width, height);
  337. }
  338. public void Render(IImageByte imageSource, double x, double y)
  339. {
  340. Render(imageSource, x, y, 0, 1, 1);
  341. }
  342. public void Render(IImageByte imageSource, double x, double y, double width, double height)
  343. {
  344. Render(imageSource, x, y, 0, width / imageSource.Width, height / imageSource.Height);
  345. }
  346. public abstract void Render(IImageByte imageSource,
  347. double x,
  348. double y,
  349. double angleRadians,
  350. double scaleX,
  351. double scaleY);
  352. public abstract void Render(IImageFloat imageSource,
  353. double x,
  354. double y,
  355. double angleRadians,
  356. double scaleX,
  357. double scaleY);
  358. public void Render(IVertexSource vertexSource, double x, double y, IColorType color)
  359. {
  360. Render(new VertexSourceApplyTransform(vertexSource, Affine.NewTranslation(x, y)), color);
  361. }
  362. public void Render(IVertexSource vertexSource, Vector2 position, IColorType color)
  363. {
  364. Render(new VertexSourceApplyTransform(vertexSource, Affine.NewTranslation(position.X, position.Y)), color);
  365. }
  366. public void RenderMaxSize(ImageBuffer image, Vector2 position, Vector2 maxSize)
  367. {
  368. var zero = Vector2.Zero;
  369. RenderMaxSize(image, position, maxSize, ref zero, out _);
  370. }
  371. public void RenderMaxSize(ImageBuffer image, Vector2 position, Vector2 maxSize, ref Vector2 origin)
  372. {
  373. RenderMaxSize(image, position, maxSize, ref origin, out _);
  374. }
  375. /// <summary>
  376. /// Renders the given image at the given position scaling down if bigger than maxSize
  377. /// </summary>
  378. /// <param name="image">The image to render</param>
  379. /// <param name="position">The postion to render it at</param>
  380. /// <param name="maxSize">The max size to allow it to render to. Will be scaled down to fit.</param>
  381. /// <param name="origin">The postion in the sourc to hold at the 'positon'</param>
  382. /// <param name="size"></param>
  383. public void RenderMaxSize(ImageBuffer image, Vector2 position, Vector2 maxSize, ref Vector2 origin, out Vector2 size)
  384. {
  385. var ratio = GetScallingBaseOnMaxSize(image, maxSize, out size);
  386. origin *= ratio;
  387. if (size.X != image.Width)
  388. {
  389. this.Render(image.CreateScaledImage(size.X / image.Width), position.X - origin.X, position.Y - origin.Y, size.X, size.Y);
  390. }
  391. else
  392. {
  393. this.Render(image, position - origin);
  394. }
  395. }
  396. public void RenderInRect(string text,
  397. double pointSize,
  398. RectangleDouble fitRect,
  399. out RectangleDouble renderedBounds,
  400. double xPositionRatio = 0,
  401. double yPositionRatio = 0,
  402. double debugBoundsWidth = 0)
  403. {
  404. RenderInRect(text, AggContext.DefaultFont, pointSize, fitRect, out renderedBounds, xPositionRatio, yPositionRatio, debugBoundsWidth);
  405. }
  406. public void RenderInRect(string text,
  407. TypeFace font,
  408. double pointSize,
  409. RectangleDouble fitRect,
  410. out RectangleDouble renderedBounds,
  411. double xPositionRatio = 0,
  412. double yPositionRatio = 0,
  413. double debugBoundsWidth = 0)
  414. {
  415. var styledTypeFace = new StyledTypeFace(font, pointSize * 300 / 72);
  416. var typeFacePrinter = new TypeFacePrinter(text, styledTypeFace);
  417. RenderInRect(new ColoredVertexSource[] { new ColoredVertexSource(typeFacePrinter, Color.Black) }, fitRect, out renderedBounds, xPositionRatio, yPositionRatio, debugBoundsWidth);
  418. }
  419. /// <summary>
  420. /// Renders the given vector source making scaled to fit the given rect. Scalling will remain proportional.
  421. /// If the vector source is smaller in one dimension it will be offset based on the position ratio
  422. /// </summary>
  423. /// <param name="source">The vector source to render</param>
  424. /// <param name="fitRect">The rect to scale to fit within</param>
  425. /// <param name="xPositionRatio">The ratio of the width to offset in x if not fully utilized</param>
  426. /// <param name="yPositionRatio">The ratio of the height to offset in y if not fully utilized</param>
  427. /// <param name="debugShowBounds">Render an outline of the total rectangle</param>
  428. public void RenderInRect(IEnumerable<ColoredVertexSource> source,
  429. RectangleDouble fitRect,
  430. out RectangleDouble renderedBounds,
  431. double xPositionRatio = 0,
  432. double yPositionRatio = 0,
  433. double debugBoundsWidth = 0)
  434. {
  435. renderedBounds = RectangleDouble.ZeroIntersection;
  436. xPositionRatio = Math.Max(0, Math.Min(1, xPositionRatio));
  437. yPositionRatio = Math.Max(0, Math.Min(1, yPositionRatio));
  438. RectangleDouble totalBounds = RectangleDouble.ZeroIntersection;
  439. foreach (var colorVertices in source)
  440. {
  441. var bounds = colorVertices.VertexSource.GetBounds();
  442. totalBounds.ExpandToInclude(bounds);
  443. }
  444. foreach (var colorVertices in source)
  445. {
  446. double scale;
  447. if (totalBounds.Width > fitRect.Width
  448. || totalBounds.Height > fitRect.Height)
  449. {
  450. // we need to scale down
  451. scale = Math.Min(fitRect.Width / totalBounds.Width, fitRect.Height / totalBounds.Height);
  452. }
  453. else
  454. {
  455. // we need to scale up
  456. scale = Math.Min(fitRect.Width / totalBounds.Width, fitRect.Height / totalBounds.Height);
  457. }
  458. // zero out the offset
  459. var transform = Affine.NewTranslation(-totalBounds.Left, -totalBounds.Bottom);
  460. // scale
  461. transform *= Affine.NewScaling(scale);
  462. // offset to the fit rect
  463. transform *= Affine.NewTranslation(fitRect.Left, fitRect.Bottom);
  464. // do we need to move it to account for position ratios
  465. var scaledBounds = totalBounds * scale;
  466. transform *= Affine.NewTranslation((fitRect.Width - scaledBounds.Width) * xPositionRatio, (fitRect.Height - scaledBounds.Height) * yPositionRatio);
  467. var flattened = new FlattenCurves(new VertexSourceApplyTransform(colorVertices.VertexSource, transform));
  468. renderedBounds.ExpandToInclude(flattened.GetBounds());
  469. this.Render(flattened, colorVertices.Color);
  470. }
  471. if (debugBoundsWidth > 0)
  472. {
  473. this.Rectangle(fitRect, Color.Red, debugBoundsWidth);
  474. }
  475. }
  476. public void RenderScale(IImageByte image, double x, double y, double sizeX)
  477. {
  478. var ratio = sizeX / image.Width;
  479. var sizeY = image.Height * ratio;
  480. this.Render(image, x, y, sizeX, sizeY);
  481. }
  482. public abstract void SetClippingRect(RectangleDouble rect_d);
  483. public void SetTransform(Affine value)
  484. {
  485. affineTransformStack.Pop();
  486. affineTransformStack.Push(value);
  487. }
  488. }
  489. public static class ColoredVertexSourceExtensions
  490. {
  491. public static RectangleDouble GetBounds(this IEnumerable<ColoredVertexSource> source)
  492. {
  493. RectangleDouble totalBounds = RectangleDouble.ZeroIntersection;
  494. foreach (var colorVertices in source)
  495. {
  496. var bounds = colorVertices.VertexSource.GetBounds();
  497. totalBounds.ExpandToInclude(bounds);
  498. }
  499. return totalBounds;
  500. }
  501. }
  502. }