Compiler.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. using System.Collections.Generic;
  2. namespace FontBuilder
  3. {
  4. internal static class BspSubdivision
  5. {
  6. /// <summary>
  7. /// Attempt to place textures into the space defined by Dimensions until no more can be fit.
  8. /// Subdivide the space after each insertion. Assume textures are sorted by size.
  9. /// </summary>
  10. /// <param name="Dimensions"></param>
  11. /// <param name="Textures"></param>
  12. internal static void TryPlaceGlyphs(Rectangle Dimensions, List<Glyph> Textures)
  13. {
  14. Glyph tex = null;
  15. //Find largest entry that fits within the dimensions.
  16. for (int i = 0; i < Textures.Count; ++i)
  17. {
  18. var Entry = Textures[i];
  19. if (Entry.Width > Dimensions.Width || Entry.Height > Dimensions.Height)
  20. continue;
  21. Textures.RemoveAt(i);
  22. tex = Entry;
  23. break;
  24. }
  25. // Quit if nothing fit.
  26. if (tex == null) return;
  27. tex.X = Dimensions.X;
  28. tex.Y = Dimensions.Y;
  29. //Subdivide remaining space.
  30. int HorizontalDifference = Dimensions.Width - tex.Width;
  31. int VerticalDifference = Dimensions.Height - tex.Height;
  32. if (HorizontalDifference == 0 && VerticalDifference == 0) //Perfect fit!
  33. return;
  34. Rectangle? ASpace = null;
  35. Rectangle? BSpace = null;
  36. // Subdivide the space on the shortest axis of the texture we just placed.
  37. if (HorizontalDifference >= VerticalDifference)
  38. {
  39. ASpace = new Rectangle(Dimensions.X + tex.Width, Dimensions.Y, HorizontalDifference, Dimensions.Height);
  40. // Remember that this isn't a perfect split - a chunk belongs to the placed texture.
  41. if (VerticalDifference > 0)
  42. BSpace = new Rectangle(Dimensions.X, Dimensions.Y + tex.Height, tex.Width, VerticalDifference);
  43. }
  44. else
  45. {
  46. ASpace = new Rectangle(Dimensions.X, Dimensions.Y + tex.Height, Dimensions.Width, VerticalDifference);
  47. if (HorizontalDifference > 0)
  48. BSpace = new Rectangle(Dimensions.X + tex.Width, Dimensions.Y, HorizontalDifference, tex.Height);
  49. }
  50. TryPlaceGlyphs(ASpace.Value, Textures);
  51. if (BSpace.HasValue) TryPlaceGlyphs(BSpace.Value, Textures);
  52. }
  53. /// <summary>
  54. /// Attempt to fit textures into working space, and if any are left, double vertical size of working space.
  55. /// </summary>
  56. /// <param name="TotalArea"></param>
  57. /// <param name="WorkingArea"></param>
  58. /// <param name="Textures"></param>
  59. /// <returns></returns>
  60. internal static Rectangle ExpandVertical(Rectangle TotalArea, Rectangle WorkingArea, List<Glyph> Textures)
  61. {
  62. TryPlaceGlyphs(WorkingArea, Textures);
  63. if (Textures.Count > 0)
  64. {
  65. WorkingArea = new Rectangle(0, TotalArea.Height, TotalArea.Width, TotalArea.Height);
  66. TotalArea.Height *= 2;
  67. return ExpandHorizontal(TotalArea, WorkingArea, Textures);
  68. }
  69. else
  70. return TotalArea;
  71. }
  72. /// <summary>
  73. /// Attempt to fit textures into working space, and if any are left, double horizontal size of working space.
  74. /// </summary>
  75. /// <param name="TotalArea"></param>
  76. /// <param name="WorkingArea"></param>
  77. /// <param name="Textures"></param>
  78. /// <returns></returns>
  79. internal static Rectangle ExpandHorizontal(Rectangle TotalArea, Rectangle WorkingArea, List<Glyph> Textures)
  80. {
  81. TryPlaceGlyphs(WorkingArea, Textures);
  82. if (Textures.Count > 0)
  83. {
  84. WorkingArea = new Rectangle(TotalArea.Width, 0, TotalArea.Width, TotalArea.Height);
  85. TotalArea.Width *= 2;
  86. return ExpandVertical(TotalArea, WorkingArea, Textures);
  87. }
  88. else
  89. return TotalArea;
  90. }
  91. }
  92. public class AtlasCompiler
  93. {
  94. public static Atlas Compile(List<Glyph> Entries)
  95. {
  96. Entries.Sort((A, B) =>
  97. {
  98. return (B.Width * B.Height) - (A.Width * A.Height);
  99. });
  100. // Find smallest power of 2 sized texture that can hold the largest entry.
  101. var largestEntry = Entries[0];
  102. var texSize = new Rectangle(0, 0, 1, 1);
  103. while (texSize.Width < largestEntry.Width)
  104. texSize.Width *= 2;
  105. while (texSize.Height < largestEntry.Height)
  106. texSize.Height *= 2;
  107. // Be sure to pass a copy of the list since the algorithm modifies it.
  108. texSize = BspSubdivision.ExpandHorizontal(texSize, texSize, new List<Glyph>(Entries));
  109. return new Atlas { Dimensions = texSize, Glyphs = Entries };
  110. }
  111. }
  112. }