sampleImage: pixel como dado
sampleImage()lê o valor RGBA de qualquer pixel de qualquer layer, em qualquer ponto do tempo. É uma das funções mais poderosas e subutilizadas do after.
A maioria das pessoas nunca chega nela, ou chega uma vez, fica com medo da performance e abandona. É um erro. Quando você entende o que ela faz, abre uma categoria inteira de lógica visual que não existe em nenhum outro plugin.
Sintaxe
layerRef.sampleImage(point, radius, postEffect, t)| Parâmetro | Tipo | Padrão | O que é |
|---|---|---|---|
point | [x, y] | ponto no layer space do layer sendo amostrado | |
radius | [w, h] | [0.5, 0.5] | área de amostragem (média dos pixels cobertos) |
postEffect | boolean | true | amostrar antes ou depois de masks e effects |
t | number | time | qual frame amostrar |
Layer space, não comp space
O point precisa estar em layer space, não em comp space. Quase sempre você vai precisar de fromWorld() ou fromComp() pra converter antes de passar pra função.
target = thisComp.layer("background");ponto = target.fromComp(thisLayer.toComp(anchorPoint));target.sampleImage(ponto, [1, 1], true, time)Jogar comp coordinates direto sem converter amostra o ponto errado.
Caso clássico: colisão de alpha (Dan Ebberts)
Detectar colisão real entre dois layers pelo alpha, não pelo bounding box. A ideia: encontrar um único pixel onde ambos têm alpha > 0 no mesmo ponto do mundo.
// aplica na opacity do Layer Afunction getBB(L) { var c1 = L.toWorld([0, 0]); var c2 = L.toWorld([L.width, 0]); var c3 = L.toWorld([L.width, L.height]); var c4 = L.toWorld([0, L.height]); return [ Math.min(c1[0], c2[0], c3[0], c4[0]), Math.min(c1[1], c2[1], c3[1], c4[1]), Math.max(c1[0], c2[0], c3[0], c4[0]), Math.max(c1[1], c2[1], c3[1], c4[1]) ];}
var A = thisLayer;var B = thisComp.layer("Colidível");
var bbA = getBB(A);var bbB = getBB(B);
var ix1 = Math.max(bbA[0], bbB[0]);var iy1 = Math.max(bbA[1], bbB[1]);var ix2 = Math.min(bbA[2], bbB[2]);var iy2 = Math.min(bbA[3], bbB[3]);
if (ix2 < ix1 || iy2 < iy1) { 30; // sem overlap = sem contato} else { var colidiu = false; for (var px = ix1; px <= ix2 && !colidiu; px++) { for (var py = iy1; py <= iy2 && !colidiu; py++) { var alphaA = A.sampleImage(A.fromWorld([px, py]))[3]; var alphaB = B.sampleImage(B.fromWorld([px, py]))[3]; if (alphaA > 0 && alphaB > 0) colidiu = true; } } colidiu ? 100 : 30;}Opacity vai pra 100 quando em contato real pixel a pixel. Funciona com formas irregulares, texto e elementos com transparência.
Luminância como dado
O conceito moderno: uma animação em preto e branco funciona como mapa de animação.
var ctrl = thisComp.layer("MAP_CONTROL");var pt = ctrl.fromComp(toComp(anchorPoint));var pixel = ctrl.sampleImage(pt, [4, 4], true, time);
// luminância normalizada 0-1var lum = 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2];
// pixel branco = wiggle forte, pixel preto = paradowiggle(3, lum * 80)Anima o layer de controle e você tem uma “frente de animação” que ativa elementos proceduralmente conforme passa por eles.
Cor como estado: elemento que se adapta ao fundo
// aplica no Fill Color de uma shape ou textovar bg = thisComp.layer("BG");var pt = bg.fromComp(toComp(anchorPoint));var cor = bg.sampleImage(pt, [5, 5], true, time);
// luminância do pixel embaixovar lum = 0.299 * cor[0] + 0.587 * cor[1] + 0.114 * cor[2];
// fundo escuro = elemento branco, fundo claro = elemento pretolum > 0.5 ? [0, 0, 0, 1] : [1, 1, 1, 1]Radius [5, 5] faz média de 10x10px, evita flicker de pixel isolado.
Alpha como gatilho: ativa quando toca
// aplica em Scalevar zona = thisComp.layer("Zona_Ativa");var pt = zona.fromWorld(toWorld(anchorPoint));var alpha = zona.sampleImage(pt, [3, 3], true, time)[3];
var contato = alpha > 0.1; // threshold de 10% pra evitar antialiasing
// transição suavevar scaleMin = [100, 100];var scaleMax = [125, 125];[ ease(alpha, 0, 1, scaleMin[0], scaleMax[0]), ease(alpha, 0, 1, scaleMin[1], scaleMax[1])]RGB como três switches
// layer de controle: vermelho ativa A, verde ativa B, azul ativa Cvar ctrl = thisComp.layer("RGB_SWITCH");var pt = ctrl.fromComp(toComp(anchorPoint));var c = ctrl.sampleImage(pt, [2, 2], true, time);
if (c[0] > 0.5) "STATE_A"else if (c[1] > 0.5) "STATE_B"else if (c[2] > 0.5) "STATE_C"else "IDLE"Um único precomp pintado com cores sólidas funciona como sistema de estados pra toda a comp.
Performance
sampleImage é cara
Cada chamada processa pixels e converte espaços de coordenadas.
- Radius grande: mais lento, mais estável. Radius
[1,1]amostra um pixel só, suscetível a ruído.[5,5]faz média de 10x10px. postEffect = falseé mais rápido. Usa quando o layer de controle é uma forma simples.- Evita em loops grandes — a versão de colisão do Dan varrendo toda a intersecção é O(n²) na pior hipótese.
- Se vários layers precisam do mesmo dado, calcula uma vez num slider de nulo intermediário e referencia esse valor.
// nulo intermediário pra evitar recalcular em cada layer// no efeito Slider Control do nulo "SAMPLER":var ctrl = thisComp.layer("MAP_CONTROL");var pt = ctrl.fromComp([thisComp.width/2, thisComp.height/2]);var px = ctrl.sampleImage(pt, [5, 5], true, time);0.299 * px[0] + 0.587 * px[1] + 0.114 * px[2]
// em qualquer outro layer:var lum = thisComp.layer("SAMPLER").effect("Lum")("Slider");wiggle(2, lum * 100)