「这是我参与11月更文挑战的第 28 天,活动详情查看:2021最后一次更文挑战」。
参加该活动的第 51 篇文章
在前一篇文章 AEJoy —— 开发第一个 After Effects 插件(一) 中,我实现了一个简单的 After Effects 插件。本文是对它的补充。
什么是颜色(位)深度
在计算机中,颜色被表示为数字。在 RGB 颜色系统中,颜色表示为 (R, G, B),如 (100,120,144) 或(0.5,0.2,0.7)。在灰度模式下,颜色表示为一个值,例如从 0 到 255 的整数或从 0.0 到 1.0 的浮点数。
颜色位深度是每个值的分辨率。当颜色为每个通道 8 bpc(每通道位),R/G/B/A 用 8 位表示。当颜色为8bpp(每像素位)时,每个像素颜色用 8 位表示,R 和 G 用3位表示,B用 2 位表示。
下面是 4 bit颜色(4bpc)和 8 bit颜色(8bpc)之间的区别的图像。
AE 中的颜色位深度
在 After Effects 中,您可以使用 8-bpc、16-bpc 或 32-bpc 颜色。(详见“颜色深度和高动态范围颜色”)
-
8位: 0-255为每个通道(256 级)
-
16位: 0-32767为每个通道(32,768 级)
-
32位: 每个通道由 32 位浮点数表示的范围(4,294,967,296 级)
颜色用 8bpc 和 16bpc 颜色的整数表示。在 8bpc 和 16bpc 中,float 值被固定为 [0.0 - 1.0] 并转换为 [0-255] 或 [0-32767] 。
AE 插件开发中的 颜色位深度
当你开发 After Effects 插件时,你应该考虑所有颜色 8/16/32 位。
8 位
8位的 ARGB 颜色存储使用 PF Pixel8
//this is from AE_Effect.h
// Basic pixel defn's
typedef struct {
A_u_char alpha, red, green, blue;
} PF_Pixel;
typedef PF_Pixel PF_Pixel8;
typedef PF_Pixel PF_UnionablePixel;
16 位
//this is from AE_Effect.h
typedef struct {
#ifdef PF_PIXEL16_RENAME_COMPONENTS
// this style is useful for debugging code converted from 8 bit
A_u_short alphaSu, redSu, greenSu, blueSu;
#else
A_u_short alpha, red, green, blue;
#endif
} PF_Pixel16;
32 位
在 CS6 (11.0)和更高版本中支持 32 位颜色。
//this is from AE_Effect.h
typedef A_FpShort PF_FpShort;
typedef A_FpLong PF_FpLong;
typedef struct {
PF_FpShort alpha, red, green, blue;
} PF_PixelFloat, PF_Pixel32;
正如你在下面看到的,每个通道在 PF_Pixel 或 PF_Pixel8 中是 char 类型(8位),在 PF_Pixel16 中是 unsigned short 类型(16位),在 PF_PixelFloat 或 PF_Pixel32 中是 float 类型(32位)。
//picked up from A.h
typedef char A_char;
typedef float A_FpShort;
typedef unsigned short A_u_short;
AE 插件开发中颜色深度处理技巧
全局设置
设置标记位
要使效果处理 16-bpc 颜色,设置 out_data->out_flags 为 PF_OutFlag_DEEP_COLOR_AWARE 。
为了使你的效果支持 32bpc 颜色,设置 out_data->out_flags2 为 PF_OutFlag2_FLOAT_COLOR_AWARE 和 PF_OutFlag2_SUPPORTS_SMART_RENDER 。
设置像素格式
在 CS6 之前,
PF_CHECKOUT_PARAM()只返回 8 位 ARGB 缓冲区,不管当前用于渲染的像素格式是什么。从 CS6 开始,一个效果可以选择获得与渲染请求相同格式的帧,不管它是 32 位浮点,还是 YUV 等。
渲染
在渲染函数中,迭代函数作为像素对像素的操作,会扫描输入帧并计算输出帧,像素函数。根据目标像素的 PrPixelFormat 分别选择迭代函数。具体查看文末的 “迭代过程中的进度” 的详细信息。
智能渲染(Smart Render)
在智能渲染函数中,像素函数和渲染函数的(像素函数)是一样的,都是根据 PrPixelFormat 单独选择的。
下面是 switch 的的条件分支。
switch (format) {
case PF_PixelFormat_ARGB128:
iterateFloatSuite ->iterate(
in_data, 0, output_worldP->height, input_worldP,
NULL, (void*)infoP, FilterImage32, output_worldP);
break;
case PF_PixelFormat_ARGB64:
iterate16Suite ->iterate(
in_data, 0, output_worldP->height, input_worldP,
NULL, (void*)infoP, FilterImage16, output_worldP);
break;
case PF_PixelFormat_ARGB32:
iterate8Suite ->iterate(
in_data, 0, output_worldP->height, input_worldP,
NULL, (void*)infoP, FilterImage8, output_worldP);
break;
default:
err = PF_Err_BAD_CALLBACK_PARAM;
break;
}
像素函数
分别为 8/16/32 位准备了像素函数。下面的例子在 SDK_Noise.cpp 中。
具体区别如下
*inP 和 *outP
// 8bit
static PF_Err FilterImage8 (
void *refcon, A_long xL, A_long yL,
PF_Pixel8 *inP, PF_Pixel8 *outP)
// 16bit
static PF_Err FilterImage16 (
void *refcon, A_long xL, A_long yL,
PF_Pixel16 *inP, PF_Pixel16 *outP)
// 32bit
static PF_Err FilterImage32 (
void *refcon, A_long xL, A_long yL,
PF_PixelFloat *inP, PF_PixelFloat *outP)
tempF
// 8bit clamp in range [0, PF_MAX_CHAN8]
tempF = rand() % PF_MAX_CHAN8;
tempF *= (niP->valF / SLIDER_MAX);
// 16bit clamp in range [0, PF_MAX_CHAN16]
tempF = rand() % PF_MAX_CHAN16;
tempF *= (niP->valF / SLIDER_MAX);
// 32bit clamp in range [0, 1 = PF_MAX_CHAN16/PF_MAX_CHAN16]
tempF = (PF_FpShort)(rand() % PF_MAX_CHAN16);
tempF *= (PF_FpShort)(niP->valF / (SLIDER_MAX * PF_MAX_CHAN16));
outP 的通道
// 8bit clamp in the range [0, PF_MAX_CHAN8]
outP->red = MIN(PF_MAX_CHAN8, inP->red + (A_u_char) tempF);
// 16bit clamp in the range[0, PF_MAX_CHAN16]
outP->red = MIN(PF_MAX_CHAN16, inP->red + (A_u_short) tempF);
// 32bit
outP->red = (inP->red + tempF);
迭代过程中的进度
After Effects 力求尽可能地响应用户交互,即使是在渲染时。通过适当地使用PF_ITERATE() 来执行同样的操作。例如,也许你在响应 PF_Cmd_RENDER 时使用了 3 次 PF_ITERATE 函数。
在这种情况下,你可以这样开始:
lines_per_iterateL = in_data->extent_hint.top - in_data->extent_hint.bottom;
total_linesL = 3 * lines_per_iterateL;
lines_so_farL = 0;
在每次迭代之后,你再把上一步计算完成的行(lines_per_iterateL)添加到当前位置:
suites.iterate8suite()->iterate( lines_so_farL,
total_linesL,
input_worldP,
&output->extent_hint,
refcon,
WhizBangPreProcessFun,
output_worldP);
lines_so_farL += lines_per_iterateL;
ERR(PF_PROGRESS(lines_so_farL, total_linesL));
suites.iterate8suite()->iterate( lines_so_farL,
total_linesL,
input_worldP,
&output->extent_hint,
refcon,
WhizBangRenderFunc,
output_worldP);
lines_so_far += lines_per_iterateL;
ERR(PF_PROGRESS(lines_so_farL, total_linesL));
suites.iterate8suite()->iterate( lines_so_farL,
total_linesL,
input_worldP,
&output->extent_hint,
refcon,
WhizBangPostProcessFunc,
output_worldP);
ERR(PF_PROGRESS(lines_so_farL, total_linesL));