Artigo Técnico

Renderizar Cores Especiais Separation e DeviceN em Delphi

O HotPDF renderiza as cores especiais Separation e DeviceN em páginas PDF carregadas resolvendo o espaço de cores através de HPDFResolveColorSpace, avaliando a função tint-transform com HPDFEvalTintTransform e convertendo o resultado através do espaço alternativo em RGB para visualização no ecrã. Desde a versão v2.375.0, esta cadeia de processamento suporta os quatro tipos de funções PDF, incluindo o avaliador de PostScript Type 4; isto permite que documentos preparados para impressão com tintas Pantone exibam as cores corretas em vez de cores de substituição. Este artigo descreve o funcionamento do fluxo de renderização e as falhas detetadas na sua implementação

O problema manifesta-se sempre da mesma forma: um ficheiro PDF gerado por uma gráfica contém títulos com uma cor especial (spot color) específica, elementos gráficos com uma mistura DeviceN de duas tintas e texto a preto simples. No Acrobat, a visualização apresenta-se correta; contudo, na sua aplicação Delphi o título é desenhado a preto, ou não é exibido de todo, o que motiva queixas de utilizadores apontando falhas no seu software. O ficheiro está correto: a falha reside simplesmente no facto de o renderizador não suportar os espaços de cores utilizados

Por que razão as cores especiais são renderizadas a preto?

As cores especiais são desenhadas a preto, ou ocultadas, se o renderizador implementar apenas os operadores de cor do dispositivo (rg, g, k) ignorando os genéricos. A norma ISO 32000-1 §8.6 define três grupos de espaços de cores: de dispositivo (DeviceGray, DeviceRGB, DeviceCMYK), baseados em CIE (CalGray, CalRGB, Lab, ICCBased) e especiais (Indexed, Separation, DeviceN, Pattern). Os espaços exteriores ao grupo do dispositivo são selecionados com os operadores genéricos: cs e CS definem o espaço pelo nome contido no dicionário de recursos da página; sc, SC, scn e SCN fornecem os valores dos componentes. Se o renderizador omitir estes operadores, a cor mantém a definição anterior, que no início da página corresponde ao preto padrão de DeviceGray

O HotPDF adicionou os operadores genéricos ao renderizador de página na versão v2.333.0, a par de um fluxo de resolução unificado: cada entrada do recurso /ColorSpace (seja nome, array ou referência indireta) é convertida num registo THPDFColorSpace, e as solicitações de cor de preenchimento ou traço são processadas por uma chamada a HPDFResolveColor. O enumerador indica a cobertura suportada:

HotPDF: As três famílias de espaços de cor PDF da secção 8.6 do ISO 32000-1, com espaços de dispositivo pintados por rg g k e espaços baseados em CIE e especiais alcançáveis apenas através dos operadores genéricos cs CS scn SCN
Só a família de dispositivo responde aos operadores diretos rg, g e k — todos os espaços baseados em CIE e especiais dependem do conjunto genérico cs, CS, scn e SCN, e um renderizador que os salte mantém a cor anterior
type
  THPDFColorSpaceFamily = (csfDeviceGray, csfDeviceRGB, csfDeviceCMYK,
                           csfIndexed, csfCalGray, csfCalRGB, csfLab,
                           csfICCBased, csfSeparation, csfDeviceN,
                           csfUnsupported);

function HPDFResolveColorSpace(Obj: THPDFObject): THPDFColorSpace;

function HPDFResolveColor(const CS: THPDFColorSpace;
  const Comps: THPDFColorComps; CompCount: Integer): THPDFRenderColor;

Uma opção de desenho revelou-se acertada: tratar csfUnsupported como uma família válida e não como um erro de execução. Desta forma, se o renderizador não souber interpretar um determinado espaço de cores, reverte para um comportamento de recurso (fallback) em vez de interromper o desenho da página. Tal comportamento coincide com o dos visualizadores padrão e evita que um preenchimento complexo oculte as restantes informações do documento

Como as funções tint-transform convertem o valor de uma tinta em cor real?

O espaço de cores Separation contém três variáveis: o nome da tinta, o espaço de cores alternativo e a função tint-transform. O array [/Separation /PANTONE485 /DeviceCMYK f] determina que o valor 0.8 scn deve ser processado pela função f com o argumento 0.8, desenhando-se os quatro canais CMYK resultantes. O DeviceN expande este conceito para N substituindo por uma função de N argumentos de entrada. O nome da tinta tem apenas caráter informativo no ecrã; a função de transformação dita as semânticas do desenho, pelo que interpretar o espaço omitindo a função não produz resultados úteis

O método HPDFEvalTintTransform constitui o motor de cálculo desta conversão. Introduzido na versão v2.334.0, avalia funções exponenciais Type 2 (C0 + x^N * (C1 - C0) com limites de Domain e Range), funções de costura Type 3 (com sub-funções recursivas selecionadas pelos limites) e funções amostradas Type 0 com amostras de 8, 16 e 32 bits. O Type 0 baseia-se na tabela de mapeamento abordada no artigo sobre criação de LUTs de cor Type 0; no desenho, o percurso é efetuado por ordem inversa, traduzindo as amostras binárias em valores de componentes

Pipeline de transformação de tonalidade Separation no HotPDF, em que um valor de tonalidade scn se resolve através do array do espaço de cores Separation e HPDFEvalTintTransform avalia os tipos de função 0, 2, 3 e 4 em componentes CMYK alternativos que convertem para sRGB
Um único valor de tinta de scn entra no espaço Separation e o motor de transformação de tinta devolve um tuplo completo de componentes do espaço alternativo, que depois converte uma vez para o ecrã

As funções de calculadora PostScript Type 4 foram as últimas a ser implementadas. Até à versão v2.375.0, revertiam para cores neutras de recurso; a partir desta versão, o método HPDFEvalPostScriptCalculator executa o conjunto completo de operadores da norma ISO 32000-1 §7.10.5 numa pilha de operandos delimitada (operações aritméticas, lógicas, manipulação de pilhas com roll e condicionais if/ifelse). As semânticas são rigorosas: o operador round do PostScript arredonda frações de meio para o maior valor inteiro (pelo que o arredondamento simétrico Round do Delphi não pode ser utilizado); operadores trigonométricos funcionam em graus (o método atan devolve valores em [0, 360)); e exp representa a potência de dois operandos (e não o exponencial natural). Este avaliador também suporta graduações de cor baseadas em funções, o que viabilizou os perfis de cálculo no artigo sobre desenho de graduações axiais e radiais

// Avaliar a conversão tint-transform em Separation/DeviceN (Type 0/2/3/4)
function HPDFEvalTintTransform(FuncObj: THPDFObject;
  const Inputs: THPDFColorComps; InputCount: Integer;
  out AltComps: THPDFColorComps): Boolean;

// Calculadora PostScript Type 4, conjunto de operadores ISO 32000-1 7.10.5
function HPDFEvalPostScriptCalculator(const Prog: TBytes;
  const Inputs: THPDFColorComps; InputCount: Integer;
  var Outputs: THPDFColorComps; OutCount: Integer): Boolean;

A precisão decimal avaliada por software em precisão dupla (double) pode apresentar ligeiras discrepâncias de arredondamento em relação a um processador RIP ou a outras implementações de limites de Range. Tais desvios são insignificantes para visualização no ecrã e testes de regressão; contudo, se implementar fluxos de prova de cor (proofing), o processamento tint-transform representa apenas a primeira fase, exigindo um motor CMM completo a jusante

Mapeamento de CalGray, CalRGB, Lab e ICCBased sem motor ICC

Famílias baseadas em CIE seguem o segundo fluxo do motor. O HotPDF converte coordenadas Lab usando o mapeamento Lab para XYZ e sRGB (incluindo o limiar de 6/29 na função de transferência inversa), tratando o CalRGB com definições de gama por canal e matriz linear 3x3, e CalGray com gama única. Para otimização de velocidade, a matriz de transformação XYZ para sRGB é adaptada (via Bradford) ao ponto branco (white point) definido no espaço de cores e guardada em cache no registo THPDFColorSpace; isto reduz o processamento por píxel a uma única multiplicação 3x3, independentemente das propriedades do iluminante

Espaços ICCBased recebem uma abordagem pragmática. A especificação PDF exige que cada fluxo ICCBased defina a propriedade /Alternate ou deduza uma a partir do número de componentes /N, permitindo a visualizadores sem motor de cor apresentar a informação graficamente. O HotPDF processa ICCBased através do espaço alternativo, ou assume DeviceGray, DeviceRGB ou DeviceCMYK de acordo com o valor 1, 3 ou 4 de /N na ausência de definição, sem ler os bytes do perfil de cor. Isto elimina a dependência do motor lcms e o custo de processamento de perfis, sacrificando a precisão colorimétrica: se o perfil divergir substancialmente da definição alternativa, será exibida a cor alternativa. Trata-se da mesma simplificação adotada pela maioria dos visualizadores para exibição no ecrã e miniaturas

