Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

GraphicGenerator.cs 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. using DragonSkills99.ControlCollection;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Drawing;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using System.Windows.Forms;
  9. namespace Buhnenrennen
  10. {
  11. static class GraphicGenerator
  12. {
  13. /// <summary>
  14. /// generates an image from field, hunter and hunted and returns it
  15. /// </summary>
  16. /// <param name="field">field to use for calculations</param>
  17. /// <param name="hunter">hunting dog ( Max )</param>
  18. /// <param name="hunted">hunted dog ( Minnie )</param>
  19. public static Image generateField( Field field, Dog hunter, Dog hunted )
  20. {
  21. TimedGroyne safePath = field.getFastestSafeRoute( Dog.MAX, Dog.MINNIE, out var hunterMap, out var huntedMap );
  22. float borderWidth = 7.5F;
  23. int boxRadius = 25;
  24. Size imagePadding = new Size( 10, 10 );
  25. Size imageSize = new Size( imagePadding.Width, imagePadding.Height );
  26. Font font = new Font( "Consolas", 30F );
  27. Font nameFont = new Font( "Consolas", 50F );
  28. Brush hunterBrush = new SolidBrush( Color.DarkRed );
  29. Brush hunterFillBrush = new SolidBrush( Color.Red );
  30. Brush huntedBrush = new SolidBrush( Color.DarkBlue );
  31. Brush huntedFillBrush = new SolidBrush( Color.Cyan );
  32. Brush safePathBrush = new SolidBrush( Color.Gold );
  33. Size boxSpacing = new Size( 700, 50 );
  34. Size boxSize = new Size( 300, 200 );
  35. int levels = ( ( int ) field.Groynes.Keys.Max() / 70 ) + 1; // + 1 cause 0 is also a level and hasn't been included yet
  36. imageSize.Width += boxSize.Width * levels;
  37. imageSize.Width += boxSpacing.Width * ( levels - 1 );
  38. int hunterBoxCount = field.Groynes.Values.Select( groyneList => groyneList.Count ).Max();
  39. imageSize.Height += boxSize.Height * hunterBoxCount;
  40. imageSize.Height += boxSpacing.Height * ( hunterBoxCount - 1 );
  41. Image img = new Bitmap( imageSize.Width, imageSize.Height );
  42. using( Graphics graphics = Graphics.FromImage( img ) )
  43. {
  44. Dictionary<Groyne, RectangleF> groynePositions = new Dictionary<Groyne, RectangleF>();
  45. Dictionary<Groyne, Brush> groyneBorderBrush = new Dictionary<Groyne, Brush>();
  46. int height = imageSize.Height - imagePadding.Height;
  47. int yPadding = ( height - 2 * boxSize.Height ) / 3;
  48. int xOffset = imagePadding.Width / 2;
  49. Rectangle rect = new Rectangle( new Point( xOffset, imagePadding.Height / 2 + yPadding ), boxSize );
  50. graphics.FillRectangle( hunterFillBrush, rect );
  51. graphics.DrawRectangle( new Pen( hunterBrush, borderWidth ), rect );
  52. graphics.DrawStringCenteredTo( "Max", nameFont, hunterBrush, rect );
  53. Groyne hunterStart = field.Groynes[ 0 ].Find( groyne => groyne.Type == hunter.Start );
  54. groynePositions.Add( hunterStart, rect );
  55. groyneBorderBrush.Add( hunterStart, hunterBrush );
  56. rect = rect.Clone();
  57. rect.Y += rect.Height + yPadding;
  58. graphics.FillRoundedRectangle( huntedFillBrush, rect, boxRadius );
  59. graphics.DrawRoundedRectangle( new Pen( huntedBrush, borderWidth ), rect, boxRadius );
  60. graphics.DrawStringCenteredTo( "Minnie", nameFont, huntedBrush, rect );
  61. Groyne huntedStart = field.Groynes[ 0 ].Find( groyne => groyne.Type == hunted.Start );
  62. groynePositions.Add( huntedStart, rect );
  63. groyneBorderBrush.Add( huntedStart, huntedBrush );
  64. rect = rect.Clone();
  65. for ( int level = 70; level <= field.Groynes.Keys.Max(); level += 70 )
  66. {
  67. int prevLevel = level - 70;
  68. int numLevel = level / 70;
  69. List<Groyne> groynes = field.Groynes[ level ];
  70. groynes = groynes.Select( groyne => groyne ).ToList(); // Clone array so original array won't be touched/sorted
  71. groynes.OrderBy( groyne => groyne.YCoord ); // Ordered by yCoord ascending
  72. xOffset += boxSize.Width;
  73. xOffset += boxSpacing.Width;
  74. yPadding = ( height - groynes.Count() * boxSize.Height ) / ( groynes.Count() + 1 );
  75. rect.Y = 0;
  76. rect.X = xOffset;
  77. //int xOffset = numLevel * boxSize.Width;
  78. //xOffset += numLevel * boxSpacing.Width;
  79. //xOffset += imagePadding.Width / 2;
  80. foreach( Groyne groyne in groynes )
  81. {
  82. rect.Y += yPadding;
  83. groynePositions.Add( groyne, rect );
  84. var huntedTimedGroyne = huntedMap[ level ].Find( timedGroyne => timedGroyne.Groyne == groyne );
  85. var hunterTimedGroyne = hunterMap[ level ].Find( timedGroyne => timedGroyne.Groyne == groyne );
  86. bool isUnsafe = huntedTimedGroyne.TotalTime > hunterTimedGroyne.TotalTime || groyneBorderBrush[ huntedTimedGroyne.ParentGroyne.Groyne ] == hunterBrush;
  87. var fillBrush = isUnsafe ? hunterFillBrush : huntedFillBrush;
  88. var brush = isUnsafe ? hunterBrush : huntedBrush;
  89. groyneBorderBrush.Add( groyne, brush );
  90. if ( groyne.Type == 'x' )
  91. {
  92. graphics.FillRectangle( fillBrush, rect );
  93. } else
  94. {
  95. graphics.FillRoundedRectangle( fillBrush, rect, boxRadius );
  96. }
  97. graphics.DrawStringCenteredTo( hunterTimedGroyne.TotalTimeConverted, font, hunterBrush, rect, VerticalPosition.Top, 10 );
  98. graphics.DrawStringCenteredTo( huntedTimedGroyne.TotalTimeConverted, font, huntedBrush, rect, VerticalPosition.Bottom, 10 );
  99. graphics.DrawStringCenteredTo( hunterTimedGroyne.DistanceConverted, font, hunterBrush, rect, VerticalPosition.Top, boxSize.Height / 4 );
  100. graphics.DrawStringCenteredTo( huntedTimedGroyne.DistanceConverted, font, huntedBrush, rect, VerticalPosition.Bottom, boxSize.Height / 4 );
  101. graphics.ConnectRectangles( new Pen( hunterBrush, borderWidth ), groynePositions[ groyne ], groynePositions[ hunterTimedGroyne.ParentGroyne.Groyne ] );
  102. graphics.ConnectRectangles( new Pen( huntedBrush, borderWidth ), groynePositions[ groyne ], groynePositions[ huntedTimedGroyne.ParentGroyne.Groyne ], borderWidth );
  103. rect = rect.Clone();
  104. rect.Y += boxSize.Height;
  105. }
  106. }
  107. TimedGroyne safePathSub = safePath;
  108. if ( safePath != null )
  109. {
  110. do
  111. {
  112. graphics.ConnectRectangles( new Pen( safePathBrush, borderWidth ), groynePositions[ safePathSub.Groyne ], groynePositions[ safePathSub.ParentGroyne.Groyne ], borderWidth );
  113. safePathSub = safePathSub.ParentGroyne;
  114. } while ( safePathSub.ParentGroyne != null );
  115. }
  116. else ColorMessageBox.Show( "Das gegebene Scenario enthält keine sichere Route für Minnie" );
  117. foreach( var groyne in groyneBorderBrush.Keys )
  118. {
  119. var brush = groyneBorderBrush[ groyne ];
  120. var rectF = groynePositions[ groyne ];
  121. if( groyne.Type == 'x' || groyne.Type == 'X' )
  122. {
  123. graphics.DrawRectangle( new Pen( brush, borderWidth ), rectF.Round() );
  124. } else
  125. {
  126. graphics.DrawRoundedRectangle( new Pen( brush, borderWidth ), rectF, boxRadius );
  127. }
  128. }
  129. }
  130. return img;
  131. }
  132. /// <summary>
  133. /// rounds a give RectangeF to a Rectangle and returns it
  134. /// </summary>
  135. /// <param name="rectangle">rectangle to round</param>
  136. public static Rectangle Round( this RectangleF rectangle )
  137. {
  138. return new Rectangle( ( int ) Math.Round( rectangle.X ), ( int ) Math.Round( rectangle.Y ), ( int ) Math.Round( rectangle.Width ), ( int ) Math.Round( rectangle.Height ) );
  139. }
  140. /// <summary>
  141. /// draws a line between two rectangles
  142. /// </summary>
  143. /// <param name="graphics">graphics object to draw to</param>
  144. /// <param name="pen">pen to draw with</param>
  145. /// <param name="from">rectangle to start at</param>
  146. /// <param name="to">rectengles to stop at</param>
  147. /// <param name="offset">offset of the drawn line ( offset = 0 means perfectly centered )</param>
  148. public static void ConnectRectangles( this Graphics graphics, Pen pen, RectangleF from, RectangleF to, float offset = 0 )
  149. {
  150. if ( from.IntersectsWith( to ) ) return; // do nothing if rectangles are overlapping to prevent errors
  151. PointF fromPt, toPt;
  152. if ( from.IntersectsVerticallyWith( to ) )
  153. {
  154. fromPt = new PointF( from.X + ( from.Width / 2 ) + offset, from.Top > to.Top ? from.Top : from.Bottom );
  155. toPt = new PointF( to.X + ( to.Width / 2 ) + offset, from.Top < to.Top ? to.Top : to.Bottom );
  156. } else
  157. {
  158. fromPt = new PointF( from.Left > to.Left ? from.Left : from.Right, from.Y + ( from.Height / 2 ) + offset );
  159. toPt = new PointF( from.Left < to.Left ? to.Left : to.Right, to.Y + ( to.Height / 2 ) + offset );
  160. }
  161. graphics.DrawLine( pen, fromPt, toPt );
  162. }
  163. /// <summary>
  164. /// tests if [me] instersects with [other] vertically
  165. /// </summary>
  166. public static bool IntersectsVerticallyWith( this RectangleF me, RectangleF other )
  167. {
  168. RectangleF meClone = me.Clone();
  169. RectangleF otherClone = other.Clone();
  170. meClone.Y = 0;
  171. otherClone.Y = 0;
  172. return meClone.IntersectsWith( otherClone );
  173. }
  174. /// <summary>
  175. /// tests if [me] instersects with [other] horizontally
  176. /// </summary>
  177. public static bool IntersectsHorizontallyWith( this RectangleF me, RectangleF other )
  178. {
  179. RectangleF meClone = me.Clone();
  180. RectangleF otherClone = other.Clone();
  181. meClone.X = 0;
  182. otherClone.X = 0;
  183. return meClone.IntersectsWith( otherClone );
  184. }
  185. /// <summary>
  186. /// draws a string horizontally centered in given rectangle
  187. /// </summary>
  188. /// <param name="graphics">graphics object to draw to</param>
  189. /// <param name="text">text to draw</param>
  190. /// <param name="font">font to draw the text with</param>
  191. /// <param name="brush">brush to use for drawing</param>
  192. /// <param name="centerTo">rectangle, the text should be centered in</param>
  193. /// <param name="positioning">vertical alignment of the text</param>
  194. /// <param name="topMiddlePadding">offset of the text from border ( ignored if positioning = centered )</param>
  195. public static void DrawStringCenteredTo( this Graphics graphics, string text, Font font, Brush brush, RectangleF centerTo, VerticalPosition positioning = VerticalPosition.Middle, float topMiddlePadding = 0 )
  196. {
  197. graphics.DrawString( text, font, brush, centerTo.CenterTo( graphics.MeasureString( text, font ), positioning, topMiddlePadding ) );
  198. }
  199. /// <summary>
  200. /// calculates point for text to be centered in rectangle and returns it
  201. /// </summary>
  202. /// <param name="rect">rectangle, the text should be centered in</param>
  203. /// <param name="measured">measured size of the text to draw</param>
  204. /// <param name="positioning">vertical alignment of the text</param>
  205. /// <param name="topMiddlePadding">offset of the text from border ( ignored if positioning = centered )</param>
  206. public static PointF CenterTo( this RectangleF rect, SizeF measured, VerticalPosition positioning = VerticalPosition.Middle, float topMiddlePadding = 0 )
  207. {
  208. PointF point = new PointF( rect.X, rect.Y );
  209. point.X += ( rect.Width / 2 ) - ( measured.Width / 2 );
  210. switch( positioning )
  211. {
  212. case VerticalPosition.Bottom:
  213. point.Y += rect.Height - ( measured.Height + topMiddlePadding );
  214. break;
  215. case VerticalPosition.Middle:
  216. point.Y += ( rect.Height / 2 ) - ( measured.Height / 2 );
  217. break;
  218. case VerticalPosition.Top:
  219. point.Y += topMiddlePadding;
  220. break;
  221. }
  222. return point;
  223. }
  224. public enum VerticalPosition
  225. {
  226. Top, Middle, Bottom
  227. }
  228. }
  229. }