AEJoy —— 如何让你的 AE 插件 适应每通道 8,16,32 位颜色

761 阅读4分钟

「这是我参与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)之间的区别的图像。

color_depth01.png

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_PixelPF_Pixel8 中是 char 类型(8位),在 PF_Pixel16 中是 unsigned short 类型(16位),在 PF_PixelFloatPF_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_flagsPF_OutFlag_DEEP_COLOR_AWARE

为了使你的效果支持 32bpc 颜色,设置 out_data->out_flags2PF_OutFlag2_FLOAT_COLOR_AWAREPF_OutFlag2_SUPPORTS_SMART_RENDER

image.png

设置像素格式

在 CS6 之前,PF_CHECKOUT_PARAM() 只返回 8 位 ARGB 缓冲区,不管当前用于渲染的像素格式是什么。从 CS6 开始,一个效果可以选择获得与渲染请求相同格式的帧,不管它是 32 位浮点,还是 YUV 等。

引用自 After Effects SDK Guide

image.png

渲染

在渲染函数中,迭代函数作为像素对像素的操作,会扫描输入帧并计算输出帧,像素函数。根据目标像素的 PrPixelFormat 分别选择迭代函数。具体查看文末的 “迭代过程中的进度” 的详细信息。

image.png

智能渲染(Smart Render)

在智能渲染函数中,像素函数和渲染函数的(像素函数)是一样的,都是根据 PrPixelFormat 单独选择的。

image.png

下面是 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 中。

image.png

具体区别如下

*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));