「这是我参与11月更文挑战的第 26 天,活动详情查看:2021最后一次更文挑战」。
参加该活动的第 43 篇文章
目标
开发我的第一个 After Effects 插件 —— 变色效果。
环境
Adobe CC2019 v16.0.0
Windows10 with environment for C++ compile
Microsoft Visual C++ 2017 ver15.8.4
具体步骤
环境搭建
下载 After Effects SDK
访问 Adobe 网站,单击 “获取 SDKs” 按钮获取 After Effects SDK。选择 AE 版本的 After Effects Plug-in SDK。
将创建路径设置为环境变量
添加环境变量 “AE_PLUGIN_BUILD_DIR” 并设置输出目录。如果它不存在,则会报错误 “fatal error LNK1104: cannot open file ***.aex”
。
创建样例插件,测试环境配置成功与否
解压 “After+Effects+CC+16.0+Win+SDK.zip”,并将解压后的目录“AfterEffectsSDK”放到合适的位置。
打开 AfterEffectsSDK\Examples\BuildAll 例子。sln 将启动 Visual Studio 。
右键单击 “SDK Noise” 并构建它。
如果出现错误 “C2220: warning treated as error - no 'object' file generated”
,修改项目配置——不将警告视为错误。
放置插件到 AE 目录
把 “SDK_Noise.aex" 文件放到 "C:\Program Files\Adobe\Adobe After Effects CC 2019\Support Files" 目录。
添加插件效果
打开 AE 项目,点击 Effect>Sample Plug-ins>SDK_Noise
一个噪声效果被添加到视频中。
拷贝模板
从 “AfterEffectsSDK\Examples\template\Skeleton” 复制骨架模板,保留“Skeleton”、“Headers”、“Resources” 和 “Util” 的目录层次结构。
重命名文件名,将 “Skeleton” 替换为 “Color Change” 。不要重命名 “SkeletonPiPL.rc”。因为二进制的 .rc 文件是自动从 .r 文件生成的(AE插件 SDK 指南 “PiPL资源” )。
EffectMain 函数
在 SkeletonPiPL.r 中定义了 Main 函数,如下。
#ifdef AE_OS_WIN
#ifdef AE_PROC_INTELx64
CodeWin64X86 {"EffectMain"},
#endif
#else
#ifdef AE_OS_MAC
CodeMacIntel64 {"EffectMain"},
#endif
#endif
EffectMain 是接受 PF_Cmd cmd
作为一个参数的函数,并使用(如PF_Cmd_ABOUT
,PF_Cmd_PARAMS_SETUP
和 PF_Cmd_RENDER
的) cmd 作为一个选择器来调用该函数。参考 “AE 插件 SDK 指南命令选择器”。
PF_Err EffectMain(
PF_Cmd cmd,
PF_InData *in_data,
PF_OutData *out_data,
PF_ParamDef *params[],
PF_LayerDef *output,
void *extra)
{
PF_Err err = PF_Err_NONE;
try {
switch (cmd) {
case PF_Cmd_ABOUT:
err = About(in_data,
out_data,
params,
output);
break;
case PF_Cmd_GLOBAL_SETUP:
err = GlobalSetup(in_data,
out_data,
params,
output);
break;
case PF_Cmd_PARAMS_SETUP:
err = ParamsSetup(in_data,
out_data,
params,
output);
break;
case PF_Cmd_RENDER:
err = Render(in_data,
out_data,
params,
output);
break;
}
}
catch(PF_Err &thrown_err){
err = thrown_err;
}
return err;
}
About 函数
About 函数用于显示描述插件的对话框。在 ColorChange_Stgrings.cpp 中修改 TableString
如下。
TableString g_strs[StrID_NUMTYPES] = {
StrID_NONE, "",
StrID_Name, "ColorChange",
StrID_Description, "Change comp color to specified color",
StrID_Gain_Param_Name, "Gain",
StrID_Color_Param_Name, "Color",
};
打开 About 对话框,就会显示如下信息
参数设置
ParamsSetup 是设置 UI、描述参数和注册它们的函数。
我将 SKELETON
重命名为 COLORCHANGE
,删除 GAIN
参数,添加 LEVEL
参数。然后在 ColorChange.h 中重置值如下。
/* Parameter defaults */
#define COLORCHANGE_LEVEL_MIN 0
#define COLORCHANGE_LEVEL_MAX 100
#define COLORCHANGE_LEVEL_DFLT 50
enum {
COLORCHANGE_INPUT = 0,
COLOECHANGE_LEVEL,
COLORCHANGE_COLOR,
COLORCHANGE_NUM_PARAMS
};
enum {
LEVEL_DISK_ID = 1,
COLOR_DISK_ID,
};
对应地,在 ColorChange.cpp 中也重命名常量名称。
渲染
渲染函数是根据输入和参数将效果渲染到输出的函数。
1. GainInfo
GainInfo
是处理参数 GAIN
的结构体。在 ColorChange.h 中创建用于传递参数数据 level
和 color
的新结构体。
typedef struct ParamInfo {
PF_FpLong level;
PF_Pixel color;
PF_Pixel16 color16;
} PramInfo, *PramInfoP, **PramInfoH;
//GainInfo giP;
//AEFX_CLR_STRUCT(giP);
ParamInfo paramDataP;
AEFX_CLR_STRUCT(paramDataP);
A_long linesL = 0;
linesL = output->extent_hint.bottom - output->extent_hint.top;
paramDataP.level = params[COLOECHANGE_LEVEL]->u.fs_d.value;
paramDataP.color = params[COLORCHANGE_COLOR]->u.cd.value;
2. iterate
迭代函数扫描输入帧并且计算输出帧作为像素对像素的操作,即像素函数。在本例中,像素函数是 “MySimpleGainFunc16” 或 “MySimpleGainFunc8” 。重命名并将它们改为 “MyColorChangeFunc16” 和 “MyColorChangeFunc8” 。
if (PF_WORLD_IS_DEEP(output))
{
paramDataP.color16.red = CONVERT8TO16(paramDataP.color.red);
paramDataP.color16.green = CONVERT8TO16(paramDataP.color.green);
paramDataP.color16.blue = CONVERT8TO16(paramDataP.color.blue);
paramDataP.color16.alpha = CONVERT8TO16(paramDataP.color.alpha);
ERR(suites.Iterate16Suite1()->iterate(
in_data,
0, // progress base
linesL, // progress final
¶ms[COLORCHANGE_INPUT]->u.ld, // src
NULL, // area - null for all pixels
(void *)¶mDataP, // refcon - your custom data pointer
MyColorChangeFunc16, // pixel function pointer
output));
}
else
{
ERR(suites.Iterate8Suite1()->iterate(
in_data,
0, // progress base
linesL, // progress final
¶ms[COLORCHANGE_INPUT]->u.ld, // src
NULL, // area - null for all pixels
(void *)¶mDataP, // refcon - your custom data pointer
MyColorChangeFunc8, // pixel function pointer
output));
}
3. MyColorChangeFunc
我将像素函数更改如下。
static PF_Err MyColorChangeFunc8(
void *refcon,
A_long xL,
A_long yL,
PF_Pixel8 *inP,
PF_Pixel8 *outP)
{
PF_Err err = PF_Err_NONE;
ParamInfo *paramDataP = reinterpret_cast<ParamInfo *>(refcon);
PF_FpLong levelF = 0;
float red_diff, green_diff, blue_diff;
if (paramDataP)
{
levelF = paramDataP->level / 100.0;
red_diff = (paramDataP->color.red - inP->red) * levelF;
green_diff = (paramDataP->color.green - inP->green) * levelF;
blue_diff = (paramDataP->color.blue - inP->blue) * levelF;
outP->alpha = inP->alpha;
outP->red = MIN(inP->red + red_diff, PF_MAX_CHAN8);
outP->green = MIN(inP->green + green_diff, PF_MAX_CHAN8);
outP->blue = MIN(inP->blue + blue_diff, PF_MAX_CHAN8);
}
return err;
}
static PF_Err MyColorChangeFunc16(
void *refcon,
A_long xL,
A_long yL,
PF_Pixel16 *inP,
PF_Pixel16 *outP)
{
PF_Err err = PF_Err_NONE;
ParamInfo *paramDataP = reinterpret_cast<ParamInfo *>(refcon);
PF_FpLong levelF = 0;
float red_diff, green_diff, blue_diff;
if (paramDataP)
{
levelF = paramDataP->level / 100.0;
red_diff = (paramDataP->color16.red - inP->red) * levelF;
green_diff = (paramDataP->color16.green - inP->green) * levelF;
blue_diff = (paramDataP->color16.blue - inP->blue) * levelF;
outP->alpha = inP->alpha;
outP->red = MIN(inP->red + red_diff, PF_MAX_CHAN16);
outP->green = MIN(inP->green + green_diff, PF_MAX_CHAN16);
outP->blue = MIN(inP->blue + blue_diff, PF_MAX_CHAN16);
}
return err;
}
构建并安装
构建解决方案,并将生成的 .aex 文件放入 Adobe AE 目录,如 “C:\Program Files\Adobe\Adobe After Effects CC 2019\Support Files”
。