As funções PDF são um dos cantos menos comentados da especificação. A maioria dos desenvolvedores se depara com elas uma única vez, como o elemento necessário para que um sombreamento axial do Tipo 2 realize a transição entre duas cores, e nunca mais volta a olhar. Isso é uma pena, porque o mecanismo de funções é um pequeno avaliador de uso geral que o formato reutiliza para sombreamentos, funções de transferência, funções de ponto de meio-tom, tonalidades de separação e curvas de transferência de máscara de transparência (soft-mask). Dos quatro tipos de funções, a Tipo 0 é a mais poderosa e a menos compreendida. Trata-se de uma função amostrada: uma grade multidimensional de valores de saída na qual o leitor realiza a interpolação. Como a grade pode conter quaisquer números que você inserir nela, uma função Tipo 0 pode expressar um mapeamento não linear arbitrário, que é o formato exato de uma tabela de pesquisa de cores (color lookup table - LUT).
Este artigo descreve o dicionário Tipo 0 conforme a ISO 32000-1 o define na seção §7.10.2 e, em seguida, mostra os dois casos de maior relevância em um fluxo de documentos: uma LUT de correção de cor de três entradas (RGB para RGB) e uma transformação de tonalidade de cor especial (spot-color) de uma entrada. O mesmo construtor de função amostrada serve a ambos, e a diferença entre eles reside unicamente em quantas entradas a grade possui.
Uma função amostrada é uma grade na qual o leitor faz a interpolação
Uma função Tipo 0 mapeia um vetor de m entradas para um vetor de n saídas armazenando amostras em uma grade regular e fazendo a interpolação entre elas. A ISO 32000-1 §7.10.2 lista as chaves que descrevem essa grade. /Domain contém dois números por entrada, os limites inferior e superior de cada eixo de entrada. /Range contém dois números por componente de saída. /Size é um array de m inteiros que fornece o número de amostras ao longo de cada eixo de entrada, de modo que uma grade que tem doze amostras de largura em três dimensões possui /Size [12 12 12] e armazena 1.728 pontos de grade. /BitsPerSample define a precisão de cada valor armazenado; o HotPDF aceita 1, 2, 4, 8, 12, 16, 24 e 32 bits, correspondendo aos valores permitidos pela Tabela 38.
O fluxo de amostras é lido em uma ordem fixa. A primeira dimensão de entrada varia mais rapidamente, depois a segunda, e assim por diante, e em cada ponto da grade os n componentes de saída são armazenados em ordem. Para uma tabela RGB para RGB, isso representa três bytes por ponto de grade a oito bits, organizados em saída de vermelho, saída de verde, saída de azul, percorrendo a entrada de vermelho primeiro. Duas chaves adicionais mapeiam o mundo contínuo na grade inteira. /Encode mapeia cada entrada de seu intervalo /Domain para o intervalo de índice de amostra 0 a Size[i] - 1, e /Decode mapeia os inteiros brutos armazenados de volta nos intervalos /Range. Quando você os mantém em seus padrões, uma entrada que abrange [0 1] cai diretamente na grade completa e um byte armazenado de 255 decodifica-se para o valor máximo de seu intervalo de saída, que é exatamente o que uma LUT de cores normalizada de [0,1] precisa.
Ordem 1 contra Ordem 3
Entre os pontos da grade, o leitor precisa fazer a interpolação, e a chave /Order define como isso é feito. /Order 1 representa a interpolação multilinear: linear em um eixo, bilinear em dois, trilinear em três. É rápida, representa exatamente o que o hardware da maioria dos visualizadores faz e, para uma transformação de cor suave, costuma ser idêntica a qualquer método mais avançado. /Order 3 solicita a interpolação por spline cúbico, que ajusta uma curva mais suave através das amostras ao custo de mais processamento e uma região de suporte mais ampla em torno de cada ponto avaliado.
O custo-benefício está na densidade da grade contra a suavidade da curva. A ordem cúbica justifica seu uso quando a grade é esparsa e o mapeamento possui curvatura visível, porque uma linha reta entre duas amostras distantes pode achatar uma curva de tons de uma maneira perceptível ao olho em gradientes. Uma vez que a grade é densa, os segmentos são curtos o suficiente para que a interpolação linear acompanhe a curva de perto, e a cúbica acrescenta muito pouco. Uma regra prática é recorrer a /Order 3 apenas com grades pequenas ou transformações acentuadas; caso contrário, mantenha o padrão linear. Observe que /Order se aplica apenas a funções Tipo 0, e o HotPDF rejeita qualquer valor diferente de 1 ou 3.
A LUT 3D: três entradas, três saídas
Uma correção de cor RGB para RGB é o caso clássico de uso de uma grade de três entradas, a tradicional LUT 3D usada em gradação de cores e correspondência de dispositivos. Cada eixo do cubo representa um canal de entrada, cada ponto da grade armazena o trio RGB corrigido para aquela coordenada de entrada, e o leitor interpola trilinearmente as amostras de canto em torno de qualquer cor recebida. Três entradas são indispensáveis aqui porque o vermelho corrigido pode depender do verde e do azul de entrada, e não apenas do vermelho de entrada; uma curva por canal não consegue expressar a interferência entre canais (crosstalk), mas um cubo consegue.
O HotPDF cria o fluxo Tipo 0 por meio de RegisterSampledFunction, que recebe o /Domain, /Range, /Size, /BitsPerSample e os bytes da amostra diretamente e retorna o objeto de função. Para um cubo normalizado padrão, você passa limites [0,1] em todos os três eixos de entrada e em todas as três saídas, um tamanho N x N x N e a tabela de amostras linearizada. O construtor valida se a contagem de bytes corresponde à grade: para uma profundidade alinhada por bytes, ele espera OutputCount x (BitsPerSample div 8) x o produto dos tamanhos, e gera um erro se o array tiver o tamanho incorreto, de modo que um cálculo errado de passo falha nitidamente no registro, em vez de se renderizar de forma corrompida posteriormente.
const
N = 17; // 17 x 17 x 17 cube, the common ICC LUT resolution
var
LutFn: THPDFStreamObject;
Samples: TBytes;
begin
// Fill Samples with N*N*N grid points, 3 bytes each (R,G,B output),
// red input varying fastest. Build the corrected triple for each
// grid coordinate with your ICC-managed conversion, then store it.
SetLength(Samples, N * N * N * 3);
BuildCorrectedCube(Samples, N); // your color-managed fill
LutFn := Pdf.RegisterSampledFunction(
[0,1, 0,1, 0,1], // /Domain: three input axes on [0,1]
[0,1, 0,1, 0,1], // /Range: three output channels on [0,1]
[N, N, N], // /Size: the cube resolution per axis
8, // /BitsPerSample
Samples,
1); // /Order 1 = trilinear
end;
A precisão colorimétrica do cubo reside em como você o preenche, e não na função PDF. O caminho correto é calcular cada ponto da grade por meio de uma conversão gerenciada por ICC, o mesmo motor que executa uma prova virtual (soft-proof), para que os números na grade façam sentido em relação a um perfil de origem e destino definidos. Registre os perfis que limitam a conversão com RegisterICCProfile, que grava um espaço de cores ICCBased (1, 3 ou 4 componentes) e retorna um nome de recurso que você pode anexar ao conteúdo que a LUT alimenta. A função Tipo 0 carrega a tabela de interpolação; o perfil ICC carrega o significado dos pontos extremos.
O caso 1D: uma transformação de tonalidade de cor especial (spot-color)
Os espaços de cores de separação (Separation color spaces) apoiam-se no mesmo mecanismo para um trabalho totalmente diferente. Um espaço de separação, definido na ISO 32000-1 §8.6.6.4, representa um único corante, uma tinta especial como Pantone ou um verniz, associando um nome a uma transformação de tonalidade (tint transform): uma função que mapeia o valor unidimensional da tonalidade, de 0 para nenhuma tinta a 1 para tinta total, em um espaço de cor alternativo que o dispositivo possa renderizar de fato, geralmente CMYK. Essa transformação de tonalidade costuma ser uma função Tipo 0, e agora a grade tem exatamente um eixo de entrada.
Este é o contraste claro com a LUT 3D. Uma tinta especial é um grau de liberdade, portanto sua transformação de tonalidade precisa de uma entrada e a grade é uma linha de amostras, cada uma contendo o valor CMYK (or outro alternativo) naquele nível de tonalidade. O cubo RGB precisa de três entradas porque seu domínio é tridimensional e os canais interagem. Trata-se do mesmo tipo de função, com as mesmas regras de interpolação, mas com dimensionalidade diferente; a especificação reutiliza um avaliador e deixa o /Size decidir se você está percorrendo uma linha, um plano ou um cubo. O HotPDF envolve toda a separação em RegisterSeparationLUT, que cria internamente a transformação de tonalidade Tipo 0 de uma entrada a partir de um array de bytes simples e retorna o nome do recurso do espaço de cores.
var
SpotCS: AnsiString;
begin
// Four CMYK output bytes per tint grid point, tint domain [0..1].
// Here 0% ink -> all zero, 100% ink -> a rich spot build,
// with two interior steps; the tint transform interpolates between.
SpotCS := Pdf.RegisterSeparationLUT(
'PANTONE 286 C', // colorant name
'DeviceCMYK', // alternate color space
[ 0, 0, 0, 0, // tint 0.00 -> 0,0,0,0
90, 60, 0, 0, // tint 0.33
100, 80, 0, 10, // tint 0.66
100, 72, 0, 18]); // tint 1.00 -> full ink build
// Use SpotCS with SetFillColorSpace / SetFillColor on a page.
end;
A contagem de amostras precisa ser um número inteiro de pontos de grade: um múltiplo positivo da contagem de componentes do espaço alternativo, e pelo menos dois pontos para que haja um segmento para interpolar. Passar três bytes por ponto contra um CMYK alternativo faz a chamada rejeitá-lo, a mesma validação defensiva que o construtor 3D aplica, que é o que você deseja de uma função que, caso contrário, falharia silenciosamente no momento da impressão.
Onde o mesmo mecanismo aparece novamente
Uma vez que você enxerga a Tipo 0 como uma tabela de interpolação genérica, outros dois recursos de controle de dispositivo deixam de parecer casos especiais. Uma função de transferência ajusta os valores dos componentes no caminho para o dispositivo de saída, e é apenas uma função por canal; o HotPDF a registra como um ExtGState por meio de RegisterTransferFunctionState, que aceita uma função combinada ou um array de funções por canal. Como essas são funções normais, você pode passar o próprio THPDFStreamObject retornado por RegisterSampledFunction e gerenciar uma curva de transferência a partir de uma tabela amostrada, em vez de uma fórmula.
var
ToneFn: THPDFStreamObject;
GsName: AnsiString;
begin
// A single-input, single-output sampled tone curve on [0,1].
ToneFn := Pdf.RegisterSampledFunction(
[0,1], [0,1], [256], 8, ToneCurveBytes, 1);
// Apply it to all channels as a combined /TR2 transfer function.
GsName := Pdf.RegisterTransferFunctionState(ToneFn, []);
// Select GsName on the page before drawing the affected content.
end;
A geração de preto (black generation) e a remoção de cor sobjacente (undercolor removal) pertencem à mesma família. Quando um dispositivo converte RGB para CMYK, ele decide quanta parte do componente cinza será impressa como tinta preta, e a especificação expressa essa decisão como uma função, especificamente nas entradas /BG2 e /UCR2 de um dicionário de estado gráfico, onde cada uma é uma curva de entrada única que mapeia o valor cinza calculado para uma quantidade de preto. Essas também são funções Tipo 0 quando você deseja uma curva medida em vez de uma analítica, criadas da mesma forma por meio de RegisterSampledFunction e inseridas no estado gráfico. A lição a ser lembrada é que a função PDF nunca é o local onde ocorre o gerenciamento de cores; ela é la tabela de pesquisa que carrega uma decisão feita com um mecanismo de cores real, e a Tipo 0 é o único tipo de função flexível o suficiente para carregar qualquer decisão.
Para uma visão mais ampla de como fontes, imagens e recursos de cores são emitidos em um documento finalizado, consulte nosso passo a passo sobre saída de relatórios com fontes e imagens. Quando o resultado precisa passar por uma verificação de preflight de arquivamento ou impressão, as regras de espaço de cores e intenção de saída abordadas no guia de validação de PDF/A, PDF/X e PDF/UA regem quais dessas funções são permitidas e como a cor do dispositivo deve ser identificada. Tudo isso é fornecido no HotPDF Component para Delphi e C++Builder, junto com as APIs de sombreamento, ICC e separação que se baseiam no mesmo núcleo Tipo 0.