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 }