Created
February 22, 2022 08:39
-
-
Save pabloko/d4a34ddf4cb95877dedda83a7bb4daca to your computer and use it in GitHub Desktop.
Image Decoder based on WIC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Image decoder based on WIC | |
* ------------------------------------------------ | |
* Provides synchronous loading of images and copy | |
* frames in ARGB format. | |
* | |
* Example of loading images: | |
* ImageDecoder::Init(); | |
* ImageDecoder* img = ImageDecoder::FromFile("C:\\path\\to\\image.png"); | |
* ImageDecoder* img = ImageDecoder::FromURL("http://example.com/img.jpg"); | |
* ImageDecoder* img = ImageDecoder::FromMemory(bufImage, sizeof(bufImage)); | |
* | |
* Supported image formats: | |
* BMP, GIF, HEIF, ICO, JPEG, PNG, TIFF, WEBP | |
*/ | |
#include <iostream> | |
#include <Windows.h> | |
#include <wincodec.h> | |
#include <shlwapi.h> | |
#pragma comment(lib,"shlwapi") | |
#pragma comment(lib,"urlmon") | |
#pragma comment(lib,"Windowscodecs") | |
/** | |
* @brief Image decoder using WIC | |
* Pabloko - 20/02/2022 | |
*/ | |
class ImageDecoder | |
{ | |
public: | |
/** | |
* @brief Create a ImageDecoder instance | |
* @param _stream image data | |
*/ | |
ImageDecoder(IStream *_stream) : stream(_stream) | |
{ | |
// header processing, read from stream | |
unsigned char header[4]; | |
err = stream->Read(header, sizeof(header), NULL); | |
if (FAILED(err)) return; | |
// seek back to istream begin | |
LARGE_INTEGER ps = { 0 }; | |
stream->Seek(ps, STREAM_SEEK_SET, NULL); | |
// get image format | |
format = GetFormatFromData(header); | |
if (format == FMT_UNK) { err = E_INVALID_PROTOCOL_FORMAT; return; } | |
// create wic image decoder | |
GUID decoderguid = GuidFormatDecoderFromFormat(format); | |
err = CoCreateInstance(decoderguid, NULL, CLSCTX_INPROC_SERVER, __uuidof(wicdecoder), reinterpret_cast<void **>(&wicdecoder)); | |
if (FAILED(err)) return; | |
// initialize wic image | |
err = wicdecoder->Initialize(stream, WICDecodeMetadataCacheOnLoad); | |
if (FAILED(err)) return; | |
// get frame count, must be > 0 | |
err = wicdecoder->GetFrameCount(&framecount); | |
if (FAILED(err)) return; | |
if (framecount == 0) { err = E_NOT_SUFFICIENT_BUFFER; return; } | |
// get the first frame to have it loaded (most formats use just one frame) | |
firstframe = GetFrameBitmapSource(0); | |
if (firstframe == NULL) { err = E_FAIL; } | |
} | |
/** | |
* @brief Destroy ImageDecoder instance | |
*/ | |
~ImageDecoder() | |
{ | |
// clean instance resources | |
if (firstframe != NULL) | |
firstframe->Release(); | |
if (wicdecoder != NULL) | |
wicdecoder->Release(); | |
if (stream != NULL) | |
stream->Release(); | |
} | |
/** | |
* @brief (Optional) Initialize process for WIC loading and better User Agent for downloads | |
*/ | |
static void Init() | |
{ | |
// if CoInitialize is called this call is not needed | |
CoInitialize(NULL); | |
// setting a modern browser UserAgent to avoid capping of IE in some websites, optional too | |
char ua[] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36"; | |
UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, ua, sizeof(ua), NULL); | |
} | |
/** | |
* @brief Create an ImageDecoder instance from file in hard disk | |
* @param path file to read from | |
* @return ImageDecoder instance or NULL if image loading fails | |
*/ | |
static ImageDecoder *FromFile(const char *path) | |
{ | |
IStream *stream = NULL; | |
SHCreateStreamOnFileA(path, STGM_READ, &stream); | |
return FromStream(stream); | |
} | |
/** | |
* @brief Create an ImageDecoder instance from a internet URL | |
* @param url url address to image | |
* @return ImageDecoder instance or NULL if image loading fails | |
*/ | |
static ImageDecoder *FromURL(const char *url) | |
{ | |
IStream *stream = NULL; | |
URLOpenBlockingStreamA(NULL, url, &stream, NULL, NULL); | |
return FromStream(stream); | |
} | |
/** | |
* @brief Create an ImageDecoder instance from a memory pointer | |
* @param ptr memory address where image starts | |
* @param size size of the image in bytes | |
* @return ImageDecoder instance or NULL if image loading fails | |
*/ | |
static ImageDecoder *FromMemory(void *ptr, size_t size) | |
{ | |
IStream *stream = SHCreateMemStream((BYTE *)ptr, size); | |
return FromStream(stream); | |
} | |
/** | |
* @brief Create an ImageDecoder instance from an IStream | |
* @param strm stream to read image | |
* @return ImageDecoder instance or NULL if image loading fails | |
*/ | |
static ImageDecoder *FromStream(IStream *strm) | |
{ | |
if (strm == NULL) return NULL; | |
ImageDecoder *dec = new ImageDecoder(strm); | |
if (FAILED(dec->err)) { dec->Release(); return NULL; } | |
return dec; | |
} | |
/** | |
* @brief Image formats supported | |
*/ | |
enum ImageFormat { FMT_UNK, FMT_BMP, FMT_GIF, FMT_HEIF, FMT_ICO, FMT_JPEG, FMT_PNG, FMT_TIFF, FMT_WEBP }; | |
private: | |
/** | |
* @brief (Internal) Obtain ImageFormat from file header | |
* @param buffer 4-byte file header | |
* @return ImageFormat detected | |
*/ | |
ImageFormat GetFormatFromData(const unsigned char *buffer) | |
{ | |
if ((buffer[0] == 0xFF) && (buffer[1] == 0xD8) && (buffer[2] == 0xFF)) | |
return FMT_JPEG; | |
else if ((buffer[0] == 0x89) && (buffer[1] == 0x50) && (buffer[2] == 0x4E) && (buffer[3] == 0x47)) | |
return FMT_PNG; | |
else if ((buffer[0] == 0x42) && (buffer[1] == 0x4d)) | |
return FMT_BMP; | |
else if ((buffer[0] == 0x47) && (buffer[1] == 0x49) && (buffer[2] == 0x46) && (buffer[3] == 0x38)) | |
return FMT_GIF; | |
else if ((buffer[0] == 0x49) && (buffer[1] == 0x49)) | |
return FMT_TIFF; | |
else if ((buffer[0] == 0x00) && (buffer[1] == 0x00) && (buffer[2] == 0x01) && (buffer[3] == 0x00)) | |
return FMT_ICO; | |
else if ((buffer[0] == 0x52) && (buffer[1] == 0x49) && (buffer[2] == 0x46) && (buffer[3] == 0x46)) | |
return FMT_WEBP; | |
else if ((buffer[0] == 0x00) && (buffer[1] == 0x00) && (buffer[2] == 0x00) && (buffer[3] == 0x18)) | |
return FMT_HEIF; | |
return FMT_UNK; | |
} | |
/** | |
* @brief (Internal) Obtain the WIC image decoder's GUID from ImageFormat | |
* @param fmt format for requested decoder | |
* @return GUID of requested decoder | |
*/ | |
GUID GuidFormatDecoderFromFormat(ImageFormat fmt) | |
{ | |
switch (fmt) | |
{ | |
case FMT_BMP: return CLSID_WICBmpDecoder; | |
case FMT_GIF: return CLSID_WICGifDecoder; | |
case FMT_HEIF: return CLSID_WICHeifDecoder; | |
case FMT_ICO: return CLSID_WICIcoDecoder; | |
case FMT_JPEG: return CLSID_WICJpegDecoder; | |
case FMT_PNG: return CLSID_WICPngDecoder; | |
case FMT_TIFF: return CLSID_WICTiffDecoder; | |
case FMT_WEBP: return CLSID_WICWebpDecoder; | |
} | |
return CLSID_NULL; | |
} | |
/** | |
* @brief (Internal) Get a WIC Bitmap from frame and return a converted ARGB WIC Bitmap | |
* @param index frame | |
* @return WIC Bitmap in ARGB colorspace | |
*/ | |
IWICBitmapSource *GetFrameBitmapSource(UINT index) | |
{ | |
if (wicdecoder == NULL) return 0; | |
// get frame | |
IWICBitmapFrameDecode *framedecoder = NULL; | |
err = wicdecoder->GetFrame(index, &framedecoder); | |
if (FAILED(err)) return 0; | |
// convert frame colorspace | |
IWICBitmapSource *wicframe = NULL; | |
err = WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, framedecoder, &wicframe); | |
framedecoder->Release(); | |
if (FAILED(err)) return 0; | |
// return converter frame | |
return wicframe; | |
} | |
public: | |
/** | |
* @brief Delete this instance and free resources | |
*/ | |
void Release() { delete this; } | |
/** | |
* @brief Get ImageFormat of this instance | |
* @return | |
*/ | |
ImageFormat Format() { return format; } | |
/** | |
* @brief Get number of frames in this instance, 0 is considered fail to load | |
* @return frame count | |
*/ | |
UINT FrameCount() { return framecount; } | |
/** | |
* @brief Write pixels of a frame in ARGB format | |
* @param index frame requested | |
* @param dstbuffer buffer to write the image | |
* @param dstbufsize size of the destnation buffer | |
* @param pitch stride of the destination buffer | |
* @param rc (OPTIONAL) select subimage, can be NULL for full image | |
* @return success | |
*/ | |
BOOL GetFrame(UINT index, void *dstbuffer, size_t dstbufsize, size_t pitch, RECT* rc = NULL) | |
{ | |
if (wicdecoder == NULL || firstframe == NULL) return false; | |
IWICBitmapSource *wicframe = (index == 0) ? firstframe : GetFrameBitmapSource(index); | |
if (wicframe == NULL) return false; | |
// copy pixels | |
err = wicframe->CopyPixels((WICRect*)rc, pitch, dstbufsize, static_cast<BYTE *>(dstbuffer)); | |
if (index != 0) wicframe->Release(); | |
if (FAILED(err)) return false; | |
return true; | |
} | |
/** | |
* @brief Get the image width and height | |
* @param width | |
* @param height | |
* @return success | |
*/ | |
BOOL GetSize(UINT *width, UINT *height) | |
{ | |
if (wicdecoder == NULL || firstframe == NULL) return false; | |
firstframe->GetSize(width, height); | |
return true; | |
} | |
/** | |
* @brief Query image metadata from image or frame | |
* https://docs.microsoft.com/en-us/windows/win32/wic/-wic-native-image-format-metadata-queries | |
* @param frame frame number of metadata, or -1 for global | |
* @param prop property name | |
* @param result pointer to a variant property | |
* @return success | |
*/ | |
BOOL GetMetadata(int frame, const WCHAR *prop, PROPVARIANT *result) | |
{ | |
IWICMetadataQueryReader *queryreader = NULL; | |
// get general metadata store | |
if (frame < 0) | |
wicdecoder->GetMetadataQueryReader(&queryreader); | |
// or get frame related metadata store | |
if (frame >= 0 && frame < framecount) | |
{ | |
IWICBitmapFrameDecode *framedec = NULL; | |
wicdecoder->GetFrame(frame, &framedec); | |
framedec->GetMetadataQueryReader(&queryreader); | |
framedec->Release(); | |
} | |
// check metadata reader | |
if (queryreader == NULL) return false; | |
// query metadata and fill propvariant, use PropVariantInit, PropVariantClear etc... | |
err = queryreader->GetMetadataByName(prop, result); | |
queryreader->Release(); | |
if (FAILED(err)) return false; | |
return true; | |
} | |
private: | |
ImageFormat format = FMT_UNK; | |
IWICBitmapDecoder *wicdecoder = NULL; | |
IWICBitmapSource *firstframe = NULL; | |
IStream *stream = NULL; | |
UINT framecount = 0; | |
HRESULT err = S_OK; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment