他人の空似自作物置場

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 &in;
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;
}