protected override AsepriteFile Read(ContentReader input, AsepriteFile existingInstance) { if (existingInstance != null) { return(existingInstance); } int width = input.ReadInt32(); int height = input.ReadInt32(); AsepriteFile aseprite = new AsepriteFile { Width = width, Height = height }; // Layers int layersCount = input.ReadInt32(); AsepriteLayer layer; List <AsepriteLayer> layersByIndex = new List <AsepriteLayer>(); for (int i = 0; i < layersCount; i++) { layer = new AsepriteLayer { Name = input.ReadString(), Opacity = input.ReadSingle(), BlendMode = (AsepriteLayer.BlendModes)input.ReadInt32(), Visible = input.ReadBoolean() }; aseprite.Layers.Add(layer); // Use this for referencing cels down further layersByIndex.Add(layer); } // Frames AsepriteFrame frame; AsepriteCel cel; int framesCount = input.ReadInt32(); int celsCount; int celOriginX; int celOriginY; int celWidth; int celHeight; int textureWidth = framesCount * width; int textureHeight = layersCount * height; Color[] pixelData = new Color[textureWidth * textureHeight]; for (int f = 0; f < framesCount; f++) { frame = new AsepriteFrame { Duration = input.ReadSingle() }; // Cels celsCount = input.ReadInt32(); for (int i = 0; i < celsCount; i++) { int layerIndex = input.ReadInt32(); cel = new AsepriteCel { Layer = layersByIndex[layerIndex] // ClipRect = new Rectangle(f * width, layerIndex * height, width, height) }; frame.Cels.Add(cel); // Get info for the texture celOriginX = input.ReadInt32(); celOriginY = input.ReadInt32(); celWidth = input.ReadInt32(); celHeight = input.ReadInt32(); var addX = cel.X; var addY = cel.Y; for (int celY = celOriginY; celY < celOriginY + celHeight; celY++) { for (int celX = celOriginX; celX < celOriginX + celWidth; celX++) { var pixel = input.ReadColor(); var ind = (f * width) + celX + addX + ((i * height) + celY + addY) * textureWidth; // fixme: debug if check, figure out why texture loads wrong if (ind > -1 && ind < pixelData.Length) { pixelData[ind] = pixel; } } } } aseprite.Frames.Add(frame); } // Dump our pixels into the texture aseprite.Texture = new Texture2D(GraphicsDevice, textureWidth, textureHeight); aseprite.Texture.SetData(pixelData); // Animations int animationsCount = input.ReadInt32(); AsepriteAnimation animation; for (int i = 0; i < animationsCount; i++) { animation = new AsepriteAnimation { Name = input.ReadString(), FirstFrame = input.ReadInt32(), LastFrame = input.ReadInt32(), Directions = (AsepriteTag.LoopDirections)input.ReadByte() }; aseprite.Animations.Add(animation.Name, animation); } // If no animations were added then just add one // that covers all frames if (aseprite.Animations.Count == 0) { animation = new AsepriteAnimation { Name = "Idle", FirstFrame = 0, LastFrame = aseprite.Frames.Count - 1 }; aseprite.Animations.Add(animation.Name, animation); } return(aseprite); }
public AsepriteFile(string filename, ContentBuildLogger logger) { using (var stream = File.OpenRead(filename)) { var reader = new BinaryReader(stream); #region File helpers // Helpers for translating the Aseprite file format reference // See: https://github.com/aseprite/aseprite/blob/master/docs/ase-file-specs.md byte BYTE() { return(reader.ReadByte()); } ushort WORD() { return(reader.ReadUInt16()); } short SHORT() { return(reader.ReadInt16()); } uint DWORD() { return(reader.ReadUInt32()); } long LONG() { return(reader.ReadInt32()); } string STRING() { return(Encoding.UTF8.GetString(BYTES(WORD()))); } byte[] BYTES(int number) { return(reader.ReadBytes(number)); } void SEEK(int number) { reader.BaseStream.Position += number; } #endregion #region Consume header int frameCount; { DWORD(); // Magic number var magic = WORD(); if (magic != 0xA5e0) { throw new Exception("File doesn't appear to be from Aseprite."); } // Basic info frameCount = WORD(); Width = WORD(); Height = WORD(); Mode = (Modes)(WORD() / 8); logger?.LogMessage($"Cels are {Width}x{Height}, mode is {Mode}"); // Ignore a bunch of stuff DWORD(); // Flags WORD(); // Speed (deprecated) DWORD(); // 0 DWORD(); // 0 BYTE(); // Palette entry SEEK(3); // Ignore these bytes WORD(); // Number of colors (0 means 256 for old sprites) BYTE(); // Pixel width BYTE(); // Pixel height SEEK(92); // For Future } #endregion #region Actual data // Some temporary holders var colorBuffer = new byte[Width * Height * (int)Mode]; var palette = new Color[256]; IUserData lastUserData = null; for (int i = 0; i < frameCount; i++) { var frame = new AsepriteFrame(); Frames.Add(frame); long frameStart; long frameEnd; int chunkCount; // Frame header { frameStart = reader.BaseStream.Position; frameEnd = frameStart + DWORD(); WORD(); // Magic number (always 0xF1FA) chunkCount = WORD(); frame.Duration = WORD() / 1000f; SEEK(6); // For future (set to zero) } for (int j = 0; j < chunkCount; j++) { long chunkStart; long chunkEnd; Chunks chunkType; // Chunk header { chunkStart = reader.BaseStream.Position; chunkEnd = chunkStart + DWORD(); chunkType = (Chunks)WORD(); } // Layer if (chunkType == Chunks.Layer) { var layer = new AsepriteLayer(); layer.Flag = (AsepriteLayer.Flags)WORD(); layer.Type = (AsepriteLayer.Types)WORD(); layer.ChildLevel = WORD(); WORD(); // width WORD(); // height layer.BlendMode = (AsepriteLayer.BlendModes)WORD(); layer.Opacity = BYTE() / 255f; SEEK(3); layer.Name = STRING(); lastUserData = layer; Layers.Add(layer); } else if (chunkType == Chunks.Cel) { // Cell var cel = new AsepriteCel(); var layerIndex = WORD(); cel.Layer = Layers[layerIndex]; // Layer is row (Frame is column) cel.X = SHORT(); cel.Y = SHORT(); cel.Opacity = BYTE() / 255f; var celType = (CelTypes)WORD(); SEEK(7); if (celType == CelTypes.RawCel || celType == CelTypes.CompressedImage) { cel.Width = WORD(); cel.Height = WORD(); var byteCount = cel.Width * cel.Height * (int)Mode; if (celType == CelTypes.RawCel) { reader.Read(colorBuffer, 0, byteCount); } else { SEEK(2); var deflate = new DeflateStream(reader.BaseStream, CompressionMode.Decompress); deflate.Read(colorBuffer, 0, byteCount); } cel.Pixels = new Color[cel.Width * cel.Height]; ConvertBytesToPixels(colorBuffer, cel.Pixels, palette); } else if (celType == CelTypes.LinkedCel) { var targetFrame = WORD(); // Frame position to link with // Grab the cel from a previous frame var targetCel = Frames[targetFrame].Cels.Where(c => c.Layer == Layers[layerIndex]).First(); cel.Width = targetCel.Width; cel.Height = targetCel.Height; cel.Pixels = targetCel.Pixels; } lastUserData = cel; frame.Cels.Add(cel); } else if (chunkType == Chunks.Palette) { // Palette var size = DWORD(); var start = DWORD(); var end = DWORD(); SEEK(8); for (int c = 0; c < (end - start) + 1; c++) { var hasName = Calc.IsBitSet(WORD(), 0); palette[start + c] = new Color(BYTE(), BYTE(), BYTE(), BYTE()); if (hasName) { STRING(); // Color name } } } else if (chunkType == Chunks.UserData) { // User data if (lastUserData != null) { var flags = DWORD(); if (Calc.IsBitSet(flags, 0)) { lastUserData.UserDataText = STRING(); } else if (Calc.IsBitSet(flags, 1)) { lastUserData.UserDataColor = new Color(BYTE(), BYTE(), BYTE(), BYTE()); } } } else if (chunkType == Chunks.FrameTags) { // Tag (animation reference) var tagsCount = WORD(); SEEK(8); for (int t = 0; t < tagsCount; t++) { var tag = new AsepriteTag(); tag.From = WORD(); tag.To = WORD(); tag.LoopDirection = (AsepriteTag.LoopDirections)BYTE(); SEEK(8); tag.Color = new Color(BYTE(), BYTE(), BYTE(), (byte)255); SEEK(1); tag.Name = STRING(); Tags.Add(tag); } } else if (chunkType == Chunks.Slice) { // Slice var slicesCount = DWORD(); var flags = DWORD(); DWORD(); var name = STRING(); for (int s = 0; s < slicesCount; s++) { var slice = new AsepriteSlice(); slice.Name = name; slice.Frame = (int)DWORD(); slice.OriginX = (int)LONG(); slice.OriginY = (int)LONG(); slice.Width = (int)DWORD(); slice.Height = (int)DWORD(); // 9 slice if (Calc.IsBitSet(flags, 0)) { LONG(); // Center X position (relative to slice bounds) LONG(); // Center Y position DWORD(); // Center width DWORD(); // Center height } else if (Calc.IsBitSet(flags, 1)) { // Pivot slice.Pivot = new Point((int)DWORD(), (int)DWORD()); } lastUserData = slice; Slices.Add(slice); } } reader.BaseStream.Position = chunkEnd; } reader.BaseStream.Position = frameEnd; } #endregion } if (logger == null) { return; } // Log out what we found logger.LogMessage("Layers:"); foreach (var layer in Layers) { logger.LogMessage($"\t{layer.Name}"); } logger.LogMessage("Animations:"); foreach (var animation in Tags) { if (animation.To == animation.From) { logger.LogMessage($"\t{animation.Name} => {animation.From + 1}"); } else { logger.LogMessage($"\t{animation.Name} => {animation.From + 1} - {animation.To + 1}"); } } }