/// <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="AsepriteSliceChunk"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal AsepriteSliceChunk(AsepriteReader reader) { TotalKeys = (int)reader.ReadDWORD(); Flags = (AsepriteSliceFlags)reader.ReadDWORD(); // Per ase file spec, ignore the next DWORD, it's "reserved" _ = reader.ReadDWORD(); Name = reader.ReadString(); Keys = new AsepriteSliceKey[TotalKeys]; for (int i = 0; i < TotalKeys; i++) { Keys[i] = new AsepriteSliceKey(reader, Flags); } }
private void ReadAsNewPalette(AsepriteReader reader) { // Read the size value int size = (int)reader.ReadDWORD(); // Read the first index value. int firstIndex = (int)reader.ReadDWORD(); // Read the last index value. int lastIndex = (int)reader.ReadDWORD(); // Per ase file spec, ignore next 8 bytes, they are reserved for future use reader.Ignore(8); Colors = new AsepritePaletteColor[size]; for (int i = 0; i < lastIndex - firstIndex + 1; i++) { Colors[i] = new AsepritePaletteColor(reader, true); } }
/// <summary> /// Creates a new <see cref="AsepriteSliceKey"/> instance. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> /// <param name="flags"> /// The <see cref="AsepriteSliceFlags"/> value of the slice this /// slice key belongs to. /// </param> internal AsepriteSliceKey(AsepriteReader reader, AsepriteSliceFlags flags) { Frame = (int)reader.ReadDWORD(); X = reader.ReadLONG(); Y = reader.ReadLONG(); Width = (int)reader.ReadDWORD(); Height = (int)reader.ReadDWORD(); if ((flags & AsepriteSliceFlags.HasNinePatch) != 0) { CenterX = reader.ReadLONG(); CenterY = reader.ReadLONG(); Width = (int)reader.ReadDWORD(); Height = (int)reader.ReadDWORD(); } if ((flags & AsepriteSliceFlags.HasPivot) != 0) { PivotX = reader.ReadLONG(); PivotY = reader.ReadLONG(); } }
/// <summary> /// Reads the userdata values for this chunk from the provided /// reader. /// </summary> /// <param name="reader"> /// The <see cref="AsepriteReader"/> instance being used to read the /// Aseprite file. /// </param> internal void ReadUserData(AsepriteReader reader) { AsepriteUserDataFlags flags = (AsepriteUserDataFlags)reader.ReadDWORD(); if ((flags & AsepriteUserDataFlags.HasText) != 0) { UserDataText = reader.ReadString(); } if ((flags & AsepriteUserDataFlags.HasColor) != 0) { UserDataColor = new byte[4]; UserDataColor[0] = reader.ReadByte(); UserDataColor[1] = reader.ReadByte(); UserDataColor[2] = reader.ReadByte(); UserDataColor[3] = reader.ReadByte(); } }
/// <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"); } } }