icon文件解析和生成
参考:ICO文件格式的演化
使用GDI+保存带Alpha通道的图像
ICON 格式
文件头 | DIRENTRY | DIRENTRY|DIRENTRY|… Imgdata|ImgdataImgdataImgdata|…
//文件头
//sizeof = 6
typedef struct ICONHEADER
{WORD idReserved;//保留用,WORD idType;//固定值 1WORD idCount;//包含的图片个数
};//标记每张图片的具体信息
//sizeof = 16
typedef struct ICONDIRENTRY
{BYTE bWidth;//图片大小,如果图片宽度大于等于256则为0BYTE bHeight;//图片大小,如果图片宽度大于等于256则为0BYTE bColorCount;BYTE bReserved;WORD wPlanes;WORD wBitCount;//图像大小w*h*4 + 40 header头 + mask//mask (h * ((w + 31) / 32 * 32 / 8)) DWORD dwBytesInRes;//图片资源大小,需要计算maskDWORD 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通道的BitmapGdiplus::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 maskint 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); //Bluec = body[index + 1];WriteFile(hIconFile, &c, 1, &dwWrite, NULL); //Greenc = body[index + 2];WriteFile(hIconFile, &c, 1, &dwWrite, NULL); //Redc = 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 encodersUINT size = 0; //size of the image encoder array in bytesint 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));}
