/// <summary>
        /// Converts an array of sprite filenames into a texture atlas object.
        /// </summary>
        public override TextureAtlasContent Process(string[] input, ContentProcessorContext context)
        {
            logger = context.Logger;
            var textureAtlas = new TextureAtlasContent
            {
                animationFPS = (int)animationFPS
            };
            var sourceSprites = new List <BitmapContent>();
            var imagePaths    = new List <string>();

            // first, we need to sort through and figure out which passed in paths are images and which are folders
            foreach (var inputPath in input)
            {
                // first, the easy one. if it isnt a directory its an image so just add it
                if (!Directory.Exists(inputPath))
                {
                    if (isValidImageFile(inputPath))
                    {
                        imagePaths.Add(inputPath);
                    }
                    continue;
                }

                // we have a directory. we need to recursively add all images in all subfolders
                processDirectory(inputPath, imagePaths, textureAtlas);
            }

            // Loop over each input sprite filename
            foreach (var inputFilename in imagePaths)
            {
                // Store the name of this sprite.
                var spriteName = getSpriteNameFromFilename(inputFilename, input);
                textureAtlas.spriteNames.Add(spriteName, sourceSprites.Count);
                context.Logger.LogMessage("Adding texture: {0}", spriteName);

                // Load the sprite texture into memory.
                var textureReference = new ExternalReference <TextureContent>(inputFilename);
                var texture          = context.BuildAndLoadAsset <TextureContent, TextureContent>(textureReference, "TextureProcessor");

                if (inputFilename.Contains(".9"))
                {
                    logger.LogMessage("\tprocessing nine patch texture");
                    textureAtlas.nineSliceSplits[spriteName] = processNinePatchTexture(texture);
                }

                sourceSprites.Add(texture.Faces[0][0]);
            }

            // Pack all the sprites into a single large texture.
            var packedSprites = TextureAtlasPacker.packSprites(sourceSprites, textureAtlas.spriteRectangles, compressTexture, context);

            textureAtlas.texture.Mipmaps.Add(packedSprites);

            if (compressTexture)
            {
                textureAtlas.texture.ConvertBitmapType(typeof(Dxt5BitmapContent));
            }

            return(textureAtlas);
        }
Beispiel #2
0
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            _identity = input.Identity;
            _logger   = context.Logger;

            // Gather all the nodes in tree traversal order.
            var nodes = input.AsEnumerable().SelectDeep(n => n.Children).ToList();

            var meshes            = nodes.FindAll(n => n is MeshContent).Cast <MeshContent>().ToList();
            var geometries        = meshes.SelectMany(m => m.Geometry).ToList();
            var distinctMaterials = geometries.Select(g => g.Material).Distinct().ToList();

            // Loop through all distinct materials, passing them through the conversion method
            // only once, and then processing all geometries using that material.
            foreach (var inputMaterial in distinctMaterials)
            {
                var geomsWithMaterial = geometries.Where(g => g.Material == inputMaterial).ToList();
                var material          = ConvertMaterial(inputMaterial, context);

                ProcessGeometryUsingMaterial(material, geomsWithMaterial, context);
            }

            var boneList = new List <ModelBoneContent>();
            var meshList = new List <ModelMeshContent>();
            var rootNode = ProcessNode(input, null, boneList, meshList, context);

            return(new ModelContent(rootNode, boneList, meshList));
        }
        public override FufParticleEmitterProcessorResult Process(FufParticleCreatorContent input,
                                                                  ContentProcessorContext context)
        {
            logger = context.Logger;
            var result = new FufParticleEmitterProcessorResult();

            // load texture
            if (input.emitterConfig.Texture == null)
            {
                throw new InvalidContentException("'texture' property of emitter configuration was not found.");
            }

            var fileDir  = Path.GetDirectoryName(input.path);
            var fullPath = Path.Combine(fileDir, input.emitterConfig.Texture);

            context.Logger.LogMessage("Looking for texture file at {0}", fullPath);
            result.texture =
                context.BuildAndLoadAsset <string, Texture2DContent>(new ExternalReference <string>(fullPath),
                                                                     "TextureProcessor");
            context.Logger.LogMessage("Texture file loaded.");

            result.emitterConfig = input.emitterConfig;

            return(result);
        }
Beispiel #4
0
 public override LibGdxAtlasProcessorResult Process(LibGdxAtlasFile input, ContentProcessorContext context)
 {
     Logger = context.Logger;
     return(new LibGdxAtlasProcessorResult {
         Data = input
     });
 }
