Audio and video development journey (63) - animation and rendering of Lottie source code analysis

Posted by Il-Belti on Mon, 17 Jan 2022 14:42:54 +0100

catalogue

  1. Animation and drawing process
  2. LayerView tree
  3. ShapeLayer analysis
  4. Advantages and disadvantages of Lottie and introduction of rLottie and PAG
  5. data
  6. harvest

In the last article, we learned to analyze The json parsing part of Lottie This is the animation and rendering part of our analysis.

Key points of analysis: how to organize the relationship between multi-layer layers and control the drawing and animation of different layers.

1, Animation and drawing process

We analyze through the entry API functions (LottieDrawable#setComposition, LottieDrawable#playAnimation).

1.1 LottieDrawable#setComposition process

public boolean setComposition(LottieComposition composition) {

    //......
    clearComposition();
    this.composition = composition;
    //The function of constructing a layer composition layer is a bit like the ViewGroup in the Android View tree, which can contain other views and viewgroups
    //To complete the initialization of CompositionLayer and ContentGroup, there are two main functions: TransformKeyframeAnimation
    buildCompositionLayer();  
  
    //Trigger notifyUpdate, and then trigger the recalculation of the progress of layers and the callback of draw (of course, the progress is 0 at this time, and the drawlayer of composition will not be triggered after various judgments)
    animator.setComposition(composition);

    //Sets the progress of the current animation
    setProgress(animator.getAnimatedFraction());

   ......

   }

You can see that setComposition mainly calls buildCompositionLayer and animator setComposition to initialize CompositionLayer and other layers (corresponding layers field in json), ContentGroup, TransformKeyframeAnimation, etc. The most used layers in Lottie animation are CompositionLayer, ShapeLayer, and ImageLayer.

Think: so what are content group, TransformKeyframeAnimation, and their relationship with layer? (we will try to analyze and answer later)

1.2 LottieDrawable#playAnimation process

   1. LottieDrawable.playAnimation
   2. LottieValueAnimator.playAnimation
   3. LottieValueAnimator.setFrame
   4. BaseLottieAnimator.notifyUpdate
   5.The callback is then triggered(LottieDrawable.progressUpdateListener)AnimatorUpdateListener.onAnimationUpdate
   6. CompositionLayer.setProgress -->Calculate current progress,Then set the progress of each layer in reverse order BaseLayer.setProgress
       6.1(transform.setProgress(progress))TransformKeyframeAnimation.setProgress Set the progress of the matrix transformation (scaling, transparency, displacement, etc.)-->Need to focus on analysis
       6.2  animations.get(i).setProgress(progress); Traverse settings for each animation Progress of
   7. BaseKeyframeAnimation.notifyListeners Callback to listener
   8. BaseLayer.onValueChanged (invalidateSelf())Trigger page redrawing,-->Namely LottieDrawable.draw(android.graphics.Canvas, android.graphics.Matrix)
   9. compositionLayer.draw(canvas, matrix, alpha)  Namely BaseLayer.draw -->This is also a key method
   10. drawLayer(canvas, matrix, alpha); Namely BaseLayer.drawLayer This method is an abstract method layer Concrete implementation
         10.1 We take ImageLayer As an example (key analysis) ImageLayer.drawLayer First pass BaseKeyframeAnimation.getValue() This uses the previous animation changes progress The current value is obtained according to the differentiator Bitmap
         10.2 Then use canvas To draw and complete the transformation of the picture

LottieValueAnimator is a subclass of ValueAnimator and implements choreographer Framecallback interface. Through the progress transformation callback of attribute animation and the doframe callback of VSYNC signal, Layer is notified to calculate the progress and value, and LottieDrawble is notified to redraw, so as to realize the animation and drawing of layers in json, that is, various layers.

The specific drawing is still realized by Canvas, which can be realized through the drawLayer of ImageLayer

public void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    Bitmap bitmap = getBitmap();
    if (bitmap == null || bitmap.isRecycled()) {
      return;
    }
    float density = Utils.dpScale();

    paint.setAlpha(parentAlpha);
    if (colorFilterAnimation != null) {
      paint.setColorFilter(colorFilterAnimation.getValue());
    }
    //Save the current state of the canvas
    canvas.save();
    //The transformation of the matrix is applied to all objects on the canvas
    canvas.concat(parentMatrix);
    //src is used to set the area to draw the bitmap, that is, whether to crop or not
    src.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
    //dst is used to set the display area on the canvas. Here you can see that the width and height of the display will be scaled according to the pixel density
    dst.set(0, 0, (int) (bitmap.getWidth() * density), (int) (bitmap.getHeight() * density));
    //The first Rect(src) represents the bitmap area to be drawn. You can cut the picture. If it is empty, the whole picture will be displayed. The second Rect(dst) is the area where the picture is displayed on the Canvas, that is, where to draw the bitmap on the screen
   // By dynamically changing the dst, you can achieve the effects of moving and scaling, and scaling according to the pixel density of the screen. By changing the src to deal with the needs of the drawn pictures, you can also achieve many interesting effects, such as displaying a part, or gradually expanding, etc
    canvas.drawBitmap(bitmap, src, dst, paint);
    //Restore the previously saved canvas state, corresponding to sava one-to-one
    canvas.restore();
  }

