예제 #1
0
            internal static void ParseLayers(JsonObject json, LottieComposition composition)
            {
                var jsonLayers = json.GetNamedArray("layers", null);
                // This should never be null. Bodymovin always exports at least an empty array.
                // However, it seems as if the unmarshalling from the React Native library sometimes
                // causes this to be null. The proper fix should be done there but this will prevent a crash.
                // https://github.com/airbnb/lottie-android/issues/279
                if (jsonLayers == null)
                {
                    return;
                }
                var length = jsonLayers.Count;
                var imageCount = 0;
                for (var i = 0; i < length; i++)
                {
                    var layer = Layer.Factory.NewInstance(jsonLayers[i].GetObject(), composition);
                    if (layer.GetLayerType() == Layer.LayerType.Image)
                    {
                        imageCount++;
                    }
                    AddLayer(composition._layers, composition._layerMap, layer);
                }

                if (imageCount > 4)
                {
                    composition.AddWarning($"You have {imageCount} images. Lottie should primarily be used with shapes. If you are using Adobe Illustrator, convert the Illustrator layers to shape layers.");
                }
            }
예제 #2
0
        /// <summary>
        /// Returns either an <see cref="AnimatablePathValue"/> or an <see cref="AnimatableSplitDimensionPathValue"/>.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="composition"></param>
        /// <returns></returns>
        internal static IAnimatableValue <Vector2?, Vector2?> ParseSplitPath(JsonReader reader, LottieComposition composition)
        {
            AnimatablePathValue  pathAnimation = null;
            AnimatableFloatValue xAnimation    = null;
            AnimatableFloatValue yAnimation    = null;

            bool hasExpressions = false;

            reader.BeginObject();
            while (reader.Peek() != JsonToken.EndObject)
            {
                switch (reader.NextName())
                {
                case "k":
                    pathAnimation = Parse(reader, composition);
                    break;

                case "x":
                    if (reader.Peek() == JsonToken.String)
                    {
                        hasExpressions = true;
                        reader.SkipValue();
                    }
                    else
                    {
                        xAnimation = AnimatableValueParser.ParseFloat(reader, composition);
                    }
                    break;

                case "y":
                    if (reader.Peek() == JsonToken.String)
                    {
                        hasExpressions = true;
                        reader.SkipValue();
                    }
                    else
                    {
                        yAnimation = AnimatableValueParser.ParseFloat(reader, composition);
                    }
                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }
            reader.EndObject();

            if (hasExpressions)
            {
                composition.AddWarning("Lottie doesn't support expressions.");
            }

            if (pathAnimation != null)
            {
                return(pathAnimation);
            }
            return(new AnimatableSplitDimensionPathValue(xAnimation, yAnimation));
        }
예제 #3
0
            internal static AnimatableTextFrame NewInstance(JsonObject json, LottieComposition composition)
            {
                if (json != null && json.ContainsKey("x"))
                {
                    composition.AddWarning("Lottie doesn't support expressions.");
                }
                var result = AnimatableValueParser <DocumentData> .NewInstance(json, 1, composition, ValueFactory.Instance).ParseJson();

                return(new AnimatableTextFrame(result.Keyframes, result.InitialValue));
            }
예제 #4
0
        internal static Mask Parse(JsonReader reader, LottieComposition composition)
        {
            var maskMode = Mask.MaskMode.MaskModeAdd;
            AnimatableShapeValue   maskPath = null;
            AnimatableIntegerValue opacity  = null;

            reader.BeginObject();
            while (reader.HasNext())
            {
                var mode = reader.NextName();
                switch (mode)
                {
                case "mode":
                    switch (reader.NextString())
                    {
                    case "a":
                        maskMode = Mask.MaskMode.MaskModeAdd;
                        break;

                    case "s":
                        maskMode = Mask.MaskMode.MaskModeSubtract;
                        break;

                    case "i":
                        composition.AddWarning(
                            "Animation contains intersect masks. They are not supported but will be treated like add masks.");
                        maskMode = Mask.MaskMode.MaskModeIntersect;
                        break;

                    default:
                        Debug.WriteLine($"Unknown mask mode {mode}. Defaulting to Add.", LottieLog.Tag);
                        maskMode = Mask.MaskMode.MaskModeAdd;
                        break;
                    }

                    break;

                case "pt":
                    maskPath = AnimatableValueParser.ParseShapeData(reader, composition);
                    break;

                case "o":
                    opacity = AnimatableValueParser.ParseInteger(reader, composition);
                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }

            reader.EndObject();

            return(new Mask(maskMode, maskPath, opacity));
        }
예제 #5
0
            internal static AnimatableFloatValue NewInstance(JsonObject json, LottieComposition composition, bool isDp = true)
            {
                var scale = isDp ? composition.DpScale : 1f;

                if (json != null && json.ContainsKey("x"))
                {
                    composition.AddWarning("Lottie doesn't support expressions.");
                }
                var result = AnimatableValueParser <float?> .NewInstance(json, scale, composition, ValueFactory.Instance).ParseJson();

                return(new AnimatableFloatValue(result.Keyframes, result.InitialValue));
            }
예제 #6
0
        internal static List <Keyframe <T> > Parse <T>(JsonReader reader, LottieComposition composition,
                                                       IValueParser <T> valueParser)
        {
            var keyframes = new List <Keyframe <T> >();

            if (reader.Peek() == JsonToken.String)
            {
                composition.AddWarning("Lottie doesn't support expressions.");
                return(keyframes);
            }

            reader.BeginObject();
            while (reader.HasNext())
            {
                switch (reader.NextName())
                {
                case "k":
                    if (reader.Peek() == JsonToken.StartArray)
                    {
                        reader.BeginArray();

                        if (reader.Peek() == JsonToken.Integer || reader.Peek() == JsonToken.Float)
                        {
                            // For properties in which the static value is an array of numbers.
                            keyframes.Add(KeyframeParser.Parse(reader, composition, valueParser, false));
                        }
                        else
                        {
                            while (reader.HasNext())
                            {
                                keyframes.Add(KeyframeParser.Parse(reader, composition, valueParser, true));
                            }
                        }
                        reader.EndArray();
                    }
                    else
                    {
                        keyframes.Add(KeyframeParser.Parse(reader, composition, valueParser, false));
                    }

                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }

            reader.EndObject();

            SetEndFrames <Keyframe <T>, T>(keyframes);
            return(keyframes);
        }
예제 #7
0
        internal static IContentModel Parse(JsonReader reader, LottieComposition composition)
        {
            string type = null;

            reader.BeginObject();
            // Unfortunately, for an ellipse, d is before "ty" which means that it will get parsed
            // before we are in the ellipse parser.
            // "d" is 2 for normal and 3 for reversed.
            var d = 2;

            while (reader.HasNext())
            {
                switch (reader.NextName())
                {
                case "ty":
                    type = reader.NextString();
                    goto typeLoop;

                case "d":
                    d = reader.NextInt();
                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }

typeLoop:

            if (type == null)
            {
                return(null);
            }

            IContentModel model = null;

            switch (type)
            {
            case "gr":
                model = ShapeGroupParser.Parse(reader, composition);
                break;

            case "st":
                model = ShapeStrokeParser.Parse(reader, composition);
                break;

            case "gs":
                model = GradientStrokeParser.Parse(reader, composition);
                break;

            case "fl":
                model = ShapeFillParser.Parse(reader, composition);
                break;

            case "gf":
                model = GradientFillParser.Parse(reader, composition);
                break;

            case "tr":
                model = AnimatableTransformParser.Parse(reader, composition);
                break;

            case "sh":
                model = ShapePathParser.Parse(reader, composition);
                break;

            case "el":
                model = CircleShapeParser.Parse(reader, composition, d);
                break;

            case "rc":
                model = RectangleShapeParser.Parse(reader, composition);
                break;

            case "tm":
                model = ShapeTrimPathParser.Parse(reader, composition);
                break;

            case "sr":
                model = PolystarShapeParser.Parse(reader, composition);
                break;

            case "mm":
                model = MergePathsParser.Parse(reader);
                composition.AddWarning("Animation contains merge paths. Merge paths are only " +
                                       "supported on KitKat+ and must be manually enabled by calling " +
                                       "enableMergePathsForKitKatAndAbove().");
                break;

            case "rp":
                model = RepeaterParser.Parse(reader, composition);
                break;

            default:
                Debug.WriteLine("Unknown shape type " + type, LottieLog.Tag);
                break;
            }

            while (reader.HasNext())
            {
                reader.SkipValue();
            }
            reader.EndObject();

            return(model);
        }
예제 #8
0
        public static Layer Parse(JsonReader reader, LottieComposition composition)
        {
            string layerName = null;

            Layer.LayerType layerType   = Layer.LayerType.Unknown;
            string          refId       = null;
            long            layerId     = 0;
            int             solidWidth  = 0;
            int             solidHeight = 0;
            Color           solidColor;
            int             preCompWidth  = 0;
            int             preCompHeight = 0;
            long            parentId      = -1;
            float           timeStretch   = 1f;
            float           startFrame    = 0f;
            float           inFrame       = 0f;
            float           outFrame      = 0f;
            string          cl            = null;

            Layer.MatteType          matteType      = Layer.MatteType.None;
            AnimatableTransform      transform      = null;
            AnimatableTextFrame      text           = null;
            AnimatableTextProperties textProperties = null;
            AnimatableFloatValue     timeRemapping  = null;

            List <Mask>          masks  = new List <Mask>();
            List <IContentModel> shapes = new List <IContentModel>();

            reader.BeginObject();
            while (reader.HasNext())
            {
                switch (reader.NextName())
                {
                case "nm":
                    layerName = reader.NextString();
                    break;

                case "ind":
                    layerId = reader.NextInt();
                    break;

                case "refId":
                    refId = reader.NextString();
                    break;

                case "ty":
                    int layerTypeInt = reader.NextInt();
                    if (layerTypeInt < (int)Layer.LayerType.Unknown)
                    {
                        layerType = (Layer.LayerType)layerTypeInt;
                    }
                    else
                    {
                        layerType = Layer.LayerType.Unknown;
                    }
                    break;

                case "parent":
                    parentId = reader.NextInt();
                    break;

                case "sw":
                    solidWidth = (int)(reader.NextInt() * Utils.Utils.DpScale());
                    break;

                case "sh":
                    solidHeight = (int)(reader.NextInt() * Utils.Utils.DpScale());
                    break;

                case "sc":
                    solidColor = Utils.Utils.GetSolidColorBrush(reader.NextString());
                    break;

                case "ks":
                    transform = AnimatableTransformParser.Parse(reader, composition);
                    break;

                case "tt":
                    matteType = (Layer.MatteType)reader.NextInt();
                    break;

                case "masksProperties":
                    reader.BeginArray();
                    while (reader.HasNext())
                    {
                        masks.Add(MaskParser.Parse(reader, composition));
                    }
                    reader.EndArray();
                    break;

                case "shapes":
                    reader.BeginArray();
                    while (reader.HasNext())
                    {
                        var shape = ContentModelParser.Parse(reader, composition);
                        if (shape != null)
                        {
                            shapes.Add(shape);
                        }
                    }
                    reader.EndArray();
                    break;

                case "t":
                    reader.BeginObject();
                    while (reader.HasNext())
                    {
                        switch (reader.NextName())
                        {
                        case "d":
                            text = AnimatableValueParser.ParseDocumentData(reader, composition);
                            break;

                        case "a":
                            reader.BeginArray();
                            if (reader.HasNext())
                            {
                                textProperties = AnimatableTextPropertiesParser.Parse(reader, composition);
                            }
                            while (reader.HasNext())
                            {
                                reader.SkipValue();
                            }
                            reader.EndArray();
                            break;

                        default:
                            reader.SkipValue();
                            break;
                        }
                    }
                    reader.EndObject();
                    break;

                case "ef":
                    reader.BeginArray();
                    List <string> effectNames = new List <string>();
                    while (reader.HasNext())
                    {
                        reader.BeginObject();
                        while (reader.HasNext())
                        {
                            switch (reader.NextName())
                            {
                            case "nm":
                                effectNames.Add(reader.NextString());
                                break;

                            default:
                                reader.SkipValue();
                                break;
                            }
                        }
                        reader.EndObject();
                    }
                    reader.EndArray();
                    composition.AddWarning("Lottie doesn't support layer effects. If you are using them for " +
                                           " fills, strokes, trim paths etc. then try adding them directly as contents " +
                                           " in your shape. Found: " + effectNames);
                    break;

                case "sr":
                    timeStretch = reader.NextDouble();
                    break;

                case "st":
                    startFrame = reader.NextDouble();
                    break;

                case "w":
                    preCompWidth = (int)(reader.NextInt() * Utils.Utils.DpScale());
                    break;

                case "h":
                    preCompHeight = (int)(reader.NextInt() * Utils.Utils.DpScale());
                    break;

                case "ip":
                    inFrame = reader.NextDouble();
                    break;

                case "op":
                    outFrame = reader.NextDouble();
                    break;

                case "tm":
                    timeRemapping = AnimatableValueParser.ParseFloat(reader, composition, false);
                    break;

                case "cl":
                    cl = reader.NextString();
                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }
            reader.EndObject();

            // Bodymovin pre-scales the in frame and out frame by the time stretch. However, that will
            // cause the stretch to be double counted since the in out animation gets treated the same
            // as all other animations and will have stretch applied to it again.
            inFrame  /= timeStretch;
            outFrame /= timeStretch;

            List <Keyframe <float?> > inOutKeyframes = new List <Keyframe <float?> >();

            // Before the in frame
            if (inFrame > 0)
            {
                Keyframe <float?> preKeyframe = new Keyframe <float?>(composition, 0f, 0f, null, 0f, inFrame);
                inOutKeyframes.Add(preKeyframe);
            }

            // The + 1 is because the animation should be visible on the out frame itself.
            outFrame = (outFrame > 0 ? outFrame : composition.EndFrame) + 1;
            Keyframe <float?> visibleKeyframe = new Keyframe <float?>(composition, 1f, 1f, null, inFrame, outFrame);

            inOutKeyframes.Add(visibleKeyframe);

            Keyframe <float?> outKeyframe = new Keyframe <float?>(composition, 0f, 0f, null, outFrame, float.MaxValue);

            inOutKeyframes.Add(outKeyframe);

            if (layerName.EndsWith(".ai") || "ai".Equals(cl))
            {
                composition.AddWarning("Convert your Illustrator layers to shape layers.");
            }

            return(new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId, masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startFrame, preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType, timeRemapping));
        }
        public static LottieComposition Parse(JsonReader reader)
        {
            var   scale      = Utils.Utils.DpScale();
            float startFrame = 0f;
            float endFrame   = 0f;
            float frameRate  = 0f;
            Dictionary <long, Layer> layerMap = new Dictionary <long, Layer>();
            List <Layer>             layers   = new List <Layer>();
            int width  = 0;
            int height = 0;
            Dictionary <string, List <Layer> >    precomps   = new Dictionary <string, List <Layer> >();
            Dictionary <string, LottieImageAsset> images     = new Dictionary <string, LottieImageAsset>();
            Dictionary <string, Font>             fonts      = new Dictionary <string, Font>();
            Dictionary <int, FontCharacter>       characters = new Dictionary <int, FontCharacter>();
            var composition = new LottieComposition();

            reader.BeginObject();
            while (reader.HasNext())
            {
                switch (reader.NextName())
                {
                case "w":
                    width = reader.NextInt();
                    break;

                case "h":
                    height = reader.NextInt();
                    break;

                case "ip":
                    startFrame = reader.NextDouble();
                    break;

                case "op":
                    endFrame = reader.NextDouble() - 0.01f;
                    break;

                case "fr":
                    frameRate = reader.NextDouble();
                    break;

                case "v":
                    var version      = reader.NextString();
                    var versions     = Regex.Split(version, "\\.");
                    var majorVersion = int.Parse(versions[0]);
                    var minorVersion = int.Parse(versions[1]);
                    var patchVersion = int.Parse(versions[2]);
                    if (!Utils.Utils.IsAtLeastVersion(majorVersion, minorVersion, patchVersion, 4, 4, 0))
                    {
                        composition.AddWarning("Lottie only supports bodymovin >= 4.4.0");
                    }
                    break;

                case "layers":
                    ParseLayers(reader, composition, layers, layerMap);
                    break;

                case "assets":
                    ParseAssets(reader, composition, precomps, images);
                    break;

                case "fonts":
                    ParseFonts(reader, fonts);
                    break;

                case "chars":
                    ParseChars(reader, composition, characters);
                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }
            reader.EndObject();

            int  scaledWidth  = (int)(width * scale);
            int  scaledHeight = (int)(height * scale);
            Rect bounds       = new Rect(0, 0, scaledWidth, scaledHeight);

            composition.Init(bounds, startFrame, endFrame, frameRate, layers, layerMap, precomps, images, characters, fonts);

            return(composition);
        }
        public static AnimatableTransform Parse(JsonReader reader, LottieComposition composition)
        {
            AnimatablePathValue anchorPoint = null;
            IAnimatableValue <Vector2?, Vector2?> position = null;
            AnimatableScaleValue   scale        = null;
            AnimatableFloatValue   rotation     = null;
            AnimatableIntegerValue opacity      = null;
            AnimatableFloatValue   startOpacity = null;
            AnimatableFloatValue   endOpacity   = null;

            bool isObject = reader.Peek() == JsonToken.StartObject;

            if (isObject)
            {
                reader.BeginObject();
            }
            while (reader.HasNext())
            {
                switch (reader.NextName())
                {
                case "a":
                    reader.BeginObject();
                    while (reader.HasNext())
                    {
                        if (reader.NextName().Equals("k"))
                        {
                            anchorPoint = AnimatablePathValueParser.Parse(reader, composition);
                        }
                        else
                        {
                            reader.SkipValue();
                        }
                    }
                    reader.EndObject();
                    break;

                case "p":
                    position = AnimatablePathValueParser.ParseSplitPath(reader, composition);
                    break;

                case "s":
                    scale = AnimatableValueParser.ParseScale(reader, composition);
                    break;

                case "rz":
                    composition.AddWarning("Lottie doesn't support 3D layers.");
                    rotation = AnimatableValueParser.ParseFloat(reader, composition, false);
                    break;

                case "r":
                    rotation = AnimatableValueParser.ParseFloat(reader, composition, false);
                    break;

                case "o":
                    opacity = AnimatableValueParser.ParseInteger(reader, composition);
                    break;

                case "so":
                    startOpacity = AnimatableValueParser.ParseFloat(reader, composition, false);
                    break;

                case "eo":
                    endOpacity = AnimatableValueParser.ParseFloat(reader, composition, false);
                    break;

                default:
                    reader.SkipValue();
                    break;
                }
            }
            if (isObject)
            {
                reader.EndObject();
            }

            if (anchorPoint == null)
            {
                // Cameras don't have an anchor point property. Although we don't support them, at least
                // we won't crash.
                Debug.WriteLine("Layer has no transform property. You may be using an unsupported layer type such as a camera.", LottieLog.Tag);
                anchorPoint = new AnimatablePathValue();
            }

            if (scale == null)
            {
                // Somehow some community animations don't have scale in the transform.
                scale = new AnimatableScaleValue(new ScaleXy(1f, 1f));
            }

            if (opacity == null)
            {
                // Repeaters have start/end opacity instead of opacity
                opacity = new AnimatableIntegerValue();
            }

            return(new AnimatableTransform(
                       anchorPoint, position, scale, rotation, opacity, startOpacity, endOpacity));
        }
예제 #11
0
파일: Layer.cs 프로젝트: 07101994/LottieUWP
            internal static Layer NewInstance(JsonObject json, LottieComposition composition)
            {
                var layerName = json.GetNamedString("nm");
                var refId     = json.GetNamedString("refId", string.Empty);

                if (layerName.EndsWith(".ai") || json.GetNamedString("cl", "").Equals("ai"))
                {
                    composition.AddWarning("Convert your Illustrator layers to shape layers.");
                }

                var       layerId     = (long)json.GetNamedNumber("ind");
                var       solidWidth  = 0;
                var       solidHeight = 0;
                Color     solidColor;
                var       preCompWidth  = 0;
                var       preCompHeight = 0;
                LayerType layerType;
                var       layerTypeInt = (int)json.GetNamedNumber("ty", -1);

                if (layerTypeInt < (int)LayerType.Unknown)
                {
                    layerType = (LayerType)layerTypeInt;
                }
                else
                {
                    layerType = LayerType.Unknown;
                }

                if (layerType == LayerType.Text && !Utils.Utils.IsAtLeastVersion(composition, 4, 8, 0))
                {
                    layerType = LayerType.Unknown;
                    composition.AddWarning("Text is only supported on bodymovin >= 4.8.0");
                }

                var parentId = (long)json.GetNamedNumber("parent", -1);

                if (layerType == LayerType.Solid)
                {
                    solidWidth  = (int)(json.GetNamedNumber("sw") * composition.DpScale);
                    solidHeight = (int)(json.GetNamedNumber("sh") * composition.DpScale);
                    solidColor  = Utils.Utils.GetSolidColorBrush(json.GetNamedString("sc"));
                    Debug.WriteLine("\tSolid=" + string.Format("{0:X}", solidColor) + " " + solidWidth + "x" + solidHeight + " " + composition.Bounds, Tag);
                }

                var transform      = AnimatableTransform.Factory.NewInstance(json.GetNamedObject("ks"), composition);
                var matteType      = (MatteType)(int)json.GetNamedNumber("tt", 0);
                var masks          = new List <Mask>();
                var inOutKeyframes = new List <IKeyframe <float?> >();
                var jsonMasks      = json.GetNamedArray("masksProperties", null);

                if (jsonMasks != null)
                {
                    for (var i = 0; i < jsonMasks.Count; i++)
                    {
                        var mask = Mask.Factory.NewMask(jsonMasks[i].GetObject(), composition);
                        masks.Add(mask);
                    }
                }

                var shapes     = new List <IContentModel>();
                var shapesJson = json.GetNamedArray("shapes", null);

                if (shapesJson != null)
                {
                    for (var i = 0; i < shapesJson.Count; i++)
                    {
                        var shape = ShapeGroup.ShapeItemWithJson(shapesJson[i].GetObject(), composition);
                        if (shape != null)
                        {
                            shapes.Add(shape);
                        }
                    }
                }

                AnimatableTextFrame      text           = null;
                AnimatableTextProperties textProperties = null;
                var textJson = json.GetNamedObject("t", null);

                if (textJson != null)
                {
                    text = AnimatableTextFrame.Factory.NewInstance(textJson.GetNamedObject("d", null), composition);
                    var namedArray     = textJson.GetNamedArray("a", null);
                    var propertiesJson = namedArray?.Count > 0 ? namedArray.GetObjectAt(0) : null;
                    textProperties = AnimatableTextProperties.Factory.NewInstance(propertiesJson, composition);
                }

                if (json.ContainsKey("ef"))
                {
                    composition.AddWarning("Lottie doesn't support layer effects. If you are using them for " +
                                           " fills, strokes, trim paths etc. then try adding them directly as contents " +
                                           " in your shape.");
                }

                var timeStretch   = (float)json.GetNamedNumber("sr", 1.0);
                var startFrame    = (float)json.GetNamedNumber("st");
                var frames        = composition.DurationFrames;
                var startProgress = startFrame / frames;

                if (layerType == LayerType.PreComp)
                {
                    preCompWidth  = (int)(json.GetNamedNumber("w") * composition.DpScale);
                    preCompHeight = (int)(json.GetNamedNumber("h") * composition.DpScale);
                }

                // Bodymovin pre-scales the in frame and out frame by the time stretch. However, that will
                // cause the stretch to be double counted since the in out animation gets treated the same
                // as all other animations and will have stretch applied to it again.
                var inFrame  = (float)json.GetNamedNumber("ip") / timeStretch;
                var outFrame = (float)json.GetNamedNumber("op") / timeStretch;

                // Before the in frame
                if (inFrame > 0)
                {
                    var preKeyframe = new Keyframe <float?>(composition, 0f, 0f, null, 0f, inFrame);
                    inOutKeyframes.Add(preKeyframe);
                }

                // The + 1 is because the animation should be visible on the out frame itself.
                outFrame = outFrame > 0 ? outFrame : composition.EndFrame + 1;
                var visibleKeyframe = new Keyframe <float?>(composition, 1f, 1f, null, inFrame, outFrame);

                inOutKeyframes.Add(visibleKeyframe);

                var outKeyframe = new Keyframe <float?>(composition, 0f, 0f, null, outFrame, float.MaxValue);

                inOutKeyframes.Add(outKeyframe);

                AnimatableFloatValue timeRemapping = null;

                if (json.ContainsKey("tm"))
                {
                    timeRemapping = AnimatableFloatValue.Factory.NewInstance(json.GetNamedObject("tm", null), composition, false);
                }

                return(new Layer(shapes, composition, layerName, layerId, layerType, parentId, refId, masks, transform, solidWidth, solidHeight, solidColor, timeStretch, startProgress, preCompWidth, preCompHeight, text, textProperties, inOutKeyframes, matteType, timeRemapping));
            }