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++
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;
|
|
}
|