/// <summary> /// Creates a new <see cref="AsepritePaletteColor"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal AsepritePaletteColor(AsepriteReader reader, bool isNewPalette) { if (isNewPalette) { // Only read flag if this is the new palette type. _flag = (AsepritePaletteFlags)reader.ReadWORD(); } Red = reader.ReadByte(); Green = reader.ReadByte(); Blue = reader.ReadByte(); if (isNewPalette) { // Only read alpha value if this is the new palette type Alpha = reader.ReadByte(); } else { Alpha = 255; } PackedValue = Utils.BytesToPacked(Red, Green, Blue, Alpha); // If the HasName flag is present, we need to read the string value, however // this seems to always never exist. Not sure what the field is for, but may // just be some compatibility thing with named palette formats like .gpl. if ((_flag & AsepritePaletteFlags.HasName) != 0) { Name = reader.ReadString(); } }
private void ReadAsOldPalleteA(AsepriteReader reader) { // Read the number of packets ushort numberOfPackets = reader.ReadWORD(); List <AsepritePaletteColor> colors = new List <AsepritePaletteColor>(); for (int i = 0; i < numberOfPackets; i++) { // Read the number of palette entries to skip from the last packet // But we're going to ignore the value since we don't need it. _ = reader.ReadByte(); // Read the number of colors in the packet int numberOfColors = reader.ReadByte(); if (numberOfColors == 0) { numberOfColors = 256; } for (int c = 0; c < numberOfColors; c++) { colors.Add(new AsepritePaletteColor(reader, false)); } } Colors = colors.ToArray(); }
/// <summary> /// Creates a new <see cref="AsepriteTagChunk"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal AsepriteTagChunk(AsepriteReader reader) { From = reader.ReadWORD(); To = reader.ReadWORD(); Direction = (AsepriteLoopDirection)reader.ReadByte(); // Per ase file spec, ignore next 8 bytes, they are reserved for future use reader.Ignore(8); ColorR = reader.ReadByte(); ColorG = reader.ReadByte(); ColorB = reader.ReadByte(); // Per ase file spec, ignore next byte, it's just an extra one set to zero reader.Ignore(1); Name = reader.ReadString(); }
/// <summary> /// Creates a new <see cref="AsepriteHeader"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal AsepriteHeader(AsepriteReader reader) { // Get the basestream position of the reader long headerStart = reader.BaseStream.Position; // Header is 128 bytes. Calcualte the base stream position that is // at the end of the header long headerEnd = headerStart + 128; // Read but ignore the file size _ = reader.ReadDWORD(); // Read and validate the magic number if (reader.ReadWORD() != 0xA5E0) { throw new Exception($"File given does not appear to be a valid .ase/.aseprite file!"); } FrameCount = reader.ReadWORD(); Width = reader.ReadWORD(); Height = reader.ReadWORD(); ColorDepth = (AsepriteColorDepth)(reader.ReadWORD() / 8); _flags = (AsepriteHeaderFlags)reader.ReadDWORD(); // Per ase file specs, the speed field is deprecated, so we'll // ignroe it. _ = reader.ReadWORD(); // Per ase file specs, next two DWORDs are ignored. _ = reader.ReadDWORD(); _ = reader.ReadDWORD(); TransparentIndex = reader.ReadByte(); // Per ase file specs, next 3 bytes are ignored reader.Ignore(3); ColorCount = reader.ReadWORD(); // We're going to ignore the rest of the header as we don't need // the information. For documentation though, the following is // what we are skipping // // Pixel Width // Pixel Height // X position of the grid // Y position of the grid // Grid width (zero if there is no grid, grid size is 16x16 on Aseprite default) // Grid height (zero if there is no grid // 84 bytes which are reserved for future use. // // Since we are skipping all this, we'll just set the reader's basestream to // the end of the header position we calculated earlier reader.BaseStream.Position = headerEnd; }
/// <summary> /// Creates a new <see cref="AsepriteLayerChunk"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal AsepriteLayerChunk(AsepriteReader reader) { // Read the flags that are set for this layer. Flags = (AsepriteLayerFlags)reader.ReadWORD(); // Read the type of layer we're dealing with. Type = (AsepriteLayerType)reader.ReadWORD(); // Read the sub level of this layer. If this layer is not a // child layer, this value will be 0. ChildLevel = reader.ReadWORD(); // Per ase file spec, default layer width in pixels is ignored _ = reader.ReadWORD(); // Per ase file spec, default layer height in pixels is ignored _ = reader.ReadWORD(); // Read the blend mode used by this layer. BlendMode = (AsepriteBlendMode)reader.ReadWORD(); // Read the opacity level for this layer. Opacity = reader.ReadByte(); // Per ase file spec, ignroe next 3 bytes, they are reserved for future use reader.Ignore(3); // Read the name of this layer. Name = reader.ReadString(); }
/// <summary> /// Reads a <see cref="AsepriteTagChunk"/> from the underlying stream /// of the provided <see cref="AsepriteReader"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> private void ReadTagChunk(AsepriteReader reader) { ushort tagCount = reader.ReadWORD(); // Per ase file spec, ignore the next eight bytes, they are reserved for future use. reader.Ignore(8); for (int i = 0; i < tagCount; i++) { AsepriteTagChunk tag = new AsepriteTagChunk(reader); File.Tags.Add(tag); } }
/// <summary> /// Creates a new <see cref="AsepriteFrame"/> instance. /// </summary> /// <param name="file"> /// The <see cref="AsepriteDocument"/> instance this frame belongs to. /// </param> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal AsepriteFrame(AsepriteDocument file, AsepriteReader reader) { Cels = new List <AsepriteCelChunk>(); File = file; // Ignore the total bytes in frame value, we don't need it _ = reader.ReadDWORD(); // Read and validate the magic number if (reader.ReadWORD() != 0xF1FA) { throw new Exception($"Invalid frame header, please ensure the .ase/.aseprite file is valid"); } // The next field contains the chunk count, but this is the old chunk count field int oldChunkCount = reader.ReadWORD(); // Aseprite stores the frame duration value as milliseconds Duration = reader.ReadWORD(); // Per ase file spec, the next two bytes are reserved for future use reader.Ignore(2); // This field contains the chunk count, but is the new chunk count field. uint newChunkCount = reader.ReadDWORD(); // Per ase file spec, if the new chunk count filed is 0, then we use the old field // value instad. int totalChunks = newChunkCount == 0 ? oldChunkCount : (int)newChunkCount; // A value indicating if the new palette chunk was found. When it is found // then we can skip reading the old palette chunk. bool newPaletteChunkFound = false; for (int i = 0; i < totalChunks; i++) { long chunkStart = reader.BaseStream.Position; uint chunkSize = reader.ReadDWORD(); long chunkEnd = chunkStart + chunkSize; AsepriteChunkType chunkType = (AsepriteChunkType)reader.ReadWORD(); System.Diagnostics.Debug.WriteLine($"Chunk Type: {chunkType}"); switch (chunkType) { case AsepriteChunkType.Layer: ReadLayerChunk(reader); break; case AsepriteChunkType.Cel: ReadCelChunk(reader, (int)chunkSize - 6); break; case AsepriteChunkType.Tags: ReadTagChunk(reader); break; case AsepriteChunkType.OldPaletteA: if (newPaletteChunkFound) { reader.BaseStream.Position = chunkEnd; } else { ReadOldPalleteAChunk(reader); } break; case AsepriteChunkType.Palette: ReadPaletteChunk(reader); newPaletteChunkFound = true; break; case AsepriteChunkType.UserData: ReadUserDataChunk(reader); break; case AsepriteChunkType.Slice: ReadSliceChunk(reader); break; case AsepriteChunkType.OldPaletteB: // Ignore case AsepriteChunkType.CelExtra: // Ignore case AsepriteChunkType.ColorProfile: // Ignore case AsepriteChunkType.Mask: // Ignore case AsepriteChunkType.Path: // Ignore // Since we are ignoreing these chunk types, we need to ensure that the // reader's basestream position is set to where the end of the ignored // chunk would be. reader.BaseStream.Position = chunkEnd; break; default: throw new Exception("Invalid chunk type detected"); } } }
/// <summary> /// Creates a new <see cref="AsepriteCelChunk"/> instance. /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> /// <param name="frame"> /// The <see cref="AsepriteFrame"/> this cel is contained within. /// </param> /// <param name="dataSize"> /// The total byte size of the data for this cel chunk. /// </param> internal AsepriteCelChunk(AsepriteReader reader, AsepriteFrame frame, int dataSize) { // We need to cache the position of the reader before reading any data so we can // calculate the amount of data to read later for the pixel info. long readerPos = reader.BaseStream.Position; LayerIndex = reader.ReadWORD(); X = reader.ReadSHORT(); Y = reader.ReadSHORT(); Opacity = reader.ReadByte(); CelType = (AsepriteCelType)reader.ReadWORD(); // Per ase file spec, ignore next 7 bytes, they are reserved for future use. reader.Ignore(7); if (CelType == AsepriteCelType.Raw || CelType == AsepriteCelType.Compressed) { Width = reader.ReadWORD(); Height = reader.ReadWORD(); // Calculate the remaning data to read in the cel chunk long bytesToRead = dataSize - (reader.BaseStream.Position - readerPos); // Read the remaning bytes into a buffer byte[] buffer = reader.ReadBytes((int)bytesToRead); if (CelType == AsepriteCelType.Raw) { // For raw cel, the buffer is the raw pixel data PixelData = new byte[buffer.Length]; Buffer.BlockCopy(buffer, 0, PixelData, 0, buffer.Length); } else { // For compressed, we need to deflate the buffer. First, we'll put it in a // memory stream to work with MemoryStream compressedStream = new MemoryStream(buffer); // The first 2 bytes of the compressed stream are the zlib header informaiton, // and we need to ignore them before we attempt to deflate _ = compressedStream.ReadByte(); _ = compressedStream.ReadByte(); // Now we can deflate the compressed stream using (MemoryStream decompressedStream = new MemoryStream()) { using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress)) { deflateStream.CopyTo(decompressedStream); PixelData = decompressedStream.ToArray(); } } } Pixels = new uint[Width * Height]; if (frame.File.Header.ColorDepth == AsepriteColorDepth.RGBA) { for (int i = 0, b = 0; i < Pixels.Length; i++, b += 4) { Pixels[i] = Utils.BytesToPacked(PixelData[b], PixelData[b + 1], PixelData[b + 2], PixelData[b + 3]); } } else if (frame.File.Header.ColorDepth == AsepriteColorDepth.Grayscale) { for (int i = 0, b = 0; i < Pixels.Length; i++, b += 2) { Pixels[i] = Utils.BytesToPacked(PixelData[b], PixelData[b], PixelData[b], PixelData[b + 1]); } } else if (frame.File.Header.ColorDepth == AsepriteColorDepth.Indexed) { for (int i = 0; i < Pixels.Length; i++) { int paletteIndex = PixelData[i]; if (paletteIndex == frame.File.Header.TransparentIndex) { Pixels[i] = Utils.BytesToPacked(0, 0, 0, 0); } else { AsepritePaletteColor paletteColor = frame.File.Palette.Colors[paletteIndex]; Microsoft.Xna.Framework.Color color = new Microsoft.Xna.Framework.Color(paletteColor.PackedValue); Pixels[i] = Utils.BytesToPacked(paletteColor.Red, paletteColor.Green, paletteColor.Blue, paletteColor.Alpha); } } } else { throw new Exception($"Unrecognized color depth mode. {frame.File.Header.ColorDepth}"); } } else if (CelType == AsepriteCelType.Linked) { ushort linkedFrame = reader.ReadWORD(); // Get a refrence to the cel this cel is linked to. LinkedCel = frame.File.Frames[linkedFrame].Cels .FirstOrDefault(c => c.LayerIndex == LayerIndex); } }