腾讯PAG动画使用

2,467 阅读10分钟

一、PAG格式简介

PAG是来自腾讯的一套完整的动画工作流解决方案,助力于将 AE 动画方便快捷的应用于各平台终端。PAG 的流程图下图所示,设计师在 AE 上设计出动画后,可以通过导出插件导出 pag 文件,同时 PAG 提供了桌面端预览工具,支持实时预览效果,在确认效果后,通过运行配置上线,各平台终端可以通过 PAG SDK 加载渲染 PAG 动画。

image.png 相比其他的动画方案,PAG动画有如下特点和优势:

  • 开源项目:无需担心团队维护问题。

  • 文件更小:PAG采用针对 AE 时间轴属性设计的二进制文件编码器,能够使用动态比特位紧凑存储,冗余信息极少,文件体积最小,解码速度最快,且支持单文件集成图片和音频等外部资源。

  • 全 AE 特性支持:在纯矢量的导出模式下,无论是哪种实现方案,在众多的 AE 特性面前,都只支持将有限的 AE特性导出渲染,PAG 方案提供了 BMP 预合成的解决方案,支持将特定图层截图导出成透明视频,实现了对于所有 AE 特性导出的支持。

  • 运行时编辑:PAG 不仅仅支持文本图层的文本编辑、图片图层的占位图替换,还支持图层级别的增加、删除及更改渲染位置,实现原子素材的自由组合,典型的应用场景就是视频模版和游戏战报,一个模版中由多个 pag 有机组合在一起。

  • 渲染架构:相对于 Lottie 、SVGA 依赖于平台端相关的渲染接口,PAG 使用了跨平台一致的 C++ 架构,平台层面仅仅提供渲染环境,渲染的主体位于 C++ 层,可以实现跨平台的渲染一致性。

  • 支持的平台更多:相比 Lottie 支持 Android、iOS、Web 和 macOS, SVGA 支持 Android、iOS、Web,PAG 实现了 Android、iOS、Web、macOS、Windows、Linux 和 微信小程序,支持的平台更多。

下面是常见文件动画特性的对比:

image.png

二、Android中使用PAG动画

在Android中接入pag动画需要满足以下条件:

  • 支持 android 4.4 及以上系统
  • 推荐使用 gradle 3.0 及以上版本编译 pag支持多种方式接入:

aar方式

  1. 将 libpag 的 aar 文件放置在 android 工程项目的 libs 目录下。
  2. 添加添加 aar 库依赖,在 app 的 gradle 文件 app/build.gradle,添加 libpag 的库依赖。
 android {
        repositories {
        flatDir {
            dirs 'libs'
        }
    }
    
    dependencies {
        //libpag 的核心库
        //将 libpag_enterprise_4.2.41_android_armeabi_armv7a_arm64v8a.aar 换成你下载的 aar 文件名
        implementation(name: 'libpag_enterprise_4.2.41_android_armeabi_armv7a_arm64v8a.aar', ext: 'aar')
        implementation("androidx.exifinterface:exifinterface:1.3.3")
    }
  1. 配置完以后,sync 一下,再编译。为了能够正常的编译,还需要在混淆列表中添加libpag 的 keep 规则:
    -keep class org.libpag.** {*;}
    -keep class androidx.exifinterface.** {*;}

maven方式

maven方式接入提供了多个版本,说明如下:

  • 企业基础版本:com.tencent.tav:libpag-enterprise:4.2.41,不包含 Movie 模块,不支持多字节 emoji,包含素材加密和 3D 图层能力。
  • 企业 movie 版本:com.tencent.tav:libpag-enterprise:4.2.41-movie,包含音频播放、素材加密、占位图一键替换视频、导出视频文件和 3D 图层以及多字节 emoji 的能力。
  • 企业 noffavc 版本:com.tencent.tav:libpag-enterprise:4.2.41-noffavc,不包含 Movie 模块和多字节 emoji 能力、内部不包含软件解码器,支持解码器外部注入。
  • 社区基础版本 com.tencent.tav:libpag:4.2.41 不支持多字节 emoji,包含 PAG 的基础能力。
  • 社区 harfbuzz 版本 com.tencent.tav:libpag:4.2.41-harfbuzz 支持多字节 emoji 的能力。
  • 社区 noffavc 版本 com.tencent.tav:libpag:4.2.41-noffavc 不支持多字节 emoji,内部不包含软件解码器,支持解码器外部注入。

首先,在 root 工程目录下面修改 build.gradle 文件,增加 mavenCentral()。

buildscript {


    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
    }
}

