参考:ICO文件格式的演化
使用GDI+保存带Alpha通道的图像
ICON 格式
文件头 | DIRENTRY | DIRENTRY|DIRENTRY|… Imgdata|ImgdataImgdataImgdata|…
//文件头
//sizeof = 6
typedef struct ICONHEADER
{
WORD idReserved;//保留用,
WORD idType;//固定值 1
WORD idCount;//包含的图片个数
};
//标记每张图片的具体信息
//sizeof = 16
typedef struct ICONDIRENTRY
{
BYTE bWidth;//图片大小,如果图片宽度大于等于256则为0
BYTE bHeight;//图片大小,如果图片宽度大于等于256则为0
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
//图像大小w*h*4 + 40 header头 + mask
//mask (h * ((w + 31) / 32 * 32 / 8))
DWORD dwBytesInRes;//图片资源大小,需要计算mask
DWORD dwImageOffset;//在文件中的偏移量
}*LPICONDIRENTRY;
如果图片尺寸 <= 128.则保存的BMP数据,否则保存的是png格式的数据。
从ICON提取PNG文件
static DWORD extractIcoPngs(const std::wstring& iconPath, const std::wstring& outpath)
{
//文件不存在
if (_waccess(iconPath.c_str(), 00) != 00 || _waccess(outpath.c_str(), 00) != 00) return -1;
//打开图标文件
HANDLE hIconFile = CreateFileW(iconPath.c_str(), GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hIconFile == INVALID_HANDLE_VALUE)
{
DWORD dwErrCode = GetLastError();
return dwErrCode;
}
//读取文件头
ICONHEADER header;
DWORD dwRead = 0;
memset(&header, 0, sizeof(ICONHEADER));
if (!ReadFile(hIconFile, &header, sizeof(ICONHEADER), &dwRead, NULL))
{
DWORD dwErrCode = GetLastError();
CloseHandle(hIconFile);
return dwErrCode;
}
if (header.idCount <= 0) return -2;
std::vector<ICONDIRENTRY> vecEntry(header.idCount);
//读取像信息块
if (!ReadFile(hIconFile, &vecEntry[0], header.idCount * sizeof(ICONDIRENTRY), &dwRead, NULL))
{
DWORD dwErrCode = GetLastError();
CloseHandle(hIconFile);
return dwErrCode;
}
//读取每一帧的数据
CLSID pngClsid;
if (!DCUtil::GetEncoderClsid(L"image/png", &pngClsid)) return -3;
for (int i = 0; i < vecEntry.size(); i++)
{
//根据偏移量读取图像数据
SetFilePointer(hIconFile, vecEntry[i].dwImageOffset, 0, FILE_BEGIN);
std::wstring wsave_file = L"";
//如果图像高度为0,则表示为256尺寸的数据
if (vecEntry[i].bHeight == 0)
{
std::vector<unsigned char> imgdata(vecEntry[i].dwBytesInRes, 0);
ReadFile(hIconFile, &imgdata[0], vecEntry[i].dwBytesInRes, &dwRead, NULL);
std::wstring file_name = A2WSTR(fmt::format("{0}_{1}x{2}.png", W2ASTR(jeflib::FileUtil::GetFileBaseNameW(iconPath)), 256, 256));
wsave_file = jeflib::FileUtil::JoinPathW(outpath, file_name);
HANDLE hImg = CreateFileW(wsave_file.c_str(), GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hImg)
{
DWORD dwWrite = 0;
WriteFile(hImg, &imgdata[0], vecEntry[i].dwBytesInRes, &dwWrite, NULL);
CloseHandle(hImg);
}
}
else
{
COLORREF* pBits = nullptr;
HBITMAP hBitMap = NULL;
BITMAPINFO bitmapinfo = { 0 };
if (!ReadFile(hIconFile, &bitmapinfo.bmiHeader, sizeof(BITMAPINFOHEADER), &dwRead, NULL))
{
continue;
}
bitmapinfo.bmiHeader.biHeight /= 2;
bitmapinfo.bmiHeader.biSizeImage = bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biWidth * 4;
hBitMap = CreateDIBSection(nullptr, &bitmapinfo, DIB_RGB_COLORS, (void**)&pBits, NULL, NULL);
if (hBitMap == nullptr || !ReadFile(hIconFile, pBits, bitmapinfo.bmiHeader.biSizeImage, &dwRead, NULL))
{
continue;
}
std::wstring file_name = A2WSTR(fmt::format("{0}_{1}x{2}.png", W2ASTR(jeflib::FileUtil::GetFileBaseNameW(iconPath)), bitmapinfo.bmiHeader.biWidth, bitmapinfo.bmiHeader.biHeight));
wsave_file = jeflib::FileUtil::JoinPathW(outpath, file_name);
//创建带alpha通道的Bitmap
Gdiplus::Bitmap* pimg = DCUtil::CreateAlphaBmpFromHBITMAP(hBitMap);
if (pimg)
{
pimg->Save(wsave_file.c_str(), &pngClsid);
delete pimg;
}
::DeleteObject(hBitMap);
}
}
CloseHandle(hIconFile);
return 0;
}
PNG合成ICO
static DWORD createIcoByPngs(const std::vector<std::wstring>& pngPaths, const std::wstring& iconPath)
{
if (pngPaths.empty() || iconPath.empty()) return -1;
//打开图标文件
HANDLE hIconFile = CreateFileW(iconPath.c_str(), GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hIconFile == INVALID_HANDLE_VALUE)
{
DWORD dwErrcode = GetLastError();
return dwErrcode;
}
//检验png有效性,一个尺寸只保存一张图
std::map<int, ENTRYDATA> mapSizePngImages;
for (int i = 0; i < pngPaths.size(); i++)
{
ENTRYDATA endata;
endata.nDataSize = FileUtil::GetFileSizeW(pngPaths[i]);
if(endata.nDataSize <= 0) continue;
endata.vFileContent.resize(endata.nDataSize);
{
HANDLE hImag = CreateFileW(pngPaths[i].c_str(), GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hImag != INVALID_HANDLE_VALUE)
{
DWORD dwRead = 0;
ReadFile(hImag, &endata.vFileContent[0], endata.vFileContent.size(), &dwRead, NULL);
CloseHandle(hImag);
}
}
std::shared_ptr<Gdiplus::Bitmap> pImg = std::shared_ptr<Gdiplus::Bitmap>(Gdiplus::Bitmap::FromFile(pngPaths[i].c_str()));
if (pImg == nullptr || pImg->GetWidth() != pImg->GetHeight() || pImg->GetWidth() <= 0) continue;
endata.pImg = pImg;
endata.strIconPath = pngPaths[i];
//超过128尺寸直接写入png图片数据,否则写入bitmap,并且计算掩码大小
//图像32位像素 + 40 header头 +AND mask
if (pImg->GetWidth() <= 128)
{
endata.nDataSize = pImg->GetWidth() * pImg->GetHeight() * 4 + 40 + (pImg->GetHeight() * ((pImg->GetWidth() + 31) / 32 * 32 / 8));
endata.vFileContent.resize(0);
mapSizePngImages[pImg->GetWidth()] = endata;
}
else
{
mapSizePngImages[256] = endata;
}
}
if (mapSizePngImages.empty())
{
CloseHandle(hIconFile);
return -2;
}
ICONHEADER header;
DWORD dwWrite = 0;
//写文件头
memset(&header, 0, sizeof(ICONHEADER));
header.idReserved = 0;
header.idCount = mapSizePngImages.size();
header.idType = 1;
WriteFile(hIconFile, &header, sizeof(ICONHEADER), &dwWrite, NULL);
//建立每一个图标的目录信息存放区域
LPICONDIRENTRY pIconDirEntry = (LPICONDIRENTRY)new BYTE[header.idCount * sizeof(ICONDIRENTRY)];
if (pIconDirEntry == NULL)
{
CloseHandle(hIconFile);
return -3;
}
memset(pIconDirEntry, 0, header.idCount * sizeof(ICONDIRENTRY));
//第一张图标的起始位置
DWORD offset = header.idCount * sizeof(ICONDIRENTRY) + sizeof(ICONHEADER);
int index = 0;
for (auto &it:mapSizePngImages)
{
pIconDirEntry[index].bWidth = it.first == 256 ? 0: it.first;
pIconDirEntry[index].bHeight = it.first == 256 ? 0 : it.first;
pIconDirEntry[index].wBitCount = 32;
pIconDirEntry[index].bReserved = 0;
pIconDirEntry[index].wPlanes = 1;
pIconDirEntry[index].dwBytesInRes = it.second.nDataSize;
pIconDirEntry[index].dwImageOffset = offset;
offset += pIconDirEntry[index].dwBytesInRes;
++index;
}
//写图像信息块
WriteFile(hIconFile, pIconDirEntry, header.idCount * sizeof(ICONDIRENTRY), &dwWrite, NULL);
delete []((BYTE*)pIconDirEntry);
//写每一帧的数据
for (auto& it : mapSizePngImages)
{
//大于128直接写入png数据
if (it.first > 128)
{
WriteFile(hIconFile, &it.second.vFileContent[0], it.second.vFileContent.size(), &dwWrite, NULL);
}
else
{
BITMAPINFOHEADER bitmapHeader;
memset(&bitmapHeader, 0, sizeof(BITMAPINFOHEADER));
bitmapHeader.biWidth = it.first;
//图像高度(XOR图高度+AND图高度)
bitmapHeader.biHeight = bitmapHeader.biWidth * 2;
bitmapHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapHeader.biPlanes = 1;
bitmapHeader.biBitCount = 32;
bitmapHeader.biSizeImage = it.second.nDataSize;
WriteFile(hIconFile, &bitmapHeader, sizeof(BITMAPINFOHEADER), &dwWrite, NULL);
//因为icon宽高相等,都等于it.first
//XOR mask
int imageDataSize = it.first * it.first;
int* imageDataBuf = new int[imageDataSize];
memset(imageDataBuf, 0, imageDataSize);
Gdiplus::Color bmpColor;
for (int y = 0; y < it.first; y++)
{
for (int x = 0; x < it.first; x++)
{
it.second.pImg->GetPixel(x, y, &bmpColor);
BYTE a = bmpColor.GetAlpha();
BYTE r = bmpColor.GetRed();
BYTE b = bmpColor.GetBlue();
BYTE g = bmpColor.GetGreen();
int index = y * it.first + x;
char* p = (char*)(imageDataBuf + index);
p[0] = b;
p[1] = g;
p[2] = r;
p[3] = a;
}
}
char* body = (char*)imageDataBuf;
for (int y = it.first - 1; y >= 0; --y)
{
for (int x = 0; x < it.first; ++x)
{
int index = (y * it.first + x) * 4;
char c = body[index];
WriteFile(hIconFile, &c, 1, &dwWrite, NULL); //Blue
c = body[index + 1];
WriteFile(hIconFile, &c, 1, &dwWrite, NULL); //Green
c = body[index + 2];
WriteFile(hIconFile, &c, 1, &dwWrite, NULL); //Red
c = body[index + 3];
WriteFile(hIconFile, &c, 1, &dwWrite, NULL); //Alpha
}
}
delete[]imageDataBuf;
//AND mask
for (int y = 0; y < (it.first * ((it.first + 31) / 32 * 32 / 8)); ++y)
{
char c = 0;
WriteFile(hIconFile, &c, 1, &dwWrite, NULL);
}
}
}
CloseHandle(hIconFile);
return true;
}
GetEncoderClsid
L"image/bmp" L"image/jpeg" L"image/gif" L"image/tiff" L"image/png"
static bool GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
if (format == nullptr || pClsid == nullptr) return false;
UINT num = 0; //number of image encoders
UINT size = 0; //size of the image encoder array in bytes
int nRet = Gdiplus::GetImageEncodersSize(&num, &size);
if (nRet != Gdiplus::Status::Ok)
{
return false;
}
Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
if (pImageCodecInfo == NULL)
{
return false;
}
Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
for (int i = 0; i < num; i++)
{
if (wcscmp(pImageCodecInfo[i].MimeType, format) == 0)
{
*pClsid = pImageCodecInfo[i].Clsid;
free(pImageCodecInfo);
return true;
}
}
free(pImageCodecInfo);
return false;
}
CreateAlphaBmpFromHBITMAP
static Gdiplus::Bitmap* CreateAlphaBmpFromHBITMAP(HBITMAP hBmp)
{
DIBSECTION dibsection = { 0 };
int nBytes = ::GetObject(hBmp, sizeof(DIBSECTION), &dibsection);
if (dibsection.dsBm.bmBitsPixel != 32)
{
return Gdiplus::Bitmap::FromHBITMAP(hBmp, NULL);
}
int width = dibsection.dsBm.bmWidth;
int height = abs(dibsection.dsBm.bmHeight);
int pitch = (((width * dibsection.dsBm.bmBitsPixel) + 31) / 32) * 4; //计算行宽,四字节对齐 ATL::CImage::ComputePitch // 32位位图不存在对齐问题,so其实没必要
LPVOID bits = dibsection.dsBm.bmBits;
if (dibsection.dsBmih.biHeight > 0) // 对于DDB,不会取到dsBmih数据,所以biHeight成员始终为0
{
bits = LPBYTE(bits) + ((height - 1) * pitch);
pitch = -pitch;
}
return new Gdiplus::Bitmap(width, height, pitch, PixelFormat32bppARGB, static_cast<BYTE*>(bits));
}