Come codificare nuovamente un'immagine JPEG con i metadati
Nell'esempio seguente viene illustrato come codificare nuovamente un'immagine e i relativi metadati in un nuovo file dello stesso formato. In questo esempio vengono inoltre aggiunti metadati per illustrare un'espressione a singolo elemento usata da un writer di query.
In questo argomento sono contenute le sezioni seguenti.
- Prerequisiti
- Parte 1: Decodificare un'immagine
- Parte 2: Creare e inizializzare il codificatore di immagini
- Parte 3: Copiare le informazioni sui frame decodificati
- Parte 4: Copiare i metadati
- Parte 5: Aggiungere metadati aggiuntivi
- Parte 6: Finalizzare l'immagine codificata
- Codice di esempio di riscritcritto JPEG
- Argomenti correlati
Prerequisiti
Per comprendere questo argomento, è necessario avere familiarità con il sistema di metadati Windows Imaging Component (WIC), come descritto in Panoramica dei metadati wic. È anche necessario avere familiarità con i componenti codec WIC, come descritto in Panoramica del componente Windows Imaging.
Parte 1: Decodificare un'immagine
Prima di poter copiare i dati o i metadati dell'immagine in un nuovo file di immagine, è prima necessario creare un decodificatore per l'immagine esistente da codificare nuovamente. Il codice seguente illustra come creare un decodificatore WIC per il file di immagine 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);
}
La chiamata a CreateDecoderFromFilename ha usato il valore WICDecodeMetadataCacheOnDemand dall'enumerazione WICDecodeOptions come quarto parametro. Questo indica al decodificatore di memorizzare nella cache i metadati quando sono necessari i metadati, ottenendo un lettore di query o usando il lettore di metadati sottostante. L'uso di questa opzione consente di conservare il flusso ai metadati, che è necessario per eseguire la codifica rapida dei metadati e consente la decodifica e la codifica senza perdita di immagini JPEG. In alternativa, è possibile usare l'altro valore WICDecodeOptions , WICDecodeMetadataCacheOnLoad, che memorizza nella cache i metadati dell'immagine incorporata non appena l'immagine viene caricata.
Parte 2: Creare e inizializzare il codificatore di immagini
Il codice seguente illustra la creazione del codificatore che verrà usato per codificare l'immagine decodificata in precedenza.
// 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);
}
Viene creato e inizializzato un flusso di file WIC piFileStream per la scrittura nel file di immagine "test2.jpg". PiFileStream viene quindi usato per inizializzare il codificatore, comunicando al codificatore dove scrivere i bit dell'immagine al termine della codifica.
Parte 3: Copiare le informazioni sui frame decodificati
Il codice seguente copia ogni frame di un'immagine in un nuovo frame del codificatore. Questa copia include dimensioni, risoluzione e formato pixel; tutti necessari per creare un frame valido.
Nota
Le immagini JPEG avranno solo un frame e il ciclo seguente non è tecnicamente necessario, ma è incluso per illustrare l'utilizzo di più fotogrammi per i formati che lo supportano.
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);
}
Il codice seguente esegue un controllo rapido per determinare se i formati di immagine di origine e di destinazione sono gli stessi. Questa operazione è necessaria come parte 4 mostra un'operazione supportata solo quando il formato di origine e di destinazione è lo stesso.
// 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: Copiare i metadati
Nota
Il codice in questa sezione è valido solo quando i formati di immagine di origine e di destinazione sono uguali. Non è possibile copiare tutti i metadati di un'immagine in una singola operazione quando si esegue la codifica in un formato di immagine diverso.
Per mantenere i metadati durante la riprocrittura di un'immagine nello stesso formato di immagine, sono disponibili metodi per copiare tutti i metadati in una singola operazione. Ognuna di queste operazioni segue un modello simile; ognuno imposta i metadati del frame decodificato direttamente nel nuovo frame codificato. Si noti che questa operazione viene eseguita per ogni singolo frame di immagine.
Il metodo preferito per la copia dei metadati consiste nell'inizializzare il writer di blocchi del nuovo frame con il lettore di blocchi del frame decodificato. Il codice seguente illustra questo metodo.
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);
}
}
In questo esempio si ottiene semplicemente il lettore di blocchi e il writer di blocco rispettivamente dal frame di origine e dal frame di destinazione. Il writer di blocchi viene quindi inizializzato dal lettore di blocchi. Inizializza il writer di blocchi con i metadati prepopolato del lettore di blocchi. Per informazioni sui metodi aggiuntivi per la copia dei metadati, vedere la sezione Scrittura di metadati nella panoramica della lettura e scrittura dei metadati dell'immagine.
Anche in questo caso, questa operazione funziona solo quando le immagini di origine e di destinazione hanno lo stesso formato. Ciò è dovuto al fatto che diversi formati di immagine archiviano i blocchi di metadati in posizioni diverse. Ad esempio, sia JPEG che Tagged Image File Format (TIFF) supportano blocchi di metadati XMP (Extensible Metadata Platform). Nelle immagini JPEG, il blocco XMP si trova nel blocco di metadati radice, come illustrato nella panoramica dei metadati WIC. In un'immagine TIFF, tuttavia, il blocco XMP è incorporato nel blocco IFD radice.
Parte 5: Aggiungere metadati aggiuntivi
Nell'esempio seguente viene illustrato come aggiungere metadati all'immagine di destinazione. Questa operazione viene eseguita chiamando il metodo SetMetadataByName del writer di query usando un'espressione di query e i dati archiviati in 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);
}
Per altre informazioni sull'espressione di query, vedere Panoramica del linguaggio di query dei metadati.
Parte 6: Finalizzare l'immagine codificata
I passaggi finali per copiare l'immagine sono la scrittura dei dati pixel per il frame, il commit del frame nel codificatore e il commit del codificatore. Il commit del codificatore scrive il flusso di immagini nel file.
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();
}
Il metodo WriteSource del frame viene usato per scrivere i dati pixel per l'immagine. Si noti che questa operazione viene eseguita dopo la scrittura dei metadati. Ciò è necessario per garantire che i metadati dispongano di spazio sufficiente all'interno del file di immagine. Dopo la scrittura dei dati pixel, il frame viene scritto nel flusso usando il metodo Commit del frame. Dopo l'elaborazione di tutti i fotogrammi, il codificatore (e quindi l'immagine) viene finalizzato usando il metodo Commit del codificatore.
Dopo aver eseguito il commit del frame, è necessario rilasciare gli oggetti COM creati nel ciclo.
Codice di esempio di riscritcritto JPEG
Di seguito è riportato il codice delle parti da 1 a 6 in un blocco 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;
}
Argomenti correlati
-
Informazioni concettuali
-
Panoramica della lettura e scrittura dei metadati delle immagini