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."); } }
/// <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)); }
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)); }
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)); }
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)); }
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); }
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); }
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)); }
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)); }