O PDFlibPas consegue codificar imagens bilevel como JBIG2 através de dois backends diferentes. Um é um codificador MMR nativo em Object Pascal que está sempre presente. O outro é um codificador externo de dicionário de símbolos que produz saída substancialmente menor em texto digitalizado, e é opcional: um projeto tem de ligar a unidade de backend para que ele exista. Essa distinção é a origem da surpresa mais comum com esta funcionalidade, por isso vale a pena enunciá-la primeiro: DefaultJBIG2EncodeOptions pede o codificador externo por defeito, e quando a unidade de backend não está ligada o pedido recai silenciosamente no caminho MMR Pascal
No Delphi e no C++Builder o backend externo é um conjunto de objetos estáticos pré-compilados. No Free Pascal teve de se tornar uma DLL, e o caminho até essa conclusão é uma história de linker útil para quem já tentou ligar objetos C++ num programa Free Pascal
O registo é o contrato
A unidade de backend regista-se a partir da sua secção de inicialização chamando RegisterJBIG2EncoderBackend. Os chamadores pedem-na quer através do bit de opções, PDF_JBIG2_OPTION_EXTERNAL_ENCODER, que tem o valor 4, quer através do parâmetro UseExternalEncoder dos pontos de entrada estendidos de imagem. O guarda-chuva da biblioteca propositadamente não puxa a unidade de backend, porque carregar um conjunto grande de objetos deve ser decisão de cada projeto; na árvore C++Builder, por exemplo, é incluído explicitamente pelos projetos que o querem
A consequência para os chamadores é que pedir o codificador externo é uma preferência, não uma garantia, e uma compilação que esqueça a unidade produz ficheiros maiores em vez de um erro. Se o tamanho da saída importa o suficiente para pedir o melhor codificador, importa o suficiente para verificar que o obteve
uses
PDFlibrary,
{$IFDEF FPC}
PDFlibJBIG2EncDLL; // backend dinâmico para Free Pascal
{$ELSE}
PDFlibJBIG2EncC; // conjunto de objetos estáticos para Delphi / C++Builder
{$ENDIF}
var
Pdf: TPDFlib;
ImageId: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.NewPage;
// Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
// BlackDotSize, LossyLevel
ImageId := Pdf.AddImageJBIG2FromFileEx('scan-page-1.tif',
0, 1, 1, 0, 0, 0);
if ImageId = 0 then
raise Exception.Create('JBIG2 encoding failed');
Pdf.SaveToFile('archive.pdf');
finally
Pdf.Free;
end;
end;
Compilar a unidade foram duas linhas. Os símbolos foram o trabalho
Fazer a própria unidade de backend compilar sob Free Pascal levou exatamente duas alterações: definir o dialeto de assembler, e substituir um construtor de definições de formato baseado em registos pela variável global predefinida. Isso reflete bem o quão portátil é o Pascal direto entre os dois compiladores
O lado dos símbolos foi o verdadeiro trabalho. O conjunto de objetos referencia 176 símbolos C. Destes, 128 já tinham implementações Pascal dentro da unidade e só precisavam de nomes de exportação anexados, porque o Delphi usa o nome da função como nome de símbolo enquanto o Free Pascal exige uma declaração de nome público explícita. Vinte e sete eram partilhados com o codec JPEG 2000 e tinham de ser exportados de exatamente um sítio, já que defini-los duas vezes parte qualquer programa que ligue ambos. Os restantes 21 eram entradas de plataforma e de runtime C, dezasseis funções de ficheiro Win32 mais um punhado de chamadas de biblioteca padrão, e foram para uma nova unidade de compatibilidade
Nada disso é conceptualmente difícil, e tudo isso é necessário antes de o linker sequer tentar. O linker é onde parou
Três rotas de ligação, três becos sem saída
O linker interno do Free Pascal não consegue ler os ficheiros de objetos, porque foram produzidos por um compilador que emite secções COMDAT associativas e o linker interno reporta que não os suporta. Isso é uma recusa limpa, não um aviso
Mudar para um linker externo parecia a resposta. O linker binutils que acompanha o Free Pascal colapsa por inteiro ao aplicar garbage collection de secções a este arquivo, e essa flag faz parte do conjunto fixo de parâmetros que o Free Pascal passa para o alvo Windows de 64 bits, pelo que não pode ser removida da linha de comandos; os switches documentados para a suprimir são ignorados neste caminho. Fornecer um binutils muito mais recente falha de outra forma: não consegue processar o script de ligação do Free Pascal de todo, produzindo uma saída vazia sem o script e uma parede de erros de relocação com ele
Uma fronteira descoberta ao longo do caminho vale a pena conhecer mesmo que nunca encontre o problema do linker. O linker externo resolve os caminhos dos ficheiros de objetos relativos ao diretório de saída do executável em vez da árvore de código-fonte, pelo que uma diretiva relativa de inclusão de objetos só funciona quando o diretório de saída coincide com o diretório de trabalho em tempo de compilação. Uma biblioteca não pode assumir isso acerca do projeto de um consumidor, o que por si só é uma razão para preferir uma biblioteca ligada a objetos soltos
Porque é que um compilador C++ diferente não ajuda
A ideia óbvia a seguir é recompilar o lado C++ com um compilador cujos objetos o Free Pascal consiga ler. Também não funciona, e a razão é fundamental em vez de uma questão de switches. Uma unidade de tradução C++ mínima contendo um template, compilada com todas as funcionalidades de geração de código desligadas, ainda emite símbolos externos fracos, porque a instanciação de templates e inline os produz por construção. O Free Pascal rejeita essa classe de símbolo de imediato. O sentido inverso também falha: um linker C++ mainstream não consegue consumir objetos do outro compilador por causa do mesmo tratamento de secções COMDAT
Assim, o código C++ não pode ser entregue como objetos ao Free Pascal por nenhuma rota disponível. Pode ser entregue como uma DLL, que foi o que aconteceu: o codificador e a sua dependência de processamento de imagem são compilados numa única biblioteca que expõe dois pontos de entrada C simples, e a unidade de backend Free Pascal vincula-os dinamicamente e regista-se exatamente como o backend estático faz. O caminho Delphi e C++Builder não foi tocado de todo, que é o desfecho correto; um problema de portabilidade numa toolchain não deve perturbar a toolchain que já funcionava
A polaridade é a única coisa que o vai morder
Entre um bitmap bilevel Windows e um codificador JBIG2 há uma incompatibilidade de convenções que nenhum sistema de tipos apanhará. Uma scanline de bitmap independente de dispositivo com um bit por píxel trata um bit ativo como branco. O codificador trata um bit ativo como preto. Entregue as scanlines sem alterar e obtém um stream JBIG2 perfeitamente válido do negativo fotográfico da sua página
// DIB de um bit: bit ativo significa branco. Codificador JBIG2: bit
// ativo significa preto. Inverta cada byte à entrada
for I := 0 to RowBytes - 1 do
Row[I] := Row[I] xor $FF;
O método de verificação importa tanto como a correção. Comparar os comprimentos dos streams comprimidos não lhe diz nada, porque uma imagem negativa comprime para um tamanho semelhante. Olhar para a página prova apenas que não está obviamente invertida. A verificação fiável é renderizar a saída de ambos os caminhos de codificação, Pascal nativo e externo, para PNG e compará-los byte a byte: ambos os codificadores são sem perdas na mesma imagem de origem, pelo que qualquer coisa que não seja uma correspondência exata é um bug num deles. Essa comparação é agora um teste de regressão permanente, e é o tipo de asserção que vale a pena construir sempre que duas implementações devem coincidir exatamente
Que backend utilizar
Para conteúdo bilevel geral, meios-tons com dithering, line art, gráficos mistos, o codificador MMR Pascal nativo é adequado e não tem custo de implantação. Para texto digitalizado, que é o caso para que o JBIG2 foi desenhado, o codificador externo de dicionário de símbolos é onde vive a redução de tamanho, porque fatoriza formas de glifos repetidas num dicionário em vez de recodificar cada ocorrência. Se estiver a produzir arquivos de documentos digitalizados, essa diferença é grande o suficiente para alterar o planeamento de armazenamento
A questão a montante, como a imagem bilevel é produzida em primeiro lugar, importa tanto para o tamanho da saída; a renderização monocromática por regiões está coberta no artigo sobre renderização de regiões monocromáticas, e a estratégia de tamanho ao nível do documento em otimização do tamanho de ficheiros PDF e subsetting de fontes. Para conjuntos de digitalizações com páginas repetidas, a desduplicação frequentemente supera uma compressão melhor, que é o assunto de desduplicação percetiva de imagens. A disponibilidade de toolchains e backends por plataforma está listada na página de produto da losLab PDF Developer Library