Artigo Técnico

Compressão de Imagens Bilevel JBIG2 Nativa em PDFs Delphi

Um contrato digitalizado é composto de algumas centenas de pontos por polegada de tinta preta sobre papel branco. Armazenado como um bitmap de um bit por pixel já é pequeno, porém uma centena dessas páginas ainda infla um PDF além do que você enviaria por e-mail. O filtro certo muda a aritmética. JBIG2 é a compressão de maior taxa que a ISO 32000-1 define para imagens bilevel, e em uma pilha de texto digitalizado rotineiramente reduz pela metade o que o CCITT Grupo 4 produz. Este é o filtro a ser utilizado quando a entrada é faxeada, digitalizada ou de outra forma reduzida a duas cores, e o HotPDF pode gravá-lo diretamente em um PDF

O formato obtém essa taxa com duas ideias que um codec de imagem genérico não possui. Ele modela como as sequências pretas se situam contra um fundo branco, e percebe que uma página digitalizada é composta principalmente das mesmas poucas centenas de formas de glifos repetidas milhares de vezes. Entender ambas é o que permite escolher as opções de codificação deliberadamente em vez de adivinhar

Onde o JBIG2 se posiciona na especificação PDF

A ISO 32000-1 lista o JBIG2Decode entre os filtros de stream no §7.4.7, disponível a partir do PDF 1.4. Ele se aplica a um único lugar: XObjects de imagem cujo /BitsPerComponent é 1 e cujo espaço de cores se resolve para um único canal. Esse é o ponto central. JBIG2 é um codec bilevel, portanto nunca compete com DCT ou JPXDecode em fotografias. Compete com CCITTFaxDecode, os filtros de fax Grupo 3 e Grupo 4, exatamente no tipo de página de dois tons que um scanner de documentos produz

O decodificador consome a organização JBIG2 incorporada que o padrão chama de perfil PDF, onde cada stream de imagem contém uma sequência de segmentos em vez de um fluxo de bits puro. Um stream /JBIG2Globals opcional carrega segmentos compartilhados entre várias imagens no mesmo documento, que é o mecanismo que permite que conteúdo repetido seja armazenado uma vez para um arquivo inteiro em vez de uma vez por página. O HotPDF emite o stream por imagem por padrão e mantém o canal de globals livre, a menos que um backend o solicite

A arquitetura de codificador com backend plugável

Um codificador JBIG2 completo é um software de grande porte, e as partes mais agressivas dele foram historicamente oneradas por patentes e distribuídas sob licenças que não se adequam a todos os produtos. O HotPDF resolve essa tensão separando a interface do motor. A unit HPDFJBIG2 define as chamadas que o restante da biblioteca faz, e fornece um codificador embutido modesto para que o JBIG2 funcione imediatamente. Quando você precisa de taxas de nível de produção, você registra um motor mais potente e a biblioteca delega a ele, sem nenhuma alteração no seu código de chamada

A troca é uma única chamada de registro. Sem nenhum backend registrado, o codificador recorre ao seu caminho embutido. Registre um e toda codificação subsequente roda por ele

uses
  HPDFJBIG2;

// Query what is active, then optionally install a stronger engine.
if not IsJBIG2EncoderBackendAvailable then
  // Production backend not present: HotPDF uses its built-in MMR path.
  RegisterJBIG2EncoderBackend(MyVendorJBIG2Encode);

// Later, to return to the built-in behaviour:
// ClearJBIG2Backends;

O mesmo gancho existe para decodificação através de RegisterJBIG2DecoderBackend, com IsJBIG2DecoderBackendAvailable para sondá-lo. É por isso que uma biblioteca fornece um pequeno caminho embutido mais uma costura de backend em vez de um codificador monolítico. O caminho embutido mantém o binário enxuto e livre de complicações de licença, enquanto a costura permite que uma equipe que licenciou um codificador completo o conecte sem tocar na camada de escrita de PDF

O que as opções de codificação realmente trocam

A codificação é configurada através de TJBIG2EncodeOptions, um registro com os campos Lossless, UseGlobalSegments, UseSymbolDictionary e LossyLevel. O wrapper amigável para componentes THPDFJBIG2Options publica Lossless, UseSymbolDictionary e LossyLevel para que possam ser definidos no Object Inspector, e converte para o registro internamente. Três intenções guiam as configurações

A reconstrução sem perdas mantém todos os pixels. Defina Lossless como True e deixe LossyLevel em zero, e o bitmap decodificado é idêntico bit a bit à entrada. Esta é a única escolha segura para arte vetorial, desenhos técnicos e qualquer página onde um pixel eliminado pudesse mudar o significado, como uma assinatura ou um carimbo. A codificação de dicionário de símbolos ativa a deduplicação com reconhecimento de texto e é a opção que separa o JBIG2 dos filtros de fax. O nível de perda, um inteiro de 0 a 9, permite que um backend capaz troque fidelidade por tamanho, tratando marcas quase idênticas como o mesmo símbolo. Zero significa sem perdas. O codificador embutido respeita apenas o caminho sem perdas e ignora qualquer nível de perda diferente de zero, portanto os níveis mais altos só têm efeito quando um backend que os implementa é registrado

