Last active
June 7, 2022 05:26
-
-
Save TheVice/e5b39ee9e6249015d2594da4b4a31872 to your computer and use it in GitHub Desktop.
This program create icon file from image file. Alternate SimpleIcon can be used - http://www.smoothdraw.com/simplyicon . As reference used https://devblogs.microsoft.com/oldnewthing/20101022-00/?p=12473 , http://www.cyberforum.ru/post7864490.html and https://www.codeguru.com/csharp/.net/net_general/graphics/article.php/c12787/IconLib-Icons-Unfol…
This file contains 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
/* | |
* The MIT License (MIT) | |
* | |
* Copyright (c) 2019 https://github.com/TheVice/ | |
* | |
*/ | |
/* | |
* References. | |
* https://devblogs.microsoft.com/oldnewthing/20101018-00/?p=12513 | |
* https://devblogs.microsoft.com/oldnewthing/20101019-00/?p=12503 | |
* https://devblogs.microsoft.com/oldnewthing/20101021-00/?p=12483 | |
* https://devblogs.microsoft.com/oldnewthing/?p=12473 | |
* http://www.cyberforum.ru/post7864490.html | |
* https://www.codeguru.com/csharp/.net/net_general/graphics/article.php/c12787/IconLib-Icons-Unfolded-MultiIcon-and-Windows-Vista-supported.htm | |
*/ | |
#include <list> | |
#include <string> | |
#include <cwchar> | |
#include <memory> | |
#include <cstdlib> | |
#include <fstream> | |
#include <cstring> | |
#include <windows.h> | |
#include <gdiplus.h> | |
#include <shlwapi.h> | |
#pragma comment(lib, "gdiplus.lib") | |
#pragma comment(lib, "shlwapi.lib") | |
enum struct ICONDIRTYPE : char | |
{ | |
ICO = 1, | |
CUR | |
}; | |
class ICONDIR | |
{ | |
uint16_t reserved; | |
uint16_t type; | |
public: | |
uint16_t count; | |
ICONDIR() : | |
reserved(0), | |
type(static_cast<uint16_t>(ICONDIRTYPE::ICO)), | |
count(1) | |
{ | |
} | |
void write(std::ostream& output) const | |
{ | |
output.write(reinterpret_cast<const char*>(&reserved), sizeof(reserved)); | |
output.write(reinterpret_cast<const char*>(&type), sizeof(type)); | |
output.write(reinterpret_cast<const char*>(&count), sizeof(count)); | |
} | |
void read(std::istream& input) | |
{ | |
input.read(reinterpret_cast<char*>(&reserved), sizeof(reserved)); | |
input.read(reinterpret_cast<char*>(&type), sizeof(type)); | |
input.read(reinterpret_cast<char*>(&count), sizeof(count)); | |
} | |
}; | |
struct ICONDIRENTRY | |
{ | |
uint8_t width; | |
uint8_t height; | |
uint8_t colors; | |
private: | |
uint8_t reserved; | |
public: | |
uint16_t planes; | |
uint16_t bitsPerPixel; | |
uint32_t size; | |
uint32_t offset; | |
ICONDIRENTRY() : | |
width(0), | |
height(0), | |
colors(0), | |
reserved(0), | |
planes(0), | |
bitsPerPixel(0), | |
size(0), | |
offset(0) | |
{ | |
} | |
void write(std::ostream& output) const | |
{ | |
output.write(reinterpret_cast<const char*>(&width), sizeof(width)); | |
output.write(reinterpret_cast<const char*>(&height), sizeof(height)); | |
output.write(reinterpret_cast<const char*>(&colors), sizeof(colors)); | |
output.write(reinterpret_cast<const char*>(&reserved), sizeof(reserved)); | |
output.write(reinterpret_cast<const char*>(&planes), sizeof(planes)); | |
output.write(reinterpret_cast<const char*>(&bitsPerPixel), sizeof(bitsPerPixel)); | |
output.write(reinterpret_cast<const char*>(&size), sizeof(size)); | |
output.write(reinterpret_cast<const char*>(&offset), sizeof(offset)); | |
} | |
void read(std::istream& input) | |
{ | |
input.read(reinterpret_cast<char*>(&width), sizeof(width)); | |
input.read(reinterpret_cast<char*>(&height), sizeof(height)); | |
input.read(reinterpret_cast<char*>(&colors), sizeof(colors)); | |
input.read(reinterpret_cast<char*>(&reserved), sizeof(reserved)); | |
input.read(reinterpret_cast<char*>(&planes), sizeof(planes)); | |
input.read(reinterpret_cast<char*>(&bitsPerPixel), sizeof(bitsPerPixel)); | |
input.read(reinterpret_cast<char*>(&size), sizeof(size)); | |
input.read(reinterpret_cast<char*>(&offset), sizeof(offset)); | |
} | |
}; | |
enum class ICONIMAGETYPE : char | |
{ | |
BMP = 1, | |
PNG | |
}; | |
struct ICONIMAGE | |
{ | |
const ICONIMAGETYPE type; | |
BITMAPINFOHEADER bmpHead; | |
std::string data; | |
ICONIMAGE(ICONIMAGETYPE IconType) : | |
type(IconType), | |
bmpHead(), | |
data() | |
{ | |
} | |
void write(std::ostream& output) const | |
{ | |
if (ICONIMAGETYPE::BMP == type) | |
{ | |
output.write(reinterpret_cast<const char*>(&bmpHead), sizeof(BITMAPINFOHEADER)); | |
} | |
output.write(data.data(), data.size()); | |
} | |
void read(std::istream& input, uint32_t size) | |
{ | |
if (ICONIMAGETYPE::BMP == type) | |
{ | |
if (size < sizeof(BITMAPINFOHEADER)) | |
{ | |
return; | |
} | |
input.read(reinterpret_cast<char*>(&bmpHead), sizeof(BITMAPINFOHEADER)); | |
size -= sizeof(BITMAPINFOHEADER); | |
} | |
data.resize(size); | |
input.read(&data[0], data.size()); | |
} | |
}; | |
struct Icon | |
{ | |
ICONDIR head; | |
std::list<ICONDIRENTRY> entries; | |
std::list<ICONIMAGE> images; | |
void write(std::ostream& output) | |
{ | |
head.write(output); | |
for (auto entry : entries) | |
{ | |
entry.write(output); | |
} | |
for (auto image : images) | |
{ | |
image.write(output); | |
} | |
} | |
void read(std::istream& input) | |
{ | |
head.read(input); | |
for (auto i = 0; i < head.count; ++i) | |
{ | |
ICONDIRENTRY entry; | |
entry.read(input); | |
entries.push_back(entry); | |
} | |
for (auto entry : entries) | |
{ | |
ICONIMAGE image((0 == entry.width || 0 == entry.height) ? ICONIMAGETYPE::PNG : ICONIMAGETYPE::BMP); | |
image.read(input, entry.size); | |
images.push_back(image); | |
} | |
} | |
}; | |
struct MemoryStream | |
{ | |
IStream* stream; | |
MemoryStream() : | |
stream(SHCreateMemStream(nullptr, 0)) | |
{ | |
} | |
bool getData(char** data, unsigned long* size) | |
{ | |
if (nullptr == stream) | |
{ | |
return false; | |
} | |
ULARGE_INTEGER position; | |
LARGE_INTEGER preferedPosition; | |
std::memset(&position, 0, sizeof(ULARGE_INTEGER)); | |
std::memset(&preferedPosition, 0, sizeof(LARGE_INTEGER)); | |
if (SUCCEEDED(stream->Seek(preferedPosition, STREAM_SEEK_CUR, &position)) && 0 < position.LowPart) | |
{ | |
if (nullptr == data) | |
{ | |
*size = position.LowPart; | |
return true; | |
} | |
if (*size < position.LowPart) | |
{ | |
return false; | |
} | |
*size = position.LowPart; | |
if (SUCCEEDED(stream->Seek(preferedPosition, STREAM_SEEK_SET, &position))) | |
{ | |
unsigned long readed = 0; | |
if (SUCCEEDED(stream->Read(*data, *size, &readed))) | |
{ | |
*size = readed; | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
bool getData(std::string& data) | |
{ | |
unsigned long size = 0; | |
if (!getData(nullptr, &size)) | |
{ | |
return false; | |
} | |
data.resize(size); | |
auto memoryPtr = &data[0]; | |
if (!getData(&memoryPtr, &size)) | |
{ | |
data.clear(); | |
return false; | |
} | |
return true; | |
} | |
~MemoryStream() | |
{ | |
if (nullptr != stream) | |
{ | |
stream->Release(); | |
} | |
stream = nullptr; | |
} | |
}; | |
#define RETURN_FALSE_IF_NO_OK(A) if (Gdiplus::Status::Ok != (A)) { return false; } | |
struct GdiPlus | |
{ | |
private: | |
ULONG_PTR token; | |
const Gdiplus::GdiplusStartupInput input; | |
const Gdiplus::Status initStatus; | |
public: | |
GdiPlus() : | |
token(0), | |
input(), | |
initStatus(Gdiplus::GdiplusStartup(&token, &input, nullptr)) | |
{ | |
} | |
bool isValid() const | |
{ | |
return Gdiplus::Status::Ok == initStatus; | |
} | |
~GdiPlus() | |
{ | |
if (isValid()) | |
{ | |
Gdiplus::GdiplusShutdown(token); | |
token = 0; | |
} | |
} | |
}; | |
int GetEncoderClsid(const wchar_t* format, CLSID* pClsid) | |
{ | |
//NOTE: Based on a C version that can be found here https://docs.microsoft.com/en-us/windows/desktop/gdiplus/-gdiplus-retrieving-the-class-identifier-for-an-encoder-use | |
if (nullptr == format || nullptr == pClsid) | |
{ | |
return -1; | |
} | |
UINT num = 0; | |
UINT size = 0; | |
if (Gdiplus::Status::Ok != Gdiplus::GetImageEncodersSize(&num, &size) || (0 == size)) | |
{ | |
return -1; | |
} | |
const std::unique_ptr<Gdiplus::ImageCodecInfo[]> imageCodecInfo(new Gdiplus::ImageCodecInfo[size]); | |
if (Gdiplus::Status::Ok != Gdiplus::GetImageEncoders(num, size, imageCodecInfo.get())) | |
{ | |
return -1; | |
} | |
size = 0; | |
while (size < num && std::wcscmp(imageCodecInfo[size].MimeType, format) != 0) | |
{ | |
size++; | |
} | |
if (num == size) | |
{ | |
return -1; | |
} | |
*pClsid = imageCodecInfo[size].Clsid; | |
// | |
return size; | |
} | |
bool addPngData(Gdiplus::Bitmap* inputBitmap, const CLSID* clsidPng, Icon& icon) | |
{ | |
ICONDIRENTRY entry; | |
entry.width = 0; | |
entry.height = 0; | |
entry.colors = 0; | |
entry.planes = 1; | |
entry.bitsPerPixel = 32; | |
// | |
const auto bitmap(std::make_unique<Gdiplus::Bitmap>(256, 256, PixelFormat32bppARGB)); | |
auto grfx = Gdiplus::Graphics::FromImage(bitmap.get()); | |
RETURN_FALSE_IF_NO_OK(grfx->DrawImage(inputBitmap, 0, 0, 256, 256)); | |
const auto memoryStream(std::make_unique<MemoryStream>()); | |
RETURN_FALSE_IF_NO_OK(bitmap->Save(memoryStream->stream, clsidPng, nullptr)); | |
ICONIMAGE png(ICONIMAGETYPE::PNG); | |
if (!memoryStream->getData(png.data)) | |
{ | |
return false; | |
} | |
entry.size = png.data.size(); | |
icon.entries.push_back(entry); | |
icon.images.push_back(png); | |
// | |
return true; | |
} | |
bool createMasks(Gdiplus::Bitmap* inputBitmap, const CLSID* clsidBmp, const std::list<Gdiplus::Size>& sizes, | |
std::list<std::string>& masks) | |
{ | |
if (sizes.empty()) | |
{ | |
return false; | |
} | |
const auto bitmap(std::make_unique<Gdiplus::Bitmap>(inputBitmap->GetWidth(), inputBitmap->GetHeight(), | |
PixelFormat1bppIndexed)); | |
const auto color(std::make_unique<Gdiplus::Color>()); | |
for (uint32_t i = 0; i < inputBitmap->GetWidth(); ++i) | |
{ | |
for (uint32_t j = 0; j < inputBitmap->GetHeight(); ++j) | |
{ | |
RETURN_FALSE_IF_NO_OK(inputBitmap->GetPixel(i, j, color.get())); | |
RETURN_FALSE_IF_NO_OK(bitmap->SetPixel(i, j, | |
(255 == color->GetAlpha()) ? Gdiplus::Color::Black : Gdiplus::Color::White)); | |
} | |
} | |
for (auto size : sizes) | |
{ | |
const auto bitmapWithMask(std::make_unique<Gdiplus::Bitmap>(size.Width, size.Height, PixelFormat1bppIndexed)); | |
auto grfx = Gdiplus::Graphics::FromImage(bitmapWithMask.get()); | |
RETURN_FALSE_IF_NO_OK(grfx->DrawImage(bitmap.get(), 0, 0, size.Width, size.Height)); | |
const auto memoryStream(std::make_unique<MemoryStream>()); | |
RETURN_FALSE_IF_NO_OK(bitmapWithMask->Save(memoryStream->stream, clsidBmp, nullptr)); | |
std::string mask; | |
if (!memoryStream->getData(mask) || sizeof(BITMAPFILEHEADER) > mask.size()) | |
{ | |
return false; | |
} | |
const auto offBits = reinterpret_cast<const BITMAPFILEHEADER*>(mask.c_str())->bfOffBits; | |
if (offBits > mask.size()) | |
{ | |
return false; | |
} | |
std::copy(mask.cbegin() + offBits, mask.cend(), mask.begin()); | |
mask.resize(mask.size() - offBits); | |
masks.push_back(mask); | |
} | |
return true; | |
} | |
bool createImages(Gdiplus::Bitmap* inputBitmap, const CLSID* clsidBmp, const std::list<Gdiplus::Size>& sizes, | |
std::list<std::string>& images, Gdiplus::PixelFormat format = PixelFormat32bppARGB) | |
{ | |
if (sizes.empty()) | |
{ | |
return false; | |
} | |
for (auto size : sizes) | |
{ | |
const auto bitmap(std::make_unique<Gdiplus::Bitmap>(size.Width, size.Height, format)); | |
auto grfx = Gdiplus::Graphics::FromImage(bitmap.get()); | |
RETURN_FALSE_IF_NO_OK(grfx->DrawImage(inputBitmap, 0, 0, size.Width, size.Height)); | |
const auto memoryStream(std::make_unique<MemoryStream>()); | |
RETURN_FALSE_IF_NO_OK(bitmap->Save(memoryStream->stream, clsidBmp, nullptr)); | |
std::string image; | |
if (!memoryStream->getData(image) || sizeof(BITMAPFILEHEADER) > image.size()) | |
{ | |
return false; | |
} | |
const auto offBits = reinterpret_cast<const BITMAPFILEHEADER*>(image.c_str())->bfOffBits; | |
if (offBits > image.size()) | |
{ | |
return false; | |
} | |
std::copy(image.cbegin() + offBits, image.cend(), image.begin()); | |
image.resize(image.size() - offBits); | |
images.push_back(image); | |
} | |
return true; | |
} | |
struct BitmapHelper | |
{ | |
// Copyright (c) 2006, Gustavo Franco | |
// Email: gustavo_franco@hotmail.com | |
// All rights reserved. | |
// Redistribution and use in source and binary forms, with or without modification, | |
// are permitted provided that the following conditions are met: | |
// Redistributions of source code must retain the above copyright notice, | |
// this list of conditions and the following disclaimer. | |
// Redistributions in binary form must reproduce the above copyright notice, | |
// this list of conditions and the following disclaimer in the documentation | |
// and/or other materials provided with the distribution. | |
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY | |
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR | |
// PURPOSE. IT CAN BE DISTRIBUTED FREE OF CHARGE AS LONG AS THIS HEADER | |
// REMAINS UNCHANGED. | |
// Source of this structure was translated to C++ from C# code. | |
// Original C# code can found here https://www.codeguru.com/csharp/.net/net_general/graphics/article.php/c12787/IconLib-Icons-Unfolded-MultiIcon-and-Windows-Vista-supported.htm at IconLib_src/IconLib/System/Drawing/IconLib/EncodingFormats/BMPEncoder.cs on line 58. | |
static int XorStride(const BITMAPINFOHEADER& header) | |
{ | |
return (static_cast<int>((header.biWidth * header.biBitCount + 31) & ~31) >> 3); | |
} | |
static int AndStride(const BITMAPINFOHEADER& header) | |
{ | |
return (static_cast<int>((static_cast<int>(header.biWidth) + 31) & ~31) >> 3); | |
} | |
static long GetXorLength(const BITMAPINFOHEADER& header) | |
{ | |
return XorStride(header) * (header.biHeight / 2); | |
} | |
static long GetAndLength(const BITMAPINFOHEADER& header) | |
{ | |
return AndStride(header) * (header.biHeight / 2); | |
} | |
}; | |
bool write(wchar_t* input, wchar_t* output) | |
{ | |
const auto gdiPlus(std::make_unique<GdiPlus>()); | |
if (!gdiPlus->isValid()) | |
{ | |
return false; | |
} | |
const auto clsidPng(std::make_unique<CLSID>()); | |
if (-1 == GetEncoderClsid(L"image/png", clsidPng.get())) | |
{ | |
return false; | |
} | |
const auto inputBitmap = Gdiplus::Bitmap::FromFile(input, FALSE); | |
Icon icon; | |
if (!addPngData(inputBitmap, clsidPng.get(), icon)) | |
{ | |
return false; | |
} | |
const auto clsidBmp(std::make_unique<CLSID>()); | |
if (-1 == GetEncoderClsid(L"image/bmp", clsidBmp.get())) | |
{ | |
return false; | |
} | |
const std::list<Gdiplus::Size> sizes( | |
{ | |
Gdiplus::Size(128, 128), | |
Gdiplus::Size(48, 48), | |
Gdiplus::Size(32, 32), | |
Gdiplus::Size(24, 24), | |
Gdiplus::Size(16, 16) | |
}); | |
// | |
std::list<std::string> masks; | |
if (!createMasks(inputBitmap, clsidBmp.get(), sizes, masks)) | |
{ | |
return false; | |
} | |
std::list<std::string> images; | |
if (!createImages(inputBitmap, clsidBmp.get(), sizes, images, PixelFormat32bppARGB)) | |
{ | |
return false; | |
} | |
auto size = sizes.cbegin(); | |
for (auto mask = masks.cbegin(), image = images.cbegin(); mask != masks.cend(); ++mask, ++image, ++size) | |
{ | |
ICONDIRENTRY entry; | |
entry.width = size->Width; | |
entry.height = size->Height; | |
entry.colors = 0; | |
entry.planes = 1; | |
entry.bitsPerPixel = 32; | |
entry.size = sizeof(BITMAPINFOHEADER); | |
// | |
ICONIMAGE bmp(ICONIMAGETYPE::BMP); | |
// | |
bmp.bmpHead.biSize = sizeof(BITMAPINFOHEADER); | |
bmp.bmpHead.biWidth = entry.width; | |
bmp.bmpHead.biHeight = 2 * entry.height; | |
bmp.bmpHead.biPlanes = entry.planes; | |
bmp.bmpHead.biBitCount = entry.bitsPerPixel; | |
bmp.bmpHead.biCompression = BI_RGB; | |
bmp.bmpHead.biXPelsPerMeter = 0; | |
bmp.bmpHead.biYPelsPerMeter = 0; | |
bmp.bmpHead.biClrUsed = 0; | |
bmp.bmpHead.biClrImportant = 0; | |
// | |
const auto xorLength = BitmapHelper::GetXorLength(bmp.bmpHead); | |
const auto andLength = BitmapHelper::GetAndLength(bmp.bmpHead); | |
// | |
bmp.bmpHead.biSizeImage = xorLength + andLength; | |
// | |
bmp.data.resize(bmp.bmpHead.biSizeImage); | |
if (mask->size() != andLength || image->size() != xorLength) | |
{ | |
continue; | |
} | |
std::copy(image->cbegin(), image->cend(), bmp.data.begin()); | |
std::copy(mask->cbegin(), mask->cend(), bmp.data.begin() + xorLength); | |
entry.size += bmp.bmpHead.biSizeImage; | |
icon.entries.push_back(entry); | |
icon.images.push_back(bmp); | |
} | |
uint32_t offset = sizeof(ICONDIR) + icon.entries.size() * sizeof(ICONDIRENTRY); | |
for (auto& entry : icon.entries) | |
{ | |
entry.offset = offset; | |
offset += entry.size; | |
} | |
icon.head.count = static_cast<uint16_t>(icon.entries.size()); | |
std::ofstream file(output, std::ios::binary); | |
if (!file.is_open()) | |
{ | |
return false; | |
} | |
icon.write(file); | |
return true; | |
} | |
int wmain(int argc, wchar_t** argv) | |
{ | |
if (argc != 3) | |
{ | |
return EXIT_FAILURE; | |
} | |
if (!write(argv[1], argv[2])) | |
{ | |
return EXIT_FAILURE; | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment