I've read a lot of articles and code but I still cannot get this to work, I've read all the 128 bytes of the header in my texture and them read 65536 bytes of compressed data of the actual texture(the texture's resolution is 256x256 and each compressed pixel uses 1 byte). I've tried to create my decompression algorithm with no success, them I've decided to use someone's else, so I found this code here. This is the arguments I was trying to pass to it so it would decompress my DDS texture.BlockDecompressImageDXT5(textureHeader.dwWidth, textureHeader.dwHeight, temp, packedData)
Note: textureHeader is a valid struct with the DDS texture's header data loaded into it, temp is a unsigned char array holding all the DDS data that was read from the DDS texture and packedData is a unsigned long array I was expecting to receive the final decompressed data. So in the code I've linked, the RGBA channels for each pixel were packed in the PackRGBA function, one byte for each color in the packedData. Before pointing the data to the texture's data at D3D11_SUBRESOURCE_DATApSysMem, I've distributed each byte from the unsigned long packedData to 4 different unsigned char m_DDSData this way:
for (int i{ 0 }, iData{ 0 }; i < textureHeader.dwPitchOrLinearSize; i++, iData += 4) //dwPitchOrLinearSize is the size in bytes of the compressed data.
{
m_DDSData[iData] = ((packedData[i] << 24) >> 24); //first char receives the 1st byte, representing the red color.
m_DDSData[iData + 1] = ((packedData[i] << 16) >> 24); //second char receives the 2nd byte, representing the green color.
m_DDSData[iData + 2] = ((packedData[i] << 8) >> 24); //third char receives the 3rd byte, representing the blue color.
m_DDSData[iData + 3] = (packedData[i] >> 24); //fourth char receives the 4th byte, representing the alpha color.
}
Note: m_DDSData should be the final data array used by D3D11_SUBRESOURCE_DATA to point to the texture's data, but when I use it this is the kind of result I get, only a frame with random colors instead of my actual texture. I also have algorithm's to other type of textures and they work properly so I can assure the problem is only in the DDS compressed format.
EDIT: Another example, this is a model of a chest and the program should be rendering the chest's texture: https://prnt.sc/11b62b6
For a full description of the BC3 compression scheme, see Microsoft Docs. BC3 is just the modern name for DXT4/DXT5 compression a.k.a. S3TC. In short, it compresses a 4x4 block of pixels at a time into the following structures resulting in 16 bytes per block:
struct BC1
{
uint16_t rgb[2]; // 565 colors
uint32_t bitmap; // 2bpp rgb bitmap
};
static_assert(sizeof(BC1) == 8, "Mismatch block size");
struct BC3
{
uint8_t alpha[2]; // alpha values
uint8_t bitmap[6]; // 3bpp alpha bitmap
BC1 bc1; // BC1 rgb data
};
static_assert(sizeof(BC3) == 16, "Mismatch block size");
CPU decompression
For the color portion, it's the same as the "BC1" a.k.a. DXT1 compressed block. This is pseudo-code, but should get the point across:
auto pBC = &pBC3->bc1;
clr0 = pBC->rgb[0]; // 5:6:5 RGB
clr0.a = 255;
clr1 = pBC->rgb[1]; // 5:6:5 RGB
clr1.a = 255;
clr2 = lerp(clr0, clr1, 1 / 3);
clr2.a = 255;
clr3 = lerp(clr0, clr1, 2 / 3);
clr3.a = 255;
uint32_t dw = pBC->bitmap;
for (size_t i = 0; i < NUM_PIXELS_PER_BLOCK; ++i, dw >>= 2)
{
switch (dw & 3)
{
case 0: pColor[i] = clr0; break;
case 1: pColor[i] = clr1; break;
case 2: pColor[i] = clr2; break;
case 3: pColor[i] = clr3; break;
}
}
Note while a BC3 contains a BC1 block, the decoding rules for BC1 are slightly modified. When decompressing BC1, you normally check the order of the colors as follows:
if (pBC->rgb[0] <= pBC->rgb[1])
{
/* BC1 with 1-bit alpha */
clr2 = lerp(clr0, clr1, 0.5);
clr2.a = 255;
clr3 = 0; // alpha of zero
}
BC2 and BC3 already include the alpha channel, so this extra logic is not used, and you always have 4 opaque colors.
For the alpha portion, BC3 uses two alpha values and then generates a look-up table based on those values:
alpha[0] = alpha0 = pBC3->alpha[0];
alpha[1] = alpha1 = pBC3->alpha[1];
if (alpha0 > alpha1)
{
// 6 interpolated alpha values.
alpha[2] = lerp(alpha0, alpha1, 1 / 7);
alpha[3] = lerp(alpha0, alpha1, 2 / 7);
alpha[4] = lerp(alpha0, alpha1, 3 / 7);
alpha[5] = lerp(alpha0, alpha1, 4 / 7);
alpha[6] = lerp(alpha0, alpha1, 5 / 7);
alpha[7] = lerp(alpha0, alpha1, 6 / 7);
}
else
{
// 4 interpolated alpha values.
alpha[2] = lerp(alpha0, alpha1, 1 / 5);
alpha[3] = lerp(alpha0, alpha1, 2 / 5);
alpha[4] = lerp(alpha0, alpha1, 3 / 5);
alpha[5] = lerp(alpha0, alpha1, 4 / 5);
alpha[6] = 0;
alpha[7] = 255;
}
uint32_t dw = uint32_t(pBC3->bitmap[0]) | uint32_t(pBC3->bitmap[1] << 8)
| uint32_t(pBC3->bitmap[2] << 16);
for (size_t i = 0; i < 8; ++i, dw >>= 3)
pColor[i].a = alpha[dw & 0x7];
dw = uint32_t(pBC3->bitmap[3]) | uint32_t(pBC3->bitmap[4] << 8)
| uint32_t(pBC3->bitmap[5] << 16);
for (size_t i = 8; i < NUM_PIXELS_PER_BLOCK; ++i, dw >>= 3)
pColor[i].a = alpha[dw & 0x7];
DirectXTex includes functions for doing all the compression/decompression for all BC formats.
If you want to know what the pseudo-function lerp does, see wikipedia or HLSL docs.
Rendering with a compressed texture
If you are going to be rendering with Direct3D, you do not need to decompress the texture. All Direct3D hardware feature levels include support for BC1 - BC3 texture compression. You just create the texture with the DXGI_FORMAT_BC3_UNORM format and create the texture as normal. Something like this:
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = textureHeader.dwWidth;
desc.Height = textureHeader.dwHeight;
desc.MipLevels = desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_BC3_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA initData = {};
initData.pSrcBits = temp;
initData.SysMemPitch = 16 * (textureHeader.dwWidth / 4);
// For BC compressed textures pitch is the number of bytes in a ROW of blocks
Microsoft::WRL::ComPtr<ID3D11Texture2D> pTexture;
hr = device->CreateTexture2D( &desc, &initData, &pTexture );
if (FAILED(hr))
// error
For a full-featured DDS loader that supports arbitrary DXGI formats, mipmaps, texture arrays, volume maps, cubemaps, cubemap arrays, etc. See DDSTextureLoader. This code is included in DirectX Tool Kit for DX11 / DX12. There's standalone versions for DirectX 9, DirectX 10, and DirectX 11 in DirectXTex.
If loading legacy DDS files (i.e. those that do not map directly to DXGI formats), then use the DDS functions in DirectXTex which does all the various pixel format conversions required (3:3:2, 3:3:2:8, 4:4, 8:8:8, P8, A8P8, etc.)
I am trying to extract frames from a stream which I create with Gstreamer and trying to save them with FreeImage or QImage ( this one is for testing ).
GstMapInfo bufferInfo;
GstBuffer *sampleBuffer;
GstStructure *capsStruct;
GstSample *sample;
GstCaps *caps;
int width, height;
const int BitsPP = 32;
/* Retrieve the buffer */
g_signal_emit_by_name (sink, "pull-sample", &sample);
if (sample) {
sampleBuffer = gst_sample_get_buffer(sample);
gst_buffer_map(sampleBuffer,&bufferInfo,GST_MAP_READ);
if (!bufferInfo.data) {
g_printerr("Warning: could not map GStreamer buffer!\n");
throw;
}
caps = gst_sample_get_caps(sample);
capsStruct= gst_caps_get_structure(caps,0);
gst_structure_get_int(capsStruct,"width",&width);
gst_structure_get_int(capsStruct,"height",&height);
auto bitmap = FreeImage_Allocate(width, height, BitsPP,0,0,0);
memcpy( FreeImage_GetBits( bitmap ), bufferInfo.data, width * height * (BitsPP/8));
// int pitch = ((((BitsPP * width) + 31) / 32) * 4);
// auto bitmap = FreeImage_ConvertFromRawBits(bufferInfo.data,width,height,pitch,BitsPP,0, 0, 0);
FreeImage_FlipHorizontal(bitmap);
bitmap = FreeImage_RotateClassic(bitmap,180);
static int id = 0;
std::string name = "/home/stadmin/pic/sample" + std::to_string(id++) + ".png";
#ifdef FREE_SAVE
FreeImage_Save(FIF_PNG,bitmap,name.c_str());
#endif
#ifdef QT_SAVE
//Format_ARGB32
QImage image(bufferInfo.data,width,height,QImage::Format_ARGB32);
image.save(QString::fromStdString(name));
#endif
fibPipeline.push(bitmap);
gst_sample_unref(sample);
gst_buffer_unmap(sampleBuffer, &bufferInfo);
return GST_FLOW_OK;
The color output in FreeImage are totally wrong like when Qt - Format_ARGB32 [ greens like blue or blues like oranges etc.. ] but when I test with Qt - Format_RGBA8888 I can get correct output. I need to use FreeImage and I wish to learn how to correct this.
Since you say Qt succeeds using Format_RGBA8888, I can only guess: the gstreamer frame has bytes in RGBA order while FreeImage expects ARGB.
Quick fix:
//have a buffer the same length of the incoming bytes
size_t length = width * height * (BitsPP/8);
BYTE * bytes = (BYTE *) malloc(length);
//copy the incoming bytes to it, in the right order:
int index = 0;
while(index < length)
{
bytes[index] = bufferInfo.data[index + 2]; //B
bytes[index + 1] = bufferInfo.data[index + 1]; //G
bytes[index + 2] = bufferInfo.data[index]; //R
bytes[index + 3] = bufferInfo.data[index + 3]; //A
index += 4;
}
//fill the bitmap using the buffer
auto bitmap = FreeImage_Allocate(width, height, BitsPP,0,0,0);
memcpy( FreeImage_GetBits( bitmap ), bytes, length);
//don't forget to
free(bytes);
I have a byte array where each byte correlates to a pixel value of my image. These byte values are either exactly 0 or 255. The array is packed from left to right and then top to bottom. From this array I want to create a grey-scale .PNG image with a bit depth of 1. I cannot use any wrappers to zlib.
The function works as it creates a valid png file. However the image created is not correct. I am fairly certain (although possibly wrong) the problem lies with the packing of data into a byte array that I pass to the Zlib deflate function. I have read through the spec: https://www.w3.org/TR/PNG/ to no avail.
I have the following code:
#include "zlib.h"
enum E_PNGImageType
{
eGreyScale = 0,
eTrueColour = 2,
eIndexedColour = 3,
eGreyScaleAlpha = 4,
eTrueColourAlpha = 6
};
enum E_PNGBitDepth
{
eOne = 1,
eTwo = 2,
eFour = 4,
eEight = 8,
eSixteen = 16
};
enum E_PNGCompressionMethod
{
eDeflate = 0
};
enum E_PNGFilterMethod
{
eAdaptive = 0
};
enum E_PNGInterlaceMethod
{
eNone = 0,
eAdam7 = 1
};
void CreatePNG(BYTE *pData, int iWidth, int iHeight)
{
/* Convert each byte to a bit and package the bits into a byte */
std::vector<BYTE> vBitData;
int bit = 0;
BYTE value = 0;
vBitData.clear();
for (int h = 0; h < iHeight; h++)
{
for (int w = 0; w < iWidth; w++)
{
if (pData[(h * iWidth) + w] > 0)
{
value += (1 << (7 - bit));
}
bit++;
if (bit == 8)
{
bit = 0;
vBitData.push_back(value);
value = 0;
}
}
}
if (bit > 0)
{
vBitData.push_back(value);
}
GeneratePNGData(vBitData.data(), iWidth, iHeight, vBitData.size(), &vPNGData);
}
void GeneratePNGData(BYTE *pData, unsigned int uiWidth, unsigned int uiHeight, unsigned int uiSize, std::vector<BYTE> *pPNGData)
{
int iCRCStartIndex;
int iSize;
unsigned int uiCRC;
z_stream strm;
const int C_BUFFER_SIZE = 20000;
unsigned char tempBuffer[C_BUFFER_SIZE];
int iLengthDataIndex;
int iRes;
pPNGData->clear();
/* PNG Signature */
pPNGData->push_back(137);
pPNGData->push_back(80);
pPNGData->push_back(78);
pPNGData->push_back(71);
pPNGData->push_back(13);
pPNGData->push_back(10);
pPNGData->push_back(26);
pPNGData->push_back(10);
/* IDHR Image Header */
/* 4 Bytes: Chunk Length */
pPNGData->push_back(0);
pPNGData->push_back(0);
pPNGData->push_back(0);
pPNGData->push_back(13);
/* Checksum Start Index */
iCRCStartIndex = pPNGData->size();
/* 4 Bytes: Chunk Type */
pPNGData->push_back(73);
pPNGData->push_back(72);
pPNGData->push_back(68);
pPNGData->push_back(82);
/* 4 Bytes: Chunk Data - Width */
pPNGData->push_back(((BYTE*)&uiWidth)[3]);
pPNGData->push_back(((BYTE*)&uiWidth)[2]);
pPNGData->push_back(((BYTE*)&uiWidth)[1]);
pPNGData->push_back(((BYTE*)&uiWidth)[0]);
/* 4 Bytes: Chunk Data - Height */
pPNGData->push_back(((BYTE*)&uiHeight)[3]);
pPNGData->push_back(((BYTE*)&uiHeight)[2]);
pPNGData->push_back(((BYTE*)&uiHeight)[1]);
pPNGData->push_back(((BYTE*)&uiHeight)[0]);
/* 1 Byte: Chunk Data - Bit Depth */
pPNGData->push_back(E_PNGBitDepth::eOne);
/* 1 Byte: Chunk Data - Colour Type */
pPNGData->push_back(E_PNGImageType::eGreyScale);
/* 1 Byte: Chunk Data - Compression Method */
pPNGData->push_back(E_PNGCompressionMethod::eDeflate);
/* 1 Byte: Chunk Data - Filter Method */
pPNGData->push_back(E_PNGFilterMethod::eAdaptive);
/* 1 Byte: Chunk Data - Interlace Method */
pPNGData->push_back(E_PNGInterlaceMethod::eNone);
/* Size of Data to Perform Checksum Over */
iSize = pPNGData->size() - iCRCStartIndex;
/* 4 Bytes: CRC */
uiCRC = AddPNGChecksumData(&pPNGData->data()[iCRCStartIndex], iSize);
pPNGData->push_back(((BYTE*)&uiCRC)[3]);
pPNGData->push_back(((BYTE*)&uiCRC)[2]);
pPNGData->push_back(((BYTE*)&uiCRC)[1]);
pPNGData->push_back(((BYTE*)&uiCRC)[0]);
/* IDAT Image Data */
/* Length Data Offset */
iLengthDataIndex = pPNGData->size();
/* 4 Bytes: Chunk Length */
pPNGData->push_back(0);
pPNGData->push_back(0);
pPNGData->push_back(0);
pPNGData->push_back(0);
/* Checksum Start Index */
iCRCStartIndex = pPNGData->size();
/* 4 Bytes: Chunk Type */
pPNGData->push_back(73);
pPNGData->push_back(68);
pPNGData->push_back(65);
pPNGData->push_back(84);
/* Length Bytes: Chunk Data - Compressed Image Data */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
deflateInit(&strm, Z_DEFLATED);
strm.avail_in = uiSize;
strm.next_in = pData;
strm.avail_out = C_BUFFER_SIZE;
strm.next_out = tempBuffer;
iRes = deflate(&strm, Z_FINISH);
if (iRes != Z_STREAM_END) MessageBox(NULL, "Error", "Error", 0);
pPNGData->insert(pPNGData->end(), tempBuffer, tempBuffer + strm.total_out);
deflateEnd(&strm);
/* Now Length Is Know Edit Length Field */
(*pPNGData)[iLengthDataIndex + 0] = ((BYTE*)&strm.total_out)[3];
(*pPNGData)[iLengthDataIndex + 1] = ((BYTE*)&strm.total_out)[2];
(*pPNGData)[iLengthDataIndex + 2] = ((BYTE*)&strm.total_out)[1];
(*pPNGData)[iLengthDataIndex + 3] = ((BYTE*)&strm.total_out)[0];
/* Size of Data to Perform Checksum Over */
iSize = pPNGData->size() - iCRCStartIndex;
/* 4 Bytes: CRC */
uiCRC = AddPNGChecksumData(&pPNGData->data()[iCRCStartIndex], iSize);
pPNGData->push_back(((BYTE*)&uiCRC)[3]);
pPNGData->push_back(((BYTE*)&uiCRC)[2]);
pPNGData->push_back(((BYTE*)&uiCRC)[1]);
pPNGData->push_back(((BYTE*)&uiCRC)[0]);
/* IEND Image trailer */
/* 4 Bytes: Chunk Length */
pPNGData->push_back(0);
pPNGData->push_back(0);
pPNGData->push_back(0);
pPNGData->push_back(0);
/* Checksum Start Index */
iCRCStartIndex = pPNGData->size();
/* 4 Bytes: Chunk Type */
pPNGData->push_back(73);
pPNGData->push_back(69);
pPNGData->push_back(78);
pPNGData->push_back(68);
/* Size of Data to Perform Checksum Over */
iSize = pPNGData->size() - iCRCStartIndex;
/* 4 Bytes: CRC */
uiCRC = AddPNGChecksumData(&pPNGData->data()[iCRCStartIndex], iSize);
pPNGData->push_back(((BYTE*)&uiCRC)[3]);
pPNGData->push_back(((BYTE*)&uiCRC)[2]);
pPNGData->push_back(((BYTE*)&uiCRC)[1]);
pPNGData->push_back(((BYTE*)&uiCRC)[0]);
/* Temp Debug Code */
FILE* pFile;
fopen_s(&pFile, "DEBUG_IMAGES\\zzz_test_output.png", "wb");
fwrite((*pPNGData).data(), 1, pPNGData->size(), pFile);
fclose(pFile);
}
You need to push an 8-bit zero at the beginning of each row (at the beginning of the loop on h in your code). That's the "filter type" byte, which in your case should be 0, meaning "no filtering".
The filter method "0" in IHDR simply says that the IDAT contains a filter byte at the beginning of each row. The option is there in IHDR to allow other methods, such as omitting those bytes and assuming a "0" filter type for each row. In the end, the PNG authors never approved any method other than the filter-byte-per-row method.
Also the check on bit>0 needs to be done at the end of the loop on h -- just move the closing bracket on the h-loop down below it, and reset value=0 there as well, so each row will be padded out to fill the final byte.
I'm trying to convert RGB32 frames to NV12 Frames to feed into an encoder.
m_iWidthIn = 1920;
m_iHeightIn = 1080;
m_iWidthOut = (((iWidthIn + 31) >> 5) << 5) //32bit align
m_heightOut = (((iHeightIn + 31) >> 5) << 5) //32bit align
m_outputPixelFormat = AV_PIX_FMT_NV12;
// allocate and fill buffers
m_sws = ::sws_getContext(m_iWidthIn, m_iHeightIn, AV_PIX_FMT_RGB32, m_iWidthOut, m_iHeightOut, m_outputPixelFormat, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);
AVFrame* frameOut = av_frame_alloc();
frameOut->height = m_iHeightOut;
frameOut->width = m_iWidthOut;
frameOut->format = m_outputPixelFormat;
av_frame_get_buffer(frameOut, 32);
int linesize[1] = { m_iWidthIn * 4 };
uint8_t * data[1] = { m_inputBuffer };
if (m_bFlip)
{
data[0] += linesize[0] * (m_iHeightIn - 1);
linesize[0] = -linesize[0];
}
::sws_scale(m_sws, data, linesize, 0, m_iHeightIn, frameOut->data, frameOut->linesize);
::av_image_copy_to_buffer(pOutputBuffer, lDataLen, frameOut->data, frameOut->linesize, m_outputPixelFormat, m_iWidthOut, m_iHeightOut, 32);
If I make m_outputPixelFormat AV_PIX_FMT_RGB32 and use a DMO colorspace converter, the video comes out correctly. However if I change it to NV12, I end up with a slanted video with missing data at the bottom.
I know this is caused by me copying the data incorrectly out of the buffer, but I'm unsure what I'm doing incorrectly.
Your problem is here:
m_heightOut = (((iHeightIn + 31) >> 5) << 5) //32bit align
You don't need to align height. So frameOut->data has m_iHeightIn height.
The correct line is:
m_heightOut = iHeightIn;
Compiler: MinGW/GCC
I'm trying to get the HICON of a file type based on what icon windows has registered for that file type, and then grab all of the HICON's images.
The problem is, I can't seem to get anything other than the 32x32 or 16x16 icon. Also, I've looked at GetIconInfoEx() but that function doesn't allow me to choose the icon size that I'm wanting, it just sort of arbitrarily pukes up whatever Windows feels like handing me at the time.
I want to at least have all of the 16x16, 32x32, and 48x48 icons, but I would really enjoy being able to extract every size that's in the HICON that I pass in.
Here's the code I'm currently working with (copy and pasted most of this from the web and stitched it together):
HBITMAP GetFileTypeIcon(const char* ext, int type, int depth)
{
HICON hIcon;
SHFILEINFO sfi= {0};
UINT flag = SHGFI_ICON|SHGFI_USEFILEATTRIBUTES;
int wh = 16;
switch(type)
{
default:
case FILE_ICON_SIZE_16:
{
wh = 16; flag|=SHGFI_SMALLICON;
}
break;
case FILE_ICON_SIZE_32:
{
wh = 32; flag|=SHGFI_LARGEICON;
}
break;
case FILE_ICON_SIZE_48:
{
wh = 48; flag|=SHGFI_SYSICONINDEX;
}
break;
case FILE_ICON_SIZE_256:
{
wh = 256; flag|=SHGFI_SYSICONINDEX;
}
break;
}
HRESULT hr = SHGetFileInfo(ext,FILE_ATTRIBUTE_NORMAL,&sfi,sizeof(sfi),flag);
if(SUCCEEDED(hr))
{
if((type == FILE_ICON_SIZE_48) || (type == FILE_ICON_SIZE_256))
{
// THIS PART DOESN'T COMPILE: undeclared function/indentifiers
// HIMAGELIST* imageList;
// hr = SHGetImageList(((type == FILE_ICON_SIZE_256)?SHIL_JUMBO:SHIL_EXTRALARGE), IID_IImageList, (void**)&imageList);
// if(SUCCEEDED(hr))
// {
// //Get the icon we need from the list. Note that the HIMAGELIST we retrieved
// //earlier needs to be casted to the IImageList interface before use.
// hr = ((IImageList*)imageList)->GetIcon(sfi.iIcon, ILD_TRANSPARENT, &hIcon);
// }
}
else
{
hIcon=sfi.hIcon;
}
}
// Convert to an HBITMAP (to get it out of the icon...)
HDC hDC = GetDC(NULL);
HDC hMemDC = CreateCompatibleDC(hDC);
HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, wh, wh);
HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
DrawIconEx(hMemDC, 0, 0, hIcon, wh, wh, 0, NULL, DI_NORMAL);
SelectObject(hMemDC, hOrgBMP);
DeleteDC(hMemDC);
ReleaseDC(NULL, hDC);
DestroyIcon(hIcon);
return hMemBmp;
}
I don't even know what to do about color depths. I'll hazard a guess: make a DC that has a certain color depth (rather than just a compatible DC) and pass that into DrawIconEx()?
Edit: I answered my own question after much research/work.
See my answer below for a way to find and parse the raw icon data.
I basically had to do everything myself (with the help of the web, Stack Overflow, and several MSDN articles) so I think I'll just post my own solution here.
I ended up parsing the registry to find the locations of the icons of each previously registered file extension, since the API functions that should have easily gotten me the information I wanted have some... problems.
After that I spent several days manually observing the data formats at hand by observing output of an icon program, and with this information in hand I was able to construct an image loader.
I used Allegro game library to make dealing with BITMAP images easier - Win32/GDI is a bit too much to deal with and would have made the code exorbitantly messy.
Finding the Icon Location and Index:
(1) Look for extension under HKEY_CLASSES_ROOT, eg HKCR\.foo\(default) = "foofile"
(2) Default data of this is the next key to look at, eg HKCR\foofile\
(3) Default data here is the description eg HKCR\foofile\(default) = "Foo Document"
(4) The icon location may be in one of two places that I know of:
Either in HKCR\foofile\DefaultIcon\(default) or there may be an entry something like HKCR\foofile\CurVer\(default) = "foofile.1" which tells you to look at the key HKCR\foofile.1\DefaultIcon\(default) for the icon location.
Parsing the Icon Location String:
The string is simply a path followed by a comma, white space, possibly a negative sign, and a number indicating the "index" of the icon.
Here's the big gotcha: Let the icon index be N. If N is negative (might want to check for negative zeros!), it is a resource ID within the file specified. If N is positive, it means to find the N'th icon within the file, but the icon is not necessarily at resource ID number N.
Parsing Icon Structures Manually:
This is the bulk of the code and time spent, but it works beautifully. First off, here's the data formats for the various sections of color and mask data.
Data Block Formats:
32bit ... Color Data:
====================================================================================
Little Endian 4 byte ARGB values.
The rows are stored in reverse order (bottom to top).
24bit ... Color Data:
====================================================================================
Little Endian 3 byte RGB values.
Tightly Packed (NO PADDING).
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
16bit ... Color Data:
====================================================================================
Little Endian 2 byte RGB values. 5 bits each with MSB = 0.
Tightly Packed (NO PADDING).
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
8bit ... Palette & Color Data:
====================================================================================
The Palette is Little Endian 4 byte RGB0 values. No alpha.
There *might* be up to 256 palette entries.
If number of colors is reported as zero, assume 256 color entires.
The Pixels are 1 byte index values.
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
4bit ... Palette & Color Data:
====================================================================================
The Palette is Little Endian 4 byte RGB0 values. No alpha.
There *might* be up to 16 palette entries.
If number of colors is reported as zero, assume 16 color entires.
The Pixels are nybble-length index values.
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
Mask Data:
====================================================================================
Is a string of bytes with mask bits starting at MSB and going towards LSB.
There are ((imagewidth+31)>>5) DWORDS per row in *BIG ENDIAN* order.
Like the color data, there is a set of DWORDS for each row.
The rows are stored in reverse order (bottom to top).
Set unused padding bits/pixels at end of each row to 1.
0 indicates opaque and 1 indicates transparent.
1bit ... XOR Mask, AND Mask, & Color Data:
====================================================================================
The Palette is Little Endian 4 byte RGB0 values. No alpha.
There should be exactly 2 palette entries: usually 0x00000000 and 0x00FFFFFF.
The two masks follow the Mask Data format decribed above.
The following results from combining two mask bits:
XOR AND RESULT:
0 0 Color #0 (Black)
0 1 Transparent
1 0 Color #1 (White)
1 1 Invert Destination Bitmap
Of course I wouldn't have left it at this. There's code to be had!
The following code will load up and convert all of the icon images for a given icon location to a vector of 32bpp BITMAPs. If loading a given image fails, it will simply just not be added to the vector (or, in the case of a corrupt icon, it will most likely generate a corrupted image, so be careful).
The code does not support the "invert" color in monochrome images, and will just generate a different color that still has zero alpha.
WARNING: Some psuedo-code is included to shorten things to just the essentials.
Icon Loader Code (Supports: EXE, DLL, 32bit ICL, ICO):
// Code written by Simion32.
// Please feel free to use it anywhere.
// Credit would be nice but isn't required.
#include "include.h" //std::vectors and whatever else you need
#include <allegro.h>
#include <winalleg.h> //Allegro and Win32
#include "Shellapi.h"
// In the following block, the (required!!) pragmas
// force correct data alignment. Needed in at least GCC.
#pragma pack( push, 1 )
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved ( must be 0)
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // How many bytes in this resource?
DWORD dwImageOffset; // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
typedef struct
{
BITMAPINFOHEADER icHeader; // DIB header
RGBQUAD icColors[1]; // Color table
BYTE icXOR[1]; // DIB bits for XOR mask
BYTE icAND[1]; // DIB bits for AND mask
} ICONIMAGE, *LPICONIMAGE;
#pragma pack( pop)
#pragma pack( push, 2 )
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // total size of the RT_ICON resource referenced by the nID member.
WORD nID; // resourceID of RT_ICON (LockResource to obtain a pointer to its ICONIMAGE)
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource type (1 for icons)
WORD idCount; // How many images?
GRPICONDIRENTRY idEntries[1]; // The entries for each image
} GRPICONDIR, *LPGRPICONDIR;
#pragma pack( pop )
uint32_t Convert16BitToARGB(uint16_t value)
{
return (0xFF000000|((value >> 7) & 0x0000F8)|((value << 6) & 0x00F800)|((value << 19) & 0xF80000));
}
uint32_t GetMaskBit(uint8_t* data, int x, int y, int w, int h)
{
uint32_t mask_data_rowsize = (((w+31)>>5) * 4);
return ((~(data[(mask_data_rowsize * ((h-1)-y)) + (x >> 3)] >> (0x07 - (x & 0x07))) & 1) * 0xFFFFFFFF);
}
uint32_t GetColorMonochrome(uint8_t* xordata, uint8_t* anddata, int x, int y, int w, int h, uint32_t* pal)
{
uint32_t mask_data_rowsize = (((w+31)>>5) * 4);
uint32_t xor_bit = (((xordata[(mask_data_rowsize * ((h-1)-y)) + (x >> 3)] >> (0x07 - (x & 0x07))) << 1) & 2);
uint32_t and_bit = (((anddata[(mask_data_rowsize * ((h-1)-y)) + (x >> 3)] >> (0x07 - (x & 0x07))) ) & 1);
uint32_t value = (xor_bit | and_bit);
return pal[value];
}
BITMAP* CreateBmp32bppFromIconResData(void* data, int size, int depth, int w, int h, int colors)
{
char* pngheader = "\211PNG\r\n\032\n";
char* cpd = (char*)data;
bool is_png = ((cpd[0]==pngheader[0])
&& (cpd[1]==pngheader[1])
&& (cpd[2]==pngheader[2])
&& (cpd[3]==pngheader[3])
&& (cpd[4]==pngheader[4])
&& (cpd[5]==pngheader[5])
&& (cpd[6]==pngheader[6])
&& (cpd[7]==pngheader[7]));
if(is_png)
{
//###########################################################
//# PSEUDO-CODE: Somehow convert the PNG file into a bitmap.
BITMAP* result = ConvertPngFileToBmp32bpp(data, size);
return result;
}
else
{
uint32_t ignore_size = ((BITMAPINFOHEADER*)(data))->biSize;
BITMAP* bmp = create_bitmap_ex(32,w,h);
uint32_t pixel_count = (w * h);
uint32_t color_data_size = ((((((w * depth)+7) >> 3) +3) & ~3) * h);
switch(depth)
{
default: return bmp; break;
case 32:
{
uint32_t* src = (uint32_t*)(((uint8_t*)data) + ignore_size);
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,src[0]);
src++;
}
//There should never be any padding to jump over here.
}
return bmp;
}
break;
case 24:
{
uint32_t* src = (uint32_t*)(((uint8_t*)data) + ignore_size);
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,((src[0] & 0x00FFFFFF) | 0xFF000000) & GetMaskBit(bitmask, xx, yy, w, h));
src++;
src = (uint32_t*)(((uint8_t*)src)-1); //go back a byte due to packing
padding_checker += 3;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src = (uint32_t*)(((uint8_t*)src)+1);
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 16:
{
//Note: there might be a color table present! ignore it.
uint16_t* src = (uint16_t*)(((uint8_t*)data) + ignore_size + (colors << 2));
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,Convert16BitToARGB(src[0]) & GetMaskBit(bitmask, xx, yy, w, h));
src++;
padding_checker += 2;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src = (uint16_t*)(((uint8_t*)src)+1);
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 8:
{
if(colors > 256) colors = 256; //Color Count must be restricted to 256 entries at the most.
if(colors <= 0) colors = 256; //Color Count might be reported as zero. This means 256.
uint8_t* src = (((uint8_t*)data) + ignore_size + (colors << 2));
uint32_t* pal = ((uint32_t*)(((uint8_t*)data) + ignore_size));
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
uint8_t color = src[0];
if(color < colors){
_putpixel32(bmp,xx,yy,(pal[color] | 0xFF000000) & GetMaskBit(bitmask, xx, yy, w, h));
}else{
_putpixel32(bmp,xx,yy,0x00FF00FF);
}
src++;
padding_checker++;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src++;
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 4:
{
if(colors > 16) colors = 16; //Color Count must be restricted to 16 entries at the most.
if(colors <= 0) colors = 16; //Color Count might be reported as zero. This means 16.
uint8_t* src = (((uint8_t*)data) + ignore_size + (colors << 2));
uint32_t* pal = ((uint32_t*)(((uint8_t*)data) + ignore_size));
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
uint8_t color = src[0];
if(xx & 1) color = ( color & 0x0F);
else color = ((color >> 4) & 0x0F);
if(color < colors){
_putpixel32(bmp,xx,yy,(pal[color] | 0xFF000000) & GetMaskBit(bitmask, xx, yy, w, h));
}else{
_putpixel32(bmp,xx,yy,0x00FF00FF);
}
if(xx & 1)
{
src++;
padding_checker++;
padding_checker &= 3;
}
}
//if the pointer hasn't incremented to the next byte yet, do so.
if(w & 1) //odd width
{
src++;
padding_checker++;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src++;
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 1:
{
if(colors > 2) colors = 2; //Color Count must be restricted to 2 entries at the most.
if(colors <= 0) colors = 2; //Color Count might be reported as zero. This means 2.
uint32_t* pal = (uint32_t*)(((uint8_t*)data) + ignore_size);
uint8_t* bitmaskXOR = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2));
uint8_t* bitmaskAND = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
uint32_t ret_colors[4] = {pal[0]|0xFF000000, 0x00FF00FF, pal[1]|0xFF000000, 0x0000FF00};
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,GetColorMonochrome(bitmaskXOR, bitmaskAND, xx, yy, w, h, ret_colors));
}
}
return bmp;
}
break;
}
return bmp;
}
}
vector< BITMAP* > ResourceToBitmapVector(HMODULE hm, HRSRC hr, bool is_group_icon)
{
vector< BITMAP* > results;
if(is_group_icon)
{
HGLOBAL hg = LoadResource(hm,hr);
GRPICONDIR* gd = (GRPICONDIR*)LockResource(hg);
if(gd->idType == 1)
{
for(int i = 0; i < gd->idCount; ++i)
{
//WARNING: The GRPICONDIRENTRY's data might be wrong!
GRPICONDIRENTRY* ie = (GRPICONDIRENTRY*)&(gd->idEntries[i]);
HRSRC ihr = FindResource(hm,MAKEINTRESOURCE(ie->nID),RT_ICON);
if(ihr != NULL)
{
HGLOBAL ihg = LoadResource(hm,ihr);
void* data = (void*)LockResource(ihg);
DWORD size = SizeofResource(hm,ihr);
uint32_t b = ((BITMAPINFOHEADER*)(data))->biBitCount;
uint32_t w = ((BITMAPINFOHEADER*)(data))->biWidth;
uint32_t h = (((BITMAPINFOHEADER*)(data))->biHeight >> 1); //icons have doubled height value.
uint32_t c = ((BITMAPINFOHEADER*)(data))->biClrUsed;
results.push_back(CreateBmp32bppFromIconResData(data, size, b, w, h, c));
}
}
}
}
else
{
HGLOBAL ihg = LoadResource(hm,hr);
void* data = (void*)LockResource(ihg);
DWORD size = SizeofResource(hm,hr);
uint32_t b = ((BITMAPINFOHEADER*)(data))->biBitCount;
uint32_t w = ((BITMAPINFOHEADER*)(data))->biWidth;
uint32_t h = (((BITMAPINFOHEADER*)(data))->biHeight >> 1); //icons have doubled height value.
uint32_t c = ((BITMAPINFOHEADER*)(data))->biClrUsed;
results.push_back(CreateBmp32bppFromIconResData(data, size, b, w, h, c));
}
return results;
}
vector< BITMAP* > IconFileToBitmapVector(void* icon_data, uint32_t icon_size)
{
vector< BITMAP* > results;
ICONDIR* gd = (ICONDIR*)icon_data;
if(gd->idType == 1)
{
for(int i = 0; i < gd->idCount; ++i)
{
//WARNING: The ICONDIRENTRY's data might be wrong!
DWORD offset = gd->idEntries[i].dwImageOffset;
DWORD size = gd->idEntries[i].dwBytesInRes;
void* data = (void*)(((uint8_t*)icon_data) + ((uint32_t)offset));
uint32_t b = ((BITMAPINFOHEADER*)(data))->biBitCount;
uint32_t w = ((BITMAPINFOHEADER*)(data))->biWidth;
uint32_t h = (((BITMAPINFOHEADER*)(data))->biHeight >> 1); //icons have doubled height value.
uint32_t c = ((BITMAPINFOHEADER*)(data))->biClrUsed;
results.push_back(CreateBmp32bppFromIconResData(data, size, b, w, h, c));
}
}
return results;
}
vector< BITMAP* > UnearthIconResource(string& file, bool self_refrence, bool res_index, int index)
{
#define LOAD_IGNORE_CODE_AUTHZ_LEVEL 0x00000010
//prevents a negative indexing error
// (the boolean res_index handles whether it's icon index VS resource ID)
index = abs(index);
vector< BITMAP* > results; //array of results to return (pointers to 32bpp images)
//extract and 'demangle' the file extension by convertng to lowercase.
string ext = get_file_extension(file.c_str());
for(int i = 0; i < ext.size(); ++i) ext[i] = tolower(ext[i]);
bool is_icl = false;
if((ext == "exe") || (ext == "dll") || (ext == "scr") || (is_icl = (ext == "icl")))
{
// Portable Executable Resource (works for both DLL and EXE)
// Also works for any 32bit Icon Library (Microangelo Studio?)
HMODULE hm = LoadLibraryEx(file.c_str(), NULL,
(DONT_RESOLVE_DLL_REFERENCES | LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_LIBRARY_AS_DATAFILE));
if(hm != NULL)
{
HRSRC hr;
if(!self_refrence)
{
if(res_index)
{
//The icon we want is at the resource ID (==index)
bool is_single_icon = false;
hr = FindResource(hm,MAKEINTRESOURCE(index),RT_GROUP_ICON);
if(hr == NULL)
{
hr = FindResource(hm,MAKEINTRESOURCE(index),RT_ICON);
is_single_icon = (hr != NULL);
}
if(hr != NULL)
{
results = ResourceToBitmapVector(hm, hr, !is_single_icon);
}
}
else
{
//The icon we want is the (index)'th icon in the file
//We must preform a manual search for the resource ID!
//WARNING: Using EnumResourceNames() *DOES NOT WORK PROPERLY* for this.
for(int nicon = 0, i = 0; i < 0x8000; ++i)
{
bool is_single_icon = false;
hr = FindResource(hm,MAKEINTRESOURCE(i),RT_GROUP_ICON);
if(hr != NULL)
{
if(nicon == index)
{
results = ResourceToBitmapVector(hm, hr, true);
break;
}
nicon++;
}
}
}
}
else
{
//The icon we want is the "first" icon in the file.
//Happens when location is a %1.
//We must preform a manual search for the resource ID!
//WARNING: Using EnumResourceNames() *DOES NOT WORK PROPERLY* for this.
for(int i = 0; i < 0x8000; ++i)
{
bool is_single_icon = false;
hr = FindResource(hm,MAKEINTRESOURCE(i),RT_GROUP_ICON);
if(hr != NULL)
{
results = ResourceToBitmapVector(hm, hr, true);
break;
}
}
}
FreeLibrary(hm);
}
else /*if(is_icl)
{//OH NOES. We have to load a *16bit* .icl file!
//not supported yet. sorry. left as another excecise to the programmer.
}*/
}
else if(ext == "ico")
{
//Single Icon File
//###################################################
//# PSEUDO-CODE: Do the file loading yourself ;)
void* data_pointer = NULL;
uint32_t data_size = 0;
if(data_pointer = MyLoadFile(file.c_str(), &data_size))
{
if(data_size)
{
results = IconFileToBitmapVector((void*)data_pointer, data_size);
}
}
MyCloseFile(data_pointer);
}
return results;
}
I think that almost covers it all...
One last thing I should mention: Be sure to ignore the size and bit depth information coming from the icon directory entries. They can often be wrong. I've seen a few 256-color images reported as 24bit, causing data corruption inside the image loader.
Wow, talk about reinventing the wheel!
With all due respect, this code is so bloated for nothing. I (and probably thousands of others) achieved the exact same result with 1/10 of this code. Also, this solution contains many inaccuracies.
Here's a quick run-down:
Why parse the registry manually? You state the API has some problems; like what? I've used reg parsing API extensively and never had a problem! The Indexing vs ResID logic is correct though.
Why do all the icon to bitmap conversions manually? This can be achieved with 3 to 5 lines of code using the right Icon API calls. Here's a complete reference.
Why limit the conversion to 32bpp? Again, using the right APIs will generate a device dependent hIcon handle with the max color bit-depth supported by that device. Check out the CreateIconFromResourceEx() API function. All you need to do is combine it with the Find/Load/Lock Resource APIs that you're already using. Using this technique will load icons of any size and color depth (from monochrome up to alpha-channel 32bpp icons).
Finally, regarding the search for icon resources by group (RT_GROUP_ICON), or by single icons (RT_ICON), and matching for a given index instead of resource, it could be done much more efficiently using EnumResourceNames(). It might be that you've failed to account for string resource identifiers when parsing the Enum return, because it seems you've omitted such case in your manual search and match procedure. This might be the source of your problems with EnumResourceNames(). It works perfectly fine for me and for others in countless online samples. At the very least, the "manual" search should match up to 0xFFFF rather than 0x8000. Res IDs are recommended in the 0x0001 to 0x8000 range, but legal in the 0x0000 to 0xFFFF range.
If it does have not to be platform independent:
a bit time ago i wrote a little class that reads a file and extract all icons.
It retreives a std::vector with HICONs.
With GetIconInfo you can retreive the HBITMAP for pixeldata an pixelmask.
The function is a little bit heuristic. It scans the binary Data for a typical icon begin and tries to load them.
The function also works on dlls, exe or icl (16bit dlls that just contain icon resources)
#ifndef __ICON_LIST_H__
#define __ICON_LIST_H__
#include <windows.h>
#include <vector>
class IconFile: public std::vector<HICON>{
public:
IconFile(){};
IconFile(std::string i_filename){
addIconsFromFile(i_filename);
};
int addIconsFromFile(std::string i_fileName){
int iCount=0;
HANDLE file = CreateFile( i_fileName.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if(file!=INVALID_HANDLE_VALUE){
int size = GetFileSize(file,NULL);
DWORD actRead;
BYTE* buffer = new BYTE[size];
ReadFile(file, buffer, size, &actRead, NULL);
CloseHandle(file);
int ind = -1;
for(int p = 0; p< size-4; ++p){
if(buffer[p]==40 && buffer[p+1]==0 && buffer[p+2]==0 && buffer[p+3]==0){
HICON icon = CreateIconFromResourceEx(&buffer[p], size-p, true, 0x00030000,0,0,0);
if(icon){
++iCount;
this->push_back(icon);
}
}
}
delete[] buffer;
}
return iCount;
};
};
#endif //__ICON_LIST_H__