var
  Options: TJBIG2EncodeOptions;
begin
  Options := DefaultJBIG2EncodeOptions;   // Lossless True, symbol dictionary on
  Options.Lossless := True;
  Options.LossyLevel := 0;                // 0 keeps every pixel
  Options.UseSymbolDictionary := True;    // dedupe repeated glyphs
  // Pass Options to a backend, or let THPDFJBIG2Options carry them.
end;

Dicionários de símbolos e por que digitalizações de texto ganham

Uma página de texto digitalizado não é realmente uma imagem de palavras. É a mesma letra e impressa várias centenas de vezes, o mesmo t, a mesma vírgula, cada instância uma cópia levemente ruidosa de uma forma subjacente. Um dicionário de símbolos captura essa estrutura. O codificador coleta as marcas distintas na página em um dicionário, armazena cada forma uma vez e depois registra a página como uma lista de posições que referenciam entradas do dicionário. Mil ocorrências do mesmo glifo custam um bitmap armazenado mais mil posicionamentos baratos

É precisamente aqui que o JBIG2 supera o CCITT Grupo 4. O Grupo 4 codifica cada linha de varredura contra a linha acima sem noção de um glifo, portanto paga o custo total de cada letra cada vez que ela aparece. O JBIG2 paga uma vez. Quando o mesmo dicionário é promovido ao stream de globals de nível de documento, a economia se multiplica em uma digitalização de várias páginas, porque as formas compartilhadas por página após página são armazenadas uma única vez para o arquivo inteiro. Em texto denso, a diferença não é marginal. É a razão pela qual o JBIG2 existe

Região genérica e MMR para tudo o mais

Nem toda imagem bilevel é texto. Mapas, esquemas, desenhos de engenharia e páginas mistas têm arte vetorial que nenhum dicionário pode resumir. Para esses casos, o JBIG2 codifica uma região genérica, um retângulo de pixels comprimidos diretamente sem nenhum treinamento de símbolo. O padrão permite que uma região genérica use MMR, a codificação modified modified READ que o fax Grupo 4 já usa, que modela cada linha de pixels contra a linha acima

Este é o caminho que o HotPDF fornece em seu codificador embutido. Quando nenhum backend é registrado e a solicitação é sem perdas, a biblioteca comprime o bitmap como uma única região genérica MMR e o envolve na estrutura de segmento JBIG2 que o perfil PDF exige. Não precisa de dicionário, nem de passagem de treinamento, nem de uma segunda imagem para referenciar, portanto é o padrão confiável para arte vetorial e conteúdo bilevel misto. Não igualará um codificador de dicionário de símbolos completo em texto puro, mas é sempre correto, sempre sem perdas e sempre presente. A superfície do codificador para ele é uma única chamada

var
  Encoder: THPDFJBIG2Encoder;
  ImageData: TJBIG2ByteArray;
  Scanlines: TJBIG2ScanlineArray;  // one byte array per row, MSB-first
  W, H: Integer;
begin
  // Scanlines, W and H describe a 1-bit page; each row is (W + 7) div 8 bytes.
  Encoder := THPDFJBIG2Encoder.Create;
  try
    if Encoder.EncodeToByteArray(Scanlines, W, H, ImageData) then
      // ImageData now holds a JBIG2 stream ready for a /JBIG2Decode XObject.
      ;
  finally
    Encoder.Free;
  end;
end;

Ativando ao construir um documento

Para uso cotidiano, você não toca diretamente na classe do codificador. O HotPDF expõe JBIG2 como uma opção de compressão de imagem no documento. A enumeração THPDFImageCompressionType inclui icJBIG2 ao lado das opções Flate, JPEG e CCITT, e o documento carrega uma propriedade JBIG2Options do tipo THPDFJBIG2Options que contém as configurações usadas quando essa compressão é selecionada. Configure ambas antes de adicionar as imagens bilevel que deseja comprimir desta forma

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.ImageCompressionType := icJBIG2;     // route 1-bit images through JBIG2
    Pdf.JBIG2Options.Lossless := True;        // keep every pixel
    Pdf.JBIG2Options.UseSymbolDictionary := True;
    Pdf.JBIG2Options.LossyLevel := 0;
    // Add pages and place your scanned 1-bit images here.
  finally
    Pdf.Free;
  end;
end;

Uma conveniência a destacar é o add-on DBGridHotPDFExport, que renderiza um TDBGrid diretamente para um PDF. Sua saída é em grande parte regras bilevel e texto, portanto um documento configurado para JBIG2 mantém essas exportações compactas sem nenhum manuseio extra da sua parte. Dois tópicos relacionados neste blog aprofundam o fluxo de trabalho ao redor. Para como imagens e fontes são dispostas ao criar relatórios, veja saída de relatórios com fontes e imagens no Delphi. Quando um documento comprimido deve satisfazer um perfil de arquivamento, as regras em validação de PDF/A, PDF/X e PDF/UA no Delphi indicam quais filtros um determinado nível de conformidade aceita. O JBIG2 é fornecido como parte do HotPDF Component para Delphi e C++Builder, ao lado das APIs de carregamento, edição e criptografia cobertas em outros lugares aqui