You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
10 KiB
C++

#include "common.h"
#include <cstdint>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <stdexcept>
#define ICBC_IMPLEMENTATION
#include "icbc.h"
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#include "stb_image.h"
static void progress_report(float p);
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_STATIC
#define STBIR_PROGRESS_REPORT(val) progress_report(val)
#include "stb_image_resize.h"
#define OUT_W 1024
#define OUT_H 512
#define STR(x) STR_(x)
#define STR_(x) #x
static bool verbose = false;
#define LOG verbose && std::clog
static void progress_report(float p)
{
if (p < 1)
LOG << "Scaling " << std::setprecision(3) << p*100 << "% done... \r"
<< std::flush;
else
LOG << "Scaling done " << std::endl;
}
namespace
{
struct FreeDeleter
{
void operator()(void* ptr) noexcept { free(ptr); }
};
struct Image
{
std::uint32_t width, height;
using Ptr = std::unique_ptr<unsigned char[], FreeDeleter>;
Ptr buf;
static constexpr std::uint32_t CHANNELS = 3;
static Image Load(const char* fname)
{
int x, y, n;
auto img = stbi_load(fname, &x, &y, &n, 3);
if (!img)
{
std::cerr << stbi_failure_reason() << std::endl;
throw std::runtime_error("Image load failed");
}
LOG << "Loaded image " << x << "x" << y << std::endl;
// max is 0x0fffffff to avoid overflows
assert(x >= 0 && x <= 0x0fffffff);
assert(y >= 0 && y <= 0x0fffffff);
return Image{std::uint32_t(x), std::uint32_t(y), Ptr{img}};
}
};
struct ImageView
{
std::uint32_t width, height, stride;
unsigned char* buf;
ImageView(const Image& img) noexcept
: width(img.width), height(img.height), stride(img.width * Image::CHANNELS),
buf{img.buf.get()} {}
ImageView(std::uint32_t width, std::uint32_t height, std::uint32_t stride,
unsigned char* buf) noexcept
: width{width}, height{height}, stride{stride}, buf{buf} {}
ImageView SubView(
std::uint32_t x, std::uint32_t y, std::uint32_t w, std::uint32_t h)
const noexcept
{
assert(x < width && w <= width && x+w <= width);
assert(y < height && h <= height && y+h <= height);
return { w, h, stride, buf + y*stride + x*Image::CHANNELS };
}
unsigned char* Get(std::uint32_t x, std::uint32_t y) const noexcept
{ return buf + y*stride + x*Image::CHANNELS;}
};
}
static auto filter = STBIR_FILTER_DEFAULT;
static Image Scale(ImageView view, std::uint32_t width, std::uint32_t height)
{
Image::Ptr buf{static_cast<unsigned char*>(
malloc(std::size_t(width) * height * Image::CHANNELS))};
if (!buf) throw std::bad_alloc();
if (!stbir_resize_uint8_generic(
view.buf, view.width, view.height, view.stride,
buf.get(), width, height, width * Image::CHANNELS, Image::CHANNELS,
STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP, filter,
STBIR_COLORSPACE_LINEAR, nullptr))
throw std::runtime_error("Resize failed?");
return Image{ width, height, std::move(buf) };
}
static icbc::Quality quality = icbc::Quality::Quality_Max;
static void Save(ImageView img, const char* fname, bool dds)
{
// std::cout << "P6 " << img.width << " " << img.height << " 255\n";
// for (std::uint32_t y = 0; y < img.height; ++y)
// std::cout.write(reinterpret_cast<const char*>(img.Get(0, y)), Image::CHANNELS*img.width);
// return;
assert(img.width % 4 == 0 && img.height % 4 == 0);
static constexpr const float input_weights[16] =
{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };
static constexpr const float color_weights[3] = { 1,1,1 };
std::ofstream os{fname};
if (!os) throw std::runtime_error("Can't open output");
os.exceptions(std::ios_base::badbit | std::ios_base::failbit);
const auto blocks = ((img.width + 3) / 4) * ((img.height + 3) / 4);
if (dds)
{
DDS dds {
/* .magic = */ {'D', 'D', 'S', ' '},
/* .size = */ 124,
/* .flags = */ 0x81007,
/* .height = */ img.height,
/* .width = */ img.width,
/* .pitch_or_linear_size = */ blocks * 8,
/* .depth = */ 0,
/* .mip_map_count = */ 0,
/* .reserved1 = */ {},
/* .ddspf = */ {
/* .size = */ sizeof(DDS_PIXELFORMAT),
/* .flags = */ 4,
/* .four_cc = */ {'D','X','T','1'},
/* .rgb_bit_count = */ 0,
/* .r_bit_mask = */ 0,
/* .g_bit_mask = */ 0,
/* .b_bit_mask = */ 0,
/* .a_bit_mask = */ 0,
},
/* .caps = */ 0x1000,
/* .caps2 = */ 0,
/* .caps3 = */ 0,
/* .caps4 = */ 0,
/* .reserved2 = */ 0,
};
os.write(reinterpret_cast<const char*>(&dds), sizeof(dds));
}
alignas(32) float colors[16 * 4];
for (std::size_t i = 0; i < 16; ++i) colors[4*i+3] = 1; // no alpha
for (std::size_t i = 0; i < blocks; ++i)
{
if (i % 128 == 0)
LOG << "DXT1 coding " << double(i) * (100. / blocks) << "%... \r"
<< std::flush;
size_t srcx, srcy;
if (dds)
{
srcx = i % (img.width/4);
srcy = i / (img.width/4);
}
else
{
assert(img.width == 1024 && img.height == 512);
swizzle_1024_512(i, &srcx, &srcy);
}
srcx *= 4; srcy *= 4;
for (std::size_t y = 0; y < 4; ++y)
for (std::size_t x = 0; x < 4; ++x)
{
auto p = img.Get(srcx + x, srcy + y);
colors[y*16 + x*4 + 0] = p[0] / 255.0f;
colors[y*16 + x*4 + 1] = p[1] / 255.0f;
colors[y*16 + x*4 + 2] = p[2] / 255.0f;
}
icbc::BlockBC1 out;
icbc::compress_bc1(quality, colors, input_weights, color_weights, true, true, &out);
os.write(reinterpret_cast<const char*>(&out), sizeof(out));
}
LOG << "DXT1 coding done " << std::endl;
os.close();
}
static bool scale = true, dds = false;
static const char* output_file;
static void Convert(const char* fname)
{
auto img = Image::Load(fname);
Image img2;
ImageView view = img;
if (scale)
{
static constexpr double RATIO = 15. / 8.;
std::uint32_t c_width = round(img.height * RATIO);
std::uint32_t c_height = round(img.width * (1. / RATIO));
if (c_width <= img.width)
view = view.SubView((img.width - c_width) / 2, 0, c_width, img.height);
else
{
assert(c_height <= img.height);
view = view.SubView(0, (img.height - c_height) / 2, img.width, c_height);
}
img2 = Scale(view, OUT_W, OUT_H);
view = img2;
}
if (view.width != OUT_W || view.height != OUT_H)
throw std::runtime_error("Image is not " STR(OUT_W) "x" STR(OUT_H));
if (output_file) Save(view, output_file, dds);
else
{
std::string out = fname;
if (auto p = out.find_last_of('.'); p != std::string::npos)
out.erase(p, std::string::npos);
out += dds ? ".dds" : ".spraw";
Save(view, out.c_str(), dds);
}
}
static void PrintHelp(const char* argv0)
{
std::cerr << "Usage: " << argv0 << " [options] input_files\n"
" -h --help: print help\n"
" -v --verbose: verbose\n"
" -s --no-scale: do not scale input, it must be already " STR(OUT_W) "x" STR(OUT_H) "\n"
" -f --filter=FILTER: specify scale filter (default, box, triangle, cubic-b-spline, catmull-rom, mitchell)\n"
" -q --quality=QUALITY: specify DXT1 compress quality (0..9, 9 is the best)\n"
" -o --out=FILE: specify output filename\n"
;
exit(0);
}
static void ParseFilter(char* filter_str)
{
if (filter_str == nullptr)
throw std::runtime_error("No filter specified");
for (char* p = strchr(filter_str, '-'); p; p = strchr(p, '-')) *p = '_';
#define CHECK(down, up) \
if (!strcmp(filter_str, #down)) { filter = STBIR_FILTER_##up; return; }
CHECK(default, DEFAULT);
CHECK(box, BOX);
CHECK(triangle, TRIANGLE);
CHECK(cubic_b_spline, CUBICBSPLINE);
CHECK(cubic_bspline, CUBICBSPLINE);
CHECK(cubicbspline, CUBICBSPLINE);
CHECK(catmull_rom, CATMULLROM);
CHECK(catmullrom, CATMULLROM);
CHECK(mitchell, MITCHELL);
#undef CHECK
throw std::runtime_error("Unknown filter");
}
static void ParseQuality(const char* q)
{
if (q == nullptr || std::strlen(q) != 1 ||
*q < '0' || *q > '9')
throw std::runtime_error("No/invalid quality specified");
quality = icbc::Quality(*q - '0');
}
static void ParseOut(const char* o)
{
if (o == nullptr || !std::strlen(o))
throw std::runtime_error("No output specified");
output_file = o;
}
int main(int argc, char** argv)
{
int outi = 1;
for (int i = 1; i < argc; ++i)
{
if (argv[i][0] == '-' && argv[i][1] != '-')
{
for (int j = 1; ; ++j)
switch (argv[i][j])
{
case 0: goto end;
case 'h': PrintHelp(argv[0]); break;
case 'v': verbose = true; break;
case 's': scale = false; break;
case 'd': dds = true; break;
#define PARSE_PARAM(fun) \
if (argv[i][j+1]) fun(argv[i]+j+1); \
else fun(argv[++i]); \
goto end
case 'f': PARSE_PARAM(ParseFilter);
case 'q': PARSE_PARAM(ParseQuality);
case 'o': PARSE_PARAM(ParseOut);
#undef PARSE_PARAM
default:
std::cerr << "Unknown argument -" << argv[i][j];
return 1;
}
end:;
}
else if (!strcmp(argv[i], "--help"))
PrintHelp(argv[0]);
else if (!strcmp(argv[i], "--verbose"))
verbose = true;
else if (!strcmp(argv[i], "--no-scale"))
scale = false;
else if (!strcmp(argv[i], "--dds"))
dds = true;
#define PARSE_PARAM(arg, fun) \
else if (!strncmp(argv[i], arg "=", std::strlen(arg "="))) \
fun(argv[i] + std::strlen(arg "=")); \
else if (!strcmp(argv[i], arg)) \
fun(argv[++i])
PARSE_PARAM("--filter", ParseFilter);
PARSE_PARAM("--quality", ParseQuality);
PARSE_PARAM("--out", ParseOut);
#undef PARSE_PARAM
else
argv[outi++] = argv[i];
}
argc = outi;
if (argc <= 1) PrintHelp(argv[0]);
if (output_file && argc != 2)
{
std::cerr << "--out only works with a single file" << std::endl;
return 1;
}
icbc::init();
for (int i = 1; i < argc; ++i)
Convert(argv[i]);
return 0;
}