美文网首页
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