Практическое руководство по повторному кодированию изображения JPEG с метаданными
В следующем примере показано, как повторно закодировать изображение и его метаданные в новый файл того же формата. Кроме того, в этом примере добавляются метаданные для демонстрации выражения с одним элементом, используемого автором запросов.
В этом разделе содержатся следующие разделы.
- Предварительные требования
- часть 1. Декодирование изображения
- часть 2. Создание и инициализация кодировщика изображений
- часть 3. Копирование декодированных сведений о кадре
- часть 4. Копирование метаданных
- часть 5. Добавление дополнительных метаданных
- часть 6. Завершение закодированного изображения
- пример кода для повторного кодирования в формате JPEG
- Связанные темы
Необходимые условия
Чтобы понять эту тему, необходимо ознакомиться с системой метаданных компонента образов Windows (WIC), как описано в обзоре метаданных WIC. Кроме того, вы должны ознакомиться с компонентами кодека WIC, как это описано в Обзоре компонента изображений Windows.
Часть 1. Декодирование изображения
Прежде чем скопировать данные изображения или метаданные в новый файл изображения, необходимо сначала создать декодатор для существующего образа, который требуется повторно закодировать. В следующем коде показано, как создать декодатор WIC для файла изображения 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);
}
Вызов CreateDecoderFromFilename использовал значение WICDecodeMetadataCacheOnDemand из перечисления WICDecodeOptions в качестве четвертого параметра. Это сообщает декодировщику кэшировать метаданные, когда они необходимы, либо путем получения средства чтения запросов, либо с помощью базового средства чтения метаданных. С помощью этого параметра вы можете сохранить поток метаданных, который необходим для выполнения быстрого кодирования и позволяет осуществлять декодирование и кодирование изображений JPEG без потерь. Кроме того, можно использовать другое WICDecodeOptions значение, WICDecodeMetadataCacheOnLoad, которое кэширует внедренные метаданные изображения по мере загрузки изображения.
Часть 2. Создание и инициализация кодировщика изображений
Следующий код демонстрирует создание кодировщика, который будет использоваться для кодирования ранее декодированного изображения.
// 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);
}
Создается и инициализируется поток файлов WIC piFileStream для записи в файл изображения "test2.jpg". PiFileStream затем используется для инициализации кодировщика, сообщая кодировщику, где записывать биты изображения после завершения кодирования.
Часть 3. Копирование декодированных сведений о кадре
Следующий код копирует каждый кадр изображения в новый кадр кодировщика. Эта копия включает размер, разрешение и формат пикселей; все из которых необходимы для создания допустимого кадра.
Заметка
Изображения JPEG будут иметь только один кадр, и приведенный ниже цикл не является технически необходимым, но включается для демонстрации использования нескольких кадров для форматов, поддерживающих его.
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);
}
Следующий код выполняет быструю проверку того, совпадают ли форматы исходного и целевого изображений. Это необходимо, как часть 4 показывает операцию, которая поддерживается только в том случае, если исходный и целевой формат совпадают.
// 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;
}
}
Часть 4. Копирование метаданных
Заметка
Код в этом разделе действителен только в том случае, если форматы исходного и целевого изображений одинаковы. Невозможно скопировать все метаданные изображения в одной операции при кодировке в другой формат изображения.
Чтобы сохранить метаданные при повторном кодировании изображения в одном формате изображения, существуют методы для копирования всех метаданных в одной операции. Каждая из этих операций соответствует аналогичному шаблону; каждый задает метаданные декодированного кадра непосредственно в новый кадр, закодированный. Обратите внимание, что это делается для каждого отдельного кадра изображения.
Предпочтительным способом копирования метаданных является инициализация записи блоков нового кадра с помощью декодированного средства чтения блоков кадра. Следующий код демонстрирует этот метод.
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);
}
}
В этом примере вы просто получите средство чтения блоков и средство записи блоков из исходного кадра и целевого кадра соответственно. Затем модуль записи блоков инициализируется из средства чтения блоков. Это инициализирует модуль записи блоков с предварительно заполненными метаданными средства чтения блоков. Дополнительные методы копирования метаданных см. в разделе "Запись метаданных" в разделе Обзор чтения и записи метаданных изображения.
Опять же, эта операция работает только в том случае, если исходные и целевые образы имеют одинаковый формат. Это связано с тем, что различные форматы изображений хранят блоки метаданных в разных расположениях. Например, как JPEG, так и тегированные форматы файлов изображений (TIFF) поддерживают блоки метаданных расширяемой платформы метаданных (XMP). В JPEG-изображениях блок XMP расположен в корневом блоке метаданных, как показано в обзоре метаданных WIC. Однако на изображении TIFF блок XMP внедрен в корневой блок IFD.
Часть 5. Добавление дополнительных метаданных
В следующем примере показано, как добавить метаданные в целевой образ. Это делается путем вызова метода записи запросов SetMetadataByName с помощью выражения запроса и данных, хранящихся в 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);
}
Дополнительные сведения о выражении запроса см. обзор языка запросов к метаданным.
Часть 6. Завершение закодированного изображения
Последние шаги для копирования изображения включают запись данных пикселей для кадра, сохранение кадра в кодировщике и завершение работы кодировщика. Применение кодировщика записывает поток изображения в файл.
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();
}
Метод WriteSource используется для записи пиксельных данных изображения в кадр. Обратите внимание, что это делается после записи метаданных. Это необходимо для обеспечения достаточного пространства метаданных в файле изображения. После записи данных пикселя кадр записывается в поток с помощью метода Commit кадра. После обработки всех кадров кодировщик (и таким образом изображение) завершается с помощью метода Commit кодировщика.
После фиксации кадра необходимо освободить COM-объекты, созданные в цикле.
Пример кода для повторного кодирования в формате JPEG
Ниже приведен код из частей 1–6 в одном удобном блоке.
#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;
}
Связанные разделы
-
концептуальные