AtlasImporter.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #region License
  2. // Copyright 2016 Kastellanos Nikolaos
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #endregion
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Globalization;
  19. using System.IO;
  20. using System.Xml;
  21. using Microsoft.Xna.Framework;
  22. using Microsoft.Xna.Framework.Content.Pipeline;
  23. using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
  24. using Microsoft.Xna.Framework.Graphics;
  25. namespace nkast.Aether.Content.Pipeline
  26. {
  27. [ContentImporter(".tmx", DisplayName = "Atlas Importer - Aether", DefaultProcessor = "AtlasProcessor")]
  28. public class AtlasImporter : ContentImporter<TextureAtlasContent>
  29. {
  30. public override TextureAtlasContent Import(string filename, ContentImporterContext context)
  31. {
  32. TextureAtlasContent output;
  33. if (Path.GetExtension(filename) == ".tmx")
  34. output = ImportTMX(filename, context);
  35. else
  36. throw new InvalidContentException("File type not supported");
  37. PackSprites(output);
  38. RenderAtlas(output);
  39. return output;
  40. }
  41. private static TextureAtlasContent ImportTMX(string filename, ContentImporterContext context)
  42. {
  43. TextureAtlasContent output = new TextureAtlasContent();
  44. output.Identity = new ContentIdentity(filename);
  45. XmlDocument xmlDoc = new XmlDocument();
  46. xmlDoc.Load(filename);
  47. XmlElement map = xmlDoc.DocumentElement;
  48. string orientation = GetAttribute(map, "orientation");
  49. if (orientation != "orthogonal")
  50. throw new InvalidContentException("Invalid orientation. Only 'orthogonal' is supported for atlases.");
  51. output.Renderorder = GetAttribute(map, "renderorder");
  52. output.MapColumns = GetAttributeAsInt(map, "width").Value;
  53. output.MapRows = GetAttributeAsInt(map, "height").Value;
  54. output.TileWidth = GetAttributeAsInt(map, "tilewidth").Value;
  55. output.TileHeight = GetAttributeAsInt(map, "tileheight").Value;
  56. output.Width = output.MapColumns * output.TileWidth;
  57. output.Height = output.MapRows * output.TileHeight;
  58. XmlNode tilesetNode = map["tileset"];
  59. output.Firstgid = GetAttributeAsInt(tilesetNode, "firstgid").Value;
  60. if (tilesetNode.Attributes["source"] != null)
  61. {
  62. string tsxFilename = tilesetNode.Attributes["source"].Value;
  63. string baseDirectory = Path.GetDirectoryName(filename);
  64. tsxFilename = Path.Combine(baseDirectory, tsxFilename);
  65. TilesetContent tileset = ImportTSX(tsxFilename, context);
  66. var sourceSprites = SourceSpritesFromTileset(tileset);
  67. output.SourceSprites.AddRange(sourceSprites);
  68. context.AddDependency(tsxFilename);
  69. }
  70. else
  71. {
  72. string rootDirectory = Path.GetDirectoryName(filename);
  73. TilesetContent tileset = ImportTileset(tilesetNode, context, rootDirectory);
  74. var sourceSprites = SourceSpritesFromTileset(tileset);
  75. output.SourceSprites.AddRange(sourceSprites);
  76. }
  77. XmlNode layerNode = map["layer"];
  78. int layerColumns = Convert.ToInt32(map.Attributes["width"].Value, CultureInfo.InvariantCulture);
  79. int layerRows = Convert.ToInt32(map.Attributes["height"].Value, CultureInfo.InvariantCulture);
  80. output.LayerColumns = layerColumns;
  81. output.LayerRows = layerRows;
  82. XmlNode layerDataNode = layerNode["data"];
  83. string encoding = layerDataNode.Attributes["encoding"].Value;
  84. if (encoding != "csv")
  85. throw new InvalidContentException("Invalid encoding. Only 'csv' is supported for data.");
  86. string data = layerDataNode.InnerText;
  87. string[] dataStringList = data.Split(',');
  88. int[] mapData = new int[dataStringList.Length];
  89. for (int i = 0; i < dataStringList.Length; i++)
  90. mapData[i] = Convert.ToInt32(dataStringList[i].Trim(), CultureInfo.InvariantCulture);
  91. output.MapData = mapData;
  92. return output;
  93. }
  94. private static TilesetContent ImportTSX(string tsxFilename, ContentImporterContext context)
  95. {
  96. XmlDocument xmlDoc = new XmlDocument();
  97. xmlDoc.Load(tsxFilename);
  98. XmlNode tilesetNode = xmlDoc.DocumentElement;
  99. string baseDirectory = Path.GetDirectoryName(tsxFilename);
  100. return ImportTileset(tilesetNode, context, baseDirectory);
  101. }
  102. private static TilesetContent ImportTileset(XmlNode tilesetNode, ContentImporterContext context, string baseDirectory)
  103. {
  104. TilesetContent tileset = new TilesetContent();
  105. if (tilesetNode["tileoffset"] != null)
  106. throw new InvalidContentException("tileoffset is not supported.");
  107. tileset.TileWidth = GetAttributeAsInt(tilesetNode, "tilewidth").Value;
  108. tileset.tileHeight = GetAttributeAsInt(tilesetNode, "tileheight").Value;
  109. foreach (XmlNode tileNode in tilesetNode.ChildNodes)
  110. {
  111. if (tileNode.Name != "tile") continue;
  112. int tileId = GetAttributeAsInt(tileNode, "id").Value;
  113. XmlNode imageNode = tileNode["image"];
  114. //string format = GetAttribute(imageNode, "format");
  115. string imageSource = GetAttribute(imageNode, "source");
  116. string fullImageSource = Path.Combine(baseDirectory, imageSource);
  117. TextureImporter txImporter = new TextureImporter();
  118. Texture2DContent textureContent = (Texture2DContent)txImporter.Import(fullImageSource, context);
  119. textureContent.Name = Path.GetFileNameWithoutExtension(fullImageSource);
  120. TileContent source = new TileContent();
  121. source.SrcTexture = textureContent;
  122. source.SrcBounds.Location = Point.Zero;
  123. source.SrcBounds.Width = textureContent.Mipmaps[0].Width;
  124. source.SrcBounds.Height = textureContent.Mipmaps[0].Height;
  125. Color? transKeyColor = GetAttributeAsColor(imageNode, "trans");
  126. if (transKeyColor != null)
  127. foreach (MipmapChain mips in textureContent.Faces)
  128. foreach (BitmapContent mip in mips)
  129. ((PixelBitmapContent<Color>)mip).ReplaceColor(transKeyColor.Value, Color.Transparent);
  130. if (tileId != tileset.SourceTiles.Count)
  131. throw new InvalidContentException("Invalid id");
  132. tileset.SourceTiles.Add(source);
  133. }
  134. return tileset;
  135. }
  136. private static List<SpriteContent> SourceSpritesFromTileset(TilesetContent tileset)
  137. {
  138. List<SpriteContent> sprites = new List<SpriteContent>();
  139. foreach (TileContent tile in tileset.SourceTiles)
  140. {
  141. SpriteContent sprite = new SpriteContent();
  142. sprite.Texture = tile.SrcTexture;
  143. sprite.Bounds = tile.SrcBounds;
  144. sprites.Add(sprite);
  145. }
  146. return sprites;
  147. }
  148. private static void PackSprites(TextureAtlasContent output)
  149. {
  150. for (int y = 0; y < output.LayerRows; y++)
  151. {
  152. for (int x = 0; x < output.LayerColumns; x++)
  153. {
  154. int posX = x * output.TileWidth;
  155. int posY = y * output.TileHeight;
  156. int tilegId = output.MapData[y * output.LayerColumns + x];
  157. if (tilegId == 0) continue;
  158. tilegId -= output.Firstgid;
  159. SpriteContent srcSprite = output.SourceSprites[tilegId];
  160. if (output.Renderorder == "right-down" || output.Renderorder == "right-up")
  161. posX += (output.TileWidth - srcSprite.Bounds.Width);
  162. if (output.Renderorder == "right-down" || output.Renderorder == "left-down")
  163. posY += (output.TileHeight - srcSprite.Bounds.Height);
  164. SpriteContent newSprite = new SpriteContent(srcSprite);
  165. newSprite.Bounds.Location = new Point(posX, posY);
  166. output.DestinationSprites.Add(newSprite);
  167. string name = srcSprite.Texture.Name;
  168. output.Sprites.Add(name, newSprite);
  169. }
  170. }
  171. }
  172. private static void RenderAtlas(TextureAtlasContent output)
  173. {
  174. PixelBitmapContent<Color> outputBmp = new PixelBitmapContent<Color>(output.Width, output.Height);
  175. foreach (SpriteContent sprite in output.DestinationSprites)
  176. {
  177. BitmapContent srcBmp = sprite.Texture.Faces[0][0];
  178. Rectangle srcRect = new Rectangle(0, 0, srcBmp.Width, srcBmp.Height);
  179. BitmapContent.Copy(srcBmp, srcRect, outputBmp, sprite.Bounds);
  180. }
  181. MipmapChain mipmapChain = new MipmapChain(outputBmp);
  182. output.Texture.Mipmaps = mipmapChain;
  183. }
  184. private static string GetAttribute(XmlNode xmlNode, string attributeName)
  185. {
  186. XmlAttribute attribute = xmlNode.Attributes[attributeName];
  187. if(attribute==null) return null;
  188. return attribute.Value;
  189. }
  190. private static int? GetAttributeAsInt(XmlNode xmlNode, string attributeName)
  191. {
  192. XmlAttribute attribute = xmlNode.Attributes[attributeName];
  193. if (attribute == null) return null;
  194. return Int32.Parse(attribute.Value, CultureInfo.InvariantCulture);
  195. }
  196. private static Color? GetAttributeAsColor(XmlNode xmlNode, string attributeName)
  197. {
  198. XmlAttribute attribute = xmlNode.Attributes[attributeName];
  199. if (attribute == null) return null;
  200. attribute.Value = attribute.Value.TrimStart(new char[] { '#' });
  201. return new Color(
  202. Int32.Parse(attribute.Value.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
  203. Int32.Parse(attribute.Value.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
  204. Int32.Parse(attribute.Value.Substring(4, 2), System.Globalization.NumberStyles.HexNumber));
  205. }
  206. }
  207. }