BoxLayouter.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. #region Using ステートメント
  2. using Microsoft.Xna.Framework;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. #endregion
  7. namespace WpfFontPipeline
  8. {
  9. /// <summary>
  10. /// レイアウト対象になる矩形情報
  11. /// </summary>
  12. class BoxLayoutItem
  13. {
  14. /// <summary>
  15. /// 矩形情報
  16. /// </summary>
  17. public Rectangle Bounds { get; set; }
  18. /// <summary>
  19. /// タグ
  20. /// </summary>
  21. public object Tag { get; set; }
  22. /// <summary>
  23. /// 配置済みか?
  24. /// </summary>
  25. public bool Placed { get; set; }
  26. }
  27. /// <summary>
  28. /// 複数矩形を1つのテクスチャにまとめるヘルパークラス
  29. /// </summary>
  30. /// <remarks>
  31. /// 高速化の為に処理する矩形の多くが同じ高さであるという前提のアルゴリズムを使用
  32. /// </remarks>
  33. class BoxLayouter
  34. {
  35. #region 定数
  36. /// <summary>
  37. /// 矩形間の空きスペース
  38. /// </summary>
  39. readonly int PadSize = 1;
  40. #endregion
  41. #region プロパティ
  42. /// <summary>
  43. /// 登録された矩形要素の取得と設定
  44. /// </summary>
  45. public List<BoxLayoutItem> Items {get; set; }
  46. #endregion
  47. #region パブリックメソッド
  48. /// <summary>
  49. /// コンストラクタ
  50. /// </summary>
  51. public BoxLayouter()
  52. {
  53. Items = new List<BoxLayoutItem>();
  54. }
  55. /// <summary>
  56. /// 矩形の追加
  57. /// </summary>
  58. /// <param name="item">追加する矩形情報</param>
  59. public void Add(BoxLayoutItem item)
  60. {
  61. // 矩形の高さ毎に分類、格納する
  62. List<BoxLayoutItem> itemBacket;
  63. if (!itemBackets.TryGetValue(item.Bounds.Height, out itemBacket))
  64. {
  65. itemBacket = new List<BoxLayoutItem>();
  66. itemBackets.Add(item.Bounds.Height, itemBacket);
  67. }
  68. itemBacket.Add(item);
  69. // 必要な矩形面積を追加
  70. int w = item.Bounds.Width + PadSize;
  71. int h = item.Bounds.Height + PadSize;
  72. totalAreaSize += (w * h);
  73. Items.Add(item);
  74. }
  75. /// <summary>
  76. /// レイアウトの実行
  77. /// </summary>
  78. /// <param name="outWidth">レイアウトに必要な横幅</param>
  79. /// <param name="outHeight">レイアウトに必要な高さ</param>
  80. public void Layout(out int outWidth, out int outHeight)
  81. {
  82. // 総面積から必要となるサイズを概算
  83. int size = (int)Math.Sqrt(totalAreaSize);
  84. int h = (int)(Math.Pow(2, (int)(Math.Log(size, 2) - 0.5)));
  85. int w = (int)(Math.Pow(2, (int)(Math.Log(size, 2) + 0.5)));
  86. while ((long)w * (long)h < totalAreaSize)
  87. {
  88. if (w <= h)
  89. w *= 2;
  90. else
  91. h *= 2;
  92. }
  93. // 高さ、幅の順に並び替える
  94. var keys = from key in itemBackets.Keys orderby key descending select key;
  95. sortedKeys = keys.ToList();
  96. foreach (int key in sortedKeys)
  97. {
  98. var items = from item in itemBackets[key]
  99. orderby item.Bounds.Width descending
  100. select item;
  101. itemBackets[key] = items.ToList();
  102. }
  103. // 現在のサイズで配置してみる
  104. while (TryLayout(w, h) == false)
  105. {
  106. // 全部配置しきれなかったので、サイズを大きくして再試行
  107. ClearPlacedInfo();
  108. if (w <= h)
  109. w *= 2;
  110. else
  111. h *= 2;
  112. }
  113. outWidth = w;
  114. outHeight = h;
  115. }
  116. #endregion
  117. #region プライベートメソッド
  118. /// <summary>
  119. /// 配置処理
  120. /// </summary>
  121. bool TryLayout(int width, int height)
  122. {
  123. // 配置位置
  124. int x = PadSize;
  125. int y = PadSize;
  126. // 列の高さ
  127. int lineHeight = sortedKeys[0];
  128. // 高さの順に配置
  129. foreach (int key in sortedKeys)
  130. {
  131. var itemBacket = itemBackets[key];
  132. for (int i = 0; i < itemBacket.Count; ++i)
  133. {
  134. var item = itemBacket[i];
  135. // 既に配置済みか?
  136. if (item.Placed)
  137. continue;
  138. // 現在の列に配置できるか?
  139. if (x + item.Bounds.Width + PadSize < width)
  140. {
  141. // 現在の列の右端に追加
  142. var bounds = item.Bounds;
  143. bounds.X = x;
  144. bounds.Y = y;
  145. item.Bounds = bounds;
  146. item.Placed = true;
  147. x += item.Bounds.Width + PadSize;
  148. }
  149. else
  150. {
  151. // 右端の空きスペースに配置できるか試す
  152. // 幅の狭いものから試す
  153. for (int j = itemBacket.Count - 1; i < j; --j)
  154. {
  155. var narrowItem = itemBacket[j];
  156. // 配置済みのものは無視
  157. if (narrowItem.Placed)
  158. continue;
  159. // この列に、これ以上配置できない
  160. if (x + narrowItem.Bounds.Width + PadSize >= width)
  161. break;
  162. var bounds = narrowItem.Bounds;
  163. bounds.X = x;
  164. bounds.Y = y;
  165. narrowItem.Bounds = bounds;
  166. narrowItem.Placed = true;
  167. x += narrowItem.Bounds.Width + PadSize;
  168. }
  169. // 次の行へ移動
  170. y += lineHeight + PadSize;
  171. // おっと、サイズ足らずで配置しきれなかった
  172. if (y + lineHeight > height)
  173. return false;
  174. lineHeight = key;
  175. x = PadSize;
  176. --i;
  177. }
  178. }
  179. }
  180. return true;
  181. }
  182. /// <summary>
  183. /// 配置情報の初期化
  184. /// </summary>
  185. void ClearPlacedInfo()
  186. {
  187. foreach (var itemBacket in itemBackets.Values)
  188. {
  189. foreach (var item in itemBacket)
  190. item.Placed = false;
  191. }
  192. }
  193. #endregion
  194. #region プライベートフィールド
  195. // 現在の行情報をDictionary<height:int, BoxLayoutItem[]>として格納
  196. Dictionary<int, List<BoxLayoutItem>> itemBackets =
  197. new Dictionary<int, List<BoxLayoutItem>>();
  198. // 追加された矩形の総面積
  199. long totalAreaSize;
  200. // 高さ毎に並び替える為のソートキー
  201. List<int> sortedKeys;
  202. #endregion
  203. }
  204. }