Artigo Técnico

Renderizando gradientes de PDF no Delphi: Axial e Radial

Um PDF cujo cabeçalho carrega um fundo de gradiente no estilo do Word ou Excel, ao ser renderizado para uma imagem, costumava retornar com um retângulo branco em branco onde a mistura de cores deveria estar. O renderizador de páginas do HotPDF agora respeita o operador sh (shading) do PDF para gradientes axiais do Tipo 2 e radiais do Tipo 3: ele avalia a função de sombreamento ao longo do eixo do gradiente e pinta a mistura de cores, onde as versões anteriores descartavam silenciosamente o operador e deixavam a região branca como o papel. Os dois tipos de sombreamento que cobrem quase todos os gradientes de documentos de escritório estão incluídos; os tipos de malha (mesh) não estão, e este artigo é honesto sobre onde essa linha cai

Por que um gradiente de PDF é renderizado como uma caixa branca?

Um gradiente de PDF é uma construção vetorial, não um bitmap que o renderizador possa transferir (blit). Um dicionário de sombreamento (ISO 32000-1 §8.7.4.5) descreve a geometria — um eixo ou um par de círculos na matriz Coords — mais uma função de cor, e o operador sh pinta esse sombreamento em toda a região de recorte atual, o mesmo modelo gráfico que o HotPDF expõe quando você desenha caminhos e preenchimentos em uma tela de página. O renderizador de páginas do HotPDF percorre o fluxo de conteúdo operador por operador, e qualquer operador para o qual não haja um caminho de código é ignorado para que o restante da página ainda seja renderizado. Antes da versão 2.335.0, o sh residia nesse conjunto ignorado: um gradiente de cabeçalho ou de página inteira não contribuía em nada para a saída, fazendo com que o fundo aparecesse em branco. O arquivo nunca estava corrompido. O renderizador simplesmente não possuía um manipulador para esse operador, e uma caixa branca é exatamente o que um manipulador ausente parece

A cadeia de avaliação de função por trás de um sombreamento

Um sombreamento não pode ser pintado até que sua função seja avaliada, portanto essa engrenagem teve que ser implementada primeiro. Os sombreamentos axiais do Tipo 2 e radiais do Tipo 3 carregam uma /Function que mapeia uma posição paramétrica ao longo do gradiente em uma cor (ISO 32000-1 §7.10), e o avaliador de função do HotPDF resolve o FunctionType e o despacha. A interpolação exponencial do Tipo 2 (§7.10.3) calcula C0 + t^N * (C1 - C0) com limitação de Domain e Range; funções amostradas do Tipo 0 (§7.10.2) leem uma grade de amostras de 8, 16 ou 32 bits e escolhem a mais próxima através do mapa Encode, o caso coberto detalhadamente em nosso guia sobre buscas de cores amostradas do Tipo 0; funções de costura (stitching) do Tipo 3 (§7.10.4) selecionam uma subfunção pela matriz Bounds e a chamam recursivamente. Funções de calculadora PostScript do Tipo 4 (§7.10.5) não são avaliadas — elas degradam suavemente para uma falha de avaliação em vez de uma cor errada, combinando com a política conservadora que o HotPDF adota em todo o caminho de renderização. Esse avaliador foi lançado na versão 2.334.0, uma versão antes do próprio operador, porque o pintor de sombreamento é tão bom quanto a função sob ele

Como o HotPDF transforma um gradiente contínuo em pixels?

O HotPDF aproxima a mistura contínua com uma execução finita de faixas de cores sólidas. O pintor de sombreamento percorre o eixo do gradiente, divide-o em até 64 faixas — menos quando o eixo é curto em pixels do dispositivo —, avalia a função na posição paramétrica de cada faixa e preenche um retângulo perpendicular ao eixo com um pincel sólido. O compromisso é a contagem de faixas contra a suavidade: mais faixas seguem a mistura mais de perto, mas custam mais preenchimentos, e 64 é o teto deliberado. Como a contagem também escala com o comprimento do eixo nos pixels do dispositivo, a resolução importa. Renderizar a mesma página em um DPI mais alto alonga o eixo, de modo que um gradiente curto ganha faixas e suas listras se suavizam até que o teto seja alcançado

