Artigo Técnico

Máscaras Suaves e Achatamento de Transparência em Delphi

A PDF Library for Delphi cria os dois tipos de máscara suave de PDF a partir do Delphi. SetImageSoftMaskEx associa uma imagem de máscara a uma imagem de destino e pode escrever a entrada /Matte que a arte pré-multiplicada precisa. NewSoftMaskGraphicsState constrói uma máscara alfa ou de luminosidade a partir de conteúdo capturado e devolve um estado gráfico que se aplica ao desenho subsequente. E quando um consumidor não consegue lidar com transparência de todo, FlattenTransparency renderiza o resultado visual e substitui cada página por ele

As máscaras suaves são a forma como o PDF exprime um desvanecimento em gradiente, uma vinheta, uma fotografia fundida num fundo, e todos os outros efeitos em que a opacidade varia ao longo de uma região. A transparência constante é um único número no estado gráfico; uma máscara suave é toda uma função da posição, e até se conseguir criar uma, toda uma categoria de design não pode ser reproduzida

Os dois tipos de máscara, e o que cada um lê

Uma máscara alfa tira os seus valores do canal alfa do conteúdo de mascaramento. Uma máscara de luminosidade tira-os do brilho do conteúdo de mascaramento, pelo que uma área branca é totalmente opaca e uma área preta totalmente transparente. A luminosidade é a que os designers procuram, porque pintar um gradiente em escala de cinzentos é uma forma natural de descrever um desvanecimento

As máscaras suaves de formulário são construídas a partir de conteúdo capturado, e vários detalhes são fixos, em vez de opcionais. O conteúdo capturado torna-se um grupo de transparência isolado em DeviceRGB, pelo que a máscara é avaliada num espaço de cor conhecido. A cor de fundo tem, por isso, de ter três componentes, correspondendo a esse grupo. A função de transferência aceita /Identity ou um objeto de função válido, permitindo remodelar a curva da máscara sem voltar a pintar a arte

Diagrama comparando máscaras suaves de luminosidade e alfa a alimentar NewSoftMaskGraphicsState, com um lembrete de que o estado gráfico persiste até ser limpo
As máscaras de luminosidade leem o brilho, enquanto as máscaras alfa leem a cobertura, e NewSoftMaskGraphicsState envolve qualquer uma delas num estado gráfico persistente
uses
  PDFlibrary;

var
  Lib: TPDFlib;
  MaskCapture, MaskGS, PhotoID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('brochure.pdf', '');

    // A página 4 contém um gradiente em escala de cinzentos pintado para ser usado como máscara
    MaskCapture := Lib.CapturePage(4);

    MaskGS := Lib.NewSoftMaskGraphicsState(MaskCapture,
      PDF_SOFT_MASK_LUMINOSITY, '0 0 0', '/Identity');

    Lib.SelectPage(1);
    Lib.SetGraphicsState(MaskGS);
    PhotoID := Lib.AddImageFromFile('hero.jpg', 0);
    Lib.SelectImage(PhotoID);
    Lib.DrawImage(40, 700, 515, 260);        // desvanece sob a máscara

    // As definições de ExtGState persistem: termine o âmbito explicitamente
    Lib.SetGraphicsState(Lib.NewSoftMaskGraphicsState(0,
      PDF_SOFT_MASK_NONE, '', ''));

    Lib.SaveToFile('brochure-masked.pdf');
  finally
    Lib.Free;
  end;
end;

Esse último passo não é decoração. Os parâmetros de estado gráfico persistem até serem alterados, e aplicar depois um estado gráfico comum não limpa uma máscara suave ativa. Sem um /SMask /None explícito, tudo o que for desenhado depois da arte mascarada herda a máscara, o que é um dos bugs mais desconcertantes de diagnosticar visualmente

Máscaras de imagem e a entrada Matte

Para mascaramento ao nível da imagem, a imagem de máscara é primeiro normalizada para DeviceGray, pelo que uma máscara fornecida em qualquer espaço de cor se comporta de forma previsível. A entrada opcional /Matte, com um a quatro componentes consoante o espaço de cor da imagem, diz a um consumidor que os dados da imagem estão pré-multiplicados contra essa cor, o que evita o halo escuro que aparece quando arte pré-multiplicada é composta como se fosse alfa direto

Diagrama mostrando uma entrada Matte em falta a produzir halos escuros, enquanto uma Matte declarada compõe de forma limpa, ao lado das barreiras de PDF 1.4, PDF/A e PDF/X
Uma /Matte declarada evita o halo escuro da arte pré-multiplicada, enquanto os ficheiros PDF/A-1 e PDF/X-1 são recusados antes de qualquer alteração a objetos

