Skip to content

Instantly share code, notes, and snippets.

@TheVice
Last active June 7, 2022 05:26
Show Gist options
  • Save TheVice/e5b39ee9e6249015d2594da4b4a31872 to your computer and use it in GitHub Desktop.
Save TheVice/e5b39ee9e6249015d2594da4b4a31872 to your computer and use it in GitHub Desktop.
/*
* 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