Skip to content

Instantly share code, notes, and snippets.

@pabloko
Created February 22, 2022 08:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pabloko/d4a34ddf4cb95877dedda83a7bb4daca to your computer and use it in GitHub Desktop.
Save pabloko/d4a34ddf4cb95877dedda83a7bb4daca to your computer and use it in GitHub Desktop.
Image Decoder based on WIC
/*
* 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