First After Effects Effects-Plug-In Development

GOAL

Develop my first After Effects Plugin, ColorChange effect.

Environment

Adobe CC2019 v16.0.0
Windows10 with environment for C++ compile
Microsoft Visual C++ 2017 ver15.8.4

Method

Environment setup

Set up the environment for After Effects plugin with reference to “The Environment for After Effects Plugin Development.”

Copy Template

Copy Skeleton template from “AfterEffectsSDK\Examples\Template\Skeleton” , with directory hierarchies of “Skeleton”, “Headers”, “Resources” and “Util” preserved .

Rename file names and replace “Skeleton” with “Color Change” in it. DON’T rename “SkeletonPiPL.rc” because binary .rc file is automatically generated from .r file (AE Plugin SDK Guide “PiPL Resources”).

EffectMain Function

Main function is defined in SkeletonPiPL.r as follows.

#ifdef AE_OS_WIN
	#ifdef AE_PROC_INTELx64
		CodeWin64X86 {"EffectMain"},
	#endif
#else
	#ifdef AE_OS_MAC
		CodeMacIntel64 {"EffectMain"},
	#endif
#endif

EffectMain is the Function accepts PF_Cmd cmd as a parameter and call functions using cmd as a selector such as “PF_Cmd_ABOUT”, “PF_Cmd_PARAMS_SETUP” and “PF_Cmd_RENDER”. Refer to “AE Plugin SDK Guide command selectors.”

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 Function

About is the function to display a dialog describing the plug-in. Change TableString in ColorChange_Stgrings.cpp as follows.

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",
};
Open dialog
The message is displayed

GlobalSetup

Now GlobalSetup function doesn’t need any change.

ParamsSetup

ParamsSetup is the function to setup UI, describe your parameters and register them.

I renamed “SKELETON” to “COLORCHANGE”, delete “GAIN”parameter and added “LEVEL” parameter. Then reset values as follows in 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,
};

Accordingly, rename constant names in ColorChange.cpp.

Render

Render function is the function to render the effect into the output, based on the input and parameters.

GainInfo

GainInfo is the structure to handle parameter “GAIN”. Create new structure for passing parameter data “level” and “color” in ColorChange.h.

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;
iterate

The iterate function scan input flame and calculate output frame as pixel to pixel operation, pixel function. In this case, the pixel function is “MySimpleGainFunc16” or “MySimpleGainFunc8”. Rename and change them into “MyColorChangeFunc16” and “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));	
}
MyColorChangeFunc

I changed the pixel function as follows.

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;
}

Build and Install

Build solution and put generated .aex file into Adobe AE directory such as “C:\Program Files\Adobe\Adobe After Effects CC 2019\Support Files.”

Appenedix

2020/09/06 Changed source codes to support 16bpc color. Thank you for comments and pointing it out.
Please take a look at the article “How to adapt your AE plugin for 8, 16, 32bit color” as well.