美文网首页
PAG文字动画流程

PAG文字动画流程

作者: hjm1fb | 来源:发表于2023-08-30 16:13 被阅读0次
    • FreeType绘制文字流程
    # 以下step,都能在libpag里找到对应的代码:
    FT_Library library;
    FT_Face face;
    FT_Error error;
    FT_Size ftSize = nullptr;
    
    /* step 1  
    Initialize FreeType library 
    */
    error = FT_Init_FreeType( &library ); // Initialize the library 
    
    /* step 2
    Load a font file using FT_New_Face or FT_Open_Face
    */
    error = FT_New_Face( library, "path/to/font.ttf", 0, &face ); 
    //libpag里是FT_Open_Face
    // auto err = FT_Open_Face(gFTLibrary->library(), &args, data.ttcIndex, &face->face);
    // 和 auto err = FT_New_Face(gFTLibrary->library(), data.path.c_str(), 0, &face->face); 
    // 是同样的功能,除了FT_Open_Face还支持自定义的字体文件数据流
    
    /* step 3
    Set the font size 
    */
    err = FT_New_Size(_face->face, &ftSize);
    err = FT_Activate_Size(ftSize);
    error = FT_Set_Pixel_Sizes(ft_face, 0, font_size);
    // libpag里是
    //err = FT_Set_Char_Size(_face->face, scaleX, scaleY, 72, 72);
    //一样的功能
    
    /* step 4 
    load glyph to face 
    */
    FT_UInt glyph_index = FT_Get_Char_Index(ft_face, character);
    FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_DEFAULT);
    //libpag里在FT_Load_Glyph之前,通过FT_Set_Transform设置了字形图像的二维仿射变换
    
    /* step 5 
    Render the character as a bitmap
    */
    error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL);
    //bitmap now is in face->glyph->bitmap
    // libpag里对应的是FT_Outline_Get_Bitmap(ftLibrary, &(outline->outline), &bitmap); 
    // 表示在bitmap上叠加字形的轮廓
    
    /* step 6
    release resources
    */
    FT_Done_Face(face);
    FT_Done_Size(ftSize);
    FT_Done_Library(_library);
    

    从FreeType得到的字形,包含以下用于布局排版的属性:


    freeType.png
    • 文字渲染核心类
      TextAnimatorRenderer
      计算当前帧应用文字动画后的结果,即更新字体的相关属性

    TextRenderer.cpp
    存储和更新字体属性,包括从TextAnimatorRenderer 获取当前帧的字体动画属性,然后用于在RenderTexts 等函数里用于计算文字排版等渲染文字所需要的TextContent/Graphic/Path等中间结果

    TextSelectorRenderer
    文字动画里的插值计算,即动画选择器。对字符逐个应用动画,计算对应的字符属性

    • 文字相关类的UML图
    1. TextAnimatorRenderer相关


      TextAnimationRenderer.jpg
    2. TextRun相关


      TextRun.png
    • 文字引擎的选择( iOS 和 web 平台,用 freetype 没法使用系统字体)
      平台 path渲染
      iOS & Mac CoreGraphics //也可以配置成使用freetype
      Android freetype
      linux freetype
      windows freetype
      web Canvas2D //也可以配置成使用freetype
      在Cmake文件里,即编译时,配置使用哪个文字引擎
      Mac端的对应代码是if ENV["PAG_USE_FREETYPE"] == 'ON' and ENV["PLATFORM"] == "mac",因此在终端里执行 PAG_USE_FREETYPE=ON pod install表示在mac上使用freetype文字引擎,而直接 pod install会使用Mac系统的CoreGraphics文字引擎

    • 文字动画流程(以FreeType为例)
      先预处理渲染数据然后执行渲染

    1. 预处理渲染数据
      PAGPlayer 在flushInternal方法里预处理渲染数据
      auto renderingStart = GetTimer();
      if (contentVersion != stage->getContentVersion()) {
        contentVersion = stage->getContentVersion();
        Recorder recorder = {};
        stage->draw(&recorder)
        lastGraphic = recorder.makeGraphic();
      }
      auto presentingStart = GetTimer();
      if (lastGraphic) {
        lastGraphic->prepare(renderCache);
      }
    

    stage->draw(&recorder) 是关键代码,会在内部继续调用
    PAGComposition::draw(recorder) -> PAGComposition::DrawChildLayer(recorder, childLayer) -> PAGTextLayer::getContent() -> TextContentCache::createContent(layerFrame) -> TextRender::RenderTexts(sourceText,pathOption,moreOption, animators,layerFrame)
    也就是在RenderTexts 方法里,最终执行文字渲染相关物料的生产和计算:
    1.1 创建字型

     if (hasTypeface) {
          glyphId = typeface->getGlyphID(name);
          if (glyphId != 0) {
            textFont.setTypeface(typeface);
          }
        }
        if (glyphId == 0) {
          // 为每个字符寻找匹配的字体,所以有可能一行文字里有不同的字体
          //比如本行包含Emoji字符,但本行的字体Helvetica的FT_HAS_COLOR为false,即不支持emoji,就会被分配支持Emoji的字体
          auto fallbackTypeface = FontManager::GetFallbackTypeface(name, &glyphId);
          textFont.setTypeface(fallbackTypeface);
        }
    

    1.2 根据文本属性和字形列表,计算文字的排版:
    auto textLayout = CreateTextLayout(textDocument.get(), glyphList);
    1.3 对字形列表应用文字动画
    TextAnimatorRenderer::ApplyToGlyphs(glyphList, animators, textDocument, layerFrame)
    1.31 文字动画里包含选择器信息,分为范围选择器RangeSelectorRenderer和摇摆选择器WigglySelectorRenderer,循环选择器列表selectorRenderers来叠加动画变换

    float TextSelectorRenderer::CalculateFactorFromSelectors(
        const std::vector<TextSelectorRenderer*>& selectorRenderers, size_t index, bool* pBiasFlag) {
      float totalFactor = 1.0f;
      bool isFirstSelector = true;
      for (auto selectorRenderer : selectorRenderers) {
        bool biasFlag = false;
        auto factor = selectorRenderer->calculateFactorByIndex(index, &biasFlag);
        if (pBiasFlag != nullptr) {
          *pBiasFlag |= biasFlag;
        }
        totalFactor = selectorRenderer->overlayFactor(totalFactor, factor, isFirstSelector);
        isFirstSelector = false;
      }
      return totalFactor;
    }
    

    1.4 将背景/黑白字符/彩色字符(emoji等),即background/normalText/colorText,依次转化成Graphic对象,最后三者一起存放到GraphicContent里并返回

    std::vector<std::shared_ptr<Graphic>> contents = {};
      if (textDocument->backgroundAlpha > 0) {
        auto background = RenderTextBackground(glyphLines, textDocument.get());
        if (background) {
          contents.push_back(background);
        }
      }
    
      std::vector<GlyphHandle> simpleGlyphs = {};
      std::vector<GlyphHandle> colorGlyphs = {};
      for (auto& line : glyphLines) {
        for (auto& glyph : line) {
          simpleGlyphs.push_back(glyph);
          auto typeface = glyph->getFont().getTypeface();
          if (typeface->hasColor()) {
            colorGlyphs.push_back(glyph);
          }
        }
      }
    
      auto normalText = Text::MakeFrom(simpleGlyphs, hasAnimators ? nullptr : &textBounds);
      if (normalText) {
        contents.push_back(normalText);
      }
      auto graphic = Graphic::MakeCompose(contents);
      auto colorText = Text::MakeFrom(colorGlyphs);
      return std::unique_ptr<TextContent>(new TextContent(std::move(graphic), std::move(colorText)));
    

    1.5 执行GraphicContent::draw(Recorder* recorder)来把Graphics信息存入Recorder里。
    1.6 执行 stage->draw(&recorder) 的下一行 lastGraphic = recorder.makeGraphic() 通过这个recorder再生成一个具体用于渲染的Graphic对象
    至此,完成文本渲染物料的准备工作

    1. 执行渲染
      PAGPlayer 在flushInternal函数里,代码!pagSurface->draw(renderCache, lastGraphic, signalSemaphore, _autoClear)以上一步的结果lastGraphic 作为输入,函数内部会在graphic树形结构里递归执行draw(canvas, cache)来渲染,文字对应的Graphic就是Text类:
      void Text::draw(Canvas* canvas, RenderCache*) const {
      drawTextRuns(static_cast<Canvas*>(canvas), 0);
      drawTextRuns(static_cast<Canvas*>(canvas), 1);
    }
    
    void Text::drawTextRuns(Canvas* canvas, int paintIndex) const {
      auto totalMatrix = canvas->getMatrix();
      for (auto& textRun : textRuns) {
        auto textPaint = textRun->paints[paintIndex];
        if (!textPaint) {
          continue;
        }
        canvas->setMatrix(totalMatrix);
        canvas->concat(textRun->matrix);
        auto glyphs = &textRun->glyphIDs[0];
        auto positions = &textRun->positions[0];
        canvas->drawGlyphs(glyphs, positions, textRun->glyphIDs.size(), textRun->textFont, *textPaint);
      }
      canvas->setMatrix(totalMatrix);
    }
    

    2.1 绘制字形
    上面代码块的17行canvas->drawGlyphs方法负责绘制每个字符

    void GLCanvas::drawGlyphs(const GlyphID glyphIDs[], const Point positions[], size_t glyphCount,
                              const Font& font, const Paint& paint) {
      auto textBlob = TextBlob::MakeFrom(glyphIDs, positions, glyphCount, font);
      if (textBlob == nullptr) {
        return;
      }
      if (font.getTypeface()->hasColor()) {
        drawColorGlyphs(glyphIDs, positions, glyphCount, font, paint);
        return;
      }
      Path path = {};
      auto stroke = paint.getStyle() == PaintStyle::Stroke ? paint.getStroke() : nullptr;
      if (textBlob->getPath(&path, stroke)) {
        auto shader = Shader::MakeColorShader(paint.getColor());
        fillPath(path, shader.get());
        return;
      }
      drawMaskGlyphs(textBlob.get(), paint);
    }
    

    2.11 如果字体自带颜色,比如emoji等(代码块里font.getTypeface()->hasColor()的判断)
    执行GLCanvas::drawColorGlyphs,通过getGlyphImage -> FTTypeface::getGlyphImage -> FTScalerContext::generateImage -> face->glyph->bitmap 获取glyph上的文字图像数据并存到PixelBuffer上,然后将PixelBuffer转化成Texture,配置一下本字符的绘制区域对应的matrix,再执行drawTexture,就完成了本字符的绘制。此时忽略字体的颜色属性,而是直接显示glyph的图像
    2.12 如果字体不包含颜色信息
    先通过freetype的接口 FT_Outline_Decompose( outline, func_interface, user ) 将字形转换成path, 即线段和贝塞尔曲线,然后执行GLCanvas::fillPath函数,内部会先通过path获取FT_Outline列表,然后循环执行FT_Outline_Get_Bitmap(ftLibrary, &(outline->outline), &bitmap)函数来将路径线段叠加绘制到最后的参数目标bitmap。这样就在目标bitmap绘制好了文本。把绘制好的文本转换成texture,再执行drawMask(quad, maskTexture.get(), shader),就完成了文本的着色。
    这里没有和2.11一样直接使用字形的Bitmap来叠加,而是将字形先转换成path,再把path绘制到Bitmap。这样做的好处是,可以对中间数据path应用PathEffect,来变换path,从而实现描边/虚线/圆角等效果
    2.13 描边效果

    void Text::draw(Canvas* canvas, RenderCache*) const {
    //第一行是以填充的模式绘制字形轮廓,即实心的线条;设置的代码为textRun->paints[0] = CreateFillPaint(firstGlyph).release();
    drawTextRuns(static_cast<Canvas*>(canvas), 0);
    //第二行是以描边的模式绘制字形,即空心的线条,对应的行是textRun->paints[1] = CreateStrokePaint(firstGlyph).release();
    drawTextRuns(static_cast<Canvas*>(canvas), 1);
    //两者叠加就是文字的描边效果了
     }
    

    描边效果是通过 pathEffect = PathEffect::MakeStroke(*stroke)来实现。
    此effect会将原始path通过SkPaint::getFillPath的方法转换成有stroke效果的path。
    文字颜色是通过GLConstColorProcessor类来处理;Emoji的纹理绘制是GLTextureFragmentProcessor类来处理。
    至此,基本梳理了LibPag里文字渲染和动画的流程

    PS:

    • LibPag通过contentVersion/FrameCache/TransformCache/ContentCache/excludeVaryingRanges等机制对绘制过程做了缓存, 极大的提高了绘制效率
    • 增加自定义文字动画的方式:
      动画的执行代码:
      for (auto animator : *animators) {
        TextAnimatorRenderer animatorRenderer(animator, textDocument, count, layerFrame);
        animatorRenderer.apply(glyphList);
      }
    

    所以自定义的动画属性,需要适配这两行代码。可以是扩展TextAnimator类,然后析构函数/excludeVaryingRanges/verify等方法都要对应更新

    相关文章

      网友评论

          本文标题:PAG文字动画流程

          本文链接:https://www.haomeiwen.com/subject/gjonmdtx.html