在 app 的 gradle 文件 app/build.gradle,添加 libpag 的库依赖。比如下面是基础版的依赖:

 dependencies {
        //基础版本,如需保持最新版本,可以使用 latest.release 指代
        implementation 'com.tencent.tav:libpag-enterprise:latest.release'
    }

使用PAGView,也可以使用PAGImageView加载PAG图片,如下所示。

    <org.libpag.PAGView
        android:id="@+id/view_pag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp" />

我们可以将PAG资源文件可以放入到assets目录下,然后通过PAGFile.Load()进行加载。setRepeatCount表示播放的次数,-1表示循环,1表示播放一次。默认load但是不调用play,只会加载出第一帧图片。

    private fun loadPageAsset(pagView: PAGView, pagRes: String, repeatCount: Int): PAGView {
        return pagView.apply {
            composition = PAGFile.Load(assets, pagRes)
            setRepeatCount(repeatCount)
        }
    }

其他核心类

以下是PAG 提供的一些核心的类和方法。

PAGLayer

PAG 的渲染图层,PAG 是一个树状结构,PAGLayer 相当于树状结构中的叶子节点。PAG 对外暴露的渲染图层包括 PAGImageLayer (图片图层)、PAGTextLayer (文本图层)、PAGSolidLayer(实色图层),只有这些图层可以二次改编辑。PAGLayer 主要提供了以下能力:以 C++ 层接口为例 ( PAG 的对外接口保持各平台一致性),通过 setMatrix 可以控制图层的位移、缩放和扭曲,通过 setVisible 控制图层的显示与隐藏,通过 localTimeToGlobal 获取该图层相对于主渲染时间轴的具体时间,通过 setStartTime 设置图层相对于主时间轴的开始时间,通过设置 setExcludedFromTimeline 可以控制该图层渲染是否脱离主时间轴由接入方控制。

PAGImageLayer

PAGImageView 为图片图层,支持通过 replaceImage 的方法替换默认占位图,同时支持通过 imageBytes 获取默认占位图的数据。它支持用户自己创建,支持指定开始时间和时长,典型的应用场景中将一个外部视频添加到 pag 渲染数据。

PAGTextLayer

PAGTextLayer 为文本图层,支持用户修改默认的文本信息、文本颜色、更换字体、字体大小等。为了方便修改文本数据,C++ 封装了 TextDocument 类,支持修改文本的排版、斜体、描边信息等,对应 iOS、Android 平台的 PAGText 类。

PAGSolidLayer

PAGSolidLayer 为实色图层,支持修改实色图层的颜色

PAGCompostion

PAGComponsition 是渲染树中的容器,继承于 PAGLayer,可以包含多个 PAGLayer,支持用户自己创建,支持增加、删除、更换渲染顺序,支持通过图层名称获取该名称对应的图层。

PAGSurface

PAGSurface 是 PAG 的渲染画布。PAGSurface 的创建, iOS 平台可以通过 CVPixelBufferRef、尺寸(内部同样创建的是 CVPixelBufferRef),Android 平台可以通过 Surface、SurfaceTexture、TextureID、尺寸等创建,不同的创建方法对应不同的应用场景。PAGSurface 提供了获取单帧渲染数据的接口,支持获取 BGRA 数据、CVPixelBufferRef 和Bitmap。

