v0.14 intial release
This commit is contained in:
parent
77b0fb85bd
commit
9bd6c5be71
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
|||||||
[submodule "external/FastLZ"]
|
[submodule "external/FastLZ"]
|
||||||
path = external/FastLZ
|
path = external/FastLZ
|
||||||
url = https://github.com/ariya/FastLZ.git
|
url = https://github.com/ariya/FastLZ.git
|
||||||
[submodule "external/murmurhash"]
|
|
||||||
path = external/murmurhash
|
|
||||||
url = https://github.com/jwerle/murmurhash.c.git
|
|
||||||
|
@ -27,15 +27,14 @@ FetchContent_MakeAvailable(argparse)
|
|||||||
# yes... im using glob... dont judge me....
|
# yes... im using glob... dont judge me....
|
||||||
file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
||||||
file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS
|
file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS
|
||||||
"external/murmurhash/murmurhash.c"
|
|
||||||
"external/FastLZ/fastlz.c")
|
"external/FastLZ/fastlz.c")
|
||||||
|
|
||||||
set(PROJECT_INCLUDE
|
set(PROJECT_INCLUDE
|
||||||
"src"
|
"src"
|
||||||
${STB_INCLUDE_DIRS}
|
${STB_INCLUDE_DIRS}
|
||||||
${LIQ_INCLUDE_DIRS}
|
${LIQ_INCLUDE_DIRS}
|
||||||
|
${COLORM_INCLUDE_DIRS}
|
||||||
"external/ChernoTimer"
|
"external/ChernoTimer"
|
||||||
"external/murmurhash"
|
|
||||||
"external/FastLZ")
|
"external/FastLZ")
|
||||||
|
|
||||||
set(PROJECT_LIBRARY
|
set(PROJECT_LIBRARY
|
||||||
|
1
external/murmurhash
vendored
1
external/murmurhash
vendored
@ -1 +0,0 @@
|
|||||||
Subproject commit 10ba9c25abbcf8952b5f4abecf9bf4fc148e8e65
|
|
38
readme.txt
38
readme.txt
@ -11,22 +11,22 @@ the idea is to simply load image data without worrying about image metadata.
|
|||||||
the metadata contain basic stuff like width, height, format, etc...
|
the metadata contain basic stuff like width, height, format, etc...
|
||||||
|
|
||||||
color palette are embedded in the file.
|
color palette are embedded in the file.
|
||||||
a palette hash is provided. this can be used to comparing
|
|
||||||
whether another image uses the same palette
|
|
||||||
|
|
||||||
usage: sillyimage -i <input image> -o <output dir> [options]
|
usage: sillyimage -i <input image> -o <output dir> [options]
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-i, --input input files [required] [may be repeated]
|
-i, --input input files [required]
|
||||||
-o, --out output directory [required]
|
-o, --out output directory [required]
|
||||||
-h, --help shows help message and exits
|
-h, --help shows help message and exits
|
||||||
-v, --version prints version information and exits
|
-v, --version prints version information and exits
|
||||||
-f, --format texture format { rgb256, rgb16, indexed4, indexed16, indexed256, indexed32a3, indexed8a5 } [default: "rgb16"]
|
-f, --format texture format { rgb256, rgb16, indexed4, indexed16, indexed256, indexed32a8, indexed8a32 } [default: "rgb16"]
|
||||||
|
-pf, --palette-format palette format { rgb16, rgb256 } [default: "rgb16"]
|
||||||
|
-be, --big-endian enable big endian mode [default: false]
|
||||||
|
|
||||||
binary format:
|
binary format:
|
||||||
[string] sillyimg (0x676D69796C6C6973 in hex or 7452728928599042419 in decimal (uint64))
|
[string] sillyimg (0x676D69796C6C6973 in hex or 7452728928599042419 in decimal (uint64))
|
||||||
[uint8] version (current version is 13)
|
[int8] version (current version is 14)
|
||||||
[uint8] format
|
[int8] format
|
||||||
0 - RGB256
|
0 - RGB256
|
||||||
1 - RGB16
|
1 - RGB16
|
||||||
2 - INDEXED4
|
2 - INDEXED4
|
||||||
@ -35,13 +35,16 @@ binary format:
|
|||||||
5 - INDEXED32A3
|
5 - INDEXED32A3
|
||||||
6 - INDEXED8A5
|
6 - INDEXED8A5
|
||||||
7 - PALETTE16
|
7 - PALETTE16
|
||||||
[uint16] width
|
[int8] big endian mode
|
||||||
[uint16] height
|
[int8] palette format
|
||||||
[uint16] palette count
|
1 - rgb16 (2 bytes per palette)
|
||||||
[uint32] palette hash
|
2 - rgb256 (4 bytes per palette)
|
||||||
[uint32] compress size
|
[int16] width
|
||||||
[uint32] original size
|
[int16] height
|
||||||
[palette buffer]
|
[int16] palette count
|
||||||
|
[int32] original size
|
||||||
|
[int32] compress size
|
||||||
|
[palette buffer] (palette count * palette format size)
|
||||||
[image buffer]
|
[image buffer]
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
@ -55,8 +58,9 @@ TODO:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse (https://github.com/p-ranav/argparse)
|
argparse (https://github.com/p-ranav/argparse)
|
||||||
stb_image (https://github.com/nothings/stb)
|
stb_image (https://github.com/nothings/stb)
|
||||||
murmurhash.c (https://github.com/jwerle/murmurhash.c)
|
libimagequant (https://github.com/ImageOptim/libimagequant)
|
||||||
Timer.h from cherno (https://gist.github.com/TheCherno/b2c71c9291a4a1a29c889e76173c8d14)
|
Timer.h from cherno (https://gist.github.com/TheCherno/b2c71c9291a4a1a29c889e76173c8d14)
|
||||||
|
FastLZ (https://github.com/ariya/FastLZ)
|
||||||
|
|
||||||
license:
|
license:
|
||||||
GPL v3
|
GPL v3
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 sillysagiri
|
* 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 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.
|
||||||
*
|
*
|
||||||
@ -16,23 +16,16 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// wporkaround surpress clangd pragma pack warning
|
|
||||||
// https://github.com/clangd/clangd/issues/1167
|
|
||||||
static_assert(true);
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct Metadata {
|
struct Metadata {
|
||||||
uint64_t header = 0x676D69796C6C6973;
|
uint64_t header = 0x676D69796C6C6973;
|
||||||
uint8_t version = 13;
|
int8_t version = 14;
|
||||||
uint8_t format;
|
int8_t format;
|
||||||
uint16_t width;
|
int16_t width;
|
||||||
uint16_t height;
|
int16_t height;
|
||||||
uint16_t palette_count;
|
int16_t palette_count;
|
||||||
uint32_t palette_hash;
|
int32_t original_size;
|
||||||
uint32_t original_size;
|
int32_t compress_size;
|
||||||
uint32_t compress_size;
|
|
||||||
};
|
};
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
enum Format
|
enum Format
|
||||||
{
|
{
|
||||||
@ -71,12 +64,37 @@ typedef std::vector<uint8_t> ImageMapped;
|
|||||||
typedef std::vector<Color> Palette;
|
typedef std::vector<Color> Palette;
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
std::vector<std::string> path_input;
|
std::string path_input;
|
||||||
std::string path_output;
|
std::string path_output;
|
||||||
std::string format;
|
std::string format;
|
||||||
|
std::string palette_format;
|
||||||
|
// std::string compression;
|
||||||
|
bool enable_be = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Verify();
|
void WriteOutput(const Options &opt, const Metadata &meta, const Palette &palette, void *image_buffer);
|
||||||
void Convert();
|
ImageMapped Quantize(const Image &image, Palette &palette_output, int num_colors, bool dither, uint8_t format);
|
||||||
|
|
||||||
std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &palette_output, int num_colors, bool dither, uint8_t format);
|
static inline uint16_t to_be_int16(int16_t value) {
|
||||||
|
uint16_t uval = static_cast<uint16_t>(value);
|
||||||
|
return (uval >> 8) | (uval << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t to_be_int32(int32_t value) {
|
||||||
|
uint32_t uval = static_cast<uint32_t>(value);
|
||||||
|
return ((uval >> 24) & 0x000000FF) |
|
||||||
|
((uval >> 8) & 0x0000FF00) |
|
||||||
|
((uval << 8) & 0x00FF0000) |
|
||||||
|
((uval << 24) & 0xFF000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint16_t to_be_int16(uint16_t value) {
|
||||||
|
return (value >> 8) | (value << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t to_be_int32(uint32_t value) {
|
||||||
|
return ((value >> 24) & 0x000000FF) |
|
||||||
|
((value >> 8) & 0x0000FF00) |
|
||||||
|
((value << 8) & 0x00FF0000) |
|
||||||
|
((value << 24) & 0xFF000000);
|
||||||
|
}
|
253
src/main.cpp
253
src/main.cpp
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 sillysagiri
|
* 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 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.
|
||||||
*
|
*
|
||||||
@ -8,16 +8,9 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 <cassert>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <stdexcept>
|
#include <stdint.h>
|
||||||
#include <format>
|
|
||||||
#include <iostream>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
|
||||||
@ -25,22 +18,22 @@
|
|||||||
#include "header.hpp"
|
#include "header.hpp"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
#include "fastlz.h"
|
#include "fastlz.h"
|
||||||
#include "murmurhash.h"
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
srand(time(0));
|
srand(time(0));
|
||||||
argparse::ArgumentParser program("sillyimage", "1.3");
|
argparse::ArgumentParser program("sillyimage", "0.14");
|
||||||
Options opt;
|
Options opt;
|
||||||
|
|
||||||
// Register argument
|
// Register argument
|
||||||
{
|
{
|
||||||
program.add_argument("-i", "--input")
|
program.add_argument("-i", "--input")
|
||||||
.help("input files")
|
.help("input files")
|
||||||
.append()
|
.default_value<std::string>("")
|
||||||
.required()
|
.required()
|
||||||
|
.nargs(1)
|
||||||
.store_into(opt.path_input);
|
.store_into(opt.path_input);
|
||||||
|
|
||||||
program.add_argument("-o", "--out")
|
program.add_argument("-o", "--out")
|
||||||
@ -54,6 +47,26 @@ int main(int argc, char **argv)
|
|||||||
.choices("rgb256", "rgb16", "indexed4", "indexed16", "indexed256", "indexed32a8", "indexed8a32")
|
.choices("rgb256", "rgb16", "indexed4", "indexed16", "indexed256", "indexed32a8", "indexed8a32")
|
||||||
.nargs(1)
|
.nargs(1)
|
||||||
.store_into(opt.format);
|
.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
|
// Parse argument
|
||||||
@ -63,9 +76,8 @@ int main(int argc, char **argv)
|
|||||||
program.parse_args(argc, argv);
|
program.parse_args(argc, argv);
|
||||||
|
|
||||||
// input list
|
// input list
|
||||||
for (auto &i : opt.path_input)
|
if (opt.path_input.empty() || !fs::exists(opt.path_input))
|
||||||
if (i.empty() || !fs::exists(i))
|
throw std::runtime_error(std::format("file doesnt exsist: \"{}\"", opt.path_input));
|
||||||
throw std::runtime_error(std::format("file doesnt exsist: \"{}\"", i));
|
|
||||||
|
|
||||||
// output directory
|
// output directory
|
||||||
if (opt.path_output.empty() || !fs::is_directory(opt.path_output))
|
if (opt.path_output.empty() || !fs::is_directory(opt.path_output))
|
||||||
@ -84,59 +96,60 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
if (opt.format == "rgb256")
|
if (opt.format == "rgb256")
|
||||||
{
|
{
|
||||||
for(auto &input : opt.path_input)
|
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++)
|
||||||
{
|
{
|
||||||
Image img;
|
img255[i] = (static_cast<uint32_t>(img.data[i*4+3]) << 24) | // A
|
||||||
img.Load(input);
|
(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
|
||||||
|
|
||||||
Metadata meta;
|
if (opt.enable_be)
|
||||||
meta.format = Format::Format_RGB_256;
|
img255[i] = to_be_int32(img255[i]);
|
||||||
meta.width = img.width;
|
|
||||||
meta.height = img.height;
|
|
||||||
meta.palette_count = 0;
|
|
||||||
meta.palette_hash = 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);
|
|
||||||
|
|
||||||
auto output = fs::path(opt.path_output) / std::format("{}.sillyimg", fs::path(input).stem().string());
|
|
||||||
std::ofstream out(output, std::ios::binary);
|
|
||||||
out.write(reinterpret_cast<const char*>(&meta), sizeof(meta));
|
|
||||||
out.write(reinterpret_cast<const char*>(compress), meta.compress_size);
|
|
||||||
out.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
if (opt.format == "rgb16")
|
||||||
{
|
{
|
||||||
for(auto &input : opt.path_input)
|
Image img;
|
||||||
{
|
img.Load(opt.path_input);
|
||||||
Image img;
|
std::vector<uint16_t> img16(img.width*img.height);
|
||||||
img.Load(input);
|
|
||||||
std::vector<uint16_t> img16(img.width*img.height);
|
|
||||||
|
|
||||||
// convert into RGB16 color
|
// convert into RGB16 color
|
||||||
for (int i=0; i<img.width*img.height; i++)
|
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]);
|
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;
|
Metadata meta;
|
||||||
meta.format = Format::Format_RGB_16;
|
meta.format = Format::Format_RGB_16;
|
||||||
meta.width = img.width;
|
meta.width = img.width;
|
||||||
meta.height = img.height;
|
meta.height = img.height;
|
||||||
meta.palette_count = 0;
|
meta.palette_count = 0;
|
||||||
meta.palette_hash = 0;
|
meta.original_size = img16.size()*sizeof(uint16_t);
|
||||||
meta.original_size = img16.size()*sizeof(uint16_t);
|
|
||||||
|
|
||||||
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
||||||
meta.compress_size = fastlz_compress_level(2, img16.data(), meta.original_size, compress);
|
meta.compress_size = fastlz_compress_level(2, img16.data(), meta.original_size, compress);
|
||||||
|
|
||||||
auto output = fs::path(opt.path_output) / std::format("{}.sillyimg", fs::path(input).stem().string());
|
Palette palette(0);
|
||||||
std::ofstream out(output, std::ios::binary);
|
|
||||||
out.write(reinterpret_cast<const char*>(&meta), sizeof(meta));
|
WriteOutput(opt, meta, palette, compress);
|
||||||
out.write(reinterpret_cast<const char*>(compress), meta.compress_size);
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opt.format == "indexed4" ||
|
if (opt.format == "indexed4" ||
|
||||||
@ -175,41 +188,22 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Palette palette;
|
Palette palette;
|
||||||
std::vector<uint16_t> palette16;
|
Image image;
|
||||||
std::vector<Image> images(opt.path_input.size());
|
image.Load(opt.path_input);
|
||||||
|
|
||||||
|
ImageMapped remap = Quantize(image, palette, numcol, false, format);
|
||||||
|
|
||||||
for (int i=0; i<images.size(); i++)
|
Metadata meta;
|
||||||
images[i].Load(opt.path_input[i]);
|
meta.format = format;
|
||||||
|
meta.width = image.width;
|
||||||
|
meta.height = image.height;
|
||||||
|
meta.palette_count = palette.size();
|
||||||
|
meta.original_size = remap.size();
|
||||||
|
|
||||||
auto remap = Quantize(images, palette, numcol, false, format);
|
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
||||||
|
meta.compress_size = fastlz_compress_level(2, remap.data(), meta.original_size, compress);
|
||||||
palette16.resize(palette.size());
|
|
||||||
for (int i=1; i<palette.size(); i++)
|
|
||||||
palette16[i] = RGB255_RGB16(palette[i].r, palette[i].g, palette[i].b, palette[i].a);
|
|
||||||
|
|
||||||
uint32_t hash = murmurhash(reinterpret_cast<const char*>(palette16.data()), sizeof(uint16_t)*palette16.size(), 0);
|
WriteOutput(opt, meta, palette, compress);
|
||||||
|
|
||||||
for (int i=0; i<images.size(); i++)
|
|
||||||
{
|
|
||||||
Metadata meta;
|
|
||||||
meta.format = format;
|
|
||||||
meta.width = images[i].width;
|
|
||||||
meta.height = images[i].height;
|
|
||||||
meta.palette_count = palette16.size();
|
|
||||||
meta.palette_hash = hash;
|
|
||||||
meta.original_size = remap[i].size();
|
|
||||||
|
|
||||||
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
|
||||||
meta.compress_size = fastlz_compress_level(2, remap[i].data(), meta.original_size, compress);
|
|
||||||
|
|
||||||
auto output = fs::path(opt.path_output) / std::format("{}.sillyimg", fs::path(opt.path_input[i]).stem().string());
|
|
||||||
std::ofstream out(output, std::ios::binary);
|
|
||||||
|
|
||||||
out.write(reinterpret_cast<const char*>(&meta), sizeof(meta));
|
|
||||||
out.write(reinterpret_cast<const char*>(palette16.data()), sizeof(uint16_t)*palette.size());
|
|
||||||
out.write(reinterpret_cast<const char*>(compress), meta.compress_size);
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(const std::exception &e)
|
catch(const std::exception &e)
|
||||||
@ -236,8 +230,85 @@ void Image::Load(const std::filesystem::path &path)
|
|||||||
stbi_uc *ptr_img = stbi_load(path.c_str(), &width, &height, &comp, 4);
|
stbi_uc *ptr_img = stbi_load(path.c_str(), &width, &height, &comp, 4);
|
||||||
if (ptr_img == nullptr) throw std::runtime_error(stbi_failure_reason());
|
if (ptr_img == nullptr) throw std::runtime_error(stbi_failure_reason());
|
||||||
|
|
||||||
int size = width*height*comp;
|
int size = width*height*4;
|
||||||
data.assign(ptr_img, ptr_img+size);
|
data.reserve(size);
|
||||||
|
for (int i = 0; i < size; ++i)
|
||||||
|
data.push_back(ptr_img[i]);
|
||||||
|
|
||||||
stbi_image_free(ptr_img);
|
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();
|
||||||
}
|
}
|
@ -1,31 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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 <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include <libimagequant.h>
|
#include <libimagequant.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "header.hpp"
|
#include "header.hpp"
|
||||||
|
|
||||||
std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &palette_output, int num_colors, bool dither, uint8_t format)
|
ImageMapped Quantize(const Image &image, Palette &palette_output, int num_colors, bool dither, uint8_t format)
|
||||||
{
|
{
|
||||||
liq_attr *attr = liq_attr_create();
|
liq_attr *attr = liq_attr_create();
|
||||||
liq_histogram *hist = liq_histogram_create(attr);
|
|
||||||
|
|
||||||
liq_set_max_colors(attr, num_colors);
|
liq_set_max_colors(attr, num_colors);
|
||||||
|
liq_set_speed(attr, 1);
|
||||||
|
liq_set_quality(attr, 0, 100);
|
||||||
|
|
||||||
std::vector<liq_image*> liq_images;
|
liq_image *liq_img = liq_image_create_rgba(attr, image.data.data(), image.width, image.height, 0);
|
||||||
liq_images.reserve(images.size());
|
|
||||||
|
|
||||||
for (const Image &img : images)
|
|
||||||
{
|
|
||||||
liq_image *liq_img = liq_image_create_rgba(attr, img.data.data(), img.width, img.height, 0);
|
|
||||||
liq_histogram_add_image(hist, attr, liq_img);
|
|
||||||
liq_images.push_back(liq_img);
|
|
||||||
}
|
|
||||||
|
|
||||||
liq_result *result;
|
liq_result *result;
|
||||||
if (LIQ_OK != liq_histogram_quantize(hist, attr, &result))
|
if (LIQ_OK != liq_image_quantize(liq_img, attr, &result))
|
||||||
throw std::runtime_error("failed to quantize histogram");
|
throw std::runtime_error("failed to quantize histogram");
|
||||||
|
|
||||||
if (dither) liq_set_dithering_level(result, 1.0f);
|
if (dither) liq_set_dithering_level(result, 1.0f);
|
||||||
else liq_set_dithering_level(result, 0.0f);
|
else liq_set_dithering_level(result, 0.0f);
|
||||||
|
|
||||||
|
ImageMapped output(image.width*image.height);
|
||||||
|
|
||||||
|
if (LIQ_OK != liq_write_remapped_image(result, liq_img, output.data(), output.size()))
|
||||||
|
throw std::runtime_error("failed to remap image");
|
||||||
|
|
||||||
const liq_palette *pal = liq_get_palette(result);
|
const liq_palette *pal = liq_get_palette(result);
|
||||||
palette_output.resize(pal->count);
|
palette_output.resize(pal->count);
|
||||||
for (int i=0; i<pal->count; i++)
|
for (int i=0; i<pal->count; i++)
|
||||||
@ -36,38 +46,25 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
|||||||
palette_output[i].a = pal->entries[i].a;
|
palette_output[i].a = pal->entries[i].a;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ImageMapped> output(images.size());
|
liq_image_destroy(liq_img);
|
||||||
|
|
||||||
for (int i=0; i<images.size(); i++)
|
|
||||||
{
|
|
||||||
output[i].resize(images[i].width*images[i].height);
|
|
||||||
if (LIQ_OK != liq_write_remapped_image(result, liq_images[i], output[i].data(), output[i].size()))
|
|
||||||
throw std::runtime_error("failed to remap image");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &i : liq_images) liq_image_destroy(i);
|
|
||||||
liq_histogram_destroy(hist);
|
|
||||||
liq_attr_destroy(attr);
|
liq_attr_destroy(attr);
|
||||||
|
liq_result_destroy(result);
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
if (format == Format_INDEXED_4)
|
if (format == Format_INDEXED_4)
|
||||||
{
|
{
|
||||||
std::vector<ImageMapped> packed(output.size());
|
ImageMapped packed;
|
||||||
|
packed.reserve((output.size()+3) / 4);
|
||||||
|
|
||||||
for (int i=0; i<output.size(); i++)
|
for (int chunk_start=0; chunk_start<output.size(); chunk_start+=4) {
|
||||||
{
|
uint8_t current_byte = 0;
|
||||||
packed[i].reserve((output[i].size()+3) / 4);
|
|
||||||
|
|
||||||
for (int chunk_start=0; chunk_start<output[i].size(); chunk_start+=4) {
|
for (int offset=0; offset<4 && (chunk_start+offset)<output.size(); ++offset) {
|
||||||
uint8_t current_byte = 0;
|
current_byte |= (output[chunk_start + offset] & 0b11) << (offset * 2);
|
||||||
|
|
||||||
for (int offset=0; offset<4 && (chunk_start+offset)<output[i].size(); ++offset) {
|
|
||||||
current_byte |= (output[i][chunk_start + offset] & 0b11) << (offset * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
packed[i].push_back(current_byte);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packed.push_back(current_byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
return packed;
|
return packed;
|
||||||
@ -75,21 +72,17 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
|||||||
|
|
||||||
else if (format == Format_INDEXED_16)
|
else if (format == Format_INDEXED_16)
|
||||||
{
|
{
|
||||||
std::vector<ImageMapped> packed(output.size());
|
ImageMapped packed;
|
||||||
|
packed.reserve((output.size()+3) / 4);
|
||||||
|
|
||||||
for (int i=0; i<output.size(); i++)
|
for (int offset=0; offset<output.size(); offset+=2) {
|
||||||
{
|
uint8_t current_byte = 0;
|
||||||
packed[i].reserve((output[i].size()+3) / 4);
|
|
||||||
|
|
||||||
for (int offset=0; offset<output[i].size(); offset+=2) {
|
current_byte |= (output[offset] & 0b1111);
|
||||||
uint8_t current_byte = 0;
|
if (offset+1 <output.size())
|
||||||
|
current_byte |= (output[offset+1] & 0b1111) << 4;
|
||||||
|
|
||||||
current_byte |= (output[i][offset] & 0b1111);
|
packed.push_back(current_byte);
|
||||||
if (i+1 <output[i].size())
|
|
||||||
current_byte |= (output[i][offset + 1] & 0b1111) << 4;
|
|
||||||
|
|
||||||
packed[i].push_back(current_byte);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return packed;
|
return packed;
|
||||||
@ -97,19 +90,15 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
|||||||
|
|
||||||
else if (format == Format_INDEXED_8A32)
|
else if (format == Format_INDEXED_8A32)
|
||||||
{
|
{
|
||||||
std::vector<ImageMapped> packed(output.size());
|
ImageMapped packed;
|
||||||
|
packed.reserve((output.size()+3) / 4);
|
||||||
|
|
||||||
for (int i=0; i<output.size(); i++)
|
for (int offset=0; offset<output.size(); offset++) {
|
||||||
{
|
uint8_t current_byte = 0;
|
||||||
packed[i].reserve((output[i].size()+3) / 4);
|
|
||||||
|
|
||||||
for (int offset=0; offset<output[i].size(); offset++) {
|
uint8_t alpha = image.data[offset*4+3] >> 3;
|
||||||
uint8_t current_byte = 0;
|
current_byte |= (output[offset] & 0b111) | (alpha << 3);
|
||||||
|
packed.push_back(current_byte);
|
||||||
uint8_t alpha = images[i].data[offset*4+3] >> 3;
|
|
||||||
current_byte |= (output[i][offset] & 0b111) | (alpha << 3);
|
|
||||||
packed[i].push_back(current_byte);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return packed;
|
return packed;
|
||||||
@ -117,19 +106,15 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
|||||||
|
|
||||||
else if (format == Format_INDEXED_32A8)
|
else if (format == Format_INDEXED_32A8)
|
||||||
{
|
{
|
||||||
std::vector<ImageMapped> packed(output.size());
|
ImageMapped packed(output.size());
|
||||||
|
packed.reserve((output.size()+3) / 4);
|
||||||
|
|
||||||
for (int i=0; i<output.size(); i++)
|
for (int offset=0; offset<output.size(); offset++) {
|
||||||
{
|
uint8_t current_byte = 0;
|
||||||
packed[i].reserve((output[i].size()+3) / 4);
|
|
||||||
|
|
||||||
for (int offset=0; offset<output[i].size(); offset++) {
|
uint8_t alpha = image.data[offset*4+3] >> 5;
|
||||||
uint8_t current_byte = 0;
|
current_byte |= (output[offset] & 0b11111) | (alpha << 5);
|
||||||
|
packed.push_back(current_byte);
|
||||||
uint8_t alpha = images[i].data[offset*4+3] >> 5;
|
|
||||||
current_byte |= (output[i][offset] & 0b11111) | (alpha << 5);
|
|
||||||
packed[i].push_back(current_byte);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return packed;
|
return packed;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user