public static AnimationChain FromGif(string fileName, string contentManagerName)
        {
            if (FileManager.IsRelative(fileName))
            {
                fileName = FileManager.RelativeDirectory + fileName;
            }

            if (FlatRedBallServices.IsLoaded<AnimationChain>(fileName, contentManagerName))
            {
                return FlatRedBallServices.GetNonDisposable<AnimationChain>(fileName, contentManagerName).Clone();
            }

            ImageDataList imageDataList = GifLoader.GetImageDataList(fileName);

            int numberOfFrames = imageDataList.Count;

            AnimationChain animationChain = new AnimationChain(numberOfFrames);

            for (int i = 0; i < numberOfFrames; i++)
            {
                // We assume GIFs are for 2D games that don't need mipmaps.  Could change this later
                // if needed
                const bool generateMipmaps = false;

                Texture2D texture2D = imageDataList[i].ToTexture2D(generateMipmaps, FlatRedBallServices.GraphicsDevice);
                texture2D.Name = 
                    fileName + i.ToString();

                if (i >= imageDataList.FrameTimes.Count)
                {
                    const double defaultFrameTime = .1;

                    animationChain.Add(
                        new AnimationFrame(
                            texture2D, (float)defaultFrameTime));
                }
                else
                {

                    animationChain.Add(
                        new AnimationFrame(
                            texture2D, (float)imageDataList.FrameTimes[i]));
                }
                FlatRedBallServices.AddDisposable(texture2D.Name, texture2D, contentManagerName);
            }

            // Don't dispose the anything because it's part of the
            // content manager.

            animationChain.ParentGifFileName = fileName;
            animationChain.Name = FileManager.RemovePath(fileName);

            return animationChain;
        }
        public static LayeredTileMap FromTiledMapSave(string fileName, string contentManager)
        {
            TiledMapSave tms = TiledMapSave.FromFile(fileName);

            // Ultimately properties are tied to tiles by the tile name.
            // If a tile has no name but it has properties, those properties
            // will be lost in the conversion. Therefore, we have to add name properties.
            tms.NameUnnamedTilesetTiles();


            string directory = FlatRedBall.IO.FileManager.GetDirectory(fileName);

            var rtmi = ReducedTileMapInfo.FromTiledMapSave(
                tms, 1, 0, directory, FileReferenceType.Absolute);

            var toReturn = FromReducedTileMapInfo(rtmi, contentManager, fileName);


            foreach (var mapObjectgroup in tms.objectgroup)
            {
                var shapeCollection = tms.ToShapeCollection(mapObjectgroup.Name);
                if (shapeCollection != null && shapeCollection.IsEmpty == false)
                {
                    shapeCollection.Name = mapObjectgroup.Name;
                    toReturn.ShapeCollections.Add(shapeCollection);
                }
            }

            foreach (var layer in tms.MapLayers)
            {
                var matchingLayer = toReturn.MapLayers.FirstOrDefault(item => item.Name == layer.Name);


                if (matchingLayer != null)
                {
                    if (layer is MapLayer)
                    {
                        var mapLayer = layer as MapLayer;
                        foreach (var propertyValues in mapLayer.properties)
                        {
                            matchingLayer.Properties.Add(new NamedValue
                            {
                                Name = propertyValues.StrippedName,
                                Value = propertyValues.value,
                                Type = propertyValues.Type
                            });
                        }

                        matchingLayer.Visible = mapLayer.visible == 1;
                    }
                }
            }

            foreach (var tileset in tms.Tilesets)
            {
                foreach (var tile in tileset.TileDictionary.Values)
                {
                    if (tile.properties.Count != 0)
                    {
                        // this needs a name:
                        string name = tile.properties.FirstOrDefault(item => item.StrippedName.ToLowerInvariant() == "name")?.value;

                        if (!string.IsNullOrEmpty(name))
                        {
                            List<NamedValue> namedValues = new List<NamedValue>();
                            foreach (var prop in tile.properties)
                            {
                                namedValues.Add(new NamedValue()
                                { Name = prop.StrippedName, Value = prop.value, Type = prop.Type });
                            }

                            toReturn.Properties.Add(name, namedValues);

                        }
                    }
                }
            }

            var tmxDirectory = FileManager.GetDirectory(fileName);

            var animationDictionary = new Dictionary<string, AnimationChain>();

            // add animations
            foreach (var tileset in tms.Tilesets)
            {

                string tilesetImageFile = tmxDirectory + tileset.Images[0].Source;

                if (tileset.SourceDirectory != ".")
                {
                    tilesetImageFile = tmxDirectory + tileset.SourceDirectory + tileset.Images[0].Source;
                }

                var texture = FlatRedBallServices.Load<Microsoft.Xna.Framework.Graphics.Texture2D>(tilesetImageFile);

                foreach (var tile in tileset.Tiles.Where(item => item.Animation != null && item.Animation.Frames.Count != 0))
                {
                    var animation = tile.Animation;

                    var animationChain = new AnimationChain();
                    foreach (var frame in animation.Frames)
                    {
                        var animationFrame = new AnimationFrame();
                        animationFrame.FrameLength = frame.Duration / 1000.0f;
                        animationFrame.Texture = texture;

                        int tileIdRelative = frame.TileId;
                        int globalTileId = (int)(tileIdRelative + tileset.Firstgid);

                        int leftPixel;
                        int rightPixel;
                        int topPixel;
                        int bottomPixel;
                        TiledMapSave.GetPixelCoordinatesFromGid((uint)globalTileId, tileset, out leftPixel, out topPixel, out rightPixel, out bottomPixel);

                        animationFrame.LeftCoordinate = MapDrawableBatch.CoordinateAdjustment + leftPixel / (float)texture.Width;
                        animationFrame.RightCoordinate = -MapDrawableBatch.CoordinateAdjustment + rightPixel / (float)texture.Width;

                        animationFrame.TopCoordinate = MapDrawableBatch.CoordinateAdjustment + topPixel / (float)texture.Height;
                        animationFrame.BottomCoordinate = -MapDrawableBatch.CoordinateAdjustment + bottomPixel / (float)texture.Height;


                        animationChain.Add(animationFrame);
                    }

                    var property = tile.properties.FirstOrDefault(item => item.StrippedNameLower == "name");

                    if (property == null)
                    {
                        throw new InvalidOperationException(
                            $"The tile with ID {tile.id} has an animation, but it doesn't have a Name property, which is required for animation.");
                    }
                    else
                    {
                        animationDictionary.Add(property.value, animationChain);
                    }

                }

            }

            toReturn.Animation = new LayeredTileMapAnimation(animationDictionary);

            toReturn.MapProperties = tms.properties
                .Select(propertySave => new NamedValue
                    { Name = propertySave.name, Value = propertySave.value, Type = propertySave.Type })
                .ToList();


            return toReturn;
        }
        public Anim.AnimationChain ToAnimationChain(string contentManagerName, TimeMeasurementUnit timeMeasurementUnit, TextureCoordinateType coordinateType)
        {
            if (!string.IsNullOrEmpty(ParentFile))
            {
#if !WINDOWS_8 && !UWP && !DESKTOP_GL
                FlatRedBall.Graphics.Animation.AnimationChain animationChain =
                    FlatRedBall.Graphics.Animation.AnimationChain.FromGif(
                        ParentFile, contentManagerName);

                animationChain.Name = Name;

                animationChain.ParentGifFileName = ParentFile;

                if (animationChain.Count == this.Frames.Count)
                {
                    for (int i = 0; i < animationChain.Count; i++)
                    {
                        animationChain[i].FlipHorizontal = Frames[i].FlipHorizontal;
                        animationChain[i].FlipVertical   = Frames[i].FlipVertical;
                        animationChain[i].FrameLength    = Frames[i].FrameLength;
                        animationChain[i].RelativeX      = Frames[i].RelativeX;
                        animationChain[i].RelativeY      = Frames[i].RelativeY;

                        animationChain[i].TopCoordinate    = Frames[i].TopCoordinate;
                        animationChain[i].BottomCoordinate = Frames[i].BottomCoordinate;
                        animationChain[i].LeftCoordinate   = Frames[i].LeftCoordinate;
                        animationChain[i].RightCoordinate  = Frames[i].RightCoordinate;
                    }
                }

                return(animationChain);
#else
                throw new NotImplementedException();
#endif
            }
            else
            {
                Anim.AnimationChain animationChain =
                    new Anim.AnimationChain();

                animationChain.Name = Name;

                float divisor = 1;

                if (timeMeasurementUnit == TimeMeasurementUnit.Millisecond)
                {
                    divisor = 1000;
                }

                foreach (AnimationFrameSave save in Frames)
                {
                    // process the AnimationFrame and add it to the newly-created AnimationChain
                    AnimationFrame frame = null;

                    bool loadTexture = true;
                    frame = save.ToAnimationFrame(contentManagerName, loadTexture, coordinateType);

                    frame.FrameLength /= divisor;
                    animationChain.Add(frame);
                }

                return(animationChain);
            }
        }