A PDF Library for Delphi consegue fundir imagens dentro de um PDF que parecem iguais mas não são idênticas byte a byte, através de DeduplicateSimilarImages. A deduplicação exata só funde imagens cujos dicionários e cargas úteis codificadas coincidam; esta compara o aspeto real das imagens, usando um hash percetual mais dois limiares de erro de pixel, e redireciona todas as referências para um único objeto canónico
Os ficheiros onde isto compensa são os que cresceram por acidente. Um lote digitalizado onde cada página traz o mesmo logótipo recodificado por um scanner diferente. Um relatório montado a partir de fontes que incorporaram cada uma o mesmo gráfico com uma qualidade JPEG ligeiramente diferente. Um catálogo cujas miniaturas foram regeneradas por duas ferramentas. Em cada caso, o ficheiro contém muitas cópias de uma imagem, e a deduplicação exata não consegue tocar em nenhuma delas
O que "suficientemente semelhante" tem de significar
Três limiares têm todos de ser satisfeitos antes de duas imagens serem fundidas, e cada um apanha um tipo diferente de diferença. A distância de hash percetual delimita a diferença estrutural: um hash de diferença de 64 bits é calculado a partir de uma amostra de 64 por 64 da imagem descodificada, e a distância de Hamming permitida vai de 0 a 7. O erro médio de canal delimita a deriva global, que é o que a recodificação produz. O erro máximo de canal único delimita a diferença localizada, que é o que uma pequena edição produz
Exigir os três é o que torna a fusão defensável. Uma imagem que difere por uma marca de água num canto tem um erro médio baixo e um erro máximo elevado, pelo que o portão de erro máximo a rejeita. Uma imagem que foi recomprimida tem um erro pequeno em toda a parte, pelo que passa. É exatamente essa a distinção que um otimizador de tamanho precisa de fazer
uses
PDFlibrary;
var
Lib: TPDFlib;
Stats: TPDFlibImageSimilarityDedupStats;
Merged: Integer;
begin
Lib := TPDFlib.Create;
try
Lib.LoadFromFile('scanned-batch.pdf', '');
// Distância de hash 2, erro médio 2, erro de canal único 4.
// As opções podem pedir uma execução de teste para que nada seja modificado
Merged := Lib.DeduplicateSimilarImages(2, 2, 4, 0, Stats);
if Merged < 0 then
raise Exception.Create('deduplication failed');
Log(Format('%d scanned, %d merged, %d refs repointed, %d bytes duplicated',
[Stats.ImagesScanned, Stats.ImagesDeduplicated,
Stats.ReferencesRepointed, Stats.DuplicatePayloadBytes]));
Log(Format('worst accepted: hash %d, mean %.2f, channel %d',
[Stats.MaxAcceptedHashDistance, Stats.MaxAcceptedMeanError,
Stats.MaxAcceptedChannelError]));
Lib.SaveToFile('scanned-batch-optimised.pdf');
finally
Lib.Free;
end;
end;
A identidade semântica vem antes da semelhança visual
Antes de quaisquer pixels serem comparados, as imagens candidatas têm de concordar semanticamente. A biblioteca constrói uma assinatura estável a partir do dicionário de imagem completo, excluindo /Length, /Filter e /DecodeParms, pelo que as dimensões, o espaço de cor, a profundidade de bits, as máscaras, o array /Decode, a interpolação, a pertença a conteúdo opcional e os metadados têm todos de corresponder
Esta ordenação evita toda uma classe de fusões erradas. Duas imagens podem parecer idênticas numa amostra de 64 por 64 e comportar-se de forma diferente na página: uma com /Interpolate verdadeiro e outra sem isso serão suavizadas de forma diferente ao escalar, e uma pertencente a um grupo de conteúdo opcional desaparecerá quando essa camada for ocultada. Fundir através dessas fronteiras produz um documento mais pequeno e errado
Só os Image XObjects indiretos são analisados. As imagens inline são uma construção diferente com uma economia diferente, e as imagens que não podem ser descodificadas são ignoradas de forma conservadora e contabilizadas, em vez de fundidas por fé — sem evidência de pixels, sem fusão
Como a pesquisa se mantém barata
Comparar cada imagem com todas as outras é quadrático, o que é inaceitável num documento com milhares de imagens. O hash é, por isso, dividido em oito bandas de 8 bits, com um índice ordenado sobre cada uma
O princípio do pombal faz o resto: se dois hashes diferirem em, no máximo, 7 bits, têm de concordar exatamente em pelo menos uma das oito bandas. Procurar candidatos banda a banda encontra, por isso, todas as correspondências permitidas sem um varrimento exaustivo par a par, e o limite publicado de 7 existe precisamente porque a garantia só se mantém até essa distância
Os candidatos e os acertos de banda são ordenados deterministicamente por número de objeto e geração, e o número de objeto mais baixo torna-se canónico. Isso torna a saída reprodutível: executar o otimizador duas vezes sobre a mesma entrada produz as mesmas escolhas canónicas, o que importa quando o resultado é comparado ou assinado
Execuções de teste e relatórios honestos
Como esta é uma otimização com perdas, no sentido de as imagens fundidas não serem idênticas, o fluxo de trabalho sensato é olhar antes de saltar. A opção de execução de teste reporta o que seria fundido e quanta carga útil seria recuperada, sem modificar o grafo de objetos, pelo que uma tarefa em lote pode registar o efeito e um humano pode aprovar os limiares uma vez, em vez de por ficheiro
Duas notas de honestidade no relatório merecem menção. O valor recuperável estima apenas a carga útil codificada dos streams duplicados; não conta a sobrecarga de dicionários, objetos indiretos e referências cruzadas, pelo que a redução real do tamanho do ficheiro é ligeiramente maior do que a reportada, e não menor. E o relatório expõe o erro máximo aceite e a carga de trabalho de comparação, para que se possa ver o quão perto as fusões chegaram dos seus limiares
A grelha de amostragem fixa de 64 por 64 é uma aproximação intencional, e tem uma consequência que vale a pena declarar claramente: uma diferença mais pequena do que uma célula de amostra pode passar despercebida mesmo com um limiar zero. Uma única palavra alterada numa página digitalizada pode não sobreviver a essa amostragem. Quando é necessária prova ao nível do byte, use antes o percurso de deduplicação exata — funde apenas recursos genuinamente idênticos e não faz qualquer julgamento visual
Onde se encaixa num pipeline de redução de tamanho
Ordene as passagens por risco. Execute primeiro a deduplicação exata de recursos, já que é sem perdas e gratuita. Depois a subconjuntação de fontes, que também é sem perdas e costuma ser o maior ganho isolado em documentos ricos em texto, como descrito em otimização do tamanho de ficheiro PDF e subconjuntação de fontes. Depois a recompressão ou subamostragem de imagens, se o suporte de destino o permitir. A deduplicação percetual pertence depois dessas, porque é o único passo cujo resultado depende de um julgamento
Escolha os limiares por classe de documento, e não de forma global. Para lotes digitalizados em que cada página traz o mesmo papel timbrado, uma distância de hash de 2 a 3 com um erro médio à volta de 2 funde o papel timbrado de forma fiável. Para documentos em que as imagens transportam dados — gráficos, mapas, imagens médicas — use limiares zero ou próximos de zero, ou salte o passo por completo, porque dois gráficos que diferem num ponto traçado são dois gráficos diferentes, por mais semelhantes que pareçam a 64 por 64
Depois de otimizar, verifique. Renderize uma amostra de páginas antes e depois e compare-as, e confirme que a contagem de páginas e de imagens mudou como esperava. O percurso de inspeção para o que um documento realmente contém está descrito em auditar o tamanho de ficheiro PDF por categoria, que é também a forma mais rápida de descobrir se as imagens são, à partida, o seu problema
A deduplicação percetual, a deduplicação exata de recursos e a auditoria de tamanho funcionam sobre o mesmo grafo de objetos para Delphi, C++Builder e Free Pascal; a lista completa de funcionalidades está disponível na página da PDF Library for Delphi