PAG 動效方案使用總結

近幾年能明顯感覺到,互聯網產品已經越來越離不開動效了:不管是APP裏會動的加載動畫UI,還是直播間裏華麗的禮物,都需要經歷動效上線的過程。而負責這些動效上線的前端同學應該有過這樣的體驗:各種加班寫代碼上線動畫效果,並且在動效還原過程中反覆和設計師聯調效果。

最近接觸到一個開源項目可以幫助大家解決這個問題,它就是PAG動效組件。

PAG 簡介:認識 PAG動效

PAG是來自騰訊的一套完整的動畫工作流解決方案,助力於將 AE 動畫方便快捷的應用於各平臺終端。PAG 的流程圖下圖所示,設計師在 AE 上設計出動畫後,可以通過導出插件導出 pag 文件,同時 PAG 提供了桌面端預覽工具,支持實時預覽效果,在確認效果後,通過運行配置上線,各平臺終端可以通過 PAG SDK 加載渲染 PAG 動畫。

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 和 微信小程序,支持的平臺更多。

這裏放一張對比圖,大家就一目瞭然了:

img

想進一步瞭解也可以去開源地址:https://github.com/Tencent/libpag

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,支持用戶自己創建,支持增加、刪除、更換渲染順序,支持通過圖層名稱獲取該名稱對應的圖層。
PAGFile PAGFile 繼承於 PAGComposition,不支持自己創建,通過加載 pag 文件獲得,相對比 PAGComposition,PAGFile 增加了替換文本圖層和圖片圖層內容的接口,因此如果需要編輯文本圖層和圖片圖層的內容,一方面可以通過圖層自身的接口,另一方面可以通過 PAGFile 的接口。
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)修改文本圖層的文本信息、替換圖片圖層中的佔位圖、修改實色圖層中的顏色

// C++
std::shared_ptr<pag::PAGFile> ReplaceImageOrText() {
  auto pagFile = pag::PAGFile::Load("../../assets/test2.pag");
  if (pagFile == nullptr) {
    return nullptr;
  }
  for (int i = 0; i < pagFile->numImages(); i++) {
    auto pagImage = pag::PAGImage::FromPath("../../assets/scene.png");
    pagFile->replaceImage(i, pagImage);
  }

  for (int i = 0; i < pagFile->numTexts(); i++) {
    auto textDocumentHandle = pagFile->getTextData(i);
    textDocumentHandle->text = "hah哈 哈哈哈哈👌";
    pagFile->replaceText(i, textDocumentHandle);
  }
  return pagFile;
}

2)渲染樹編輯

渲染樹編輯指的是通過使用 PAGComposition 的相關接口,完成多個圖層、多個 pag 文件的自由組合。具體如何使用可以參考下面的代碼:

// 以 OC 版本爲例
- (PAGComposition *)makeComposition {
    PAGComposition* compostion = [PAGComposition Make:self.view.bounds.size];
    float itemWidth = self.view.bounds.size.width / 5;
    float itemHeight = 100;
    for (int i = 0; i < 20; i++) {
        PAGFile* pagFile = [self getPAGFile:i / 5 clume:i % 5 name:[NSString stringWithFormat:@"%d", i] itemWidth:itemWidth itemHeight:itemHeight];
        [compostion addLayer:pagFile];
    }
    return compostion;
}

- (PAGFile*)getPAGFile:(int)row clume:(int)colume name:(NSString*)name itemWidth:(float)itemWidth itemHeight:(float)itemHeight {
    PAGFile* pagFile = [PAGFile Load:[[NSBundle mainBundle] pathForResource:name ofType:@"pag"]];
    if (pagFile) {
        float scaleX = itemWidth * 1.0f / [pagFile width];
        CGAffineTransform transform = CGAffineTransformMakeScale(scaleX, scaleX);
        CGAffineTransform tranflate = CGAffineTransformMakeTranslation(itemWidth * colume, row * itemHeight + 150);
        transform = CGAffineTransformConcat(transform, tranflate);
        [pagFile setMatrix:transform];
        [pagFile setStartTime:0];
        [pagFile setDuration:10000000];
    }
    return pagFile;
}
PAG UI場景及列表播放

當一個頁面中含有多個pag 文件時,如果使用多個 PAGView,由於每一個 PAGView 都需要一個獨立的渲染環境,內存和 CPU 佔用相對較高,推薦的處理方法有兩種:

1)通過使用 PAG 的組合模式用將多個 PAG 文件添加到同一個 PAGComposition 中,具體使用方法見運行時編輯,從而將多個渲染環境縮減到一個渲染環境,降低內存和 CPU 佔用;

2)對於一些 UI 列表場景,需要有多個 View 的存在, pag 文件無法添加到一個 PAGComposition 中,此時可以使用 PAGImageView。

// 以 OC 版本爲例
#import <libpag/PAGImageView.h>

#define WIDTH  100

@interface PAGCell : UITableViewCell
@property (nonatomic,strong) PAGImageView* pagImageView;
@property (nonatomic,strong) NSString* filePath;
@end

