Skip to main content

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âmetroTipoPadrãoO 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)
postEffectbooleantrueamostrar antes ou depois de masks e effects
tnumbertimequal 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 A
function 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-1
var lum = 0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2];
// pixel branco = wiggle forte, pixel preto = parado
wiggle(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 texto
var bg = thisComp.layer("BG");
var pt = bg.fromComp(toComp(anchorPoint));
var cor = bg.sampleImage(pt, [5, 5], true, time);
// luminância do pixel embaixo
var lum = 0.299 * cor[0] + 0.587 * cor[1] + 0.114 * cor[2];
// fundo escuro = elemento branco, fundo claro = elemento preto
lum > 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 Scale
var 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 suave
var 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 C
var 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)