// O discretizador de sombreamento usa até 64 faixas de cores ao longo do eixo do gradiente, mas nunca mais do que o comprimento do eixo nos pixels do dispositivo. Renderizar a mesma página em um DPI mais alto alonga esse eixo, de modo que um gradiente curto ganha faixas e a segmentação (banding) se suaviza até que o teto de 64 faixas seja atingido.
Thumb := Pdf.RenderLoadedPageToBitmap(PageIndex, 96);   // coarser bands
Proof := Pdf.RenderLoadedPageToBitmap(PageIndex, 300);  // up to 64 bands

Renderizando uma página sombreada em um bitmap

O ponto de entrada público permanece inalterado: o RenderLoadedPageToBitmap recebe um índice de página baseado em zero e um valor de DPI, retorna um bitmap de 24 bits de propriedade do chamador e devolve nil em caso de falha em vez de gerar uma exceção, de modo que o suporte a sombreamento é transparente para o código que você já possui. Uma página que costumava renderizar seu cabeçalho de gradiente em branco agora retorna o mesmo bitmap com a mistura de cores pintada, sem necessidade de alteração de API para obtê-lo. A única coisa que vale a pena lembrar é a propriedade: o chamador libera o bitmap retornado. O pipeline de renderização completo — interpretação de fluxo de conteúdo, glifos incorporados e cache de página — é abordado no guia para renderizar páginas PDF em um bitmap no Delphi

var
  Pdf: THotPDF;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('quarterly-report.pdf') > 0 then
    begin
      // Página 1 (baseada em zero) a 150 DPI. Um cabeçalho preenchido com gradiente que costumava retornar em branco agora carrega sua mistura de cores.
      Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
      if Assigned(Bmp) then
      try
        Bmp.SaveToFile('page1.bmp');
      finally
        Bmp.Free;
      end;
    end;
  finally
    Pdf.Free;
  end;
end;

Uma proteção simples contra regressão surge da mesma chamada. Renderize a página, colete uma amostra de pixel onde a faixa do gradiente pertence e confirme que ela não é mais o branco do papel ao qual o renderizador costumava recorrer — uma verificação de linha única que detecta um retorno silencioso ao antigo comportamento sem operação

Bmp := Pdf.RenderLoadedPageToBitmap(0, 150);
if Assigned(Bmp) then
try
  // True se o operador sh pintou; False se a região voltou a ficar branca.
  HeaderPainted := Bmp.Canvas.Pixels[Bmp.Width div 2, 8] <> clWhite;
finally
  Bmp.Free;
end;

O que o HotPDF não pinta: sombreamentos de malha (mesh) e bordas de recorte

Os quatro tipos de sombreamento de malha (mesh) estão fora de escopo, e um documento deve saber disso de antemão. Malhas de triângulos Gouraud de formato livre (Tipo 4), malhas Gouraud em formato de grade (Tipo 5), malhas de Coons (Tipo 6) e malhas de patch de produto tensorial (Tipo 7), definidas no ISO 32000-1 §8.7.4.5.5 a §8.7.4.5.8, não são renderizadas; uma página que usa uma delas ainda é renderizada, apenas essa região é deixada como está. Mais dois limites valem a pena ser ditos claramente. Os retângulos de faixas cobrem a faixa de sombreamento em vez de serem cortados exatamente no caminho de recorte atual, portanto, um gradiente mascarado em uma forma arredondada ou irregular é pintado como sua faixa delimitadora até que o recorte de região preciso chegue em uma rodada posterior. O HotPDF também assume que a saída da função é RGB ou escala de cinza, de modo que um sombreamento criado em CMYK ou em um espaço de cores exótico é aproximado em vez de ser gerenciado por cores. Para os documentos que a maioria das equipes Delphi e C++Builder realmente processa — relatórios, faturas, extratos e apresentações exportadas para PDF —, os tipos axial e radial cobrem essencialmente todos os gradientes que você encontrará, e provas de design com muitas malhas são os casos a serem tratados como uma visualização em vez de uma prova

O renderizador de páginas do HotPDF, o suporte ao operador sh descrito aqui e o avaliador de função Tipo 0/2/3 sob ele são fornecidos como parte do Componente HotPDF para Delphi e C++Builder — uma biblioteca VCL nativa sem dependências de DLL externas, cobrindo criação, edição, extração de texto e renderização de páginas de PDF em um único pacote