Artigo Técnico

Compressão de Imagens Bilevel JBIG2 Nativa em PDFs com Delphi

Um contrato digitalizado é uns poucos pontos por polegada de tinta preta em papel branco. Armazenado como um mapa de bits de um bit por pixel já é pequeno, mas mesmo assim cem páginas dessas inflam um PDF para além do que se enviaria por correio eletrónico. O filtro certo muda a aritmética. O JBIG2 é a compressão de maior rácio que a ISO 32000-1 define para imagens bilevel, e num conjunto de texto digitalizado reduz habitualmente para metade o que o CCITT Grupo 4 produz. Este é o filtro a utilizar quando a entrada é fax, digitalização ou de outra forma reduzida a duas cores, e o HotPDF pode escrevê-lo diretamente num PDF

O formato obtém o rácio com duas ideias que um codec de imagem genérico não tem. Modela como as manchas negras se encontram contra um fundo branco e nota que uma página digitalizada é composta sobretudo pelas mesmas poucas centenas de formas de glifo repetidas milhares de vezes. Compreender ambas é o que permite escolher as opções de codificação deliberadamente em vez de adivinhar

Onde o JBIG2 se situa na especificação PDF

A ISO 32000-1 lista JBIG2Decode entre os filtros de fluxo no §7.4.7, disponível a partir do PDF 1.4. Aplica-se a um único lugar: XObjects de imagem cujo /BitsPerComponent é 1 e cujo espaço de cores resolve para um único canal. Esse é todo o ponto. O JBIG2 é um codec bilevel, por isso 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 a duas cores que um scanner de documentos produz

O descodificador consome a organização JBIG2 incorporada que a norma designa como perfil PDF, onde cada fluxo de imagem contém uma sequência de segmentos em vez de um bitstream puro. Um fluxo /JBIG2Globals opcional transporta segmentos partilhados por várias imagens no mesmo documento, que é o mecanismo que permite que conteúdo repetido seja armazenado uma única vez para todo o ficheiro em vez de uma vez por página. O HotPDF emite o fluxo por imagem por defeito e mantém o canal de globais livre a menos que um backend o solicite

A arquitetura de codificador centrada no backend

Um codificador JBIG2 completo é um software de grande dimensão, e as partes mais agressivas dele foram historicamente gravadas 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 unidade HPDFJBIG2 define as chamadas que o resto da biblioteca efetua, e inclui um codificador incorporado modesto para que o JBIG2 funcione imediatamente. Quando se precisam de rácios de nível de produção, regista-se um motor mais potente e a biblioteca delega-lhe, sem qualquer alteração no código de chamada

A mudança é uma única chamada de registo. Sem nenhum backend registado, o codificador recorre ao seu caminho incorporado. Registe um e cada codificação subsequente é executada através dele

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 mecanismo existe para descodificação através de RegisterJBIG2DecoderBackend, com IsJBIG2DecoderBackendAvailable para verificá-lo. É por isso que uma biblioteca inclui um caminho incorporado pequeno mais uma articulação de backend em vez de um codificador monolítico. O caminho incorporado mantém o binário enxuto e livre de emaranhamentos de licença, enquanto a articulação permite que uma equipa que tenha licenciado um codificador completo o conecte sem tocar na camada de escrita de PDF

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

A codificação é configurada através de TJBIG2EncodeOptions, um registo com os campos Lossless, UseGlobalSegments, UseSymbolDictionary e LossyLevel. O wrapper compatível com componentes THPDFJBIG2Options publica Lossless, UseSymbolDictionary e LossyLevel para que possam ser definidos a partir do Inspetor de Objetos, e converte para o registo internamente. Três intenções orientam as definições