ShapeLayer and CompositionLayer are somewhat complex. We will analyze them separately below.

Thinking: if there are multiple layers, how to ensure the correlation between multiple layers (like ViewTree, how to manage the relationship between them and the drawing order).

2, LayerView tree

There are various layers in Lottie:

So what is the relationship between them? How to carry out management and hierarchical control?

Construction of CompositionLayer

  public CompositionLayer(LottieDrawable lottieDrawable, Layer layerModel, List<Layer> layerModels,
      LottieComposition composition) {

   //It is mainly the initialization of TransformKeyframeAnimation
    super(lottieDrawable, layerModel);
LongSparseArray<BaseLayer> layerMap =
        new LongSparseArray<>(composition.getLayers().size());

    BaseLayer mattedLayer = null;
    //Each Layer is produced in reverse order according to the size of layers
    for (int i = layerModels.size() - 1; i >= 0; i--) {
      Layer lm = layerModels.get(i);
      //This is an engineering method, which constructs the corresponding Layer according to the layerType
      BaseLayer layer = BaseLayer.forModel(this, lm,   lottieDrawable, composition);
      if (layer == null) {
        continue;
      }
      layerMap.put(layer.getLayerModel().getId(), layer);
      ......
     }

    
    for (int i = 0; i < layerMap.size(); i++) {
      long key = layerMap.keyAt(i);
      BaseLayer layerView = layerMap.get(key);
      if (layerView == null) {
        continue;
      }
     // Determine the parent-child relationship between layer s
      BaseLayer parentLayer =   layerMap.get(layerView.getLayerModel().getParentId());
      if (parentLayer != null) {
        layerView.setParentLayer(parentLayer);
      }
    }

}

Factory method: BaseLayer#forModel

static BaseLayer forModel(
      CompositionLayer compositionLayer, Layer layerModel, LottieDrawable drawable, LottieComposition composition) {
    //Corresponding to object - > layers - > ty in json
    switch (layerModel.getLayerType()) {
        //Contour / shape layer is basically the most used type in lottie animation
      case SHAPE:
        return new ShapeLayer(drawable, layerModel, compositionLayer);
        //Composite layer, equivalent to the role of ViewGroup of ViewTree
      case PRE_COMP:
        return new CompositionLayer(drawable, layerModel,
            composition.getPrecomps(layerModel.getRefId()), composition);
        //Fill Layer 
      case SOLID:
        return new SolidLayer(drawable, layerModel);
        //Picture layers are also commonly used, especially when making some template effects
      case IMAGE:
        return new ImageLayer(drawable, layerModel);
        //An empty layer can be used as a parent of other layers
      case NULL:
        return new NullLayer(drawable, layerModel);
        //Text layer
      case TEXT:
        return new TextLayer(drawable, layerModel);
      case UNKNOWN:
      default:
        // Do nothing
        Logger.warning("Unknown layer type " + layerModel.getLayerType());
        return null;
    }
  }

