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"]
|
||||
path = external/FastLZ
|
||||
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....
|
||||
file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS "src/*.cpp")
|
||||
file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS
|
||||
"external/murmurhash/murmurhash.c"
|
||||
"external/FastLZ/fastlz.c")
|
||||
|
||||
set(PROJECT_INCLUDE
|
||||
"src"
|
||||
${STB_INCLUDE_DIRS}
|
||||
${LIQ_INCLUDE_DIRS}
|
||||
${COLORM_INCLUDE_DIRS}
|
||||
"external/ChernoTimer"
|
||||
"external/murmurhash"
|
||||
"external/FastLZ")
|
||||
|
||||
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...
|
||||
|
||||
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]
|
||||
|
||||
options:
|
||||
-i, --input input files [required] [may be repeated]
|
||||
-o, --out output directory [required]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
-f, --format texture format { rgb256, rgb16, indexed4, indexed16, indexed256, indexed32a3, indexed8a5 } [default: "rgb16"]
|
||||
-i, --input input files [required]
|
||||
-o, --out output directory [required]
|
||||
-h, --help shows help message and exits
|
||||
-v, --version prints version information and exits
|
||||
-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:
|
||||
[string] sillyimg (0x676D69796C6C6973 in hex or 7452728928599042419 in decimal (uint64))
|
||||
[uint8] version (current version is 13)
|
||||
[uint8] format
|
||||
[int8] version (current version is 14)
|
||||
[int8] format
|
||||
0 - RGB256
|
||||
1 - RGB16
|
||||
2 - INDEXED4
|
||||
@ -35,13 +35,16 @@ binary format:
|
||||
5 - INDEXED32A3
|
||||
6 - INDEXED8A5
|
||||
7 - PALETTE16
|
||||
[uint16] width
|
||||
[uint16] height
|
||||
[uint16] palette count
|
||||
[uint32] palette hash
|
||||
[uint32] compress size
|
||||
[uint32] original size
|
||||
[palette buffer]
|
||||
[int8] big endian mode
|
||||
[int8] palette format
|
||||
1 - rgb16 (2 bytes per palette)
|
||||
2 - rgb256 (4 bytes per palette)
|
||||
[int16] width
|
||||
[int16] height
|
||||
[int16] palette count
|
||||
[int32] original size
|
||||
[int32] compress size
|
||||
[palette buffer] (palette count * palette format size)
|
||||
[image buffer]
|
||||
|
||||
TODO:
|
||||
@ -55,8 +58,9 @@ TODO:
|
||||
dependencies:
|
||||
argparse (https://github.com/p-ranav/argparse)
|
||||
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)
|
||||
FastLZ (https://github.com/ariya/FastLZ)
|
||||
|
||||
license:
|
||||
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.
|
||||
*
|
||||
@ -16,23 +16,16 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// wporkaround surpress clangd pragma pack warning
|
||||
// https://github.com/clangd/clangd/issues/1167
|
||||
static_assert(true);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct Metadata {
|
||||
uint64_t header = 0x676D69796C6C6973;
|
||||
uint8_t version = 13;
|
||||
uint8_t format;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint16_t palette_count;
|
||||
uint32_t palette_hash;
|
||||
uint32_t original_size;
|
||||
uint32_t compress_size;
|
||||
int8_t version = 14;
|
||||
int8_t format;
|
||||
int16_t width;
|
||||
int16_t height;
|
||||
int16_t palette_count;
|
||||
int32_t original_size;
|
||||
int32_t compress_size;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
enum Format
|
||||
{
|
||||
@ -71,12 +64,37 @@ typedef std::vector<uint8_t> ImageMapped;
|
||||
typedef std::vector<Color> Palette;
|
||||
|
||||
struct Options {
|
||||
std::vector<std::string> path_input;
|
||||
std::string path_input;
|
||||
std::string path_output;
|
||||
std::string format;
|
||||
std::string palette_format;
|
||||
// std::string compression;
|
||||
bool enable_be = false;
|
||||
};
|
||||
|
||||
void Verify();
|
||||
void Convert();
|
||||
void WriteOutput(const Options &opt, const Metadata &meta, const Palette &palette, void *image_buffer);
|
||||
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);
|
||||
}
|
251
src/main.cpp
251
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.
|
||||
*
|
||||
@ -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/>.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <stdint.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
|
||||
@ -25,22 +18,22 @@
|
||||
#include "header.hpp"
|
||||
#include "Timer.h"
|
||||
#include "fastlz.h"
|
||||
#include "murmurhash.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
srand(time(0));
|
||||
argparse::ArgumentParser program("sillyimage", "1.3");
|
||||
argparse::ArgumentParser program("sillyimage", "0.14");
|
||||
Options opt;
|
||||
|
||||
// Register argument
|
||||
{
|
||||
program.add_argument("-i", "--input")
|
||||
.help("input files")
|
||||
.append()
|
||||
.default_value<std::string>("")
|
||||
.required()
|
||||
.nargs(1)
|
||||
.store_into(opt.path_input);
|
||||
|
||||
program.add_argument("-o", "--out")
|
||||
@ -54,6 +47,26 @@ int main(int argc, char **argv)
|
||||
.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
|
||||
@ -63,9 +76,8 @@ int main(int argc, char **argv)
|
||||
program.parse_args(argc, argv);
|
||||
|
||||
// input list
|
||||
for (auto &i : opt.path_input)
|
||||
if (i.empty() || !fs::exists(i))
|
||||
throw std::runtime_error(std::format("file doesnt exsist: \"{}\"", i));
|
||||
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))
|
||||
@ -84,59 +96,60 @@ int main(int argc, char **argv)
|
||||
|
||||
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;
|
||||
img.Load(input);
|
||||
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
|
||||
|
||||
Metadata meta;
|
||||
meta.format = Format::Format_RGB_256;
|
||||
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();
|
||||
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")
|
||||
{
|
||||
for(auto &input : opt.path_input)
|
||||
{
|
||||
Image img;
|
||||
img.Load(input);
|
||||
std::vector<uint16_t> img16(img.width*img.height);
|
||||
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]);
|
||||
// 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.palette_hash = 0;
|
||||
meta.original_size = img16.size()*sizeof(uint16_t);
|
||||
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);
|
||||
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
||||
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());
|
||||
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();
|
||||
}
|
||||
Palette palette(0);
|
||||
|
||||
WriteOutput(opt, meta, palette, compress);
|
||||
}
|
||||
|
||||
if (opt.format == "indexed4" ||
|
||||
@ -175,41 +188,22 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
Palette palette;
|
||||
std::vector<uint16_t> palette16;
|
||||
std::vector<Image> images(opt.path_input.size());
|
||||
Image image;
|
||||
image.Load(opt.path_input);
|
||||
|
||||
for (int i=0; i<images.size(); i++)
|
||||
images[i].Load(opt.path_input[i]);
|
||||
ImageMapped remap = Quantize(image, palette, numcol, false, format);
|
||||
|
||||
auto remap = Quantize(images, 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();
|
||||
|
||||
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);
|
||||
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
||||
meta.compress_size = fastlz_compress_level(2, remap.data(), meta.original_size, compress);
|
||||
|
||||
uint32_t hash = murmurhash(reinterpret_cast<const char*>(palette16.data()), sizeof(uint16_t)*palette16.size(), 0);
|
||||
|
||||
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();
|
||||
}
|
||||
WriteOutput(opt, meta, palette, compress);
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (ptr_img == nullptr) throw std::runtime_error(stbi_failure_reason());
|
||||
|
||||
int size = width*height*comp;
|
||||
data.assign(ptr_img, ptr_img+size);
|
||||
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();
|
||||
}
|
@ -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 <stdexcept>
|
||||
#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_histogram *hist = liq_histogram_create(attr);
|
||||
|
||||
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_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_image *liq_img = liq_image_create_rgba(attr, image.data.data(), image.width, image.height, 0);
|
||||
|
||||
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");
|
||||
|
||||
if (dither) liq_set_dithering_level(result, 1.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);
|
||||
palette_output.resize(pal->count);
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<ImageMapped> output(images.size());
|
||||
|
||||
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_image_destroy(liq_img);
|
||||
liq_attr_destroy(attr);
|
||||
liq_result_destroy(result);
|
||||
|
||||
// ---
|
||||
|
||||
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++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
for (int chunk_start=0; chunk_start<output.size(); chunk_start+=4) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
for (int chunk_start=0; chunk_start<output[i].size(); chunk_start+=4) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
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);
|
||||
for (int offset=0; offset<4 && (chunk_start+offset)<output.size(); ++offset) {
|
||||
current_byte |= (output[chunk_start + offset] & 0b11) << (offset * 2);
|
||||
}
|
||||
|
||||
packed.push_back(current_byte);
|
||||
}
|
||||
|
||||
return packed;
|
||||
@ -75,21 +72,17 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
||||
|
||||
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++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
for (int offset=0; offset<output.size(); offset+=2) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
for (int offset=0; offset<output[i].size(); offset+=2) {
|
||||
uint8_t current_byte = 0;
|
||||
current_byte |= (output[offset] & 0b1111);
|
||||
if (offset+1 <output.size())
|
||||
current_byte |= (output[offset+1] & 0b1111) << 4;
|
||||
|
||||
current_byte |= (output[i][offset] & 0b1111);
|
||||
if (i+1 <output[i].size())
|
||||
current_byte |= (output[i][offset + 1] & 0b1111) << 4;
|
||||
|
||||
packed[i].push_back(current_byte);
|
||||
}
|
||||
packed.push_back(current_byte);
|
||||
}
|
||||
|
||||
return packed;
|
||||
@ -97,19 +90,15 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
||||
|
||||
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++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
for (int offset=0; offset<output.size(); offset++) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
for (int offset=0; offset<output[i].size(); offset++) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
uint8_t alpha = images[i].data[offset*4+3] >> 3;
|
||||
current_byte |= (output[i][offset] & 0b111) | (alpha << 3);
|
||||
packed[i].push_back(current_byte);
|
||||
}
|
||||
uint8_t alpha = image.data[offset*4+3] >> 3;
|
||||
current_byte |= (output[offset] & 0b111) | (alpha << 3);
|
||||
packed.push_back(current_byte);
|
||||
}
|
||||
|
||||
return packed;
|
||||
@ -117,19 +106,15 @@ std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &pal
|
||||
|
||||
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++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
for (int offset=0; offset<output.size(); offset++) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
for (int offset=0; offset<output[i].size(); offset++) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
uint8_t alpha = images[i].data[offset*4+3] >> 5;
|
||||
current_byte |= (output[i][offset] & 0b11111) | (alpha << 5);
|
||||
packed[i].push_back(current_byte);
|
||||
}
|
||||
uint8_t alpha = image.data[offset*4+3] >> 5;
|
||||
current_byte |= (output[offset] & 0b11111) | (alpha << 5);
|
||||
packed.push_back(current_byte);
|
||||
}
|
||||
|
||||
return packed;
|
||||
|
Loading…
x
Reference in New Issue
Block a user