Como codificar novamente uma imagem JPEG com metadados
O exemplo a seguir demonstra como recodificar uma imagem e seus metadados para um novo arquivo do mesmo formato. Além disso, este exemplo adiciona metadados para demonstrar uma expressão de item único usada por um gravador de consulta.
Este tópico inclui as seções a seguir.
- Pré-requisitos
- Parte 1: Decodificar uma imagem
- Parte 2: Criar e inicializar o codificador de imagem
- Parte 3: Copiar informações de quadro decodificadas
- Parte 4: Copiar os metadados
- Parte 5: Adicionar metadados adicionais
- Parte 6: Finalizar a imagem codificada
- Código de exemplo de recodificação jpeg
- Tópicos relacionados
Pré-requisitos
Para entender este tópico, você deve estar familiarizado com o sistema de metadados wic (componente de imagem do Windows), conforme descrito na Visão geral de metadados do WIC. Você também deve estar familiarizado com os componentes do codec wic, conforme descrito na Visão geral do componente de imagem do Windows.
Parte 1: Decodificar uma imagem
Antes de copiar dados de imagem ou metadados para um novo arquivo de imagem, primeiro você deve criar um decodificador para a imagem existente que deseja recodificar. O código a seguir demonstra como criar um decodificador WIC para o arquivo de imagem test.jpg.
// Initialize COM.
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
IWICImagingFactory *piFactory = NULL;
IWICBitmapDecoder *piDecoder = NULL;
// Create the COM imaging factory.
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_WICImagingFactory,
NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&piFactory));
}
// Create the decoder.
if (SUCCEEDED(hr))
{
hr = piFactory->CreateDecoderFromFilename(L"test.jpg", NULL, GENERIC_READ,
WICDecodeMetadataCacheOnDemand, //For JPEG lossless decoding/encoding.
&piDecoder);
}
A chamada para CreateDecoderFromFilename usou o valor WICDecodeMetadataCacheOnDemand da enumeração WICDecodeOptions como o quarto parâmetro. Isso instrui o decodificador a armazenar em cache os metadados quando os metadados forem necessários, seja obtendo um leitor de consulta ou usando o leitor de metadados subjacente. O uso dessa opção permite que você mantenha o fluxo para os metadados, o que é necessário para executar a codificação de metadados rápida e permite a decodificação e a codificação sem perdas de imagens JPEG. Como alternativa, você pode usar o outro valor WICDecodeOptions , WICDecodeMetadataCacheOnLoad, que armazena em cache os metadados da imagem inserida assim que a imagem é carregada.
Parte 2: Criar e inicializar o codificador de imagem
O código a seguir demonstra a criação do codificador que você usará para codificar a imagem que você decodificou anteriormente.
// Variables used for encoding.
IWICStream *piFileStream = NULL;
IWICBitmapEncoder *piEncoder = NULL;
IWICMetadataBlockWriter *piBlockWriter = NULL;
IWICMetadataBlockReader *piBlockReader = NULL;
WICPixelFormatGUID pixelFormat = { 0 };
UINT count = 0;
double dpiX, dpiY = 0.0;
UINT width, height = 0;
// Create a file stream.
if (SUCCEEDED(hr))
{
hr = piFactory->CreateStream(&piFileStream);
}
// Initialize our new file stream.
if (SUCCEEDED(hr))
{
hr = piFileStream->InitializeFromFilename(L"test2.jpg", GENERIC_WRITE);
}
// Create the encoder.
if (SUCCEEDED(hr))
{
hr = piFactory->CreateEncoder(GUID_ContainerFormatJpeg, NULL, &piEncoder);
}
// Initialize the encoder
if (SUCCEEDED(hr))
{
hr = piEncoder->Initialize(piFileStream,WICBitmapEncoderNoCache);
}
Um piFileStream de fluxo de arquivos WIC é criado e inicializado para gravar no arquivo de imagem "test2.jpg". PiFileStream é usado para inicializar o codificador, informando ao codificador onde gravar os bits de imagem quando a codificação for concluída.
Parte 3: Copiar informações de quadro decodificadas
O código a seguir copia cada quadro de uma imagem para um novo quadro do codificador. Essa cópia inclui tamanho, resolução e formato de pixel; todos os quais são necessários para criar um quadro válido.
Observação
As imagens JPEG terão apenas um quadro e o loop abaixo não é tecnicamente necessário, mas está incluído para demonstrar o uso de vários quadros para formatos que dão suporte a ele.
if (SUCCEEDED(hr))
{
hr = piDecoder->GetFrameCount(&count);
}
if (SUCCEEDED(hr))
{
// Process each frame of the image.
for (UINT i=0; i<count && SUCCEEDED(hr); i++)
{
// Frame variables.
IWICBitmapFrameDecode *piFrameDecode = NULL;
IWICBitmapFrameEncode *piFrameEncode = NULL;
IWICMetadataQueryReader *piFrameQReader = NULL;
IWICMetadataQueryWriter *piFrameQWriter = NULL;
// Get and create the image frame.
if (SUCCEEDED(hr))
{
hr = piDecoder->GetFrame(i, &piFrameDecode);
}
if (SUCCEEDED(hr))
{
hr = piEncoder->CreateNewFrame(&piFrameEncode, NULL);
}
// Initialize the encoder.
if (SUCCEEDED(hr))
{
hr = piFrameEncode->Initialize(NULL);
}
// Get and set the size.
if (SUCCEEDED(hr))
{
hr = piFrameDecode->GetSize(&width, &height);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->SetSize(width, height);
}
// Get and set the resolution.
if (SUCCEEDED(hr))
{
piFrameDecode->GetResolution(&dpiX, &dpiY);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->SetResolution(dpiX, dpiY);
}
// Set the pixel format.
if (SUCCEEDED(hr))
{
piFrameDecode->GetPixelFormat(&pixelFormat);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->SetPixelFormat(&pixelFormat);
}
O código a seguir executa uma marcar rápida para determinar se os formatos de imagem de origem e destino são os mesmos. Isso é necessário, pois a Parte 4 mostra uma operação que só tem suporte quando o formato de origem e destino são os mesmos.
// Check that the destination format and source formats are the same.
bool formatsEqual = FALSE;
if (SUCCEEDED(hr))
{
GUID srcFormat;
GUID destFormat;
hr = piDecoder->GetContainerFormat(&srcFormat);
if (SUCCEEDED(hr))
{
hr = piEncoder->GetContainerFormat(&destFormat);
}
if (SUCCEEDED(hr))
{
if (srcFormat == destFormat)
formatsEqual = true;
else
formatsEqual = false;
}
}
Parte 4: Copiar os metadados
Observação
O código nesta seção é válido somente quando os formatos de imagem de origem e destino são os mesmos. Não é possível copiar todos os metadados de uma imagem em uma única operação ao codificar para um formato de imagem diferente.
Para preservar metadados ao recodificar uma imagem para o mesmo formato de imagem, há métodos disponíveis para copiar todos os metadados em uma única operação. Cada uma dessas operações segue um padrão semelhante; cada um define os metadados do quadro decodificado diretamente no novo quadro que está sendo codificado. Observe que isso é feito para cada quadro de imagem individual.
O método preferencial para copiar metadados é inicializar o gravador de blocos do novo quadro com o leitor de blocos do quadro decodificado. O código a seguir demonstra esse método.
if (SUCCEEDED(hr) && formatsEqual)
{
// Copy metadata using metadata block reader/writer.
if (SUCCEEDED(hr))
{
piFrameDecode->QueryInterface(IID_PPV_ARGS(&piBlockReader));
}
if (SUCCEEDED(hr))
{
piFrameEncode->QueryInterface(IID_PPV_ARGS(&piBlockWriter));
}
if (SUCCEEDED(hr))
{
piBlockWriter->InitializeFromBlockReader(piBlockReader);
}
}
Neste exemplo, você simplesmente obtém o leitor de bloco e o gravador de bloco do quadro de origem e do quadro de destino, respectivamente. Em seguida, o gravador de bloco é inicializado do leitor de bloco. Isso inicializa o gravador de blocos com os metadados pré-preenchidos do leitor de bloco. Para saber mais sobre métodos para copiar metadados, consulte a seção Escrevendo metadados na visão geral da leitura e gravação de metadados de imagem.
Novamente, essa operação só funciona quando as imagens de origem e destino têm o mesmo formato. Isso ocorre porque diferentes formatos de imagem armazenam os blocos de metadados em locais diferentes. Por exemplo, os blocos de metadados JPEG e TIFF (Tagged Image File Format) dão suporte a blocos de metadados XMP (Extensible Metadata Platform). Em imagens JPEG, o bloco XMP está no bloco de metadados raiz, conforme ilustrado na Visão geral de metadados do WIC. No entanto, em uma imagem TIFF, o bloco XMP é inserido no bloco IFD raiz.
Parte 5: Adicionar metadados adicionais
O exemplo a seguir demonstra como adicionar metadados à imagem de destino. Isso é feito chamando o método SetMetadataByName do gravador de consulta usando uma expressão de consulta e os dados armazenados em um PROPVARIANT.
if(SUCCEEDED(hr))
{
hr = piFrameEncode->GetMetadataQueryWriter(&piFrameQWriter);
}
if (SUCCEEDED(hr))
{
// Add additional metadata.
PROPVARIANT value;
value.vt = VT_LPWSTR;
value.pwszVal= L"Metadata Test Image.";
hr = piFrameQWriter->SetMetadataByName(L"/xmp/dc:title", &value);
}
Para obter mais informações sobre a expressão de consulta, consulte a Visão geral da linguagem de consulta de metadados.
Parte 6: Finalizar a imagem codificada
As etapas finais para copiar a imagem são gravar os dados de pixel para o quadro, confirmar o quadro no codificador e confirmar o codificador. Confirmar que o codificador grava o fluxo de imagem no arquivo.
if (SUCCEEDED(hr))
{
hr = piFrameEncode->WriteSource(
static_cast<IWICBitmapSource*> (piFrameDecode),
NULL); // Using NULL enables JPEG loss-less encoding.
}
// Commit the frame.
if (SUCCEEDED(hr))
{
hr = piFrameEncode->Commit();
}
if (piFrameDecode)
{
piFrameDecode->Release();
}
if (piFrameEncode)
{
piFrameEncode->Release();
}
if (piFrameQReader)
{
piFrameQReader->Release();
}
if (piFrameQWriter)
{
piFrameQWriter->Release();
}
}
}
if (SUCCEEDED(hr))
{
piEncoder->Commit();
}
if (SUCCEEDED(hr))
{
piFileStream->Commit(STGC_DEFAULT);
}
if (piFileStream)
{
piFileStream->Release();
}
if (piEncoder)
{
piEncoder->Release();
}
if (piBlockWriter)
{
piBlockWriter->Release();
}
if (piBlockReader)
{
piBlockReader->Release();
}
O método WriteSource do quadro é usado para gravar os dados de pixel para a imagem. Observe que isso é feito depois que os metadados são gravados. Isso é necessário para garantir que os metadados têm espaço suficiente dentro do arquivo de imagem. Depois que os dados de pixel são gravados, o quadro é gravado no fluxo usando o método Commit do quadro. Depois que todos os quadros tiverem sido processados, o codificador (e, portanto, a imagem) será finalizado usando o método Commit do codificador.
Depois de confirmar o quadro, você deve liberar os objetos COM criados no loop.
Código de exemplo de recodificação jpeg
Veja a seguir o código das Partes 1 a 6 em um bloco convieniente.
#include <Windows.h>
#include <Wincodecsdk.h>
int main()
{
// Initialize COM.
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
IWICImagingFactory *piFactory = NULL;
IWICBitmapDecoder *piDecoder = NULL;
// Create the COM imaging factory.
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_WICImagingFactory,
NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&piFactory));
}
// Create the decoder.
if (SUCCEEDED(hr))
{
hr = piFactory->CreateDecoderFromFilename(L"test.jpg", NULL, GENERIC_READ,
WICDecodeMetadataCacheOnDemand, //For JPEG lossless decoding/encoding.
&piDecoder);
}
// Variables used for encoding.
IWICStream *piFileStream = NULL;
IWICBitmapEncoder *piEncoder = NULL;
IWICMetadataBlockWriter *piBlockWriter = NULL;
IWICMetadataBlockReader *piBlockReader = NULL;
WICPixelFormatGUID pixelFormat = { 0 };
UINT count = 0;
double dpiX, dpiY = 0.0;
UINT width, height = 0;
// Create a file stream.
if (SUCCEEDED(hr))
{
hr = piFactory->CreateStream(&piFileStream);
}
// Initialize our new file stream.
if (SUCCEEDED(hr))
{
hr = piFileStream->InitializeFromFilename(L"test2.jpg", GENERIC_WRITE);
}
// Create the encoder.
if (SUCCEEDED(hr))
{
hr = piFactory->CreateEncoder(GUID_ContainerFormatJpeg, NULL, &piEncoder);
}
// Initialize the encoder
if (SUCCEEDED(hr))
{
hr = piEncoder->Initialize(piFileStream,WICBitmapEncoderNoCache);
}
if (SUCCEEDED(hr))
{
hr = piDecoder->GetFrameCount(&count);
}
if (SUCCEEDED(hr))
{
// Process each frame of the image.
for (UINT i=0; i<count && SUCCEEDED(hr); i++)
{
// Frame variables.
IWICBitmapFrameDecode *piFrameDecode = NULL;
IWICBitmapFrameEncode *piFrameEncode = NULL;
IWICMetadataQueryReader *piFrameQReader = NULL;
IWICMetadataQueryWriter *piFrameQWriter = NULL;
// Get and create the image frame.
if (SUCCEEDED(hr))
{
hr = piDecoder->GetFrame(i, &piFrameDecode);
}
if (SUCCEEDED(hr))
{
hr = piEncoder->CreateNewFrame(&piFrameEncode, NULL);
}
// Initialize the encoder.
if (SUCCEEDED(hr))
{
hr = piFrameEncode->Initialize(NULL);
}
// Get and set the size.
if (SUCCEEDED(hr))
{
hr = piFrameDecode->GetSize(&width, &height);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->SetSize(width, height);
}
// Get and set the resolution.
if (SUCCEEDED(hr))
{
piFrameDecode->GetResolution(&dpiX, &dpiY);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->SetResolution(dpiX, dpiY);
}
// Set the pixel format.
if (SUCCEEDED(hr))
{
piFrameDecode->GetPixelFormat(&pixelFormat);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->SetPixelFormat(&pixelFormat);
}
// Check that the destination format and source formats are the same.
bool formatsEqual = FALSE;
if (SUCCEEDED(hr))
{
GUID srcFormat;
GUID destFormat;
hr = piDecoder->GetContainerFormat(&srcFormat);
if (SUCCEEDED(hr))
{
hr = piEncoder->GetContainerFormat(&destFormat);
}
if (SUCCEEDED(hr))
{
if (srcFormat == destFormat)
formatsEqual = true;
else
formatsEqual = false;
}
}
if (SUCCEEDED(hr) && formatsEqual)
{
// Copy metadata using metadata block reader/writer.
if (SUCCEEDED(hr))
{
piFrameDecode->QueryInterface(IID_PPV_ARGS(&piBlockReader));
}
if (SUCCEEDED(hr))
{
piFrameEncode->QueryInterface(IID_PPV_ARGS(&piBlockWriter));
}
if (SUCCEEDED(hr))
{
piBlockWriter->InitializeFromBlockReader(piBlockReader);
}
}
if(SUCCEEDED(hr))
{
hr = piFrameEncode->GetMetadataQueryWriter(&piFrameQWriter);
}
if (SUCCEEDED(hr))
{
// Add additional metadata.
PROPVARIANT value;
value.vt = VT_LPWSTR;
value.pwszVal= L"Metadata Test Image.";
hr = piFrameQWriter->SetMetadataByName(L"/xmp/dc:title", &value);
}
if (SUCCEEDED(hr))
{
hr = piFrameEncode->WriteSource(
static_cast<IWICBitmapSource*> (piFrameDecode),
NULL); // Using NULL enables JPEG loss-less encoding.
}
// Commit the frame.
if (SUCCEEDED(hr))
{
hr = piFrameEncode->Commit();
}
if (piFrameDecode)
{
piFrameDecode->Release();
}
if (piFrameEncode)
{
piFrameEncode->Release();
}
if (piFrameQReader)
{
piFrameQReader->Release();
}
if (piFrameQWriter)
{
piFrameQWriter->Release();
}
}
}
if (SUCCEEDED(hr))
{
piEncoder->Commit();
}
if (SUCCEEDED(hr))
{
piFileStream->Commit(STGC_DEFAULT);
}
if (piFileStream)
{
piFileStream->Release();
}
if (piEncoder)
{
piEncoder->Release();
}
if (piBlockWriter)
{
piBlockWriter->Release();
}
if (piBlockReader)
{
piBlockReader->Release();
}
return 0;
}
Tópicos relacionados
-
Conceitual