PAGPlayer

PAG 的播放控制类,持有 PAGSurface 和 PAGComposition, 可以通过 setProgress 控制渲染进度, flush 完成当前帧的渲染,可以通过 getBounds 接口获取各 PAGLayer 相对于 PAGSurface 渲染画布的位置信息, 通过 getLayersUnderPoint 获取特定位置下的所有图层。PAGComposition、PAGSurface、PAGPlayer 为 PAG 的三个组件,PAGComposition 提供渲染数据,PAGSurface 提供渲染画布,PAGPlayer 控制渲染进度。在视频后编辑场景,这种使用方式经常被用到。

 PAGView

主要使用在 UI 动画场景,存在 Android、iOS、macOS、Web、微信小程序等平台,iOS 平台继承于 UIView, Andoroid 平台继承于 TextView, PAGView 支持通过本地路径 和 PAGComposition 加载, 调用 play 方法进行播放,内部有一个定时器,同时也提供了 PAGViewListener 的接口监听动画播放的状态,PAGView 内部实现了 PAGPlayer、PAGSurface、PAGComposition 的封装处理。

PAGDecoder

PAGDecoder 目前存在于 iOS 和 Android 平台, 用于获取 pag 文件的渲染数据,支持通过 PAGComposition 创建,渲染的尺寸和 PAGCompositon 的尺寸一致,同时增加 sacle 参数支持用户设置渲染尺寸,通过 maxFrameRate 的参数设置最大渲染帧率。在数据读取层面,支持获取数据为 UIImage 、Bitmap 和 RGBA 数据。

PAGImageView

PAGImageView 主要应用于 UI 列表以及页面中含有多个 pag 文件同时渲染的场景。这些场景使用 PAGView 实现时,页面中需要含有多个 PAGView,每一个 PAGView 都需要有一个 GPU 的渲染环境,任何一个 GPU 渲染的方案都会有一个初始的屏幕缓冲区开销,从而造成 内存占用的增加。PAGImageView 增加了磁盘缓存的功能,针对渲染过的内容,都会缓存到本地,当所有帧的数据缓存完成后,就会销毁 PAG 的渲染环境,剩余的就是磁盘数据的读取,实现了pag 文件渲染与素材的无关性。如果 pag 文件的相关内容没有被编辑过(如编辑文本、替换占位图等),下次加载就会直接读取缓存数据,无需创建 PAG 渲染环境。PAGImageView 的缓存支持两种模式:全磁盘和全内存, 默认为全磁盘模式,此时内存占用是最小的,CPU 占用相对较低,全内存模式的 CPU 占用最低,但内存占用较高,适用于对 CPU 占用要求较高、PAG 文件帧数较少的场景。

三、PAG常用方法解读

PAG 运行时编辑

PAG 的运行时编辑主要分为两类: 1)修改文本图层的文本信息、替换图片图层中的占位图。

const pagFile = await PAG.PAGFile.load(buffer);
// Get TextDate by editableIndex.
const textData = pagFile.getTextData(0);
// Modify textData by editableIndex.
textData.text = 'Hello World';
pagFile.replaceText(0, textData);
// Ensure image is loaded.
const image = await new Promise((resolve) => {
  const img = new Image();
  img.onload = () => resolve(img);
  img.src = '../assets/cat.png';
});
// Replace Image by editableIndex.
const pagImage = await PAG.PAGImage.fromSource(image);
pagFile.replaceImage(0, pagImage);

2)渲染树编辑 渲染树编辑指的是通过使用 PAGComposition 的相关接口,完成多个图层、多个 pag 文件的自由组合,参考代码如下。