Beispiel #5
0
        public override ParticleDesignerProcessorResult Process(ParticleDesignerContent input, ContentProcessorContext context)
        {
            logger = context.Logger;
            var result = new ParticleDesignerProcessorResult();

            // check for an embedded tiff texture
            if (input.emitterConfig.texture.data != null)
            {
                context.Logger.LogMessage("pex file has an embedded tiff. Extracting now.");
                using (var memoryStream = new MemoryStream(Convert.FromBase64String(input.emitterConfig.texture.data), writable: false))
                {
                    using (var stream = new GZipStream(memoryStream, CompressionMode.Decompress))
                    {
                        const int size   = 4096;
                        byte[]    buffer = new byte[size];
                        using (var memory = new MemoryStream())
                        {
                            int count = 0;
                            do
                            {
                                count = stream.Read(buffer, 0, size);
                                if (count > 0)
                                {
                                    memory.Write(buffer, 0, count);
                                }
                            } while(count > 0);

                            result.textureTiffData = memory.ToArray();
                        }
                    }
                }

                var tempFile = Path.Combine(Path.GetTempPath(), "tempParticleTexture.tif");
                File.WriteAllBytes(tempFile, result.textureTiffData);
                context.Logger.LogMessage("writing tiff to temp file: {0}", tempFile);

                context.Logger.LogMessage("running TextureImportor on tiff");
                var textureImporter = new TextureImporter();
                result.texture      = textureImporter.Import(tempFile, input.context) as Texture2DContent;
                result.texture.Name = input.emitterConfig.texture.name;

                context.Logger.LogMessage("deleting temp file");
                File.Delete(tempFile);

                // process
                context.Logger.LogMessage("processing TextureContent");
                var textureProcessor = new TextureProcessor {
                    GenerateMipmaps = false,
                    TextureFormat   = TextureProcessorOutputFormat.Color
                };
                result.texture = (Texture2DContent)textureProcessor.Process(result.texture, context);
                context.Logger.LogMessage("TextureContent processed");
            }

            result.particleEmitterConfig = input.emitterConfig;

            return(result);
        }
Beispiel #6
0
        public override Dictionary <string, object> Import(string filename, ContentImporterContext context)
        {
            logger = context.Logger;
            logger.LogMessage("Importing uiskin file: {0}", filename);

            using (var reader = new StreamReader(filename))
            {
                return(JsonConvert.DeserializeObject <Dictionary <string, object> >(reader.ReadToEnd()));
            }
        }
Beispiel #7
0
        public override TexturePackerFile Process(TexturePackerFile input, ContentProcessorContext context)
        {
            Logger = context.Logger;

            if (ParseAnimations)
            {
                ProcessAnimations(input);
            }

            return(input);
        }
Beispiel #8
0
        public override IDictionary <string, object> Import(string filename, ContentImporterContext context)
        {
            Logger = context.Logger;
            Logger.LogMessage("Importing uiskin file: {0}", filename);

            using (var reader = new StreamReader(filename))
            {
                var ret = JsonConvert.DeserializeObject <IDictionary <string, object> >(reader.ReadToEnd(), new JsonConverter[] { new JsonDictionaryConverter() });
                return(ret);
            }
        }
Beispiel #9
0
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            _identity = input.Identity;
            _logger   = context.Logger;

            // Gather all the nodes in tree traversal order.
            var nodes = input.AsEnumerable().SelectDeep(n => n.Children).ToList();

            var meshes            = nodes.FindAll(n => n is MeshContent).Cast <MeshContent>().ToList();
            var geometries        = meshes.SelectMany(m => m.Geometry).ToList();
            var distinctMaterials = geometries.Select(g => g.Material).Distinct().ToList();

            // Loop through all distinct materials, passing them through the conversion method
            // only once, and then processing all geometries using that material.
            foreach (var inputMaterial in distinctMaterials)
            {
                var geomsWithMaterial = geometries.Where(g => g.Material == inputMaterial).ToList();
                var material          = ConvertMaterial(inputMaterial, context);

                ProcessGeometryUsingMaterial(material, geomsWithMaterial, context);
            }

            // Hierarchy
            var bones      = nodes.OfType <BoneContent>().ToList();
            var modelBones = new List <ModelBoneContent>();

            for (var i = 0; i < bones.Count; i++)
            {
                var bone = bones[i];

                // Find the parent
                var parentIndex         = bones.IndexOf(bone.Parent as BoneContent);
                ModelBoneContent parent = null;
                if (parentIndex > -1)
                {
                    parent = modelBones[parentIndex];
                }

                modelBones.Add(new ModelBoneContent(bone.Name, i, bone.Transform, parent));
            }

            foreach (var bone in modelBones)
            {
                bone.Children = new ModelBoneContentCollection(modelBones.FindAll(b => b.Parent == bone));
            }

            return(new ModelContent(modelBones[0], modelBones, _meshes));
        }
