Renderizar uma página PDF para um JPEG envolve duas operações que as pessoas tendem a executar juntas e depois depurar separadamente. Primeiro, você rasteriza a página em um bitmap de pixels em uma resolução de sua escolha. Em seguida, você passa esse bitmap para um codificador JPEG e escolhe uma qualidade. O Componente PDFium é responsável pela primeira metade através do RenderPage; a segunda metade é VCL puro, TJPEGImage da Vcl.Imaging.jpeg. A junção entre elas é onde residem as decisões interessantes, porque a resolução que você escolhe no lado da renderização e a qualidade que você escolhe no lado da codificação se equilibram entre si e com o tamanho do arquivo de maneiras fáceis de errar
O que você deve internalizar antes de qualquer código: uma página PDF não possui pixels. Ela é descrita em pontos, onde um ponto equivale a 1/72 de polegada, e a página é um desenho vetorial medido nesses pontos. Quando você pede ao PDFium para renderizar, você está escolhendo sobre quantos pixels projetar esse desenho, e essa escolha é o DPI. Se errar na aritmética, você renderizará uma miniatura borrada quando queria uma matriz de impressão, ou alocará um bitmap de 200 megapixels para algo destinado a ser uma visualização de 120 pixels
De DPI para dimensões de pixels
O RenderPage requer Width e Height inteiros em pixels, não um DPI. Então, o primeiro trabalho é converter. Uma página informa seu tamanho em pontos através de PageWidth e PageHeight (ambos Double), e a conversão é a mesma que todo rasterizador usa: pixels equivalem a pontos vezes o DPI alvo dividido por 72. Uma página US Letter tem 612 por 792 pontos. A 150 DPI isso se torna 1275 por 1650 pixels; a 72 DPI ela permanece com 612 por 792, um pixel por ponto, o que é o caso que as pessoas esquecem que é apenas a identidade
// Pdf.PageNumber must already point at the page you want.
PixelW := Round(Pdf.PageWidth * Dpi / 72);
PixelH := Round(Pdf.PageHeight * Dpi / 72);
Bitmap := Pdf.RenderPage(0, 0, PixelW, PixelH, ro0, [], clWhite);
// ... use Bitmap ...
Bitmap.Free; // the function-form RenderPage hands you ownership
Dois detalhes nestas quatro linhas decidem se o código está correto. O primeiro é que o formato de função de RenderPage retorna um TBitmap que você possui. O PDFium o alocou e o deixou; se você não fizer o Free em cada iteração, um lote de algumas centenas de páginas vazará algumas centenas de bitmaps e o processo inchará até que algo falhe. O segundo é o argumento Color, clWhite aqui. As páginas PDF geralmente são desenhadas assumindo um substrato branco opaco, e uma página com transparência renderizada em uma cor de fundo incorreta produz bordas turvas ou halos escuros dispersos. O branco é o padrão correto para quase todos os documentos; o parâmetro existe para os raros casos em que não é
Os 0, 0 são os deslocamentos de Left e Top na página, no espaço de coordenadas dimensionado, e você os deixa em zero, a menos que esteja cortando. O ro0 é a rotação: deixe-o em zero e o PDFium respeitará qualquer rotação que a página já declare em sua entrada /Rotate, de modo que uma página criada em paisagem sai em paisagem sem você precisar fazer nada
Codificando o bitmap como JPEG
Assim que o bitmap existe, o JPEG é a parte fácil e é Delphi puro. O TJPEGImage.Assign copia o bitmap para dentro dele, CompressionQuality define a qualidade em uma escala de 1 a 100, e SaveToFile grava o arquivo. A única regra de ordenação é que a qualidade deve ser definida antes de você salvar, porque ela governa a codificação que o SaveToFile aciona
uses
Vcl.Graphics, Vcl.Imaging.jpeg, PDFium;
procedure SavePageAsJpeg(Pdf: TPdf; PageNumber, Dpi, Quality: Integer;
const FileName: string);
var
Bitmap: TBitmap;
Jpeg: TJPEGImage;
begin
Pdf.PageNumber := PageNumber;
Bitmap := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Dpi / 72),
Round(Pdf.PageHeight * Dpi / 72),
ro0, [], clWhite);
try
Jpeg := TJPEGImage.Create;
try
Jpeg.Assign(Bitmap);
Jpeg.CompressionQuality := Quality; // 1..100
Jpeg.SaveToFile(FileName);
finally
Jpeg.Free;
end;
finally
Bitmap.Free;
end;
end;
Aquele try/finally aninhado parece exigente para um utilitário de uma única página, mas é perfeitamente adequado para processamento em lote. O bloco interno libera o codificador, o bloco externo libera o bitmap, e se qualquer um deles for acionado em uma exceção ainda assim libera o que possui. Se você juntar os dois em um só, uma exceção durante a codificação poderá deixar o bitmap órfão. A longo prazo, essa é a diferença entre um conversor que termina e um que morre na página 300 com um arquivo corrompido e uma caixa de diálogo de falta de memória
Escolhendo DPI e qualidade juntos
Os dois controles não são independentes do objetivo da saída, e o erro comum é aumentar ambos por precaução. Uma miniatura para web renderizada a 300 DPI e salva na qualidade 95 tem centenas de kilobytes fingindo ser uma imagem de 120 pixels; o navegador descarta quase tudo no redimensionamento. Combine a resolução com os pixels que a saída realmente necessita e depois escolha uma qualidade que sobreviva à compressão com perdas do JPEG sem artefatos visíveis
| Saída | DPI | Qualidade JPEG |
|---|---|---|
| Miniatura de lista | 72 | 60-70 |
| Visualização em tela | 96-150 | 80-85 |
| Visualização de altos detalhes | 200-300 | 85-95 |
| Matriz de impressão | 300-600 | 90-100 |
A qualidade JPEG merece uma palavra de cautela por si só. Não é um controle linear. O salto de 70 para 85 proporciona uma melhora visual real para um crescimento modesto do arquivo; o salto de 95 para 100 aproximadamente dobra o arquivo por uma diferença que quase ninguém pode ver, porque a qualidade 100 ainda não é sem perdas, ela apenas para de descartar muito. Para páginas carregadas de texto, a compressão baseada em blocos do JPEG mancha as bordas nítidas dos glifos em um ruído fraco, e é por isso que uma qualidade abaixo de cerca de 80 cria um texto com aspecto de digitalizado naquilo que deveria ser uma saída nítida. Se as páginas forem principalmente texto e você puder alterar os formatos, o PNG renderiza esse texto sem o ruído; o JPEG ganha seu espaço em conteúdo fotográfico e misto onde sua compressão é genuinamente menor
Miniaturas mais rápidas e menores
Quando o destino for uma miniatura em vez de uma reprodução fiel, você pode dizer ao renderizador para fazer menos trabalho. O parâmetro Options usa um conjunto de flags TRenderOption e algumas delas trocam a fidelidade pela velocidade exatamente do jeito que uma pequena pré-visualização precisa. reGrayscale remove a cor, o que renderiza mais rápido e produz um bitmap menor para codificar. reNoSmoothImage e reNoSmoothPath ignoram o anti-aliasing que de qualquer forma é invisível na escala da miniatura
function RenderThumbnail(Pdf: TPdf; PageNumber, MaxW, MaxH: Integer): TBitmap;
var
Scale: Double;
begin
Pdf.PageNumber := PageNumber;
// Fit the page inside MaxW x MaxH while preserving aspect ratio.
Scale := Min(MaxW / Pdf.PageWidth, MaxH / Pdf.PageHeight);
Result := Pdf.RenderPage(0, 0,
Round(Pdf.PageWidth * Scale),
Round(Pdf.PageHeight * Scale),
ro0, [reGrayscale, reNoSmoothImage], clWhite);
end;
O caso da miniatura também mostra a forma mais limpa de pensar no dimensionamento. Em vez de passar por DPI, calcule um único fator de escala que caiba a página dentro de uma caixa delimitadora e preserve a proporção da imagem, que é o que o Min das duas proporções faz. Uma página em retrato e uma página em paisagem terminam dentro da mesma caixa sem distorção, e você nunca tem que pensar em que DPI corresponde a "caber em 200 por 280". Uma advertência sobre o reGrayscale: ele converte conteúdo de imagem rasterizada para cinza, mas preenchimentos vetoriais e textos mantêm seus valores de cor no motor, então uma página que for principalmente arte vetorial pode voltar menos monocromática do que o nome da flag sugere. Para um resultado verdadeiramente totalmente em escala de cinza, converter o bitmap renderizado usando GrayscalePdfBitmap é o caminho confiável
Processando todo um documento em lote
Reunir tudo para um documento completo é um loop sobre o PageCount, com o PageNumber avançando uma página de cada vez. As páginas são baseadas em 1: a página um é PageNumber := 1, e o loop vai até PageCount de forma inclusiva, e não PageCount - 1. A outra coisa que o lote deve respeitar é o contrato de carregamento silencioso. Definir Active := True nunca gera exceção em um arquivo danificado ou com senha incorreta; ele simplesmente deixa o Active como False. Verifique isso antes de renderizar uma única página, senão o primeiro RenderPage trabalhará sobre um documento que nunca foi aberto
procedure ExportAllPages(const PdfPath, OutDir: string; Dpi, Quality: Integer);
var
Pdf: TPdf;
I, Digits: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := PdfPath;
Pdf.Active := True;
if not Pdf.Active then
raise Exception.Create('Could not open ' + PdfPath);
Digits := Length(IntToStr(Pdf.PageCount)); // zero-pad so files sort right
for I := 1 to Pdf.PageCount do
SavePageAsJpeg(Pdf, I, Dpi, Quality,
Format('%s\page_%.*d.jpg', [OutDir, Digits, I]));
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
O preenchimento com zeros através de Digits é um detalhe que salva uma tarde mais tarde. Se nomear os arquivos de page_1.jpg a page_10.jpg, qualquer ferramenta que os ordenar como strings colocará page_10 logo depois de page_1, embaralhando a ordem. Preencher de acordo com a largura do maior número de página, de forma que um documento de 300 páginas crie page_001.jpg, mantém a ordem léxica e a ordem das páginas idênticas em todos os pontos subsequentes
Para documentos suficientemente grandes de modo que a conversão leve um tempo perceptível, execute-o fora da thread da UI ou bombeie as mensagens entre as páginas para que o aplicativo permaneça responsivo, e ofereça ao usuário uma forma de parar. Se você estiver renderizando páginas muito grandes e quiser um cancelamento que afete o meio da página e não apenas entre as páginas, o Componente PDFium possui um caminho de renderização progressiva com um token de cancelamento; esse é um mecanismo mais pesado que a maioria das exportações em lote necessita, mas ele está lá quando uma única página a 600 DPI for lenta o suficiente para travar
Uma última combinação que vale a pena conhecer. Rasterizar uma página descarta sua camada de texto: o JPEG é feito de pixels, e as palavras nele não são mais selecionáveis ou pesquisáveis. Quando você precisa da imagem e do texto subjacente, renderize para obter a imagem e extraia o texto separadamente, processo abordado no artigo complementar sobre a extração de texto de documentos PDF com o Componente PDFium. As sobrecargas e opções de renderização do RenderPage exibidas aqui fazem parte do Componente PDFium para Delphi e C++Builder