png2cur.zip/png2cur.cpp
#include <iostream>
#include <memory>
#include <array>
#include <algorithm>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/utility.hpp>
#include <boost/spirit/home/support/detail/endian.hpp>
#define NOMINMAX
#include <Windows.h>
// smallがマクロ定義されて使えないため
#undef small
#include <zlib.h>
#include <png.h>
namespace endian = boost::spirit::endian;
struct TrimInfo {
public:
unsigned int x;
unsigned int y;
unsigned int width;
unsigned int height;
TrimInfo(const unsigned int x, const unsigned int y, const unsigned int width, const unsigned int height) :
x(x), y(y), width(width), height(height)
{}
};
class IStreamWrapper : public boost::noncopyable {
private:
std::istream ∈
public:
IStreamWrapper(std::istream &in) : in(in) {}
static void Read(png_struct * const png, unsigned char * const data, const png_size_t size) {
IStreamWrapper * const ptr = reinterpret_cast<IStreamWrapper *>(::png_get_io_ptr(png));
if (ptr == nullptr) {
return;
}
ptr->Read(data, size);
}
void Read(unsigned char * const data, const png_size_t size) {
in.read(reinterpret_cast<char *>(data), size);
}
};
struct Color {
public:
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
};
class Png : public boost::noncopyable {
private:
class Row {
private:
const Png &png;
const unsigned int y;
public:
Row(const Png &png, const unsigned int y) : png(png), y(y) {}
const Color &operator[](const unsigned int x) const {
return png.colors[y * png.width + x];
}
};
public:
const unsigned int width;
const unsigned int height;
const std::vector<Color> colors;
Png(const unsigned int width, const unsigned int height, const std::vector<Color> colors) : width(width), height(height), colors(std::move(colors)) {}
static std::shared_ptr<const Png> readFromFile(const boost::filesystem::path &path) {
if (!boost::filesystem::is_regular_file(path)) {
return false;
}
const std::shared_ptr<png_struct> png(::png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr), [](png_struct *png){
::png_destroy_read_struct(&png, nullptr, nullptr);
});
const auto freeInfo = [png](png_info *info) {
::png_destroy_info_struct(png.get(), &info);
};
const std::shared_ptr<png_info> info(::png_create_info_struct(png.get()), freeInfo);
boost::filesystem::ifstream ifs(path, std::ios::binary);
IStreamWrapper isw(ifs);
::png_set_read_fn(png.get(), &isw, IStreamWrapper::Read);
unsigned char signature[8] = { 0 };
isw.Read(signature, _countof(signature));
if (::png_sig_cmp(signature, 0, 8) != 0) {
return{};
}
::png_set_sig_bytes(png.get(), _countof(signature));
::png_read_png(png.get(), info.get(), PNG_TRANSFORM_PACKING | PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_BGR | PNG_TRANSFORM_GRAY_TO_RGB, NULL);
const unsigned int width = ::png_get_image_width(png.get(), info.get());
const unsigned int height = ::png_get_image_height(png.get(), info.get());
const Color * const * const rows = reinterpret_cast<const Color * const *>(::png_get_rows(png.get(), info.get()));
std::vector<Color> colors(width * height);
for (unsigned int y = 0; y < height; y++) {
for (unsigned int x = 0; x < width; x++) {
colors[(height - y - 1) * width + x] = rows[y][x];
}
}
return std::make_shared<Png>(width, height, std::move(colors));
}
Row operator[](const unsigned int y) const {
return Row(*this, y);
}
std::shared_ptr<const Png> trim(const TrimInfo &infoRaw) const {
TrimInfo info = infoRaw;
if (info.x + info.width > width || info.y + info.height > height) {
return{};
}
info.y = height - info.height - info.y;
std::vector<Color> trimColors(info.width * info.height);
for (unsigned int y = 0; y < info.height; y++) {
for (unsigned int x = 0; x < info.width; x++) {
trimColors[y * info.width + x] = colors[(info.y + y) * width + info.x + x];
}
}
return std::make_shared<Png>(info.width, info.height, std::move(trimColors));
}
};
class Ico : public boost::noncopyable {
private:
struct Image {
public:
const std::shared_ptr<const Png> body;
const struct Hotspot {
const unsigned int x;
const unsigned int y;
Hotspot(const unsigned int x, const unsigned int y) : x(x), y(y) {}
} hotspot;
Image(const std::shared_ptr<const Png> &body, const unsigned int hotspotX, const unsigned int hotspotY) : body(body), hotspot(hotspotX, hotspotY) {}
};
const Image small;
const Image normal;
const bool isCursor;
public:
Ico(const Image &small, const Image &normal, const bool isCursor) : small(small), normal(normal), isCursor(isCursor) {}
static std::shared_ptr<const Ico> createFromPng(const Png &png, const unsigned int hotspotX, const unsigned int hotspotY) {
const Image small = resize(png, 32, hotspotX, hotspotY);
const Image normal = resize(png, 48, hotspotX, hotspotY);
const bool isCursor = true;
return std::make_shared<Ico>(small, normal, isCursor);
}
static Image resize(const Png &png, const unsigned int size, const unsigned int origHotspotX, const unsigned int origHotspotY) {
struct Point {
double x;
double y;
};
Point center;
center.x = static_cast<double>(png.width) / 2.0;
center.y = static_cast<double>(png.height) / 2.0;
const double space = static_cast<double>(std::max(png.width, png.height)) / size;
std::vector<Color> colors(size * size);
const auto isExist = [&png](const int x, const int y) {
return 0 <= x && static_cast<unsigned int>(x) < png.width && 0 <= y && static_cast<unsigned int>(y) < png.height;
};
for (unsigned int y = 0; y < size; y++) {
for (unsigned int x = 0; x < size; x++) {
Point cur;
cur.x = center.x + (static_cast<double>(-static_cast<int>(size)) / 2.0 + x + 0.5) * space;
cur.y = center.y + (static_cast<double>(-static_cast<int>(size)) / 2.0 + y + 0.5) * space;
Point start;
start.x = std::floor(cur.x) - 1;
start.y = std::floor(cur.y) - 1;
const std::array<double, 4> weightX = calcWeight(start.x, cur.x);
const std::array<double, 4> weightY = calcWeight(start.y, cur.y);
double r = 0.0;
double g = 0.0;
double b = 0.0;
double a = 0.0;
double weight = 0.0;
for (unsigned int i = 0; i < 4; i++) {
for (unsigned int j = 0; j < 4; j++) {
const double curWeight = weightX[j] * weightY[i];
weight += curWeight;
const int colX = static_cast<int>(start.x) + j;
const int colY = static_cast<int>(start.y) + i;
if (!isExist(colX, colY)) {
continue;
}
const Color &col = png[colY][colX];
r += col.r * curWeight;
g += col.g * curWeight;
b += col.b * curWeight;
a += col.a * curWeight;
}
}
Color &col = colors[y * size + x];
if (weight == 0.0) {
col.r = col.g = col.b = col.a = 0;
continue;
}
const auto calcColor = [weight](const double c) {
return static_cast<unsigned int>(std::min(255, std::max(0, static_cast<int>(c / weight))));
};
col.r = calcColor(r);
col.g = calcColor(g);
col.b = calcColor(b);
col.a = calcColor(a);
}
}
const auto fit = [size](const double value) {
return std::min(size, static_cast<unsigned int>(std::max(0, static_cast<int>(std::round(value)))));
};
const unsigned int hotspotX = fit((static_cast<double>(origHotspotX) - center.x) / space + static_cast<double>(size) / 2.0);
const unsigned int hotspotY = fit((static_cast<double>(origHotspotY) - center.y) / space + static_cast<double>(size) / 2.0);
return Image(std::make_shared<Png>(size, size, std::move(colors)), hotspotX, hotspotY);
}
static std::array<double, 4> calcWeight(const double start, const double pos) {
std::array<double, 4> weightList;
for (unsigned int i = 0; i < 4; i++) {
const double distance = std::abs(start + i - pos);
double &weight = weightList[i];
if (distance >= 2.0) {
weight = 0.0;
} else if (distance <= 1.0) {
weight = 1 - 2 * std::pow(distance, 2) + std::pow(distance, 3);
} else {
weight = 4 - 8 * distance + 5 * std::pow(distance, 2) - std::pow(distance, 3);
}
}
return std::move(weightList);
}
static unsigned int getMaskRowSize(const Png &png) {
return ((png.width + 7) / 8 + 3) / 4 * 4;
}
static unsigned int getMaskSize(const Png &png) {
return getMaskRowSize(png) * png.height;
}
std::vector<unsigned char> toBinary() const {
const std::array<Image, 2> ary = {small, normal};
unsigned int dataSize = 6;
for (const Image &image : ary) {
const Png &png = *image.body;
dataSize += 16 + 40 + png.width * png.height * 4 + getMaskSize(png);
}
std::vector<unsigned char> data(dataSize);
unsigned char *ptr = &data.front();
struct Header {
endian::ulittle16_t reserved;
endian::ulittle16_t type;
endian::ulittle16_t count;
};
Header &header = *reinterpret_cast<Header *>(ptr);
header.reserved = 0;
header.type = (isCursor ? 2 : 1);
header.count = static_cast<unsigned short>(ary.size());
ptr += 6;
unsigned int offsetCount = 6 + 16 * ary.size();
for (const Image &image : ary) {
const Png &png = *image.body;
struct Info {
endian::ulittle8_t width;
endian::ulittle8_t height;
endian::ulittle8_t colorCount;
endian::ulittle8_t reserved1;
endian::ulittle16_t hotspotX;
endian::ulittle16_t hotspotY;
endian::ulittle32_t size;
endian::ulittle32_t offset;
};
Info &info = *reinterpret_cast<Info *>(ptr);
info.width = png.width;
info.height = png.height;
info.colorCount = 0;
info.reserved1 = 0;
info.hotspotX = image.hotspot.x;
info.hotspotY = image.hotspot.y;
info.size = 40 + png.width * png.height * 4 + getMaskSize(png);
info.offset = offsetCount;
ptr += 16;
offsetCount += info.size;
}
for (const Image &image : ary) {
const Png &png = *image.body;
struct Info {
endian::ulittle32_t thisSize;
endian::little32_t width;
endian::little32_t height;
endian::ulittle16_t planes;
endian::ulittle16_t bitCount;
endian::ulittle32_t compressType;
endian::ulittle32_t size;
endian::ulittle32_t xDpi;
endian::ulittle32_t yDpi;
endian::ulittle32_t paletteCount;
endian::ulittle32_t paletteIndex;
};
Info &info = *reinterpret_cast<Info *>(ptr);
info.thisSize = 40;
info.width = png.width;
info.height = png.height * 2;
info.planes = 1;
info.bitCount = 32;
info.compressType = 0;// 無圧縮
info.size = 0;
info.xDpi = 0;
info.yDpi = 0;
info.paletteCount = 0;
info.paletteIndex = 0;
ptr += 40;
for (const Color &color : png.colors) {
Color &out = *reinterpret_cast<Color *>(ptr);
out = color;
ptr += 4;
}
for (unsigned int y = 0; y < png.height; y++) {
for (unsigned int i = 0; i < getMaskRowSize(png); i++, ptr++) {
*ptr = 0;
if (i * 8 >= png.width) {
continue;
}
for (unsigned int j = 0; j < 8; j++) {
*ptr <<= 1;
if (i * 8 + j >= png.width) {
continue;
}
*ptr |= (png[y][i * 8 + j].a != 0xFF ? 1 : 0);
}
}
}
}
if (ptr != &data.front() + data.size()) {
return{};
}
return std::move(data);
}
};
bool proc(const boost::filesystem::path &filePath, const TrimInfo &trimInfo, const unsigned int hotspotX, const unsigned int hotspotY) {
const std::shared_ptr<const Png> png = Png::readFromFile(filePath);
if (!png) {
return false;
}
const std::shared_ptr<const Png> trimPng = png->trim(trimInfo);
if (!trimPng) {
return false;
}
const std::shared_ptr<const Ico> ico = Ico::createFromPng(*trimPng, hotspotX, hotspotY);
if (!ico) {
return false;
}
std::vector<unsigned char> binary = ico->toBinary();
if (binary.empty()) {
return false;
}
boost::filesystem::path outPath = filePath;
outPath.replace_extension(".cur");
boost::filesystem::ofstream ofs(outPath, std::ios::binary);
ofs.write(reinterpret_cast<const char *>(&binary.front()), binary.size());
if (!ofs.good()) {
return false;
}
return true;
}
int main() {
unsigned int argc = 0;
const wchar_t * const * const argv = ::CommandLineToArgvW(::GetCommandLineW(), reinterpret_cast<int *>(&argc));
if (argc != 8) {
std::wcout << boost::wformat(L"usage: %s file_path trim_x trim_y trim_width trim_height hotspot_x hotspot_y") % boost::filesystem::path(argv[0]).filename();
return 1;
}
const boost::filesystem::path filePath = argv[1];
const TrimInfo trimInfo = {
boost::lexical_cast<unsigned int>(argv[2]),
boost::lexical_cast<unsigned int>(argv[3]),
boost::lexical_cast<unsigned int>(argv[4]),
boost::lexical_cast<unsigned int>(argv[5]),
};
if (!proc(filePath, trimInfo, boost::lexical_cast<unsigned int>(argv[6]), boost::lexical_cast<unsigned int>(argv[7]))) {
std::wcout << L"failure\n";
return 1;
}
std::wcout << L"success\n";
return 0;
}