Artigo Técnico

Reduzir PDFs ao Fundir Imagens Quase Idênticas em Delphi

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

Três limiares controlam a fusão de imagens quase idênticas: distância de hash, erro médio de canal e erro máximo de canal único
Os três portões usam pixels descodificados — um par com marca de água morre no portão de erro máximo, enquanto um gémeo recodificado passa sem problemas
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

O hash de diferença de 64 bits divide-se em oito bandas de 8 bits, e o princípio do pombal garante que uma banda continua a corresponder mesmo após sete inversões de bit
Sete bits invertidos distribuídos por oito bandas têm forçosamente de deixar uma banda intacta, o que transforma um varrimento quadrático em oito pesquisas ordenadas

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

Ordem de pipeline recomendada, desde a deduplicação exata sem perdas e a subconjuntação de fontes até à recompressão condicional de imagens, à deduplicação percetual e à verificação
Cada passo acrescenta risco, pelo que os ganhos sem perdas vêm primeiro e a fusão por julgamento corre por último, seguida de uma verificação de renderizar e comparar

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