@implementation PAGCell

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        self.pagImageView = [[PAGImageView alloc] initWithFrame:CGRectMake(20, 0, WIDTH, WIDTH)];
        [self.contentView addSubview:self.pagImageView];
    }
    return self;
}

- (void)setFilePath:(NSString *)filePath {
    if ([filePath length] > 0) {
        [self.pagImageView setPath:filePath];
        [self.pagImageView setCurrentFrame:0];
        [self.pagImageView setRepeatCount:-1];
        [self.pagImageView play];
    }
}
PAG 字體註冊

PAG 除了支持修改文本圖層的文本信息外,還支持修改字體。具體方法如下:

(1)通過 PAGFont 獲取字體的相關信息,然後賦值給 PAGText,使用到的接口如下:

獲取字體信息

  // C++
  /**
   * Registers a font required by the text layers in pag files, so that they can be rendered as
   * designed.
   */
  static PAGFont RegisterFont(const std::string& fontPath, int ttcIndex,
                              const std::string& fontFamily = "",
                              const std::string& fontStyle = "");
  /**
   * Registers a font required by the text layers in pag files, so that they can be rendered as
   * designed.
   */
  static PAGFont RegisterFont(const void* data, size_t length, int ttcIndex,
                              const std::string& fontFamily = "",
                              const std::string& fontStyle = "");
  PAGFont(std::string fontFamily, std::string fontStyle)
      : fontFamily(std::move(fontFamily)), fontStyle(std::move(fontStyle)) {
  }

  /**
   * A string with the name of the font family.
   **/
  const std::string fontFamily;
  /**
   * A string with the style information — e.g., “bold”, “italic”.
   **/
  const std::string fontStyle;                              
                              

(2)fontFamlily 和 fontStyle 賦值給 PAGText,PAGText 再填充到 PAGTextLayer 中。

 // C++
   for (int i = 0; i < pagFile->numTexts(); i++) {
    auto textDocumentHandle = pagFile->getTextData(i);
    textDocumentHandle->text = "hah哈 哈哈哈哈👌";
    // Use special font
    auto pagFont = pag::PAGFont::RegisterFont("../../resources/font/NotoSansSC-Regular.otf", 0);
    textDocumentHandle->fontFamily = pagFont.fontFamily;
    textDocumentHandle->fontStyle = pagFont.fontStyle;
    pagFile->replaceText(i, textDocumentHandle);
  }

如果使用了特定字體而又沒有註冊或字體文件中沒有包含該字符,PAG 內部(Android、iOS、macOS、Windows 平臺)有一個默認字體列表(同時支持外部設置字體回退列表,外部設置時會覆蓋默認設置),會回退到 PAG 的默認字體列表中,此時使用那種字體對於業務方而言是不確定的。

// C++
  /**
   * Resets the fallback font names. It should be called only once when the application is being
   * initialized.
   */
  static void SetFallbackFontNames(const std::vector<std::string>& fontNames);

  /**
   * Resets the fallback font paths. It should be called only once when the application is being
   * initialized.
   */
  static void SetFallbackFontPaths(const std::vector<std::string>& fontPaths,
                                   const std::vector<int>& ttcIndices);
PAG 視頻編輯場景

在視頻編輯場景,使用的不是 PAGView,而是 PAGPlayer、PAGSurface 和 PAGComposition。

PAGSurface 可以通過 CVPixelBufferRef 或紋理創建,方便快捷的與視頻後編輯中的 CVPixelBuffer 或 紋理進行整合。同時 PAGImage 也支持通過 CVPixelBufferRef 或 紋理創建,通過 PAGPlayer 控制播放進度,將視頻內容填充進圖片圖層的佔位圖。

// OC
/**
 * Creates a PAGImage object from the specified CVPixelBuffer, return null if the CVPixelBuffer is
 * invalid.
 */
+ (PAGImage*)FromPixelBuffer:(CVPixelBufferRef)pixelBuffer;

// java
public static PAGImage FromTexture(int textureID, int textureTarget, int width, int height);

public static PAGImage FromTexture(int textureID, int textureTarget, int width, int height, boolean flipY);
PAG 軟解注入

爲什麼會有軟解注入?PAG 的導出方式中支持 BMP 預合成導出,在 pag 文件中,如果含有 BMP 預合成,一個 BMP 預合成相當於一個視頻,視頻則需要解碼。 在 PAG SDK 中默認使用硬件解碼,但硬件解碼存在兩個問題:

1)移動端硬件解碼器的瞬時存在數目是有限制的,不能無限的創建,如果創建硬件解碼器的數目超過限制,就會出現解碼器創建失敗的情況,在視頻編輯場景中需要關注;

2)Android 平臺由於機型兼容性、碎片化驗證,不能保證所有的機型都能硬件解碼成功,因此 Android 平臺軟解是必須的。