We see layerview setParentLayer(ParentLayer); So what's the use of this ParentLayer? It is mainly used when determining the boundary and drawing of each layer

 // BaseLayer#buildParentLayerListIfNeeded
 //This method will call draw when determining the current layer boundary getboundaries and drawing the layer
  private void buildParentLayerListIfNeeded() {
    if (parentLayers != null) {
      return;
    }
    //If the layer has a parent layer, it will be updated
    if (parentLayer == null) {
      parentLayers = Collections.emptyList();
      return;
    }

    //The LayerViewTree of the layer
    parentLayers = new ArrayList<>();
    BaseLayer layer = parentLayer;
    //Recursively find the parent layer, grandfather layer, great grandfather layer, and so on
    while (layer != null) {
      parentLayers.add(layer);
      layer = layer.parentLayer;
    }
  }

BaseLayer#getBounds

 public void getBounds(
      RectF outBounds, Matrix parentMatrix, boolean applyParents) {
    rect.set(0, 0, 0, 0);
    //Determine the LayerViewTree: parentLayers of the layer
    buildParentLayerListIfNeeded();
    //The matrix transformation of the sub layer is based on the matrix transformation of the parent layer
    boundsMatrix.set(parentMatrix);

    if (applyParents) {
      //Recursively call the parent layer amount matrix transformation to multiply the matrix
      if (parentLayers != null) {
        for (int i = parentLayers.size() - 1; i >= 0; i--) {
          boundsMatrix.preConcat(parentLayers.get(i).transform.getMatrix());
        }
      } else if (parentLayer != null) {
        boundsMatrix.preConcat(parentLayer.transform.getMatrix());
      }
    }

    //Finally, it is multiplied by the matrix transformation of the current layer to determine the final boundary matrix
    boundsMatrix.preConcat(transform.getMatrix());
  }

BaseLayer#draw The matrix processing method is the same as baselayer #getboundaries.

Establish the LayerViewTree of the layer through parentid, and then determine its own bound and draw according to the LayerView during measurement and drawing.

3, ShapeLayer analysis

The reason why ShapeLayer is brought out alone is that it is very important in lottie animation ShapeLayer is a subclass of layers drawn by vector graphics rather than bitmap. Specify properties such as color and lineweight, and use Path to define the drawing

public class ShapeLayer extends BaseLayer {
  ......
  
 //What is this contentgroup? You can see that both drawLayer and getBound of ShapeLayer are represented by contentgroup.
  private final ContentGroup contentGroup;
  

  ShapeLayer(LottieDrawable lottieDrawable, Layer layerModel, CompositionLayer compositionLayer) {
    ......
    //ContentGroup construct
    contentGroup = new ContentGroup(lottieDrawable, this, shapeGroup);
    contentGroup.setContents(Collections.<Content>emptyList(), Collections.<Content>emptyList());
  }

  @Override void drawLayer(@NonNull Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    //The draw of contentGroup was called
    contentGroup.draw(canvas, parentMatrix, parentAlpha);
  }

  @Override public void getBounds(RectF outBounds, Matrix parentMatrix, boolean applyParents) {
    ......
    contentGroup.getBounds(outBounds, boundsMatrix, applyParents);
  }
  ......
}

What is ContentGroup? You can see that both drawLayer and getBound of ShapeLayer are represented by contentGroup. Let's take a look at the implementation of draw of ContentGroup

public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha){

    //Traverse and call content. If it is DrawingContent, draw. What is DrawingContent over there
    for (int i = contents.size() - 1; i >= 0; i--) {
      Object content = contents.get(i);
      if (content instanceof DrawingContent) {
        ((DrawingContent) content).draw(canvas, matrix, childAlpha);
      }
    }

}

Traverse and call the content. If it is DrawingContent, draw. Which content is DrawingContent?

