Beispiel #1
0
        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>
        ///     Reads a <see cref="AsepriteLayerChunk"/> 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 ReadLayerChunk(AsepriteReader reader)
        {
            AsepriteLayerChunk layer = new AsepriteLayerChunk(reader);

            File.Layers.Add(layer);
            reader.LastChunkRead = layer;
        }
Beispiel #3
0
        /// <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 Load()
        {
            if (!Directory.Exists(config.Directory))
            {
                throw new DirectoryNotFoundException(config.Directory);
            }

            foreach (var fileName in Directory.GetFiles(config.Directory))
            {
                var extension = Path.GetExtension(fileName);
                if (extension != ".ase" && extension != ".aseprite")
                {
                    continue;
                }

                var name = Path.GetFileNameWithoutExtension(fileName);
                aseprites.TryGetValue(name, out var sprite);
                if (sprite == null)
                {
                    sprite          = new Aseprite();
                    aseprites[name] = sprite;
                }

                AsepriteReader.ReadFromFile(fileName, sprite);
            }
        }
        /// <summary>
        ///     Proces method that is called by the content pipeline.
        /// </summary>
        /// <remarks>
        ///     If you are using this processor without the assistance of the Content Pipeline Tool,
        ///     then the other Process(AsepriteImporterResult) overload should be used instead.
        /// </remarks>
        /// <param name="input">
        ///     The <see cref="AsepriteImporterResult"/> instance created as part
        ///     of the import process.
        /// </param>
        /// <param name="context"></param>
        /// <returns>
        ///     A new <see cref="AsepriteDocumentProcessorResult"/> instance containing the result
        ///     of the processing.
        /// </returns>
        public override AsepriteDocumentProcessorResult Process(AsepriteImporterResult input, ContentProcessorContext context)
        {
            AsepriteDocumentProcessorOptions options = new AsepriteDocumentProcessorOptions
            {
                BorderPadding        = BorderPadding,
                IgnoreEmptyFrames    = IgnoreEmptyFrames,
                InnerPadding         = InnerPadding,
                MergeDuplicateFrames = MergeDuplicateFrames,
                OnlyVisibleLayers    = OnlyVisibleLayers,
                SheetType            = SheetType,
                Spacing = Spacing
            };

            //  Read the aseprite document from the stream.
            AsepriteDocument doc;

            using (MemoryStream stream = new MemoryStream(input.Data))
            {
                using (AsepriteReader reader = new AsepriteReader(stream))
                {
                    doc = new AsepriteDocument(reader);;
                }
            }

            //  Convert the document into an AnimationProcessorResult.
            AsepriteDocumentProcessorResult result = new AsepriteDocumentProcessorResult(doc, options);

            return(result);
        }
        /// <summary>
        ///     Reads a <see cref="AsepritePaletteChunk"/> 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 ReadPaletteChunk(AsepriteReader reader)
        {
            AsepritePaletteChunk palette = new AsepritePaletteChunk(reader, true);

            File.Palette         = palette;
            reader.LastChunkRead = palette;
        }
        private void ReadOldPalleteAChunk(AsepriteReader reader)
        {
            AsepritePaletteChunk palette = new AsepritePaletteChunk(reader, false);

            File.Palette         = palette;
            reader.LastChunkRead = palette;
        }
Beispiel #8
0
        /// <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="AsepriteSliceChunk"/> 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 ReadSliceChunk(AsepriteReader reader)
        {
            AsepriteSliceChunk slice = new AsepriteSliceChunk(reader);

            File.Slices.Add(slice);
            reader.LastChunkRead = slice;
        }
        /// <summary>
        ///     Reads a <see cref="AsepriteCelChunk"/> 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>
        /// <param name="dataSize">
        ///     The size of the cel chunks data, in bytes.
        /// </param>
        private void ReadCelChunk(AsepriteReader reader, int dataSize)
        {
            AsepriteCelChunk cel = new AsepriteCelChunk(reader, this, dataSize);

            Cels.Add(cel);
            reader.LastChunkRead = cel;
        }
Beispiel #11
0
        /// <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;
        }
Beispiel #12
0
 /// <summary>
 ///     Creates a new <see cref = "AsepritePaletteChunk" /> instance.
 /// </ summary >
 /// < param name="reader">
 ///     The<see cref="AsepriteReader"/> instance being used to read the
 ///     Aseprite file.
 /// </param>
 internal AsepritePaletteChunk(AsepriteReader reader, bool isNewPalette)
 {
     if (isNewPalette)
     {
         ReadAsNewPalette(reader);
     }
     else
     {
         ReadAsOldPalleteA(reader);
     }
 }
        /// <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="AsepriteDocument"/> instance.
        /// </summary>
        /// <param name="reader">
        ///     The <see cref="AsepriteReader"/> instance being used to
        ///     read the Aseprite document file.
        /// </param>
        internal AsepriteDocument(AsepriteReader reader)
        {
            Frames = new List <AsepriteFrame>();
            Slices = new List <AsepriteSliceChunk>();
            Tags   = new List <AsepriteTagChunk>();
            Layers = new List <AsepriteLayerChunk>();

            Header = new AsepriteHeader(reader);

            for (int i = 0; i < Header.FrameCount; i++)
            {
                Frames.Add(new AsepriteFrame(this, reader));
            }
        }
        /// <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);
            }
        }
Beispiel #16
0
        /// <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();
            }
        }
Beispiel #17
0
        /// <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();
        }
Beispiel #18
0
        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);
            }
        }
Beispiel #19
0
        /// <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>
        ///     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");
                }
            }
        }
Beispiel #21
0
        /// <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);
            }
        }
 /// <summary>
 ///     Reads the <see cref="IAsepriteUserData"/> from the underlying stream
 ///     of the provided <see cref="AsepriteReader"/> instance and applies
 ///     it to the most reacently read <see cref="AsepriteChunk"/>
 /// </summary>
 /// <param name="reader">
 ///     The <see cref="AsepriteReader"/> instance being used to read the
 ///     Aseprite file.
 /// </param>
 private void ReadUserDataChunk(AsepriteReader reader)
 {
     reader.LastChunkRead.ReadUserData(reader);
 }