O PDF armazena imagens como objetos de primeira classe dentro de seus fluxos de conteúdo. Quando uma página faz referência a uma fotografia, uma digitalização ou um diagrama, os dados dos pixels ficam em um dicionário XObject ao lado da geometria da página. O Componente PDFium expõe isso através de duas propriedades em TPdf: BitmapCount, que retorna quantos bitmaps incorporados estão na página atual, e Bitmap[Index], que decodifica um deles para um TBitmap que você possui e deve liberar. Esse é todo o modelo de extração. O loop possui quatro linhas; o que exige critério é o encanamento ao redor
Abrindo o documento
A primeira coisa a se saber sobre TPdf é que Active := True nunca gera exceção. Falhas de carregamento, palavras-passe incorretas, ficheiros corrompidos: tudo isso é engolido internamente e o componente simplesmente permanece inativo. Você tem que verificar a flag por conta própria após a atribuição, senão irá avançar para o loop de página com o PageCount retornando zero e se perguntará por que nada foi extraído
Os ficheiros protegidos por palavra-passe seguem o mesmo padrão: atribua o Pdf.Password antes de definir Active := True. Se a palavra-passe estiver incorreta, o Active continuará como False e não haverá nenhuma exceção para tratar. Em uma ferramenta em lote que processa centenas de ficheiros, esse comportamento silencioso na verdade é bastante útil: você acumula as falhas em uma lista, em vez de voltar à pilha de chamadas para resolver a cada uma
Iterando as páginas e extraindo bitmaps
O BitmapCount é por página, então você define Pdf.PageNumber antes de lê-lo. Os números das páginas são baseados em 1; o padrão é 0, o que significa que nenhuma página está carregada. A propriedade Bitmap[Index] é baseada em 0 e retorna um TBitmap que é de propriedade do chamador. Você deve liberá-lo. Se negligenciar a liberação dentro de um longo loop sobre um documento grande, a memória subirá rápido, porque cada bitmap pode ter vários megabytes de dados brutos de pixels antes de qualquer compressão
A proteção Assigned é importante. Um pequeno número de geradores de PDF gravam XObjects de imagem com dimensões de zero pixels ou dados malformados; nesses casos, o componente retorna nil em vez de um bitmap vazio. Tratar um retorno nil como um erro e cancelar a extração é a reação errada: pule-o, registre a página e o índice se você precisar de uma trilha de auditoria, e continue. O restante da página ainda pode gerar imagens válidas
Observe que o loop externo define o Pdf.PageNumber a cada iteração. Essa atribuição é o que carrega a página no estado interno do componente e faz com que o BitmapCount tenha significado. Pule-a e você lerá repetidamente a contagem da mesma página. O padrão parece redundante ao escrevê-lo, mas é assim que a API foi projetada: a página é um cursor, não uma coleção
Escolhendo um formato de saída
BMP é um formato sem perdas e sempre disponível sem unidades adicionais, o que o torna um padrão adequado quando você ainda não sabe o que a imagem contém. Quando o tamanho do ficheiro importa, o formato de pixels do TBitmap retornado lhe dirá qual é o codec apropriado. Um bitmap de 32 bits carrega um canal alfa; o PNG preserva isso sem perdas. Uma imagem grande de 24 bits com tom contínuo é uma candidata ao JPEG. Imagens menores ou daquelas que foram desenhadas com uma paleta restrita geralmente ficam melhores deixadas como BMP em vez de passarem por JPEG, o que adiciona artefatos de bloco em configurações de baixa qualidade e economiza pouco nas altas
Na prática, a seleção do formato é determinada pelo Bmp.PixelFormat e pelas dimensões. Se PixelFormat = pf32bit você precisará de um formato que possua o canal alfa; PNG é a escolha óbvia, embora necessite da unidade PNGImage em versões mais antigas do Delphi. Para imagens de 24 bits mais largas do que aproximadamente 300 pixels, o JPEG na qualidade 85 proporciona uma redução de tamanho de três para um em relação ao BMP sem nenhuma perda perceptível na maior parte do conteúdo fotográfico. Abaixo desse limite, o BMP possui tamanho comparável e evita por completo qualquer decisão relacionada à qualidade
O que BitmapCount conta e não conta
O PDF faz distinção entre XObjects de imagem e gráficos vetoriais desenhados com operadores de caminho. Uma página que parece visualmente complexa pode retornar um BitmapCount igual a zero se cada elemento for vetorial. Páginas escaneadas quase sempre retornam exatamente um: o scanner grava todo o escaneamento como um único XObject de imagem de página inteira na resolução em que o scanner estava configurado. Páginas que misturam texto tipográfico com fotografias incorporadas retornam uma entrada por fotografia. Linhas de regra decorativas, planos de fundo sombreados e bordas de tabela normalmente não aparecem de forma alguma na contagem de bitmaps
A contagem também não inclui imagens em linha, uma construção de PDF raramente utilizada onde os dados da imagem são embutidos diretamente no fluxo de conteúdo da página em vez de serem como um XObject nomeado. Elas ficam fora do escopo do que esta API expõe; elas são suficientemente incomuns em documentos reais que a maioria das ferramentas de extração simplesmente nem as tratam
Um detalhe que vale a pena ter em mente: o BitmapCount que você lê é para a página atual de acordo com a última atribuição feita a PageNumber. Se o seu código se ramificar ou chamar qualquer função que altere PageNumber entre a contagem e a busca, você poderá ler menos imagens do que havia alocado espaço para isso, ou irá indexar até passar do final. Mantenha a leitura da contagem e o loop do Bitmap[] na mesma página, sem tocar em PageNumber durante o meio-tempo
Utilizando TPdfView em uma aplicação de formulário
O componente TPdfView expõe as mesmas propriedades BitmapCount e Bitmap[], mas a página da qual ele lê é a página atualmente exibida na visualização e não TPdf.PageNumber. Os dois ponteiros de página são independentes; definir um não afeta o outro. Em uma aplicação de formulário VCL com um visualizador em tempo real, você pode chamar Pdf.PageNumber := N para direcionar a extração por meio de TPdf, enquanto o visualizador fica aonde o utilizador rolou pela última vez. Essa separação é intencional e mantém o estado de exibição do visualizador limpo enquanto uma extração em segundo plano é executada
Memória e desempenho em processamentos em lote
Ao longo de um ficheiro grande, o orçamento de memória é a principal coisa a se observar. Cada chamada a Bitmap[] aloca um novo TBitmap na heap, e em uma página escaneada a 300 DPI, isso equivale facilmente a 25 MB de dados brutos de pixels antes de qualquer codificação. Se você processar páginas em um loop estreito sem liberá-las entre as iterações, o conjunto de trabalho crescerá linearmente de acordo com o número de imagens. A forma correta é sempre: buscar um bitmap, fazer o que é necessário, liberá-lo e então buscar o próximo. Se precisar manter referências a vários bitmaps ao mesmo tempo para uma etapa de comparação, conte-os primeiro com BitmapCount e aloque seu contêiner correspondentemente, e depois libere cada um assim que não for mais usá-lo, em vez de adiar a limpeza para o final do documento. Em um documento com 500 páginas escaneadas, essa distinção pode significar a diferença entre 25 MB e 12 GB de pico de RSS
As propriedades BitmapCount e Bitmap[] aqui exibidas integram o Componente PDFium para Delphi e C++Builder