Let's take FillContent as an example to see the implementation of its draw

public void draw(Canvas canvas, Matrix parentMatrix, int parentAlpha) {
    ......
    //Get color, transparency, etc. set the color of the brush paint
    int color = ((ColorKeyframeAnimation) this.colorAnimation).getIntValue();
    int alpha = (int) ((parentAlpha / 255f * opacityAnimation.getValue() / 100f) * 255);
    paint.setColor((clamp(alpha, 0, 255) << 24) | (color & 0xFFFFFF));

    //Set colorFilter
    if (colorFilterAnimation != null) {
      paint.setColorFilter(colorFilterAnimation.getValue());
    }

    ......
    //Set path path
    path.reset();
    for (int i = 0; i < paths.size(); i++) {
      path.addPath(paths.get(i).getPath(), parentMatrix);
    }

    //Using cavas drawpath
    canvas.drawPath(path, paint);

  }

You can draw ShapeContent's DrawingContent through Canvas.

That's all for the animation and rendering analysis part of Lottie. BaseKeyframeAnimation mainly implements the interpolation calculation of animation in Layer and DrawingContent. There is no detailed analysis. You need to see it again.

Thinking: can you render through OpenGL ES?

5, Advantages and disadvantages of Lottie and simple comparison with PAG

Advantages and disadvantages of Lottie

advantage:
Support cross platform (although each end implements one set)
Good performance
 It can be distributed through configuration“ json And material.

Insufficient points:
Lottie Interaction and editing are not supported
Lottie Compressed bitmaps are not supported if used png Wait for bitmap, you need to tiny And other compression platforms to compress pictures and reduce package volume.
Lottie existence mask,matters When, you need to first saveLayer,Re call drawLayer return.
saveLayer It is a time-consuming operation. You need to allocate and draw one first offscreen This increases the rendering time

A brief introduction to the advantages and disadvantages of PAG

PAG It is an animation component just opened by Tencent yesterday, except lottie In addition to the advantages of,
 Support more AE Special effects,
 Support text and sequence frames,
 Supports template editing,
 Use secondary value files instead of json,File size and parsing performance will be better
 Render level: Lottie The implementation of rendering layer depends on the platform side interface, which may vary from platform to platform. PAG Render layer usage C++Implementation, all platforms share the same set of implementation, and the platform side only encapsulates interface calls, provides a rendering environment, and the rendering effect is consistent.


PAG Rendering based on google GPl  skia 2d To achieve. Increased package size. four.0 The version of will be improved and removed skia 2d. Realize simple rendering encapsulation by yourself (it is estimated that opengl perhaps metal ,vulkan). 

Brief introduction to rlottie

[Samsung-rlottie](https://github.com/Samsung/rlottie)

rLottie And lottie Workflow consistency, in SDK The implementation is different, rLottie No platform specific implementation is used, which is unified C++realization,Material support lottie of json File, vector rendering performance is good, but it lacks encapsulation of various platforms and supports AE Features are not complete, and text, sequence frames, etc. are not supported

This has not analyzed its source code implementation. Take time to analyze and study.

6, Information

  1. Lottie implementation ideas and source code analysis
  2. Analysis of Lottie animation principle
  3. Uncover the advantages, disadvantages and principles of Lottie animation
  4. Lottie Android framework usage and source code analysis
  5. Analysis of Android source code of Lottie animation library
  6. Tencent open source PAG
  7. Samsung-rlottie
  8. Compare PAG and lottie from the decoding and rendering level

7, Harvest

Through the learning analysis of this article

  1. Combed the lottie animation and rendering process
  2. The concept and understanding of LayerView tree, and find out how lottie manages the relationship between different layers
  3. The analysis focuses on CompositionLayer, BaseLayer, ImageLayer and ShapeLayer, where ShapeLayer contains content group
  4. lottie, PAG and rlottie were simply compared

Thank you for reading Welcome to the official account of the "audio and video development tour" and learn and grow together. Welcome to communicate