Beispiel #10
0
 public override Overlap2DProcessorResult Process(SceneVO input, ContentProcessorContext context)
 {
     logger = context.Logger;
     try
     {
         var output = new Overlap2DProcessorResult {
             scene = input
         };
         return(output);
     }
     catch (Exception ex)
     {
         context.Logger.LogMessage("Error {0}", ex);
         throw;
     }
 }
Beispiel #11
0
 public override LibGdxAtlasProcessorResult Process(LibGdxAtlasFile input, ContentProcessorContext context)
 {
     logger = context.Logger;
     try
     {
         var output = new LibGdxAtlasProcessorResult {
             Data = input
         };
         return(output);
     }
     catch (Exception ex)
     {
         context.Logger.LogMessage("Error {0}", ex);
         throw;
     }
 }
        public override SceneVO Import(string filename, ContentImporterContext context)
        {
            var projectFolder = Directory.GetParent(Path.GetDirectoryName(filename)).FullName;
            var projectfile   = Path.Combine(projectFolder, "project.dt");

            // if the project file isnt a directly up check the same directory
            if (!File.Exists(projectfile))
            {
                projectfile = Path.Combine(Path.GetDirectoryName(filename), "project.dt");

                if (!File.Exists(projectfile))
                {
                    projectfile = null;
                    logger.LogMessage("could not find project.dt file. Using a pixelToWorld of 1. If this is not the correct value make sure your project.dt file is present!");
                }
            }

            // we need the pixelToWorld from the project file so we have to load it up
            ProjectInfoVO project;

            if (projectfile != null)
            {
                using (var reader = new StreamReader(projectfile))
                {
                    logger = context.Logger;
                    context.Logger.LogMessage("deserializing project file: {0}", projectfile);

                    project = JsonConvert.DeserializeObject <ProjectInfoVO>(reader.ReadToEnd());
                }
            }
            else
            {
                project = new ProjectInfoVO();
            }

            using (var reader = new StreamReader(filename))
            {
                logger = context.Logger;
                context.Logger.LogMessage("deserializing scene file: {0}", filename);

                var scene = JsonConvert.DeserializeObject <SceneVO>(reader.ReadToEnd());
                scene.pixelToWorld = project.pixelToWorld;

                return(scene);
            }
        }
Beispiel #13
0
        private List <int> GetDimensions(string line, ContentBuildLogger logger)
        {
            int colonPosition = line.IndexOf(':');
            int comaPosition  = line.IndexOf(',');

            List <int> res = new List <int>();
            string     x   = line.Substring(colonPosition + 1, comaPosition - colonPosition - 1).Trim();
            string     y   = line.Substring(comaPosition + 1, line.Length - comaPosition - 1).Trim();

            logger.LogMessage(x);
            logger.LogMessage(y);

            res.Add(int.Parse(x));
            res.Add(int.Parse(y));

            return(res);
        }
        public override TiledMapProcessorResult Process(TmxMap map, ContentProcessorContext context)
        {
            logger = context.Logger;
            foreach (var layer in map.layers.OfType <TmxTileLayer>())
            {
                var data = layer.data;

                if (data.encoding == "csv")
                {
                    data.tiles = data.value
                                 .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                                 .Select(uint.Parse)
                                 .Select(gid => new TmxDataTile((uint)gid))
                                 .ToList();
                }
                else if (data.encoding == "base64")
                {
                    var encodedData = data.value.Trim();
                    var decodedData = Convert.FromBase64String(encodedData);

                    using (var stream = OpenStream(decodedData, data.compression))
                        using (var reader = new BinaryReader(stream))
                        {
                            data.tiles = new List <TmxDataTile>();

                            for (var y = 0; y < layer.width; y++)
                            {
                                for (var x = 0; x < layer.height; x++)
                                {
                                    var gid = reader.ReadUInt32();
                                    data.tiles.Add(new TmxDataTile(gid));
                                }
                            }
                        }
                }
            }

            // deal with tilesets that have image collections
            foreach (var tileset in map.tilesets)
            {
                setTilesetTextureIfNecessary(tileset, context);
            }

            return(new TiledMapProcessorResult(map));
        }
