AEJoy —— 开发第一个 After Effects 插件

「这是我参与11月更文挑战的第 26 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 43 篇文章

目标

开发我的第一个 After Effects 插件 —— 变色效果。

Plugin.gif

环境

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” 并构建它。

image.png

如果出现错误 “C2220: warning treated as error - no 'object' file generated”,修改项目配置——不将警告视为错误

image.png

放置插件到 AE 目录

把 “SDK_Noise.aex" 文件放到 "C:\Program Files\Adobe\Adobe After Effects CC 2019\Support Files" 目录。

添加插件效果

打开 AE 项目,点击 Effect>Sample Plug-ins>SDK_Noise

image.png

一个噪声效果被添加到视频中。

image.png

拷贝模板

从 “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_ABOUTPF_Cmd_PARAMS_SETUPPF_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",
};
复制代码

image.png

打开 About 对话框,就会显示如下信息

image.png

参数设置

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 中创建用于传递参数数据 levelcolor 的新结构体。

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
        &params[COLORCHANGE_INPUT]->u.ld, // src
        NULL,                             // area - null for all pixels
        (void *)&paramDataP,              // refcon - your custom data pointer
        MyColorChangeFunc16,              // pixel function pointer
        output));
}
else
{
    ERR(suites.Iterate8Suite1()->iterate(
        in_data,
        0,                                // progress base
        linesL,                           // progress final
        &params[COLORCHANGE_INPUT]->u.ld, // src
        NULL,                             // area - null for all pixels
        (void *)&paramDataP,              // 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”

Guess you like

Origin juejin.im/post/7035378840809504804