Artigo Técnico

Incorporar Imagens AVIF, HEIF e JPEG XL em PDF a partir do Delphi

A PDF Library for Delphi aceita imagens AVIF, HEIF e JPEG XL como entrada através de AddModernImageFromFile e das suas variantes de fluxo e de string, preservando o alfa, o perfil ICC incorporado e os canais de 16 bits no caminho para dentro do objeto de imagem PDF. A deteção de formato acontece sobre uma leitura limitada de número mágico, e a descodificação corre através de um backend substituível, pelo que nada externo é invocado para um ficheiro que não seja realmente um desses formatos

Estes formatos chegaram aos fluxos de trabalho de documentos através dos telemóveis. O iOS produz HEIC por defeito há anos, os dispositivos Android produzem AVIF, e um técnico de campo que fotografa uma peça danificada envia uma imagem que um gerador de relatórios PDF construído em 2015 simplesmente não consegue abrir. O caminho de recurso genérico, descodificar através de um bitmap da plataforma, produz de forma consistente cor de 8 bits e perde o alfa e o perfil de cor pelo caminho

O que é que o caminho de imagem moderna preserva que uma conversão para bitmap perde?

Três coisas, e cada uma tem um fluxo de trabalho que depende dela. O alfa sobrevive, o que importa para logótipos e recortes de produto compostos sobre o conteúdo da página. O perfil ICC sobrevive, o que importa para tudo o que vá ser impresso ou correspondido em cor. E os canais de 16 bits sobrevivem, o que importa para imagens médicas e científicas onde a quantização a 8 bits destrói exatamente as gradações para as quais a imagem foi captada

Passar uma imagem por um bitmap da plataforma perde as três coisas num único passo, e fá-lo silenciosamente: o PDF resultante parece aproximadamente correto, e ninguém repara até uma gráfica perguntar por que razão o vermelho corporativo está errado. O valor de opção 8 nas chamadas de imagem moderna é o sinalizador que mantém o alfa, o ICC e os canais de 16 bits em conjunto, e é a predefinição para essas chamadas

Acrescentar uma imagem a uma página

A chamada devolve um identificador de imagem, que é depois selecionado e desenhado, ou desenhado e libertado num único passo:

uses
  PDFlibrary, PDFlibModernImage;

var
  Lib: TPDFlib;
  ImageID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.SetPageSize('A4');
    Lib.NewPage;

    // Options = 8 mantém o alfa, o ICC e os canais de 16 bits
    ImageID := Lib.AddModernImageFromFile('site-photo.heic', 8);
    if ImageID > 0 then
      Lib.DrawImageAndRelease(ImageID, 40, 40, 515, 340)
    else
      Lib.DrawText(40, 40, 'image could not be decoded');

    Lib.SaveToFile('inspection-report.pdf');
  finally
    Lib.Free;
  end;
end;

A deteção antecede a descodificação e é deliberadamente restrita. A biblioteca lê um cabeçalho limitado, reconhece as marcas de formato de ficheiro multimédia base ISO que identificam o AVIF e o HEIF, e reconhece tanto as assinaturas em bruto como as de contentor do JPEG XL, e depois restaura a posição de fluxo do chamador. Uma entrada desconhecida ou disfarçada nunca chega ao codec externo, o que impede que um executável renomeado seja entregue a um descodificador como se fosse uma fotografia

Onde é que a descodificação realmente acontece?

Os formatos de imagem modernos são codecs grandes e complexos, e colocar um deles dentro de uma biblioteca PDF seria uma escolha de design estranha. O backend predefinido carrega dinamicamente um módulo MagickWand implantável no mesmo processo e procura-o por uma ordem documentada: um ficheiro ou diretório explícito definido pelo utilizador, variáveis de ambiente, o diretório do executável, e o caminho de pesquisa do sistema

As aplicações que já incluem um descodificador, ou que não podem de todo carregar um módulo externo, registam em alternativa o seu próprio callback. O contrato é pequeno: ler o fluxo de entrada, escrever um PNG no fluxo de saída, respeitar a orientação pedida:

function MyDecoder(InStream, OutPNG: TStream;
  ImageFormat: TPDFlibModernImageFormat;
  ApplyOrientation: Boolean): Boolean;
begin
  // Descodifique InStream com o seu próprio codec e escreva bytes PNG em OutPNG
  Result := DecodeWithBundledCodec(InStream, OutPNG,
    ImageFormat, ApplyOrientation);
end;

begin
  RegisterModernImageDecoderBackend(MyDecoder);
  // ... acrescentar imagens ...
  ClearModernImageDecoderBackend;    // voltar ao backend predefinido
end;

A implantação recebe uma conveniência e uma contenção deliberada. Se o diretório do codec contiver um subdiretório modules\coders, a biblioteca preenche as variáveis de ambiente de codec que esse esquema exige, mas apenas quando a aplicação anfitriã ainda não as definiu. Uma aplicação com a sua própria estratégia de implantação de runtime mantém-na

Por que razão PNG no meio?

Fazer a ponte através de um PNG em memória em vez de um buffer de pixels em bruto parece um passo extra e é, na verdade, o passo correto mais económico. O PNG exprime tudo o que tem de sobreviver, alfa, tipo de cor, profundidade de bits e um perfil ICC incorporado, e a biblioteca já tem um caminho maduro e bem testado de PNG para um objeto de imagem PDF com os filtros e o espaço de cor corretos. Reutilizá-lo significa que os formatos modernos herdam anos de trabalho de correção em vez de receberem uma implementação paralela

A ponte fica inteiramente em memória, pelo que não são criados ficheiros temporários e não é preciso nenhuma limpeza numa falha. Uma particularidade exigiu tratamento explícito: algumas conversões descartam o perfil ICC ao mudar de formato. O backend captura, por isso, o perfil de origem antes da mudança de formato, comprime-o com Flate, constrói um bloco iCCP válido com um CRC recalculado, e remove qualquer bloco sRGB que entrasse em conflito com ele. Em testes, um AVIF descodificado manteve RGBA de 16 bits com alfa de 16 bits, e o perfil extraído do PDF resultante correspondeu ao perfil de origem byte a byte, com 60 960 bytes

Notas práticas antes de ativar isto em produção

Verifique a disponibilidade no arranque, não na primeira fotografia. ModernImageCodecAvailable reporta se um backend pode ser usado, e SetModernImageCodecLibrary aponta para um ficheiro ou diretório explícito quando a implantação coloca o codec algures fora do padrão:

Lib.SetModernImageCodecLibrary('C:\MyApp\codecs');
if Lib.ModernImageCodecAvailable = 0 then
  Log('modern image input unavailable - HEIC and AVIF will be refused');

Fique atento ao tamanho do ficheiro do resultado. Uma imagem RGBA de 16 bits com um perfil incorporado é um objeto de imagem PDF grande, e um relatório com quarenta destas será grande. Quando o documento se destina a visualização em ecrã e não a impressão, reduzir a resolução antes de incorporar é a opção certa, e as alavancas gerais de tamanho estão descritas em otimização do tamanho de ficheiro PDF

Por fim, decida a política de cor deliberadamente. Manter o perfil de origem é correto para trabalho de arquivo e de impressão; converter para um espaço à escala do documento é correto quando um conjunto misto de fotografias tem de parecer consistente, e o percurso de conversão está descrito em recolorir um documento para outro espaço de cor. Se precisar de confirmar o que realmente ficou no ficheiro, o percurso de inspeção em extração de texto, imagem e fonte reporta os objetos de imagem que um documento transporta

A entrada de imagem moderna, a gestão de cor e a otimização de imagem fazem parte da mesma biblioteca para Delphi, C++Builder e Free Pascal; a lista completa de funcionalidades está na página da PDF Library for Delphi