A falha que inviabilizava a pesquisa de espaços de cores por nome

A versão v2.333.0 continha uma falha que persistiu durante quarenta e duas versões: os manipuladores de cs e CS pesquisavam o operando preservando a barra inicial (/CS0) no dicionário de recursos cujas chaves eram gravadas sem a barra (CS0). Por conseguinte, todas as pesquisas falhavam e a cor revertia de forma silenciosa para o preto de DeviceGray. O sintoma visual era difícil de detetar: uma cor Separation com o valor 1 scn convertia-se para o valor 1.0 de DeviceGray, desenhando a branco (o que, numa página branca, passava despercebido). A correção, introduzida na versão v2.375.0, aplicou um utilitário de normalização de nomes a todas as pesquisas de recursos

Duas falhas adicionais foram corrigidas: em primeiro lugar, referências indiretas a objetos do tipo array eram devolvidas sem resolução: o renderizador possuía métodos específicos para fluxos e dicionários, pelo que /CS0 5 0 R apontando para um array [/Separation ...] isolado retornava a referência binária interpretando o espaço como não suportado. Em segundo lugar, o método HPDFReadNumericArray exigia correspondência exata de tamanho para arrays PDF em relação ao buffer. A leitura das propriedades /C0 e /C1 de funções Type 2 para buffers de quatro canais falhava em espaços alternativos de um ou três canais, mantendo os arrays a zero e forçando a renderização a preto de tonalidades não-CMYK, comportamento corrigido na versão v2.376.0 com a introdução do método tolerante HPDFReadNumericArrayUpTo. Lição: chaves PDF que contenham arrays de tamanho variável devem ser lidas com utilitários flexíveis sob pena de silenciar valores válidos

Como testar a renderização de cores especiais com precisão?

A falha na suite de testes foi analisada: o teste original SeparationRendersDistinguishable validava apenas se a imagem resultante não era totalmente preta. Como os desvios desenhavam elementos a cinzento ou branco, a imagem continha píxeis e o teste passava com a funcionalidade inoperacional. Testes vagos como "não vazio" ou "não totalmente preto" falham na deteção de erros de renderização, dado que a maioria das falhas continua a gerar píxeis na imagem

A validação recomendada deve focar a cor esperada: desenhar um PDF de teste simplificado onde o canal Separation resolva numa cor conhecida e contar os píxeis com essa tonalidade. A renderização em bitmap para análise utiliza o método RenderLoadedPageToBitmap detalhado no guia de renderização de página para bitmap:

HotPDF: Asserções fracas versus fortes de renderização de cores diretas, em que um predicado de não-tudo-preto passa com resultado cinzento e partido, enquanto contar pixels dominados pelo vermelho com um limiar RedHits acima de 500 apanha o pipeline morto
Um predicado não-todo-preto passa em saída cinzenta enquanto todo o pipeline de cores diretas está morto, e contar píxeis do tom esperado é o que força o defeito à luz
var
  Pdf: THotPDF;
  Bmp: TBitmap;
  X, Y, RedHits: Integer;
  Px: TColor;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('spot-red-fixture.pdf', '') > 0 then
    begin
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 96);
      try
        RedHits := 0;
        for Y := 0 to Bmp.Height - 1 do
          for X := 0 to Bmp.Width - 1 do
          begin
            Px := Bmp.Canvas.Pixels[X, Y];
            if (GetRValue(Px) > 180) and (GetGValue(Px) < 100) and
               (GetBValue(Px) < 100) then
              Inc(RedHits);
          end;
        // Validar a cor esperada: exige uma área com píxeis predominantemente vermelhos
        // sem aceitar a mera verificação "não totalmente preto"
        Assert(RedHits > 500);
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

O ficheiro de teste (fixture) é tão relevante quanto a validação. Um PDF simplificado com apenas um preenchimento Separation garante que a cor obtida é inequívoca; ficheiros reais correm mais linhas de código mas dificultam identificar a origem de falhas. A contagem de píxeis da cor esperada passou a constituir o teste mínimo para qualquer renderização, por ser o único método eficaz na deteção destas falhas

O desenho de cores especiais e espaços CIE integra as rotinas do HotPDF Delphi Component para Delphi e C++Builder, a par do motor de funções, graduações de cor e exportação para bitmaps