Uma lâmina médica digitalizada, uma imagem aérea de levantamento, um quadro de filme arquivado com toda a sua faixa dinâmica. Essas são as imagens que chegam como JPEG 2000, e chegam assim por uma razão. O formato mantém 12 ou 16 bits por canal, comprime com uma transformada wavelet em vez do DCT em blocos que o JPEG usa, e pode codificar a mesma imagem tanto sem perdas quanto com perdas a partir de um único codestream. Quando um documento construído a partir dessas fontes precisa se tornar um PDF, a imagem tem que percorrer um filtro que a especificação PDF reserva especificamente para esse codec
O HotPDF v2.228.0 restaurou um motor de decodificação JPEG 2000 funcional para esse caminho. Uma versão anterior havia fornecido a unit com funções stub que retornavam nil, portanto a API existia mas não decodificava nada. O motor atual vincula o OpenJPEG 2.5.4 estaticamente e converte uma fonte JP2 ou J2K em pixels que o HotPDF pode posicionar em uma página
O filtro JPXDecode no PDF
A ISO 32000-1 define o filtro JPXDecode no §7.4.9. Um XObject de imagem PDF nomeia sua compressão na entrada /Filter do dicionário de stream, e JPXDecode é o valor que indica que os dados do stream são um codestream JPEG 2000 em vez do JPEG base que o /DCTDecode carrega. O filtro é o que permite que um PDF contenha dados de imagem comprimidos por wavelet com alta profundidade de bits, e admite os modos sem perdas e com perdas do codec, porque o modo é uma propriedade do próprio codestream e não do invólucro ao seu redor
Esse último ponto é o mais importante a reter. O JPEG 2000 é um único algoritmo com um caso especial sem perdas, não dois formatos separados. A wavelet reversível 5/3 reconstrói as amostras originais exatamente; a wavelet irreversível 9/7 troca essa exatidão por um arquivo menor. Um decodificador trata ambos da mesma forma no momento da leitura, razão pela qual o HotPDF precisa de apenas um caminho de decodificação para aceitar qualquer coisa que um stream JPXDecode lhe envie
O que o decodificador faz com os pixels
XObjects de imagem PDF no caso comum esperam 8 bits por componente em DeviceGray ou DeviceRGB. O JPEG 2000 rotineiramente excede isso, e seu modelo de componentes é mais geral do que um raster empacotado, portanto o decodificador tem três tarefas a realizar antes que os dados possam ser usados como uma imagem normal
Primeiro, componentes de alta profundidade de bits são reamostrados para 8 bits. Uma amostra de 12 ou 16 bits é reduzida para o intervalo de 0 a 255 para que o resultado seja um raster comum de 8 bits. Componentes com sinal são deslocados para o intervalo sem sinal primeiro. O detalhe importa porque é com perdas em si mesmo: uma digitalização em tons de cinza de 16 bits perde sua profundidade tonal no momento em que se torna uma imagem PDF de 8 bits, o que é a troca correta para saída em tela e impressão, mas não para rearquivamento
Segundo, um espaço de cores YCbCr (o codec o chama de SYCC) é convertido para RGB. O JPEG 2000 frequentemente armazena cor em um espaço de luma-croma para eficiência de compressão, a mesma ideia que o JPEG base usa, e o decodificador aplica a transformada inversa padrão para que a página receba RGB verdadeiro
Terceiro, componentes com subamostragem são ampliados por replicação de vizinho mais próximo. Os canais de croma são frequentemente armazenados na metade da resolução, portanto o decodificador lê cada componente em suas próprias dimensões e seu próprio fator de amostragem, depois replica amostras para trazer cada canal ao tamanho total da imagem antes da intercalação. O vizinho mais próximo mantém o passo barato; o croma que está sendo preenchido tinha baixa frequência para começar, portanto o custo visual é pequeno
Caixas JP2 versus um codestream J2K puro
Um arquivo JPEG 2000 vem em duas formas, e o HotPDF detecta qual está lendo pelos primeiros bytes em vez da extensão do arquivo. Um arquivo JP2 é um contêiner estruturado em caixas: abre com a caixa de assinatura de doze bytes 00 00 00 0C 6A 50 20 20 e envolve o codestream junto com caixas que descrevem espaço de cores, resolução e metadados. Um codestream J2K puro não tem contêiner e começa com o marcador SOC FF 4F FF 51. O decodificador lê esses bytes iniciais, reconhece a assinatura e seleciona o codec OpenJPEG correspondente para cada caso
Ambas as formas são tratadas porque ambas ocorrem na prática. Dispositivos de captura e arquivos que precisam de metadados laterais emitem JP2; ferramentas que querem o menor payload possível emitem o codestream puro. O tipo de formato é modelado como um enum, TJpeg2000FileType, com os membros jtInvalid, jtJP2, jtJ2K e jtJPT. O membro JPT nomeia a variante de streaming JPIP; o detector de assinatura de bytes resolve as duas formas que pode decodificar, JP2 e J2K, e reporta qualquer outra coisa como jtInvalid para que uma entrada não suportada falhe claramente em vez de produzir lixo
uses
HPDFJpeg2000;
var
Decoder: THPDFJpeg2000Decoder;
Pixels: TJpeg2000ByteArray;
begin
Decoder := THPDFJpeg2000Decoder.Create;
try
if Decoder.LoadFromStream(Input) then // JP2 or J2K, auto-detected
if Decoder.GetImageData(Pixels) then
// Pixels is 8-bit interleaved, ColorComponents channels wide,
// row-major top to bottom: ready for a DeviceGray/DeviceRGB XObject.
ProcessRaster(Decoder.Width, Decoder.Height,
Decoder.ColorComponents, Pixels);
finally
Decoder.Free;
end;
end;
Sem perdas e com perdas no lado de codificação
O decodificador lê ambos os modos sem ser informado qual é. A escolha só se torna um parâmetro quando você vai na direção oposta e produz um arquivo JPEG 2000, o que o HotPDF também pode fazer através da classe TJpeg2000Bitmap, um descendente de TBitmap que carrega e salva dados raster como JP2. Duas propriedades governam a saída. LosslessCompression é um booleano que seleciona a wavelet reversível quando verdadeiro; CompressionQuality é um TJpeg2000QualityRange, um inteiro de 1 a 100 onde 1 é pequeno e feio e 100 é grande e fiel. Os padrões estão em constantes nomeadas: Jpeg2000DefaultLosslessCompression é False e Jpeg2000DefaultLossyQuality é 80
A decisão é uma decisão de conteúdo. Sem perdas se adequa a uma cópia mestre, uma digitalização médica ou legal, qualquer coisa que possa ser recodificada posteriormente e não deve acumular perda de geração. Com perdas na qualidade 80 se adequa a uma imagem destinada à tela ou impressão, onde a degradação elegante da wavelet fornece um arquivo notavelmente menor sem artefato que um leitor perceberia. Há uma ressalva CMYK a destacar: o bitmap expõe SetCMYK para marcar dados de quatro canais como CMYK em vez de RGBA, o que importa para pipelines de impressão que mantêm separações intactas
uses
HPDFJpeg2000;
var
Bmp: TJpeg2000Bitmap;
begin
Bmp := TJpeg2000Bitmap.Create;
try
Bmp.LoadFromStream(Source); // decode an existing JP2/J2K
Bmp.LosslessCompression := True; // reversible 5/3 wavelet
// or, for a smaller lossy file:
// Bmp.LosslessCompression := False;
// Bmp.CompressionQuality := 80; // matches the default
Bmp.SaveToStream(Output); // always writes a JP2 file
finally
Bmp.Free;
end;
end;
Por que não há pipeline de filtro de decodificação no carregamento
Um fato arquitetural molda como você usa qualquer coisa disso, e é fácil assumir o oposto. O HotPDF não tem um filtro de imagem geral de decodificação no carregamento. Quando você abre um PDF que já contém uma imagem JPXDecode, o motor não decodifica esse stream. Ele mantém os bytes JPEG 2000 exatamente como estão, portanto uma cópia de página ou uma mesclagem de documento carrega a imagem sem alterações, byte a byte. O decodificador tem um único ponto de entrada, e está no lado de criação: o AddImage baseado em arquivo, despachado por extensão de arquivo para lidar com fontes .jp2, .j2k, .jpt e .jpc
Essa divisão é o design correto em vez de uma limitação. Decodificar um stream JPX incorporado no carregamento, apenas para recodificá-lo ao salvar, converteria uma imagem arquivada sem perdas em uma com perdas e inflaria cada mesclagem, tudo por uma imagem que você pretendia apenas mover de um PDF para outro. Passar o stream verbatim é uma operação sem perdas e rápida. A decodificação é adiada para o único momento em que é genuinamente necessária: quando você entrega ao motor um arquivo JPEG 2000 do disco e pede que ele rasterize essa imagem para posicionamento em uma nova página. Nesse ponto o arquivo tem que se tornar pixels, e o decodificador roda
Registrando suporte e posicionando uma imagem
O registro de imagens JPEG 2000 é opcional atrás do switch de compilação HPDF_REGISTER_JPEG2000_PICTURE, que está desativado por padrão. O motivo é um conflito real, não cautela: registrar os formatos de arquivo jp2, j2k e jpc globalmente com TPicture pode interferir com a detecção de formato BLOB da qual o TppDBImage do ReportBuilder depende. Defina o switch quando essa integração não estiver em jogo, e os formatos de arquivo se registram para que o TPicture os reconheça; deixe-o indefinido e o despacho de extensão do AddImage ainda decodifica arquivos JPEG 2000 diretamente, porque esse caminho não passa pelo TPicture de forma alguma
Com isso entendido, posicionar uma imagem JPEG 2000 é o mesmo ritmo de três chamadas que qualquer outra imagem HotPDF. Passe ao AddImage um caminho .jp2 e um tipo de compressão para como a imagem deve ser armazenada na saída, depois posicione o índice de imagem retornado na página com ShowImage
var
Pdf: THotPDF;
ImgIndex: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.AddPage;
// The .jp2 source is decoded through the OpenJPEG backend, then
// re-embedded with the compression you request here.
ImgIndex := Pdf.AddImage('Scan_16bit.jp2', icJpeg);
// x, y, width, height in points; final 0 is the rotation angle.
Pdf.ShowImage(ImgIndex, 72, 72, 400, 300, 0);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
A compressão que você passa ao AddImage controla como a imagem decodificada é rearmazenada, não como foi lida. Um arquivo JPEG 2000 decodificado para um bitmap pode sair como um JPEG DCTDecode, um raster Flate ou outro filtro suportado, o que for adequado para o documento. A decodificação de JP2 ou J2K ocorre primeiro independentemente, portanto a mesma chamada aceita uma fonte comprimida por wavelet e a incorpora na forma que o restante do seu pipeline espera
Para uma visão mais ampla de como imagens e fontes aparecem na saída gerada, veja nossas notas sobre saída de relatórios com fontes e imagens. Quando o documento que você está montando reutiliza conteúdo de PDFs existentes, o comportamento de passthrough descrito aqui se combina com a mecânica de mesclagem e revisão em streams de objeto e atualizações incrementais. O motor de decodificação JPEG 2000 é fornecido como parte do HotPDF Component para Delphi e C++Builder, ao lado das APIs de imagem, fonte e documento cobertas em outros lugares neste blog