Skip to content

Fix incorrect ImageBytes size and anchor for masked video layers in AE exporter#3589

Merged
shlzxjp merged 3 commits into
mainfrom
bugfix/markffan_video_mask
Jul 20, 2026
Merged

Fix incorrect ImageBytes size and anchor for masked video layers in AE exporter#3589
shlzxjp merged 3 commits into
mainfrom
bugfix/markffan_video_mask

Conversation

@CodeJhF

@CodeJhF CodeJhF commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

背景

参见 discussion #3578

当视频图层(video layer)在 After Effects 中添加了蒙版(mask)时,导出的占位图(ImageBytes)尺寸和锚点不正确,导致播放端图片定位错位。根因有两处:

  1. 渲染时间不正确AEGP_NewFromLayer 默认使用当前时间指示器位置,可能落在图层可见区间之外,导出的首帧内容错误(尤其存在 footage slip 时)。
  2. 蒙版裁剪未处理:当视频图层带蒙版时,AE 的 Layer Render 会把渲染结果裁剪到蒙版包围盒,导致导出的 width/height 变成裁剪后的尺寸,且未记录裁剪偏移,锚点随之错位。

改动

仅涉及 exporter/src/export/data/ImageBytes.cpp

  • 在渲染视频图层尺寸与像素前,显式将渲染时间设置为图层 in-point(layer time),确保渲染的是首个可见帧(含 footage slip)。
  • 新增 GetVideoLayerMaskedOffset,通过 AEGP_GetLayerMaskedBounds 获取蒙版包围盒在 layer space 的偏移。对包围盒 left/top 使用 floor 向下取整,与 AE 的像素级裁剪原点保持一致(避免负小数边界下产生 1px 偏差)。
  • 视频图层改用源素材尺寸作为逻辑内容尺寸;仅当渲染尺寸与源尺寸不一致(即发生蒙版裁剪)时,才计算并把蒙版偏移记入 anchorX/anchorY,使播放端能在 layer space 正确定位占位图。

验证

  • 本地以 -DPAG_BUILD_TESTS=ON 编译 PAGFullTest 通过。

Resolves discussion #3578

@codecov-commenter

codecov-commenter commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.58%. Comparing base (0be6f16) to head (9d6750d).
⚠️ Report is 14 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread exporter/src/export/data/ImageBytes.cpp Outdated
// 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 = {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在 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 无关,建议修正措辞。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释措辞已按建议修正,去掉了误导性的「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。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释修正已确认。关于保留动态查询 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 = {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的 AEGP_GetLayerInPoint/AEGP_GetLayerMaskedBounds(以及上方两处 AEGP_SetTime)均未检查返回的 A_Err,也缺少零值防护。若 AEGP_GetLayerMaskedBounds 失败,maskedBounds 保持 {}floor(0)=0 会让偏移静默归零、退化回原来的错位问题且无任何提示。项目已有同类防护先例(AEHelper.cpp 中 GetLayerStartTimeinPoint.scale == 0 做了保护),建议这里也补上失败/零值早退。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修复。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)更安全。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已确认修复。err 检查 + inPoint.scale==0 早退与项目既有惯例(StreamValue.h、Marker.cpp)一致;SetTime 不加守卫的取舍解释也合理。此问题已解决。

Comment thread exporter/src/export/data/ImageBytes.cpp Outdated
}
A_Time inPointLayer = {};
Suites->LayerSuite6()->AEGP_GetLayerInPoint(layerHandle, AEGP_LTimeMode_LayerTime, &inPointLayer);
Suites->LayerRenderOptionsSuite2()->AEGP_SetTime(renderOptions, inPointLayer);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这 3 行「取 InPoint + SetTime」与 GetVideoLayerRenderImageSize 中完全重复,且与 GetVideoLayerMaskedOffset 里第三次取 InPoint 逻辑重叠。建议抽成一个 helper(如 SetVideoLayerRenderTime(renderOptions, layerHandle))复用,项目已有 SetRenderTime 先例。另外同款 3 行在 GetVideoLayerRenderImageSize 有详细注释、这里却没有,两处注释不一致,抽出后可统一。

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修复。抽出了 helper SetVideoLayerRenderTime(layerHandle, renderOptions),在 GetVideoLayerRenderImageSize 和 GetVideoLayerRenderImage 两处复用,消除了重复的「取 InPoint + SetTime」代码,注释也统一到 helper 上。

GetVideoLayerMaskedOffset 里的第三处取 InPoint 未并入该 helper:它用的是 AEGP_LTimeMode_CompTime,且取到的 inPoint 是作为参数喂给 AEGP_GetLayerMaskedBounds 查询遮罩包围盒,与渲染时间设置(LayerTime)用途不同,故保持独立。

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已确认修复。helper 抽取到位、两处复用、注释统一;第三处保持独立的解释合理。此问题已解决。

AEGP_ItemH itemHandle = GetLayerItemH(layerHandle);
auto dimensions = GetItemDimensions(itemHandle);
sourceWidth = dimensions.width();
sourceHeight = dimensions.height();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

健壮性建议: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 的既有防御风格保持一致。

@shlzxjp
shlzxjp merged commit 2929925 into main Jul 20, 2026
9 checks passed
@shlzxjp
shlzxjp deleted the bugfix/markffan_video_mask branch July 20, 2026 12:18
CodeJhF added a commit that referenced this pull request Jul 21, 2026
CodeJhF added a commit that referenced this pull request Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants