sillyimage/src/main.cpp
2025-06-16 00:19:27 +07:00

314 lines
9.2 KiB
C++

/*
* Copyright (C) 2025 sillysagiri
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <cstdint>
#include <fstream>
#include <stdint.h>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <argparse/argparse.hpp>
#include "header.hpp"
#include "Timer.h"
#include "fastlz.h"
namespace fs = std::filesystem;
int main(int argc, char **argv)
{
srand(time(0));
argparse::ArgumentParser program("sillyimage", "0.14");
Options opt;
// Register argument
{
program.add_argument("-i", "--input")
.help("input files")
.default_value<std::string>("")
.required()
.nargs(1)
.store_into(opt.path_input);
program.add_argument("-o", "--out")
.help("output directory")
.required()
.store_into(opt.path_output);
program.add_argument("-f", "--format")
.help("texture format { rgb256, rgb16, indexed4, indexed16, indexed256, indexed32a8, indexed8a32 }")
.default_value<std::string>("rgb16")
.choices("rgb256", "rgb16", "indexed4", "indexed16", "indexed256", "indexed32a8", "indexed8a32")
.nargs(1)
.store_into(opt.format);
program.add_argument("-pf", "--palette-format")
.help("palette format { rgb16, rgb256 }")
.default_value<std::string>("rgb16")
.choices("rgb16", "rgb256")
.nargs(1)
.store_into(opt.palette_format);
// program.add_argument("-c", "--compression")
// .help("compression { none, fastlz }")
// .default_value<std::string>("fastlz")
// .choices("none", "fastlz")
// .nargs(1)
// .store_into(opt.compression);
program.add_argument("-be", "--big-endian")
.help("enable big endian mode")
.default_value<bool>(false)
.nargs(1)
.store_into(opt.enable_be);
}
// Parse argument
{
try
{
program.parse_args(argc, argv);
// input list
if (opt.path_input.empty() || !fs::exists(opt.path_input))
throw std::runtime_error(std::format("file doesnt exsist: \"{}\"", opt.path_input));
// output directory
if (opt.path_output.empty() || !fs::is_directory(opt.path_output))
throw std::runtime_error(std::format("invalid output directory: \"{}\"", opt.path_output));
}
catch(const std::exception &e)
{
std::cerr << std::format("Error: {}\n\n", e.what()) << program << "\n";
return 1;
}
}
// Convert
try {
if (opt.format == "rgb256")
{
Image img;
img.Load(opt.path_input);
uint32_t img255_size = img.width*img.height;
uint32_t img255[img255_size];
for (int i=0; i<img255_size; i++)
{
img255[i] = (static_cast<uint32_t>(img.data[i*4+3]) << 24) | // A
(static_cast<uint32_t>(img.data[i*4+0]) << 16) | // R
(static_cast<uint32_t>(img.data[i*4+1]) << 8) | // G
static_cast<uint32_t>(img.data[i*4+2]); // B
if (opt.enable_be)
img255[i] = to_be_int32(img255[i]);
}
Metadata meta;
meta.format = Format::Format_RGB_256;
meta.width = img.width;
meta.height = img.height;
meta.palette_count = 0;
meta.original_size = img.data.size();
uint8_t compress[uint64_t(meta.original_size*1.5)];
meta.compress_size = fastlz_compress_level(1, img.data.data(), meta.original_size, compress);
Palette palette(0);
WriteOutput(opt, meta, palette, compress);
}
if (opt.format == "rgb16")
{
Image img;
img.Load(opt.path_input);
std::vector<uint16_t> img16(img.width*img.height);
// convert into RGB16 color
for (int i=0; i<img.width*img.height; i++)
img16[i] = RGB255_RGB16(img.data[i*4], img.data[i*4+1], img.data[i*4+2], img.data[i*4+3]);
Metadata meta;
meta.format = Format::Format_RGB_16;
meta.width = img.width;
meta.height = img.height;
meta.palette_count = 0;
meta.original_size = img16.size()*sizeof(uint16_t);
uint8_t compress[uint64_t(meta.original_size*1.5)];
meta.compress_size = fastlz_compress_level(2, img16.data(), meta.original_size, compress);
Palette palette(0);
WriteOutput(opt, meta, palette, compress);
}
if (opt.format == "indexed4" ||
opt.format == "indexed16" ||
opt.format == "indexed256" ||
opt.format == "indexed32a8" ||
opt.format == "indexed8a32")
{
uint16_t numcol;
uint8_t format;
if (opt.format == "indexed4")
{
numcol = 4;
format = Format_INDEXED_4;
}
else if (opt.format == "indexed16")
{
numcol = 16;
format = Format_INDEXED_16;
}
else if (opt.format == "indexed256")
{
numcol = 256;
format = Format_INDEXED_256;
}
else if (opt.format == "indexed32a8")
{
numcol = 32;
format = Format_INDEXED_32A8;
}
else if (opt.format == "indexed8a32")
{
numcol = 8;
format = Format_INDEXED_8A32;
}
Palette palette;
Image image;
image.Load(opt.path_input);
ImageMapped remap = Quantize(image, palette, numcol, false, format);
Metadata meta;
meta.format = format;
meta.width = image.width;
meta.height = image.height;
meta.palette_count = palette.size();
meta.original_size = remap.size();
uint8_t compress[uint64_t(meta.original_size*1.5)];
meta.compress_size = fastlz_compress_level(2, remap.data(), meta.original_size, compress);
WriteOutput(opt, meta, palette, compress);
}
}
catch(const std::exception &e)
{
std::cerr << std::format("Exception caught: {}\n", e.what());
return 1;
}
catch (...)
{
std::cerr << std::format("An unknown exception occurred\n");
return 1;
}
std::cout << "done\n";
return 0;
}
// // ----
void Image::Load(const std::filesystem::path &path)
{
int comp;
stbi_uc *ptr_img = stbi_load(path.c_str(), &width, &height, &comp, 4);
if (ptr_img == nullptr) throw std::runtime_error(stbi_failure_reason());
int size = width*height*4;
data.reserve(size);
for (int i = 0; i < size; ++i)
data.push_back(ptr_img[i]);
stbi_image_free(ptr_img);
}
void WriteOutput(const Options &opt, const Metadata &meta, const Palette &palette, void *image_buffer)
{
std::filesystem::path input_file(opt.path_input);
std::filesystem::path output_dir(opt.path_output);
std::string basename = input_file.stem();
std::string output_file = output_dir / std::format("{}.sillyimg", basename);
std::ofstream out(output_file, std::ios::binary);
out.write(reinterpret_cast<const char*>(&meta.header), sizeof(uint64_t));
out.write(reinterpret_cast<const char*>(&meta.version), sizeof(int8_t));
out.write(reinterpret_cast<const char*>(&meta.format), sizeof(int8_t));
int8_t isBE = opt.enable_be;
int8_t palette_format;
if (opt.palette_format == "rgb16") palette_format = 1;
if (opt.palette_format == "rgb256") palette_format = 2;
out.write(reinterpret_cast<const char*>(&isBE), sizeof(int8_t));
out.write(reinterpret_cast<const char*>(&palette_format), sizeof(int8_t));
if (!opt.enable_be)
{
out.write(reinterpret_cast<const char*>(&meta.width), sizeof(int16_t));
out.write(reinterpret_cast<const char*>(&meta.height), sizeof(int16_t));
out.write(reinterpret_cast<const char*>(&meta.palette_count), sizeof(int16_t));
out.write(reinterpret_cast<const char*>(&meta.original_size), sizeof(int32_t));
out.write(reinterpret_cast<const char*>(&meta.compress_size), sizeof(int32_t));
}
else
{
uint16_t be_width = to_be_int16(meta.width);
uint16_t be_height = to_be_int16(meta.height);
uint16_t be_palette_count = to_be_int16(meta.palette_count);
uint32_t be_original_size = to_be_int32(meta.original_size);
uint32_t be_compress_size = to_be_int32(meta.compress_size);
out.write(reinterpret_cast<const char*>(&be_width), sizeof(uint16_t));
out.write(reinterpret_cast<const char*>(&be_height), sizeof(uint16_t));
out.write(reinterpret_cast<const char*>(&be_palette_count), sizeof(uint16_t));
out.write(reinterpret_cast<const char*>(&be_original_size), sizeof(uint32_t));
out.write(reinterpret_cast<const char*>(&be_compress_size), sizeof(uint32_t));
}
if (!palette.empty())
{
if (palette_format == 1)
{
uint16_t pal16[meta.palette_count];
for (int i=0; i<meta.palette_count; i++)
pal16[i] = RGB255_RGB16(palette[i].r, palette[i].g, palette[i].b, palette[i].a);
out.write(reinterpret_cast<const char*>(pal16), sizeof(uint16_t)*meta.palette_count);
}
else if (palette_format == 2)
{
uint32_t pal256[meta.palette_count];
for (int i=0; i<meta.palette_count; i++)
{
pal256[i] = (static_cast<uint32_t>(palette[i].a) << 24) | // A
(static_cast<uint32_t>(palette[i].r) << 16) | // R
(static_cast<uint32_t>(palette[i].g) << 8) | // G
static_cast<uint32_t>(palette[i].b); // B
if (opt.enable_be) pal256[i] = to_be_int32(pal256[i]);
}
out.write(reinterpret_cast<const char*>(pal256), sizeof(uint32_t)*meta.palette_count);
}
}
out.write(reinterpret_cast<const char*>(image_buffer), meta.compress_size);
out.close();
}