Beispiel #15
0
        public override TextureContent Process(TextureContent input, ContentProcessorContext context)
        {
            Logger = context.Logger;
            Logger.LogMessage("sending texture to base TextureProcessor for initial processing");

            var textureContent = base.Process(input, context);
            var bmp            = (PixelBitmapContent <Color>)textureContent.Faces[0][0];
            var destData       = bmp.GetData();

            // process the data
            if (FlattenImage)
            {
                Logger.LogMessage("flattening image");
                destData = TextureUtils.CreateFlatHeightmap(destData, OpaqueColor, TransparentColor);
            }

            if (BlurType != BlurType.None)
            {
                Logger.LogMessage("blurring image width blurDeviation: {0}", BlurDeviation);
                if (BlurType == BlurType.Color)
                {
                    destData = TextureUtils.CreateBlurredTexture(destData, bmp.Width, bmp.Height,
                                                                 (double)BlurDeviation);
                }
                else
                {
                    destData = TextureUtils.CreateBlurredGrayscaleTexture(destData, bmp.Width, bmp.Height,
                                                                          (double)BlurDeviation);
                }
            }

            Logger.LogMessage("generating normal map with {0}", EdgeDetectionFilter);
            destData = TextureUtils.CreateNormalMap(destData, EdgeDetectionFilter, bmp.Width, bmp.Height,
                                                    NormalStrength, InvertX, InvertY);

            bmp.SetData(destData);

            return(textureContent);
        }
 public MonoGameImporterContext(string intermediateDir, string outputDir)
 {
     _intermediateDir = intermediateDir;
     _outputDir = outputDir;
     _logger = new MonoGameConsoleLogger();
 }
Beispiel #17
0
 public ProcessorContext(ILogger logger, string path)
 {
     this.ContentLogger = new MyLogger(logger);
     this.Dictionary    = new OpaqueDataDictionary();
     this.Path          = path;
 }
        void Diagnostic(ContentBuildLogger logger, string message)
        {
#if DIAGNOSTICS
            logger.LogMessage(message);
#endif
        }
        public override ParticleDesignerProcessorResult Process(ParticleDesignerContent input, ContentProcessorContext context)
        {
            logger = context.Logger;
            var result = new ParticleDesignerProcessorResult();

            // check for an embedded tiff texture
            if (input.emitterConfig.texture.data != null)
            {
                context.Logger.LogMessage("pex file has an embedded tiff. Extracting now.");
                using (var memoryStream = new MemoryStream(Convert.FromBase64String(input.emitterConfig.texture.data), writable: false))
                {
                    using (var stream = new GZipStream(memoryStream, CompressionMode.Decompress))
                    {
                        const int size   = 4096;
                        byte[]    buffer = new byte[size];
                        using (var memory = new MemoryStream())
                        {
                            int count = 0;
                            do
                            {
                                count = stream.Read(buffer, 0, size);
                                if (count > 0)
                                {
                                    memory.Write(buffer, 0, count);
                                }
                            } while(count > 0);

                            result.textureTiffData = memory.ToArray();
                        }
                    }
                }

                var tempFile = Path.Combine(Path.GetTempPath(), "tempParticleTexture.tif");
                File.WriteAllBytes(tempFile, result.textureTiffData);
                context.Logger.LogMessage("writing tiff to temp file: {0}", tempFile);

                context.Logger.LogMessage("running TextureImportor on tiff");
                var textureImporter = new TextureImporter();
                result.texture      = textureImporter.Import(tempFile, input.context) as Texture2DContent;
                result.texture.Name = input.emitterConfig.texture.name;

                context.Logger.LogMessage("deleting temp file");
                File.Delete(tempFile);

                // process
                context.Logger.LogMessage("processing TextureContent");
                var textureProcessor = new TextureProcessor
                {
                    GenerateMipmaps = false,
                    TextureFormat   = TextureProcessorOutputFormat.Color
                };
                result.texture = (Texture2DContent)textureProcessor.Process(result.texture, context);
                context.Logger.LogMessage("TextureContent processed");
            }
            else             // no tiff data, so let's try loading the texture with the texture name, from the same directory as the particle file
            {
                string fileDirectory = Path.GetDirectoryName(input.path);
                string fullPath      = Path.Combine(fileDirectory, input.emitterConfig.texture.name);
                context.Logger.LogMessage("Looking for texture file at {0}", fullPath);
                result.texture = context.BuildAndLoadAsset <string, Texture2DContent>(new ExternalReference <string>(fullPath), "TextureProcessor");
                context.Logger.LogMessage("Texture file loaded.");
            }

            result.particleEmitterConfig = input.emitterConfig;
            context.Logger.LogMessage("Emitter configuration loaded.");

            return(result);
        }