// Fetch pag file.
const [buffer1, buffer2] = await Promise.all([
  fetch('../assets/like.pag').then((response) => response.arrayBuffer()),
  fetch('../assets/snowman.pag').then((response) => response.arrayBuffer()),
]);
// Load the PAGFile from file.
const [pagFile1, pagFile2] = await Promise.all([PAG.PAGFile.load(buffer1), PAG.PAGFile.load(buffer2)]);
// Make new PAGComposition.
const pagComposition = PAG.PAGComposition.make(1080, 1080);
// Add PAGFile as PAGLayer to PAGComposition.
pagComposition.addLayer(pagFile1);
// Make scale matrix
const matrix1 = PAG.Matrix.makeScale(2, 2);
pagFile1.setMatrix(matrix1);
// Add PAGFile as PAGLayer to PAGComposition.
pagComposition.addLayer(pagFile2);
// Make translate matrix
const matrix2 = PAG.Matrix.makeTrans(100, 100);
pagFile2.setMatrix(matrix2);
pagFile2.setStartTime(pagFile1.duration())
// Create PAGView.
const pagView = await PAG.PAGView.init(pagComposition, canvas);

PAG视频编辑

在视频编辑场景,使用的不是 PAGView,而是 PAGPlayer、PAGSurface 和 PAGComposition。

PAGSurface 可以通过 Canvas 或纹理创建,方便快捷的与视频后编辑中的 Canvas 或 纹理进行整合。同时 PAGImage 也支持通过 Canvas 或 纹理创建,通过 PAGPlayer 控制播放进度,将视频内容填充进图片图层的占位图,对应的代码如下。

// PAGSurface interface
class PAGSurface {
    /**
     * Make a PAGSurface from canvas.
     */
    static fromCanvas(canvasID: string): PAGSurface;
    /**
     * Make a PAGSurface from texture.
     */
    static fromTexture(textureID: number, width: number, height: number, flipY: boolean): PAGSurface;
    /**
     * Make a PAGSurface from frameBuffer.
     */
    static fromRenderTarget(frameBufferID: number, width: number, height: number, flipY: boolean): PAGSurface;
}


// PAGImage interface
class PAGImage {
    /**
     * Create pag image from image file.
     */
    static fromFile(data: File): Promise<PAGImage>;
    /**
     * Create pag image from image source or video source.
     * Make sure the target pixel is shown on the screen.
     * Like
     * ``` javascript
     * Image.onload = async () => {
     *   return await PAGImage.fromSource(Image)
     * }
     * ```
     */
    static fromSource(source: TexImageSource): PAGImage;
    /**
     *  Creates a PAGImage object from an array of pixel data, return null if it's not valid pixels.
     */
    static fromPixels(pixels: Uint8Array, width: number, height: number, colorType: ColorType, alphaType: AlphaType): PAGImage;
    /**
     * Creates a PAGImage object from the specified backend texture, return null if the texture is
     * invalid.
     */
    static fromTexture(textureID: number, width: number, height: number, flipY: boolean): PAGImage;
}

PAG 软解注入

PAG 的导出方式中支持 BMP 预合成导出,在 pag 文件中,如果含有 BMP 预合成,一个 BMP 预合成相当于一个视频,视频则需要解码。 在 PAG SDK 中默认使用硬件解码,但硬件解码在 Web 存在问题:

在部分移动端浏览器环境中存在“用户与页面交互之后才可以使用 Video 标签进行视频播放”的规则限制。而当业务宁愿牺牲性能也希望做到不需要用户交互而进行播放时,可以使用软件解码器 ffavc。

<script src="https://cdn.jsdelivr.net/npm/libpag@latest/lib/ffavc.min.js"></script>

完成软件解码器的注册后,接下来就可以使用了。

// Initialize ffavc webassembly module.
const FFAVC = await window.ffavc.FFAVCInit();
const ffavcDecoderFactory = new FFAVC.FFAVCDecoderFactory();
PAG.registerSoftwareDecoderFactory(ffavcDecoderFactory);

链接:pag.io/