Fix incorrect ImageBytes size and anchor for masked video layers in AE exporter#3589
Conversation
… pixel-floor crop origin.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3589 +/- ##
==========================================
+ Coverage 81.31% 82.58% +1.26%
==========================================
Files 653 707 +54
Lines 77271 91382 +14111
Branches 21918 25871 +3953
==========================================
+ Hits 62836 75465 +12629
- Misses 9994 10435 +441
- Partials 4441 5482 +1041 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| // AEGP_NewFromLayer uses the current time indicator position which may be outside the layer's | ||
| // visible range. Explicitly set the time to the layer's in point in layer time to ensure the | ||
| // first visible frame is rendered correctly (including footage slip). | ||
| A_Time inPointLayer = {}; |
There was a problem hiding this comment.
在 layer time 下 AEGP_GetLayerInPoint 恒返回 0(Adobe SDK 文档:In layer time, the in_pointPT is always 0),所以这里等价于 SetTime(0),绕一圈取一个恒为 0 的值可读性较差,直接构造 A_Time{0, fps} 更清晰。另外上方注释「including footage slip」有误导:footage slip 体现在 layer→footage 的时间映射中,与 layer-time in-point 无关,建议修正措辞。
There was a problem hiding this comment.
注释措辞已按建议修正,去掉了误导性的「including footage slip」,改为说明 footage slip / time stretch 会使 in-point 偏离 source start。
但「直接构造 A_Time{0, fps}」这个建议未采纳。实测发现:当一个合成引用多个视频时,图层的 layer-time 0 并不对应视频源的 time 0(footage slip / time stretch 会把它偏移),硬编码为 0 会渲染到错误的视频帧。因此这里保留动态查询 AEGP_GetLayerInPoint,不能假设其恒为 0。
There was a problem hiding this comment.
注释修正已确认。关于保留动态查询 AEGP_GetLayerInPoint 而不硬编码 0——认同你的判断:多视频合成 + footage slip/time stretch 下 layer-time in-point 可能非 0,动态查询更稳健,我原评论的这一点不成立。此问题已解决。
| static void GetVideoLayerMaskedOffset(const AEGP_LayerH& layerHandle, A_long& offsetX, | ||
| A_long& offsetY) { | ||
| const auto& Suites = GetSuites(); | ||
| A_Time inPoint = {}; |
There was a problem hiding this comment.
新增的 AEGP_GetLayerInPoint/AEGP_GetLayerMaskedBounds(以及上方两处 AEGP_SetTime)均未检查返回的 A_Err,也缺少零值防护。若 AEGP_GetLayerMaskedBounds 失败,maskedBounds 保持 {},floor(0)=0 会让偏移静默归零、退化回原来的错位问题且无任何提示。项目已有同类防护先例(AEHelper.cpp 中 GetLayerStartTime 对 inPoint.scale == 0 做了保护),建议这里也补上失败/零值早退。
There was a problem hiding this comment.
已修复。GetVideoLayerMaskedOffset 现在对 AEGP_GetLayerInPoint 和 AEGP_GetLayerMaskedBounds 均检查 A_Err,并对 inPoint.scale == 0 做早退(对齐 AEHelper.cpp 中 GetLayerStartTime 的先例)。任一失败时直接返回,maskedOffset 保持调用方初始化的 0,不再有 floor(0) 静默归零后被误当作有效偏移的问题。
注:SetVideoLayerRenderTime 中的 SetTime 未加同样的早退守卫,是有意为之——若查询失败就早退跳过 SetTime,会让 renderOptions 停留在默认时间指示器位置,反而重现本 PR 要修的原始 bug;此处沿用 AEHelper.cpp SetRenderTime 的既有模式(不预检、直接 SetTime)更安全。
There was a problem hiding this comment.
已确认修复。err 检查 + inPoint.scale==0 早退与项目既有惯例(StreamValue.h、Marker.cpp)一致;SetTime 不加守卫的取舍解释也合理。此问题已解决。
| } | ||
| A_Time inPointLayer = {}; | ||
| Suites->LayerSuite6()->AEGP_GetLayerInPoint(layerHandle, AEGP_LTimeMode_LayerTime, &inPointLayer); | ||
| Suites->LayerRenderOptionsSuite2()->AEGP_SetTime(renderOptions, inPointLayer); |
There was a problem hiding this comment.
这 3 行「取 InPoint + SetTime」与 GetVideoLayerRenderImageSize 中完全重复,且与 GetVideoLayerMaskedOffset 里第三次取 InPoint 逻辑重叠。建议抽成一个 helper(如 SetVideoLayerRenderTime(renderOptions, layerHandle))复用,项目已有 SetRenderTime 先例。另外同款 3 行在 GetVideoLayerRenderImageSize 有详细注释、这里却没有,两处注释不一致,抽出后可统一。
There was a problem hiding this comment.
已修复。抽出了 helper SetVideoLayerRenderTime(layerHandle, renderOptions),在 GetVideoLayerRenderImageSize 和 GetVideoLayerRenderImage 两处复用,消除了重复的「取 InPoint + SetTime」代码,注释也统一到 helper 上。
GetVideoLayerMaskedOffset 里的第三处取 InPoint 未并入该 helper:它用的是 AEGP_LTimeMode_CompTime,且取到的 inPoint 是作为参数喂给 AEGP_GetLayerMaskedBounds 查询遮罩包围盒,与渲染时间设置(LayerTime)用途不同,故保持独立。
There was a problem hiding this comment.
已确认修复。helper 抽取到位、两处复用、注释统一;第三处保持独立的解释合理。此问题已解决。
…t against API failures.
…renders the correct video frame.
| AEGP_ItemH itemHandle = GetLayerItemH(layerHandle); | ||
| auto dimensions = GetItemDimensions(itemHandle); | ||
| sourceWidth = dimensions.width(); | ||
| sourceHeight = dimensions.height(); |
There was a problem hiding this comment.
健壮性建议:sourceWidth/sourceHeight 来自 GetItemDimensions,未做 <= 0 校验就直接写入 imageBytes->width/height。而 ImageBytes::verify()(src/base/ImageBytes.cpp)强制要求 width > 0 && height > 0,一旦为 0 会导致导出的 PAG 加载时 verify 失败。虽然「渲染成功但源 item 无效」的场景实际几乎不会发生,但建议对 sourceWidth <= 0 || sourceHeight <= 0 时回退到渲染得到的 width/height,与上方 width < 0 || height < 0 的既有防御风格保持一致。
背景
参见 discussion #3578
当视频图层(video layer)在 After Effects 中添加了蒙版(mask)时,导出的占位图(ImageBytes)尺寸和锚点不正确,导致播放端图片定位错位。根因有两处:
AEGP_NewFromLayer默认使用当前时间指示器位置,可能落在图层可见区间之外,导出的首帧内容错误(尤其存在 footage slip 时)。改动
仅涉及
exporter/src/export/data/ImageBytes.cpp:GetVideoLayerMaskedOffset,通过AEGP_GetLayerMaskedBounds获取蒙版包围盒在 layer space 的偏移。对包围盒 left/top 使用floor向下取整,与 AE 的像素级裁剪原点保持一致(避免负小数边界下产生 1px 偏差)。anchorX/anchorY,使播放端能在 layer space 正确定位占位图。验证
-DPAG_BUILD_TESTS=ON编译PAGFullTest通过。Resolves discussion #3578