Duas barreiras importam aqui. O PDF/A-1 e o PDF/X-1 proíbem a transparência, e a chamada recusa antes de modificar qualquer objeto, em vez de produzir um ficheiro que falha na validação mais tarde. E uma máscara suave bem-sucedida exige PDF 1.4, que a biblioteca define automaticamente — a transparência não existia antes dessa versão, e um cabeçalho 1.3 num ficheiro com máscaras suaves é simplesmente falso

Os estados repetidos são reutilizados: o mesmo formulário capturado, tipo de máscara, fundo e assinatura de função de transferência produzem o mesmo objeto de estado gráfico, em vez de um novo por chamada. Num documento que mascara cem imagens de forma idêntica, isso são noventa e nove objetos que nunca chegam a ser escritos

Porquê achatar, e o que o achatamento realmente faz

"Parece correto no ecrã e errado na imprensa" continua a ter a transparência como causa mais comum. RIPs mais antigos, alguns sistemas de submissão regulatória e o PDF/X-1a exigem todos um ficheiro sem qualquer transparência ativa

O achatamento aqui é deliberadamente literal: cada página no intervalo pedido é renderizada a um DPI escolhido sobre um fundo opaco, e o conteúdo e os recursos da página são substituídos por essa única imagem. Como o renderizador produz o resultado composto final, isto cobre tudo de uma vez — grupos de transparência, modos de mistura, máscaras suaves, padrões e formulários aninhados — sem lógica de aproximação por construção a poder falhar. O pico de memória de rasterização mantém-se limitado a uma página

Diagrama de pipeline do FlattenTransparency a renderizar cada página pedida a 300 DPI sobre um fundo opaco e a gravar um ficheiro PDF 1.3 achatado pronto para impressão
Cada página pedida é renderizada uma vez ao DPI escolhido e devolvida como uma única imagem opaca, gravada num ficheiro PDF 1.3 verdadeiro
// Achatar as páginas 1-8 a 300 DPI sobre branco, e depois gravar como PDF 1.3
Lib.FlattenTransparency('1-8', 300, $FFFFFF, 0);
Lib.SaveToFile('press-ready.pdf');

A contrapartida é igualmente literal: a saída é uma imagem, pelo que o texto deixa de ser selecionável ou pesquisável, e os contornos vetoriais são reamostrados ao DPI escolhido. O achatamento é um passo de entrega, não um passo de edição. Mantenha o original transparente e achate uma cópia

Páginas rodadas, unidades de utilizador e as caixas de página

Uma página rodada precisa de cuidados que uma implementação ingénua ignora. Depois de renderizar, a largura e a altura visuais são escritas de volta na MediaBox, a unidade de utilizador é incorporada no DPI efetivo e no novo tamanho de página, e a entrada /Rotate, a unidade de utilizador e as caixas de página auxiliares são removidas

Ignorar isso produz um ficheiro em que a imagem achatada está correta, mas um visualizador volta a rodá-la, ou escala-a por uma unidade de utilizador que já não se aplica. Remover as caixas, em vez de as recalcular, é a decisão certa, porque uma TrimBox que descreve a geometria anterior ao achatamento é pior do que nenhuma TrimBox

As anotações e os campos de formulário são renderizados na imagem estática por predefinição, e os objetos interativos removidos, que é o que um entregável pronto para impressão quer. Se, em vez disso, mantiver os objetos interativos, a passagem de anotações do renderizador é desligada, para que nada seja desenhado duas vezes

A verificação de versão que analisa tudo

Gravar como PDF 1.3 exige que não reste transparência nenhuma em lado nenhum, e a verificação prévia à gravação examina todos os objetos indiretos, não apenas os alcançáveis a partir das páginas. Isso é mais estrito do que parece à primeira vista, e está correto: uma máscara suave órfã, deixada para trás por uma edição anterior, continua no ficheiro, e um consumidor que percorra objetos, em vez de páginas, vai encontrá-la

Para tornar a redução de versão exequível, uma passagem de marcar e varrer corre por predefinição assim que cada página de destino tiver sido substituída com sucesso, libertando os objetos inalcançáveis. A consequência a prever é que os identificadores de imagem, de página capturada e de estado gráfico obtidos antes dessa varredura podem deixar de ser válidos depois. Volte a obter os identificadores de recurso de que precisa antes de continuar a editar, ou desative a varredura e aceite que a versão não pode então ser reduzida

Dois complementos completam o quadro. A arte vetorial que usa sombreados e gradientes em vez de máscaras está descrita em gráficos vetoriais, shaders e gradientes, e é frequentemente o percurso melhor quando o efeito pode ser expresso como um sombreado. E se a razão para achatar for um requisito de cor, e não de transparência, o percurso de conversão em recolorir um documento para outro espaço de cor preserva o texto ativo onde o achatamento não o faria

A criação de máscaras suaves, o achatamento e o motor de renderização por trás deles fazem parte da mesma biblioteca para Delphi, C++Builder e Free Pascal; a lista completa de funcionalidades está disponível na página da PDF Library for Delphi