关于视频:视频编辑场景下的文字模版技术方案

37次阅读

共计 6651 个字符,预计需要花费 17 分钟才能阅读完成。

作者 | Lok’tar ogar

导读

本文依据度咔剪辑 APP 文字模版开发实际,分享视频编辑场景下,动态文字模版渲染能力的技术计划。作为富文本渲染计划的父集,此技术计划能够扩大到其余须要简单富文本渲染的场景下。

全文 6745 字,预计浏览工夫 17 分钟。

先睹为快

文字模版成果展现:

△文字模版在度咔剪辑中的利用

01 背景

视频创作工具的外围竞争力之一是其丰盛的素材库,其中包含各种视频素材、音频素材以及贴纸素材等等。其中的文字模版也是不可或缺的一部分。文字模版提供了富文本的编辑性能,使用户可能在视频中增加更多样式柔美的文字信息,从而削减了视频素材的多样性。此外,通过预设的款式,用户能够更加不便地抉择适宜本人的文字模版,节俭了素材抉择的工夫,晋升了用户体验。
在度咔的晚期版本中,咱们并没有提供文字模版这一素材类型。为了晋升产品的竞争力和进步素材渗透率,咱们进行了肯定的研发工作,最终推出了文字模版素材。这些文字模版素材不仅能够满足用户的需要,而且能够为用户提供更多的创作灵感和思路。同时,咱们也一直地更新和优化咱们的素材库,以确保用户可能取得最新和最优质的素材资源。
文字模版须要出现的图文款式较为简单,度咔文字模版已反对的个性见上面的列表:

02 整体设计

咱们基于已建设的素材平台,新增了文字模版这一类型,并且在素材平台提供了素材编辑、预览、配置上线的性能。素材生产和预览相结合,能够在同一界面预览刚刚调整的成果,能够间接匹配度咔的字体库,以及间接批改图片资源。这种素材生产方式具备高可复用性,用一个文字模版,改一个背景图、新增一个描边,就能够间接生产出另一个文字模版。公布这一模版,并导出效果图,即进入待审核队列,审核后可配置上线。

截至目前,咱们已上线了 361 套文字模版,并实现了【素材生产】-【素材平台预览】-【素材下发和客户端载入】-【客户端渲染】的残缺链路。

03 性能实现

3.1 素材生产

目前视频编辑行业支流的素材格局通常采纳资源文件和配置文件(形容文件)的形式进行。其中,资源文件包含图片资源和字体文件,而配置文件则次要用于形容文字模版的排版属性和渲染参数。这种形式的长处在于,生产端只须要通过特定字段来形容相干个性,就能够在渲染端出现进去。这种形式灵便度较高,能够依据具体场景须要由简略到简单地迭代相干个性,同时实现老本也绝对较低。不过,毛病在于素材生产模式是自定义的,须要肯定的设计学习老本。
除此之外,还有一种生产方式是针对业余设计软件的,以 Photoshop(PS)为例。PS 有比拟成熟的文件格式文档,蕴含了各类数据结构,能够间接应用 PSD 文件解析图文属性进行渲染。这种形式的长处在于素材生产方式较为通用,设计简直没有学习老本。不过,毛病在于咱们须要的一些个性无奈通过 PSD 简略地满足,比方多层暗影成果,其是由多个文本框层层叠加失去的成果。在批改文字内容的时候,咱们须要同步批改这几个文本框,因而须要把它们作为一个组来解决,逻辑就变得比较复杂。如果用配置文件来形容,则能够间接进行多层绘制,免去简单的逻辑解决。
思考到业务 ROI 和短期上线性能的可行性,咱们采纳了第一种形式,并借鉴了黄油相机团队的素材生产规范,设计了用于形容排版属性和渲染参数的 JSON构造。

3.2 端渲染

在视频编辑场景下,文字的解决须要进行文字排版和文字绘制两个局部的解决。对于文字排版,iOS 平台采纳了 CoreText 底层框架来进行排版解决,而 Android 则能够通过 FontMetrics 等获取底层 FreeType 对于字形解决的后果。不论是将一段文字作为整体进行排版解决,还是别离计算每个文字的地位,总的解决的性能耗费是雷同的。
在进行文字绘制方面,须要在性能开销和开发成本之间寻求均衡。最终,iOS 采纳了 QuartzCore 框架,Android 则应用 Canvas 来进行文字绘制。这样,在预览时,文字能够间接出现在视图上,反对实时编辑预览。当须要将视频导出时,咱们将其解决为贴纸的模式增加在视频中。以 iOS 为例,花字组件架构如下:

3.3 形容文件设计

上文提到,咱们用 json 文件形容文字模版的排版属性和渲染参数,资源下发到客户端后,客户端会解析对应参数,来进行文字模版的排版和最终成果出现。形容文件中会波及以下内容:

(1)文字排版属性

  • baseline:字符基线,baseline 是虚构的线
  • ascent:字形最高点到 baseline 的举荐间隔
  • descent:字形最低点到 baseline 的举荐间隔
  • leading:行间距,即前一行的 descent 与下一行的 ascent 之间的间隔
  • advance width:Origin 到下一个字形 Origin 的间隔
  • left-side bearing:Origin 到字形最右边的间隔
  • right-side bearing:字形最左边到下一个字形 Origin 的间隔
  • bounding box:蕴含字形的最小矩形
  • x-height:个别指小写字母 x 最高点到 baseline 的举荐间隔
  • Cap-height:个别指 H 或 I 最高点到 baseline 的举荐间隔

(2)文本对象组合
下图为两个文字绘制区域的组合示例

3.4 排版绘制流程

在咱们的文字模版中,排版和绘制是密不可分的,须要在代码逻辑中交叉进行解决。咱们的绘制步骤是从底层到顶层逐层绘制,但因为一些绘制过程会耗费大量工夫,为了防止阻塞主线程,咱们应用了异步绘制的技术。在异步绘制的过程中,咱们将一些比拟耗时的绘制过程放在了后盾线程中进行解决,这样就可能不影响用户的失常应用。同时,在异步绘制的过程中,咱们也会进行文字排版的计算,这样可能在后续绘制过程中疾速获取到文字的相干信息,进而进步绘制效率。总的来说,咱们通过采纳异步绘制的形式,可能保障文字模版的排版和绘制过程顺利进行,同时也不会对用户造成太多的烦扰。

04 难点与挑战

1、多端成果的对齐

咱们的我的项目反对 web、iOS 和 Android 端的渲染,但因为通用的跨端计划须要在底层应用 OpenGL 渲染,而过后的人力资源限度使得短期内难以实现。因而,咱们采纳了多端独立渲染的形式,每个平台都有独立的渲染计划。这种形式也带来了一个问题:不同平台的渲染成果会有差别。
为了解决这个问题,咱们须要保障多端成果的一致性。因为在技术层面难以抹平差别,咱们决定通过规定和规范的对立来实现一致性。在设计 json 文件的格局时,咱们就对立了多端渲染的规范,比方文字装璜绝对于文字的初始地位是左上角对齐还是居中对齐,坐标原点的对立等。同时,咱们还对立了对应的参数所用的单位,从而最大水平地保障了最终出现成果的一致性。这样,无论在哪个平台上渲染,咱们都可能失去统一的后果,使用户体验更加对立和良好。

2、文字预排版

在文字模版中,咱们将字号分为两种类型:固定字号和非固定字号。对于固定字号,咱们能够间接进行文字排版计算和绘制。然而,对于非固定字号的字体,咱们须要进行预排版的计算,以算出以后文字内容下对应的字号大小。这里有些计划是采纳二分法,先定一个较大的字号值,在 0 到该字号值的范畴内逐步迫近正确值,但这样其实造成了非必要的工夫损耗,联合文字排版的根底逻辑和限定条件,咱们能够做出工夫复杂度靠近 O(1)的算法:计算最大字高 -> 计算最小字高 -> 计算字数最长行的字高 -> 依据行数算字高 -> 算出最终字高 -> 依据字高算字号。iOS 中用 CoreText 排版技术时,少部分状况会主动裁切填不满的文字,间接用计算出的字号会导致局部文字被裁切,所以要将以上后果作为估算后果,将 size 逐次减 1,直至能填入 path。

        CGFloat ascent, descent;
        UIFont *font = [self.calFont fontWithSize:size];
        CTFontRef fontRefMeasure = (__bridge CTFontRef)font;
        [attrString addAttribute:(id)kCTFontAttributeName value:(__bridge id)fontRefMeasure range:NSMakeRange(0, attrString.length)];
        CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)attrString);
        CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
        
        //calculate max font size
        CGFloat calFontHeight = MIN(height, width);
        self.maxFontHeight = calFontHeight;
        
        //calculate min font size
        CGFloat maxLine = self.document.maxLine * BDTZBigFontDataOriginScale;
        if (maxLine <= 0) {maxLine = 1;}
        calFontHeight = [self itemWidth] / (maxLine + (maxLine - 1) * (self.leadingRatio * BDTZBigFontDataOriginScale - 1));
        self.minFontHeight = MIN(self.maxFontHeight, calFontHeight);
        
        // longest column
        int64_t n = 0;
        NSArray *strArray = [self.document.content componentsSeparatedByString:@"\n"];
        NSString *measureStr = self.document.content;
        // 这里是针对多行文本的解决,循环次数为行数,量级较小(个别为 1 -10 行)for (NSString *str in strArray) {if (str.length > n) {
                n = str.length;
                measureStr = str;
            }
        }
        CGFloat fontWidthRatioOrigin = (self.document.fontWidthRatio * BDTZBigFontDataOriginScale);
        CGFloat trackingRatio = (self.document.trackingRatio * BDTZBigFontDataOriginScale) * (ascent + descent) / ascent;
        CGRect rect = [@"我" boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.calFont} context:nil];
        CGFloat fontWidthRatio = fontWidthRatioOrigin > 0 ? fontWidthRatioOrigin * (ascent + descent) / ascent : rect.size.width / rect.size.height;
        CGFloat fontHeight = width / (n * fontWidthRatio + n * trackingRatio);
        
        if (strArray.count > 1) {
            //calculate font size accoring column count
            calFontHeight = [self itemWidth] / (strArray.count + (strArray.count - 1) * (self.leadingRatio * BDTZBigFontDataOriginScale - 1));
            //take the min value of the above two font sizes
            fontHeight = MIN(fontHeight, calFontHeight);
        }
                
        if (fontHeight > self.maxFontHeight) {fontHeight = self.maxFontHeight;} else if (fontHeight < self.minFontHeight) {fontHeight = self.minFontHeight;}
        
        CGFloat calSize = fontHeight;
        calFontHeight = [self calculateFontHeightSize:calSize];
        calSize = floorf(calSize / (calFontHeight * (ascent + descent) / ascent) * calSize);
        
        //exact value, calculate repeatedly with frame until the path can be filled
        
        // 依据估算后果,将 size 逐次减 1,直至能填入 path,此处代码省略
        
        if (calSize <= 0) {return calSize;}
        calFontHeight = [self calculateFontHeightSize:calSize];
        self.fontHeight = calFontHeight * (ascent + descent) / ascent;
        
        self.font = [self.calFont fontWithSize:calSize];

3、绘制性能

文字模版的实时预览须要频繁绘制,这会对 CPU 带来较大的累赘,从而导致卡顿。为了解决这个问题,咱们必须采纳异步绘制的形式。具体来说,咱们能够创立一个异步的串行队列,来存储用户每次操作的文字内容状态。每当用户进行批改操作时,咱们就将以后状态退出到队列中,期待后盾线程进行异步绘制。以后一个状态绘制实现后,咱们再从队列中取出下一个待绘制状态,直到所有状态都被绘制结束。这样,既实现了异步绘制避免卡主线程,又将用户每次批改的后果残缺出现进去。
为了进一步优化文字模版的用户体验,除了异步绘制之外,还能够思考应用缓存机制来晋升渲染性能。在用户对文字模版进行操作时,文字视图会从新进行排版和绘制,如果每次都从新绘制整个模版,不仅会占用大量 CPU 资源,而且会升高用户体验。因而,咱们能够应用缓存来存储已绘制的模版视图,当用户批改文字内容时,只须要从新绘制被批改的局部,而不是整个视图。通过这种形式,咱们能够进步渲染性能,同时还能缩小资源耗费,进步零碎的响应速度。

4、内存优化

咱们的文字模版次要用于视频编辑场景,用户须要依据具体情况对文字模版进行放大或放大。如果应用纯矢量绘制刷新形式,当用户将文字模版放大到肯定水平时,内存的占用量将十分高。此外,咱们的用户在编辑器中通常会增加许多素材,如贴纸、特效和字幕等,而这些素材中单个占用内存较高,应用一段时间后,内存容易减少到 OOM 阈值,导致利用解体。因而,咱们目前将单个文字模版的内存管制在 20M 以下,并依据不同视频宽高的画幅,计算出文字模版达到预期清晰度所需的宽高阈值,以实现清晰度和占用内存大小的均衡,每个文字模版都有一个不同的均衡参数。只管这只是一个内存优化的细节,但对于咱们管制素材的内存占用以及线上 OOM 率的管制起到了很大的作用。

05 结语

在视频编辑畛域中,富文本渲染是一个相当简单的过程。就端渲染而言,没有一种万能的解决方案,只有最适宜特定场景的解决方案。在设计和实现文字模版渲染计划的过程中,有许多细节须要思考。同时,还须要深刻理解支流设计软件如 PS、Figma 等的文件格式。
咱们团队提供了动态文字模版相干的技术计划,这些计划能够满足较为广泛的富文本渲染场景。整体思路对于文字排版和绘制都是大抵相似的。在本文中,咱们介绍了根底概念和富文本个性,以帮忙读者更好地了解咱们的技术计划。
然而,即便是咱们提供的计划,实现起来也须要思考许多细节。咱们须要思考字体的大小、色彩、对齐形式、字距、行距等因素,以确保渲染进去的富文本可能达到预期的成果。
因而,为了实现富文本渲染的最佳成果,须要投入大量工夫和精力进行设计和实现。只有深刻了解富文本的个性和设计原理,能力为用户提供高质量的视频编辑体验。

——END——

举荐浏览:
浅谈流动场景下的图算法在反作弊利用

Serverless:基于个性化服务画像的弹性伸缩实际

图片动画化利用中的动作合成办法

性能平台数据提速之路

采编式 AIGC 视频生产流程编排实际

百度工程师漫谈视频了解

正文完
 0