skinpeel

Change vita's lockscreen foreground and background randomly
git clone https://git.neptards.moe/neptards/skinpeel.git
Log | Files | Refs | README | LICENSE

convert.cpp (10271B)


      1 #include "common.h"
      2 
      3 #include <cstdint>
      4 #include <cstring>
      5 #include <fstream>
      6 #include <iomanip>
      7 #include <iostream>
      8 #include <limits>
      9 #include <memory>
     10 #include <stdexcept>
     11 
     12 #define ICBC_IMPLEMENTATION
     13 #include "icbc.h"
     14 
     15 #define STB_IMAGE_IMPLEMENTATION
     16 #define STB_IMAGE_STATIC
     17 #include "stb_image.h"
     18 
     19 static void progress_report(float p);
     20 #define STB_IMAGE_RESIZE_IMPLEMENTATION
     21 #define STB_IMAGE_RESIZE_STATIC
     22 #define STBIR_PROGRESS_REPORT(val) progress_report(val)
     23 #include "stb_image_resize.h"
     24 
     25 #define OUT_W 1024
     26 #define OUT_H 512
     27 
     28 #define STR(x) STR_(x)
     29 #define STR_(x) #x
     30 
     31 static bool verbose = false;
     32 #define LOG verbose && std::clog
     33 
     34 static void progress_report(float p)
     35 {
     36   if (p < 1)
     37     LOG << "Scaling " << std::setprecision(3) << p*100 << "% done...        \r"
     38         << std::flush;
     39   else
     40     LOG << "Scaling done          " << std::endl;
     41 }
     42 
     43 namespace
     44 {
     45   struct FreeDeleter
     46   {
     47     void operator()(void* ptr) noexcept { free(ptr); }
     48   };
     49 
     50   struct Image
     51   {
     52     std::uint32_t width, height;
     53     using Ptr = std::unique_ptr<unsigned char[], FreeDeleter>;
     54     Ptr buf;
     55     static constexpr std::uint32_t CHANNELS = 3;
     56 
     57     static Image Load(const char* fname)
     58     {
     59       int x, y, n;
     60       auto img = stbi_load(fname, &x, &y, &n, 3);
     61       if (!img)
     62       {
     63         std::cerr << stbi_failure_reason() << std::endl;
     64         throw std::runtime_error("Image load failed");
     65       }
     66       LOG << "Loaded image " << x << "x" << y << std::endl;
     67       // max is 0x0fffffff to avoid overflows
     68       assert(x >= 0 && x <= 0x0fffffff);
     69       assert(y >= 0 && y <= 0x0fffffff);
     70       return Image{std::uint32_t(x), std::uint32_t(y), Ptr{img}};
     71     }
     72   };
     73 
     74   struct ImageView
     75   {
     76     std::uint32_t width, height, stride;
     77     unsigned char* buf;
     78 
     79     ImageView(const Image& img) noexcept
     80       : width(img.width), height(img.height), stride(img.width * Image::CHANNELS),
     81         buf{img.buf.get()} {}
     82     ImageView(std::uint32_t width, std::uint32_t height, std::uint32_t stride,
     83               unsigned char* buf) noexcept
     84       : width{width}, height{height}, stride{stride}, buf{buf} {}
     85 
     86     ImageView SubView(
     87       std::uint32_t x, std::uint32_t y, std::uint32_t w, std::uint32_t h)
     88       const noexcept
     89     {
     90       assert(x < width && w <= width && x+w <= width);
     91       assert(y < height && h <= height && y+h <= height);
     92       return { w, h, stride, buf + y*stride + x*Image::CHANNELS };
     93     }
     94 
     95     unsigned char* Get(std::uint32_t x, std::uint32_t y) const noexcept
     96     { return buf + y*stride + x*Image::CHANNELS;}
     97   };
     98 }
     99 
    100 static auto filter = STBIR_FILTER_DEFAULT;
    101 static Image Scale(ImageView view, std::uint32_t width, std::uint32_t height)
    102 {
    103   Image::Ptr buf{static_cast<unsigned char*>(
    104       malloc(std::size_t(width) * height * Image::CHANNELS))};
    105   if (!buf) throw std::bad_alloc();
    106 
    107   if (!stbir_resize_uint8_generic(
    108         view.buf, view.width, view.height, view.stride,
    109         buf.get(), width, height, width * Image::CHANNELS, Image::CHANNELS,
    110         STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, filter,
    111         STBIR_COLORSPACE_LINEAR, nullptr))
    112     throw std::runtime_error("Resize failed?");
    113 
    114   return Image{ width, height, std::move(buf) };
    115 }
    116 
    117 static icbc::Quality quality = icbc::Quality::Quality_Max;
    118 static void Save(ImageView img, const char* fname, bool dds)
    119 {
    120   // std::cout << "P6 " << img.width << " " << img.height << " 255\n";
    121   // for (std::uint32_t y = 0; y < img.height; ++y)
    122   //   std::cout.write(reinterpret_cast<const char*>(img.Get(0, y)), Image::CHANNELS*img.width);
    123   // return;
    124 
    125   assert(img.width % 4 == 0 && img.height % 4 == 0);
    126   static constexpr const float input_weights[16] =
    127     { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
    128   static constexpr const float color_weights[3] = { 1,1,1 };
    129 
    130   std::ofstream os{fname};
    131   if (!os) throw std::runtime_error("Can't open output");
    132   os.exceptions(std::ios_base::badbit | std::ios_base::failbit);
    133 
    134   const auto blocks = ((img.width + 3) / 4) * ((img.height + 3) / 4);
    135   if (dds)
    136   {
    137     DDS dds {
    138       /* .magic = */ {'D', 'D', 'S', ' '},
    139       /* .size = */ 124,
    140       /* .flags = */ 0x81007,
    141       /* .height = */ img.height,
    142       /* .width = */ img.width,
    143       /* .pitch_or_linear_size = */ blocks * 8,
    144       /* .depth = */ 0,
    145       /* .mip_map_count = */ 0,
    146       /* .reserved1 = */ {},
    147       /* .ddspf = */ {
    148         /* .size = */ sizeof(DDS_PIXELFORMAT),
    149         /* .flags = */ 4,
    150         /* .four_cc = */ {'D','X','T','1'},
    151         /* .rgb_bit_count = */ 0,
    152         /* .r_bit_mask = */ 0,
    153         /* .g_bit_mask = */ 0,
    154         /* .b_bit_mask = */ 0,
    155         /* .a_bit_mask = */ 0,
    156       },
    157       /* .caps = */ 0x1000,
    158       /* .caps2 = */ 0,
    159       /* .caps3 = */ 0,
    160       /* .caps4 = */ 0,
    161       /* .reserved2 = */ 0,
    162     };
    163     os.write(reinterpret_cast<const char*>(&dds), sizeof(dds));
    164   }
    165 
    166   alignas(32) float colors[16 * 4];
    167   for (std::size_t i = 0; i < 16; ++i) colors[4*i+3] = 1; // no alpha
    168 
    169   for (std::size_t i = 0; i < blocks; ++i)
    170   {
    171     if (i % 128 == 0)
    172       LOG << "DXT1 coding " << double(i) * (100. / blocks) << "%...    \r"
    173           << std::flush;
    174 
    175     size_t srcx, srcy;
    176     if (dds)
    177     {
    178       srcx = i % (img.width/4);
    179       srcy = i / (img.width/4);
    180     }
    181     else
    182     {
    183       assert(img.width == 1024 && img.height == 512);
    184       swizzle_1024_512(i, &srcx, &srcy);
    185     }
    186     srcx *= 4; srcy *= 4;
    187 
    188     for (std::size_t y = 0; y < 4; ++y)
    189       for (std::size_t x = 0; x < 4; ++x)
    190       {
    191         auto p = img.Get(srcx + x, srcy + y);
    192         colors[y*16 + x*4 + 0] = p[0] / 255.0f;
    193         colors[y*16 + x*4 + 1] = p[1] / 255.0f;
    194         colors[y*16 + x*4 + 2] = p[2] / 255.0f;
    195       }
    196 
    197     icbc::BlockBC1 out;
    198     icbc::compress_bc1(quality, colors, input_weights, color_weights, true, true, &out);
    199     os.write(reinterpret_cast<const char*>(&out), sizeof(out));
    200   }
    201 
    202   LOG << "DXT1 coding done      " << std::endl;
    203   os.close();
    204 }
    205 
    206 static bool scale = true, dds = false;
    207 static const char* output_file;
    208 static void Convert(const char* fname)
    209 {
    210   auto img = Image::Load(fname);
    211   Image img2;
    212 
    213   ImageView view = img;
    214   if (scale)
    215   {
    216     static constexpr double RATIO = 15. / 8.;
    217 
    218     std::uint32_t c_width = round(img.height * RATIO);
    219     std::uint32_t c_height = round(img.width * (1. / RATIO));
    220     if (c_width <= img.width)
    221       view = view.SubView((img.width - c_width) / 2, 0, c_width, img.height);
    222     else
    223     {
    224       assert(c_height <= img.height);
    225       view = view.SubView(0, (img.height - c_height) / 2, img.width, c_height);
    226     }
    227 
    228     img2 = Scale(view, OUT_W, OUT_H);
    229     view = img2;
    230   }
    231 
    232   if (view.width != OUT_W || view.height != OUT_H)
    233     throw std::runtime_error("Image is not " STR(OUT_W) "x" STR(OUT_H));
    234 
    235   if (output_file) Save(view, output_file, dds);
    236   else
    237   {
    238     std::string out = fname;
    239     if (auto p = out.find_last_of('.'); p != std::string::npos)
    240       out.erase(p, std::string::npos);
    241     out += dds ? ".dds" : ".spraw";
    242     Save(view, out.c_str(), dds);
    243   }
    244 }
    245 
    246 static void PrintHelp(const char* argv0)
    247 {
    248   std::cerr << "Usage: " << argv0 << " [options] input_files\n"
    249     " -h --help: print help\n"
    250     " -v --verbose: verbose\n"
    251     " -s --no-scale: do not scale input, it must be already " STR(OUT_W) "x" STR(OUT_H) "\n"
    252     " -f --filter=FILTER: specify scale filter (default, box, triangle, cubic-b-spline, catmull-rom, mitchell)\n"
    253     " -q --quality=QUALITY: specify DXT1 compress quality (0..9, 9 is the best)\n"
    254     " -o --out=FILE: specify output filename\n"
    255     ;
    256   exit(0);
    257 }
    258 
    259 static void ParseFilter(char* filter_str)
    260 {
    261   if (filter_str == nullptr)
    262     throw std::runtime_error("No filter specified");
    263 
    264   for (char* p = strchr(filter_str, '-'); p; p = strchr(p, '-')) *p = '_';
    265 
    266 #define CHECK(down, up) \
    267   if (!strcmp(filter_str, #down)) { filter = STBIR_FILTER_##up; return; }
    268   CHECK(default, DEFAULT);
    269   CHECK(box, BOX);
    270   CHECK(triangle, TRIANGLE);
    271   CHECK(cubic_b_spline, CUBICBSPLINE);
    272   CHECK(cubic_bspline, CUBICBSPLINE);
    273   CHECK(cubicbspline, CUBICBSPLINE);
    274   CHECK(catmull_rom, CATMULLROM);
    275   CHECK(catmullrom, CATMULLROM);
    276   CHECK(mitchell, MITCHELL);
    277 #undef CHECK
    278 
    279   throw std::runtime_error("Unknown filter");
    280 }
    281 
    282 static void ParseQuality(const char* q)
    283 {
    284   if (q == nullptr || std::strlen(q) != 1 ||
    285       *q < '0' || *q > '9')
    286     throw std::runtime_error("No/invalid quality specified");
    287   quality = icbc::Quality(*q - '0');
    288 }
    289 
    290 static void ParseOut(const char* o)
    291 {
    292   if (o == nullptr || !std::strlen(o))
    293     throw std::runtime_error("No output specified");
    294   output_file = o;
    295 }
    296 
    297 int main(int argc, char** argv)
    298 {
    299   int outi = 1;
    300   for (int i = 1; i < argc; ++i)
    301   {
    302     if (argv[i][0] == '-' && argv[i][1] != '-')
    303     {
    304       for (int j = 1; ; ++j)
    305         switch (argv[i][j])
    306         {
    307         case 0: goto end;
    308         case 'h': PrintHelp(argv[0]); break;
    309         case 'v': verbose = true; break;
    310         case 's': scale = false; break;
    311         case 'd': dds = true; break;
    312 #define PARSE_PARAM(fun)                        \
    313           if (argv[i][j+1]) fun(argv[i]+j+1);   \
    314           else fun(argv[++i]);                  \
    315           goto end
    316         case 'f': PARSE_PARAM(ParseFilter);
    317         case 'q': PARSE_PARAM(ParseQuality);
    318         case 'o': PARSE_PARAM(ParseOut);
    319 #undef PARSE_PARAM
    320         default:
    321           std::cerr << "Unknown argument -" << argv[i][j];
    322           return 1;
    323         }
    324     end:;
    325     }
    326     else if (!strcmp(argv[i], "--help"))
    327       PrintHelp(argv[0]);
    328     else if (!strcmp(argv[i], "--verbose"))
    329       verbose = true;
    330     else if (!strcmp(argv[i], "--no-scale"))
    331       scale = false;
    332     else if (!strcmp(argv[i], "--dds"))
    333       dds = true;
    334 #define PARSE_PARAM(arg, fun) \
    335     else if (!strncmp(argv[i], arg "=", std::strlen(arg "=")))  \
    336       fun(argv[i] + std::strlen(arg "="));                      \
    337     else if (!strcmp(argv[i], arg))                             \
    338       fun(argv[++i])
    339     PARSE_PARAM("--filter", ParseFilter);
    340     PARSE_PARAM("--quality", ParseQuality);
    341     PARSE_PARAM("--out", ParseOut);
    342 #undef PARSE_PARAM
    343     else
    344       argv[outi++] = argv[i];
    345   }
    346   argc = outi;
    347   if (argc <= 1) PrintHelp(argv[0]);
    348   if (output_file && argc != 2)
    349   {
    350     std::cerr << "--out only works with a single file" << std::endl;
    351     return 1;
    352   }
    353 
    354   icbc::init();
    355   for (int i = 1; i < argc; ++i)
    356     Convert(argv[i]);
    357   return 0;
    358 }