A reconstrução sem perdas mantém todos os píxeis. Defina Lossless como True e deixe LossyLevel em zero, e o mapa de bits descodificado é bit a bit idêntico à entrada. Esta é a única escolha segura para arte vetorial, desenhos técnicos e qualquer página onde um píxel perdido possa mudar o significado, como uma assinatura ou um carimbo. A codificação por dicionário de símbolos ativa a desduplicação com reconhecimento de texto e é a opção que separa o JBIG2 dos filtros de fax. O nível com perdas, um inteiro de 0 a 9, permite que um backend capaz troque fidelidade por tamanho ao tratar marcas quase idênticas como o mesmo símbolo. Zero significa sem perdas. O codificador incorporado honra apenas o caminho sem perdas e ignora qualquer nível com perdas diferente de zero, pelo que os níveis mais altos só têm efeito quando é registado um backend que os implementa

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 as 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 ligeiramente ruidosa de uma forma subjacente. Um dicionário de símbolos captura essa estrutura. O codificador recolhe as marcas distintas da página num dicionário, armazena cada forma uma vez e depois regista a página como uma lista de posições que referenciam entradas do dicionário. Mil ocorrências do mesmo glifo custam um mapa de bits armazenado mais mil posicionamentos económicos

É precisamente aqui que o JBIG2 ultrapassa o CCITT Grupo 4. O Grupo 4 codifica cada linha de digitalização contra a linha acima sem qualquer noção de glifo, por isso paga o custo total de cada letra cada vez que a letra aparece. O JBIG2 paga uma vez. Quando o mesmo dicionário é promovido para o fluxo de globais ao nível do documento, a poupança acumula-se ao longo de uma digitalização de múltiplas páginas, porque as formas partilhadas de página a página são armazenadas uma única vez para todo o ficheiro. Em texto denso a diferença não é marginal. É a razão pela qual o JBIG2 existe

Região genérica e MMR para todo o resto

Nem toda a imagem bilevel é texto. Mapas, esquemas, desenhos de engenharia e páginas mistas têm arte vetorial que nenhum dicionário pode resumir. Para esses, o JBIG2 codifica uma região genérica, um retângulo de píxeis comprimido diretamente sem qualquer treino de símbolos. A norma permite que uma região genérica use MMR, a codificação READ modificada que o fax Grupo 4 já utiliza, que modela cada linha de píxeis contra a linha acima

Este é o caminho que o HotPDF inclui no seu codificador incorporado. Quando nenhum backend está registado e o pedido é sem perdas, a biblioteca comprime o mapa de bits como uma única região genérica MMR e envolve-a na estrutura de segmento JBIG2 que o perfil PDF requer. Não precisa de dicionário, de passagem de treino nem de segunda imagem para referência, pelo que é o padrão confiável para arte vetorial e conteúdo bilevel misto. Não corresponderá a 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 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;

Ativá-lo ao construir um documento

Para uso quotidiano não se toca na classe de codificador diretamente. O HotPDF expõe o JBIG2 como uma escolha 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 tem uma propriedade JBIG2Options do tipo THPDFJBIG2Options que contém as definições usadas quando essa compressão é selecionada. Configure ambas antes de adicionar as imagens bilevel a 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 que vale a pena referir é o add-on DBGridHotPDFExport, que renderiza um TDBGrid diretamente para um PDF. A sua saída é em grande parte linhas bilevel e texto, por isso um documento configurado para JBIG2 mantém essas exportações compactas sem qualquer tratamento adicional da sua parte. Dois tópicos relacionados neste blogue aprofundam o fluxo de trabalho envolvente. Para saber como imagens e fontes são dispostas ao criar relatórios, consulte saída de relatório com fontes e imagens em Delphi. Quando um documento comprimido tem de satisfazer um perfil de arquivo, as regras em validação PDF/A, PDF/X e PDF/UA em Delphi indicam que filtros um dado nível de conformidade aceita. O JBIG2 é fornecido como parte do Componente HotPDF para Delphi e C++Builder, a par das APIs de carregamento, edição e encriptação descritas neste blogue