Beispiel #20
0
 public OgmoMapProcessorResult(Tilemap map, ContentBuildLogger logger)
 {
     Map    = map;
     Logger = logger;
 }
 /// <summary>
 /// Constructor.
 /// </summary>
 public CustomImporterContext(ContentBuildLogger logger)
 {
     this.logger = logger;
 }
 public override TexturePackerFile Process(TexturePackerFile input, ContentProcessorContext context)
 {
     logger = context.Logger;
     return(input);
 }
Beispiel #23
0
 public TiledMapProcessorResult(TmxMap map, ContentBuildLogger logger)
 {
     Map    = map;
     Logger = logger;
 }
Beispiel #24
0
 public TiledTileSetProcessorResult(TiledTileSetContent input, ContentBuildLogger logger)
 {
     this.TileSet = input;
     this.logger  = logger;
 }
 public XnaImporterContext(string intermediateDir, string outputDir)
 {
     _intermediateDir = intermediateDir;
     _outputDir = outputDir;
     _logger = new XnaConsoleLogger();
 }
 /// <summary>
 /// Constructor.
 /// </summary>
 public CustomProcessorContext(TargetPlatform targetPlatform, GraphicsProfile targetProfile, ContentBuildLogger logger)
 {
     this.targetPlatform = targetPlatform;
     this.targetProfile  = targetProfile;
     this.logger         = logger;
 }
        /// <summary>
        /// Converts an array of sprite filenames into a texture atlas object.
        /// </summary>
        public override TextureAtlasContent Process(string[] input, ContentProcessorContext context)
        {
            Logger = context.Logger;
            var textureAtlas = new TextureAtlasContent
            {
                AnimationFPS = (int)AnimationFPS,
            };
            var sourceSprites = new List <BitmapContent>();
            var imagePaths    = new List <string>();

            // first, we need to sort through and figure out which passed in paths are images and which are folders
            foreach (var inputPath in input)
            {
                // first, the easy one. if it isnt a directory its an image so just add it
                if (!Directory.Exists(inputPath))
                {
                    if (IsValidImageFile(inputPath))
                    {
                        imagePaths.Add(inputPath);
                    }
                    continue;
                }

                // we have a directory. we need to recursively add all images in all subfolders
                ProcessDirectory(inputPath, imagePaths, textureAtlas);
            }

            // Loop over each input sprite filename
            foreach (var inputFilename in imagePaths)
            {
                // Store the name of this sprite.
                var spriteName = GetSpriteNameFromFilename(inputFilename, input);
                textureAtlas.SpriteNames.Add(spriteName, sourceSprites.Count);
                context.Logger.LogMessage("Adding texture: {0}", spriteName);

                // Load the sprite texture into memory.
                var textureReference = new ExternalReference <TextureContent>(inputFilename);
                var texture          =
                    context.BuildAndLoadAsset <TextureContent, TextureContent>(textureReference, "TextureProcessor");

                if (inputFilename.Contains(".9"))
                {
                    Logger.LogMessage("\tprocessing nine patch texture");
                    textureAtlas.NineSliceSplits[spriteName] = ProcessNinePatchTexture(texture);
                }

                // Convert sprite's color key color to transparent
                if (ColorKeyEnabled)
                {
                    var originalType = texture.Faces[0][0].GetType();
                    try
                    {
                        texture.ConvertBitmapType(typeof(PixelBitmapContent <Vector4>));
                    }
                    catch (Exception ex)
                    {
                        context.Logger.LogImportantMessage("Could not convert input texture for processing. " +
                                                           ex.ToString());
                        throw ex;
                    }

                    var bmp = (PixelBitmapContent <Vector4>)texture.Faces[0][0];
                    bmp.ReplaceColor(ColorKeyColor.ToVector4(), Vector4.Zero);
                    texture.Faces[0][0] = bmp;
                    texture.ConvertBitmapType(originalType);
                }

                sourceSprites.Add(texture.Faces[0][0]);
            }

            // Pack all the sprites into a single large texture.
            var packedSprites = TextureAtlasPacker.PackSprites(sourceSprites, textureAtlas.SpriteRectangles,
                                                               CompressTexture, context);

            textureAtlas.Texture.Mipmaps.Add(packedSprites);

            if (CompressTexture)
            {
                textureAtlas.Texture.ConvertBitmapType(typeof(Dxt5BitmapContent));
            }

            return(textureAtlas);
        }
Beispiel #28
0
        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}");
                }
            }
        }