於是,在提供的製品庫中, iOS 平臺由於沒有兼容性的問題,默認是不帶軟解的,Android 提供了兩個包,普通的包默認是內置軟解的,noffavc 的包是沒有內置軟解的。

具體怎麼注入軟解呢:Android 平臺可以選擇完整製品庫,iOS 平臺可以引入 ffavc。

pod 'ffavc'

通過如下方法完成軟件解碼器的註冊:

// OC
-(void)registerSoftwareDecoder
{
    // 註冊軟件解碼器工廠指針
    auto factory = ffavc::DecoderFactory::GetHandle();
    [PAGVideoDecoder RegisterSoftwareDecoderFactory:(void*)factory];
    }

如果接入方自己的 APP 中已經內置了軟解庫,可以通過外部注入的方式注入軟解。

PAG 官網提供了下面這個軟解注入接口,需要業務方基於自己的解碼器實現,PAG BMP 預合成中的視頻編碼格式爲 h264。

https://github.com/libpag/ffavc/blob/main/vendor/libpag/include/pag/decoder.h

具體的實現方式可以參考:

ffavc/FFAVCDecoder.cpp at main · libpag/ffavc · GitHub

然後自己通過上面提到的方式注入軟解。

PAG 服務端渲染

和 Lottie、SVGA 不同的是,PAG 支持服務端渲染,儘管 PAG 的渲染依賴 OpenGL 環境,但 服務端卻可以繼續使用 CPU 服務器。具體實現層面,PAG 通過 swiftshader 在 CPU 服務器上構建出了 OpenGL 環境,使得 pag 文件可以在 CPU 環境中正常渲染。

在服務端的具體使用如下,獲取到的是 BGRA 的數據,該數據可以用於視頻編碼。

 // C++
  auto pagFile = pag::PAGFile::Load("../../assets/test2.pag");
  auto pagSurface = pag::PAGSurface::MakeOffscreen(pagFile->width(), pagFile->height());
  if (pagSurface == nullptr) {
    printf("---pagSurface is nullptr!!!\n");
    return -1;
  }
  auto pagPlayer = new pag::PAGPlayer();
  pagPlayer->setSurface(pagSurface);
  pagPlayer->setComposition(pagFile);

  auto totalFrames = TimeToFrame(pagFile->duration(), pagFile->frameRate());
  auto currentFrame = 0;

  int bytesLength = pagFile->width() * pagFile->height() * 4;

  while (currentFrame <= totalFrames) {
    pagPlayer->setProgress(currentFrame * 1.0 / totalFrames);
    auto status = pagPlayer->flush();

    // PAG 渲染數據讀取
    auto data = new uint8_t[bytesLength];
    pagSurface->readPixels(pag::ColorType::BGRA_8888, pag::AlphaType::Premultiplied, data,
                           pagFile->width() * 4);
    delete[] data;

    currentFrame++;
  }

  delete pagPlayer;

開始使用 :如何接入

接入使用分爲開發部分和設計部分,這裏分別爲大家介紹下:

(1) 開發者——接入SDK

在PAG的Github Wiki中有非常詳細的接入指引(包括Android、iOS、Web等平臺的接入方法和範例工程),大家自行學習就好:https://github.com/Tencent/libpag/wiki

img

(2)設計師——下載導出插件和預覽工具

設計師想要使用PAG,只需在官網下載預覽工具 PAGViewer和 AE 導出插件即可:https://pag.art/docs/install.html

img

整體看來,PAG的核心價值如下:

上線難點 傳統方案痛點 PAG動效價值
研發成本 每個動效都需要研發通過代碼來還原,需要大量的研發人力持續投入,由於研發人力有限,導致這個流程無法批量化生產素材。 研發只有一次性接入 SDK的成本,在後續整個素材生產流程都無需研發人力介入。整套工作流不在受制於研發的人力瓶頸,就能夠開放給更多的設計師使用,批量化的進行素材生產。
生產週期 設計師和研發人員的聯調成本高,效果還原度需要反覆確認,中間產生較長的上線週期,拖延產品運營節奏。 由於砍掉了研發成本,最耗時的研發和設計的聯調環節也不存在了。設計師可以所見即所得地生產素材,極大地縮短了生產週期,能夠快速響應運營熱點。
動效視覺 AE裏有很多複雜動效,使用純代碼還原起來非常困難,設計師只能不斷簡化效果以達到跟開發成本的平衡,導致上線的視覺效果大打折扣。 PAG的SDK完全還原了AE整個動效的渲染系統,接入一次,設計師就可以充分利用AE動效的原子能力,組合出無限的視覺動效,不用因爲代碼還原成本的問題而打折扣。

據官網顯示已經有很多頭部應用接入使用(如微信、王者榮耀、小紅書、知乎等),穩定性應該很有保證,如果有動效上線相關業務的朋友非常值得試試。

最後順便附上 PAG 相關的資源,大家感興趣可以進一步瞭解:

官網: https://pag.art/

Github:https://github.com/Tencent/libpag

QQ 羣:893379574

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章