back using libimagequant
This commit is contained in:
parent
82cc0b60b7
commit
77b0fb85bd
@ -13,6 +13,7 @@ project(${PROJECT_NAME} VERSION ${PROJECT_VERSION})
|
||||
list(APPEND CMAKE_MODULE_PATH "/opt/cmake")
|
||||
|
||||
find_package(STB)
|
||||
find_package(LIQ)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
@ -51,3 +52,5 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_INCLUDE})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_LIBRARY})
|
||||
target_compile_definitions(${PROJECT_NAME} PUBLIC ${PROJECT_DEFINITION})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/dist")
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
|
||||
|
@ -46,29 +46,30 @@ enum Format
|
||||
Format_PALETTE_16,
|
||||
};
|
||||
|
||||
struct Color {
|
||||
double r = 0;
|
||||
double g = 0;
|
||||
double b = 0;
|
||||
double a = 255;
|
||||
|
||||
inline uint16_t toRGB16(uint8_t alpha_threshold = 128) const {
|
||||
const uint16_t alpha_bit = (a >= alpha_threshold) ? 0b1000000000000000 : 0;
|
||||
return alpha_bit
|
||||
| (((uint8_t)r >> 3) & 0b00011111)
|
||||
| (((uint8_t)g >> 3) & 0b00011111) << 5
|
||||
| (((uint8_t)b >> 3) & 0b00011111) << 10;
|
||||
}
|
||||
};
|
||||
static inline uint16_t RGB255_RGB16(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255, uint8_t alpha_threshold = 128) {
|
||||
const uint16_t alpha_bit = (a >= alpha_threshold) ? 0b1000000000000000 : 0;
|
||||
// const uint16_t alpha_bit = 0b1000000000000000;
|
||||
return alpha_bit
|
||||
| (((uint8_t)r >> 3) & 0b00011111)
|
||||
| (((uint8_t)g >> 3) & 0b00011111) << 5
|
||||
| (((uint8_t)b >> 3) & 0b00011111) << 10;
|
||||
}
|
||||
|
||||
struct Image {
|
||||
int width = 0, height = 0;
|
||||
std::vector<Color> data;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
~Image() {}
|
||||
void Load(const std::filesystem::path &path);
|
||||
};
|
||||
|
||||
struct Color {
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
|
||||
typedef std::vector<uint8_t> ImageMapped;
|
||||
typedef std::vector<Color> Palette;
|
||||
|
||||
struct Options {
|
||||
std::vector<std::string> path_input;
|
||||
std::string path_output;
|
||||
@ -78,11 +79,4 @@ struct Options {
|
||||
void Verify();
|
||||
void Convert();
|
||||
|
||||
std::vector<Color> QuantizePalette(const int numColor, const Image &image);
|
||||
std::vector<uint8_t> RemapImage(const Image &image, const std::vector<Color> &palette, const int &format);
|
||||
|
||||
std::vector<Color> inc_online_kmeans ( const Image *img, const int num_colors,
|
||||
const double lr_exp = 0.5, const double sample_rate = 0.5);
|
||||
|
||||
/* Max. L_2^2 distance in 24-bit RGB space = 3 * 255 * 255 */
|
||||
#define MAX_RGB_DIST 195075
|
||||
std::vector<ImageMapped> Quantize(const std::vector<Image>& images, Palette &palette_output, int num_colors, bool dither, uint8_t format);
|
341
src/kmean.cpp
341
src/kmean.cpp
@ -1,341 +0,0 @@
|
||||
/*
|
||||
https://github.com/AmberAbernathy/Color_Quantization/blob/main/test_km_algs.cpp
|
||||
|
||||
Online K-Means (MacQueen, 1967)
|
||||
Incremental Online K-Means (Abernathy & Celebi, 2022)
|
||||
|
||||
Authors: Amber Abernathy & M. Emre Celebi
|
||||
|
||||
Contact email: ecelebi@uca.edu
|
||||
|
||||
If you find this program useful, please cite:
|
||||
A. D. Abernathy and M. E. Celebi,
|
||||
The Incremental Online K-Means Clustering Algorithm
|
||||
and Its Application to Color Quantization,
|
||||
Expert Systems with Applications,
|
||||
in press, https://doi.org/10.1016/j.eswa.2022.117927, 2022.
|
||||
*/
|
||||
|
||||
#include <climits>
|
||||
#include <math.h>
|
||||
#include "header.hpp"
|
||||
|
||||
typedef unsigned char uchar;
|
||||
typedef unsigned int uint;
|
||||
typedef unsigned long ulong;
|
||||
|
||||
struct RGB_Cluster
|
||||
{
|
||||
int size;
|
||||
Color center;
|
||||
};
|
||||
|
||||
/* Max. # colors that can be requested */
|
||||
#define MAX_NUM_COLORS 256
|
||||
|
||||
/*
|
||||
Powers of two for 0, 1, ..., 16. Note that 2^16 must equal MAX_NUM_COLORS.
|
||||
If you want to quantize to more than MAX_NUM_COLORS colors, extend the POW2
|
||||
array accordingly.
|
||||
*/
|
||||
static inline int POW2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256 };
|
||||
|
||||
/*
|
||||
Function to generate two quasirandom numbers from
|
||||
a 2D Sobol sequence. Adapted from Numerical Recipies
|
||||
in C. Upon return, X and Y fall in [0,1).
|
||||
*/
|
||||
|
||||
#define MAX_BIT 30
|
||||
|
||||
void
|
||||
sob_seq ( double *x, double *y )
|
||||
{
|
||||
int j, k, l;
|
||||
ulong i, im, ipp;
|
||||
static double fac;
|
||||
static int init = 0;
|
||||
static ulong ix1, ix2;
|
||||
static ulong in, *iu[2 * MAX_BIT + 1];
|
||||
static ulong mdeg[3] = { 0, 1, 2 };
|
||||
static ulong ip[3] = { 0, 0, 1 };
|
||||
static ulong iv[2 * MAX_BIT + 1] =
|
||||
{ 0, 1, 1, 1, 1, 1, 1, 3, 1, 3, 3, 1, 1, 5, 7, 7, 3, 3, 5, 15, 11, 5, 15, 13, 9 };
|
||||
|
||||
if ( !init )
|
||||
{
|
||||
init = 1;
|
||||
for ( j = 1, k = 0; j <= MAX_BIT; j++, k += 2 )
|
||||
{
|
||||
iu[j] = &iv[k];
|
||||
}
|
||||
|
||||
for ( k = 1; k <= 2; k++ )
|
||||
{
|
||||
for ( j = 1; j <= ( int ) mdeg[k]; j++ )
|
||||
{
|
||||
iu[j][k] <<= ( MAX_BIT - j );
|
||||
}
|
||||
|
||||
for ( j = mdeg[k] + 1; j <= MAX_BIT; j++ )
|
||||
{
|
||||
ipp = ip[k];
|
||||
i = iu[j - mdeg[k]][k];
|
||||
i ^= ( i >> mdeg[k] );
|
||||
|
||||
for ( l = mdeg[k] - 1; l >= 1; l-- )
|
||||
{
|
||||
if ( ipp & 1 )
|
||||
{
|
||||
i ^= iu[j - l][k];
|
||||
}
|
||||
|
||||
ipp >>= 1;
|
||||
}
|
||||
|
||||
iu[j][k] = i;
|
||||
}
|
||||
}
|
||||
|
||||
fac = 1.0 / ( 1L << MAX_BIT );
|
||||
in = 0;
|
||||
}
|
||||
|
||||
im = in;
|
||||
for ( j = 1; j <= MAX_BIT; j++ )
|
||||
{
|
||||
if ( ! ( im & 1 ) )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
im >>= 1;
|
||||
}
|
||||
|
||||
im = (j - 1) * 2;
|
||||
*x = (ix1 ^= iv[im + 1]) * fac;
|
||||
*y = (ix2 ^= iv[im + 2]) * fac;
|
||||
in++;
|
||||
}
|
||||
|
||||
#undef MAX_BIT
|
||||
|
||||
/*
|
||||
Function to determine if an integer is a power of 2:
|
||||
http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
|
||||
*/
|
||||
|
||||
bool
|
||||
is_pow2 ( const int x )
|
||||
{
|
||||
uint ux = ( uint ) x;
|
||||
|
||||
return ux && !( ux & ( ux - 1 ) );
|
||||
}
|
||||
|
||||
/*
|
||||
Online K-Means Algorithm:
|
||||
S. Thompson, M. E. Celebi, and K. H. Buck,
|
||||
Fast Color Quantization Using MacQueen’s K-Means Algorithm,
|
||||
Journal of Real-Time Image Processing,
|
||||
17(5): 1609-1624, 2020.
|
||||
|
||||
Notes:
|
||||
1) LR_EXP: Learning rate exponent (must be in [0.5, 1])
|
||||
2) SAMPLE_RATE: Fraction of the input pixels (must be in (0, 1])
|
||||
used during the clustering process.
|
||||
3) CLUST: When the function is called, CLUST represents the initial
|
||||
centers. Upon return, CLUST represents the final centers.
|
||||
*/
|
||||
|
||||
void
|
||||
online_kmeans ( const Image *img, const int num_colors, const double lr_exp,
|
||||
const double sample_rate, RGB_Cluster *clust )
|
||||
{
|
||||
int min_dist_index;
|
||||
int old_size, new_size;
|
||||
int row_idx, col_idx;
|
||||
int num_samples;
|
||||
double sob_x, sob_y;
|
||||
double del_red, del_green, del_blue;
|
||||
double dist, min_dist;
|
||||
double learn_rate;
|
||||
Color rand_pixel;
|
||||
|
||||
if ( lr_exp < 0.5 || lr_exp > 1. )
|
||||
{
|
||||
fprintf ( stderr, "Learning rate exponent (%g) must be in [0.5, 1]\n", lr_exp );
|
||||
exit ( EXIT_FAILURE );
|
||||
}
|
||||
else if ( sample_rate <= 0.0 || sample_rate > 1. )
|
||||
{
|
||||
fprintf ( stderr, "Sampling rate (%g) must be in (0, 1]\n", sample_rate );
|
||||
exit ( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
num_samples = ( int ) ( sample_rate * (img->width*img->height) + 0.5 ); /* round */
|
||||
|
||||
for ( int i = 0; i < num_samples; i++ )
|
||||
{
|
||||
/* Sample the image quasirandomly based on a Sobol' sequence */
|
||||
sob_seq ( &sob_x, &sob_y );
|
||||
|
||||
/* Find the corresponding row/column indices */
|
||||
row_idx = ( int ) ( sob_y * img->height + 0.5 ); /* round */
|
||||
if ( row_idx == img->height )
|
||||
{
|
||||
row_idx--;
|
||||
}
|
||||
|
||||
col_idx = ( int ) ( sob_x * img->width + 0.5 ); /* round */
|
||||
if ( col_idx == img->width )
|
||||
{
|
||||
col_idx--;
|
||||
}
|
||||
|
||||
rand_pixel = img->data[row_idx * img->width + col_idx];
|
||||
|
||||
/* Find the nearest center */
|
||||
min_dist = MAX_RGB_DIST;
|
||||
min_dist_index = -INT_MAX;
|
||||
for ( int j = 0; j < num_colors; j++ )
|
||||
{
|
||||
del_red = clust[j].center.r - rand_pixel.r;
|
||||
del_green = clust[j].center.g - rand_pixel.g;
|
||||
del_blue = clust[j].center.b - rand_pixel.b;
|
||||
|
||||
dist = del_red * del_red + del_green * del_green + del_blue * del_blue;
|
||||
if ( dist < min_dist )
|
||||
{
|
||||
min_dist = dist;
|
||||
min_dist_index = j;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the size of the nearest cluster */
|
||||
old_size = clust[min_dist_index].size;
|
||||
new_size = old_size + 1;
|
||||
|
||||
/* Compute the learning rate */
|
||||
learn_rate = pow ( new_size, -lr_exp );
|
||||
|
||||
/* Update the center of the nearest cluster */
|
||||
clust[min_dist_index].center.r += learn_rate *
|
||||
( rand_pixel.r - clust[min_dist_index].center.r );
|
||||
clust[min_dist_index].center.g += learn_rate *
|
||||
( rand_pixel.g - clust[min_dist_index].center.g );
|
||||
clust[min_dist_index].center.b += learn_rate *
|
||||
( rand_pixel.b - clust[min_dist_index].center.b );
|
||||
clust[min_dist_index].size = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Function to compute the centroid of an image */
|
||||
|
||||
RGB_Cluster
|
||||
compute_centroid ( const Image *img )
|
||||
{
|
||||
double sum_red = 0.0, sum_green = 0.0, sum_blue = 0.0;
|
||||
Color pixel;
|
||||
RGB_Cluster centroid;
|
||||
|
||||
for (int i = 0; i < (img->width*img->height); i++)
|
||||
{
|
||||
pixel = img->data[i];
|
||||
sum_red += pixel.r;
|
||||
sum_green += pixel.g;
|
||||
sum_blue += pixel.b;
|
||||
}
|
||||
|
||||
centroid.center.r = sum_red / (img->width*img->height);
|
||||
centroid.center.g = sum_green / (img->width*img->height);
|
||||
centroid.center.b = sum_blue / (img->width*img->height);
|
||||
|
||||
return centroid;
|
||||
}
|
||||
|
||||
/*
|
||||
Incremental Online K-Means Algorithm:
|
||||
|
||||
A. D. Abernathy and M. E. Celebi,
|
||||
The Incremental Online K-Means Clustering Algorithm
|
||||
and Its Application to Color Quantization,
|
||||
Expert Systems with Applications,
|
||||
accepted for publication, 2022.
|
||||
|
||||
Notes:
|
||||
1) NUM_COLORS must be a power of 2 (otherwise the code must be
|
||||
modified slightly, see Abernathy & Celebi, 2022).
|
||||
2) LR_EXP: Learning rate exponent (must be in [0.5, 1])
|
||||
3) SAMPLE_RATE: Fraction of the input pixels (must be in (0, 1])
|
||||
used during the clustering process.
|
||||
*/
|
||||
|
||||
std::vector<Color>
|
||||
inc_online_kmeans ( const Image *img, const int num_colors,
|
||||
const double lr_exp, const double sample_rate )
|
||||
{
|
||||
int index, num_splits;
|
||||
Color pixel;
|
||||
RGB_Cluster *tmp_clust, *clust;
|
||||
|
||||
if ( !is_pow2 ( num_colors ) )
|
||||
{
|
||||
fprintf ( stderr, "Number of colors (%d) must be a power of 2!\n", num_colors );
|
||||
exit ( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
/* Compute log2 ( num_colors ) */
|
||||
num_splits = ( int ) ( log ( num_colors ) / log ( 2 ) + 0.5 ); /* round */
|
||||
|
||||
tmp_clust = ( RGB_Cluster * ) malloc ( ( 2 * num_colors - 1 ) * sizeof ( RGB_Cluster ) );
|
||||
clust = ( RGB_Cluster * ) malloc ( num_colors * sizeof ( RGB_Cluster ) );
|
||||
|
||||
/* Set first center to be the dataset centroid */
|
||||
tmp_clust[0] = compute_centroid ( img );
|
||||
tmp_clust[0].size = 0;
|
||||
|
||||
for ( int t = 0; t < num_splits; t++ )
|
||||
{
|
||||
for ( int n = POW2[t] - 1; n < POW2[t + 1] - 1; n++ )
|
||||
{
|
||||
/* Split c_n into c_{2n + 1} and c_{2n + 2} */
|
||||
pixel = tmp_clust[n].center;
|
||||
|
||||
/* Left child */
|
||||
index = 2 * n + 1;
|
||||
tmp_clust[index].center.r = pixel.r;
|
||||
tmp_clust[index].center.g = pixel.g;
|
||||
tmp_clust[index].center.b = pixel.b;
|
||||
tmp_clust[index].size = 0;
|
||||
|
||||
/* Right child */
|
||||
index++;
|
||||
tmp_clust[index].center.r = pixel.r;
|
||||
tmp_clust[index].center.g = pixel.g;
|
||||
tmp_clust[index].center.b = pixel.b;
|
||||
tmp_clust[index].size = 0;
|
||||
}
|
||||
|
||||
/* Refine the new centers using online k-means */
|
||||
online_kmeans ( img, POW2[t + 1], lr_exp, sample_rate,
|
||||
tmp_clust + POW2[t + 1] - 1 );
|
||||
}
|
||||
|
||||
/* Last NUM_COLORS centers are the final centers */
|
||||
for ( int j = 0; j < num_colors; j++ )
|
||||
{
|
||||
clust[j].center.r = tmp_clust[j + num_colors - 1].center.r;
|
||||
clust[j].center.g = tmp_clust[j + num_colors - 1].center.g;
|
||||
clust[j].center.b = tmp_clust[j + num_colors - 1].center.b;
|
||||
}
|
||||
|
||||
free (tmp_clust);
|
||||
|
||||
std::vector<Color> result(num_colors);
|
||||
for (int i=0; i<num_colors; i++)
|
||||
result[i] = clust[i].center;
|
||||
|
||||
free(clust);
|
||||
return result;
|
||||
}
|
190
src/main.cpp
190
src/main.cpp
@ -95,7 +95,7 @@ int main(int argc, char **argv)
|
||||
meta.height = img.height;
|
||||
meta.palette_count = 0;
|
||||
meta.palette_hash = 0;
|
||||
meta.original_size = img.width*img.height*4;
|
||||
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);
|
||||
@ -114,11 +114,11 @@ int main(int argc, char **argv)
|
||||
{
|
||||
Image img;
|
||||
img.Load(input);
|
||||
uint16_t img16[img.width*img.height];
|
||||
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] = img.data[i].toRGB16();
|
||||
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;
|
||||
@ -126,10 +126,10 @@ int main(int argc, char **argv)
|
||||
meta.height = img.height;
|
||||
meta.palette_count = 0;
|
||||
meta.palette_hash = 0;
|
||||
meta.original_size = img.width*img.height*2;
|
||||
meta.original_size = img16.size()*sizeof(uint16_t);
|
||||
|
||||
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
||||
meta.compress_size = fastlz_compress_level(1, img16, 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());
|
||||
std::ofstream out(output, std::ios::binary);
|
||||
@ -174,59 +174,41 @@ int main(int argc, char **argv)
|
||||
format = Format_INDEXED_8A32;
|
||||
}
|
||||
|
||||
std::vector<Color> palette;
|
||||
Palette palette;
|
||||
std::vector<uint16_t> palette16;
|
||||
std::vector<Image> images(opt.path_input.size());
|
||||
|
||||
for (int i=0; i<images.size(); i++)
|
||||
images[i].Load(opt.path_input[i]);
|
||||
|
||||
if (images.size() > 1)
|
||||
{
|
||||
int combined_size = 0;
|
||||
Image combined;
|
||||
|
||||
// pre-calclate size
|
||||
for (auto &image : images)
|
||||
combined_size += image.data.size();
|
||||
auto remap = Quantize(images, palette, numcol, false, format);
|
||||
|
||||
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);
|
||||
|
||||
combined.data.reserve(combined_size);
|
||||
|
||||
for (const auto& image : images)
|
||||
combined.data.insert(combined.data.end(), image.data.begin(), image.data.end());
|
||||
|
||||
palette = QuantizePalette(numcol, combined);
|
||||
}
|
||||
else palette = QuantizePalette(numcol, images[0]);
|
||||
|
||||
uint16_t palette16[palette.size()];
|
||||
for (int i=0; i<palette.size(); i++)
|
||||
palette16[i] = palette[i].toRGB16();
|
||||
|
||||
uint32_t hash = murmurhash(reinterpret_cast<const char*>(palette16), sizeof(uint16_t) * palette.size(), 0);
|
||||
uint32_t hash = murmurhash(reinterpret_cast<const char*>(palette16.data()), sizeof(uint16_t)*palette16.size(), 0);
|
||||
|
||||
for (int i=0; i<images.size(); i++)
|
||||
{
|
||||
std::vector<uint8_t> remap = RemapImage(images[i], palette, format);
|
||||
// std::vector<uint8_t> remap = {1,2,3,4,54,5,6,7,78,23,34,32,32,12,5,34};
|
||||
|
||||
Metadata meta;
|
||||
meta.format = format;
|
||||
meta.width = images[i].width;
|
||||
meta.height = images[i].height;
|
||||
meta.palette_count = palette.size();
|
||||
meta.palette_count = palette16.size();
|
||||
meta.palette_hash = hash;
|
||||
meta.original_size = remap.size();
|
||||
meta.original_size = remap[i].size();
|
||||
|
||||
uint8_t compress[uint64_t(meta.original_size*1.5)];
|
||||
meta.compress_size = fastlz_compress_level(1, remap.data(), meta.original_size, compress);
|
||||
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);
|
||||
std::ofstream out(output, std::ios::binary);
|
||||
|
||||
// out.write(reinterpret_cast<const char*>(&meta), sizeof(meta));
|
||||
// out.write(reinterpret_cast<const char*>(palette16), sizeof(uint16_t) * palette.size());
|
||||
// out.write(reinterpret_cast<const char*>(compress), meta.compress_size);
|
||||
// out.close();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -248,134 +230,14 @@ int main(int argc, char **argv)
|
||||
|
||||
// // ----
|
||||
|
||||
std::vector<Color> QuantizePalette(const int numColor, const Image &image)
|
||||
{
|
||||
return inc_online_kmeans(&image, numColor);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> RemapImage(const Image &image, const std::vector<Color> &palette, const int &format)
|
||||
{
|
||||
std::vector<uint8_t> remap(image.width*image.height);
|
||||
|
||||
int min_dist_index;
|
||||
double del_red, del_green, del_blue;
|
||||
double dist, min_dist;
|
||||
|
||||
// TODO: explore more distance algorithm
|
||||
for (int i=0; i<image.data.size(); i++)
|
||||
{
|
||||
const auto &pixel = image.data[i];
|
||||
min_dist = MAX_RGB_DIST;
|
||||
min_dist_index = -INT_MAX;
|
||||
|
||||
for (int j=0; j<palette.size(); j++)
|
||||
{
|
||||
del_red = palette[j].r - pixel.r;
|
||||
del_green = palette[j].g - pixel.g;
|
||||
del_blue = palette[j].b - pixel.b;
|
||||
|
||||
dist = del_red * del_red + del_green * del_green + del_blue * del_blue;
|
||||
if ( dist < min_dist )
|
||||
{
|
||||
min_dist = dist;
|
||||
min_dist_index = j;
|
||||
}
|
||||
}
|
||||
|
||||
remap[i] = min_dist_index;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
if (format == Format_INDEXED_4)
|
||||
{
|
||||
std::vector<uint8_t> packed;
|
||||
packed.reserve((remap.size() + 3) / 4); // Round up
|
||||
|
||||
for (size_t i = 0; i < remap.size(); ) {
|
||||
uint8_t byte = 0;
|
||||
byte |= (remap[i] & 0b00000011);
|
||||
|
||||
if (++i < remap.size()) byte |= (remap[i] & 0b00000011) << 2;
|
||||
if (++i < remap.size()) byte |= (remap[i] & 0b00000011) << 4;
|
||||
if (++i < remap.size()) byte |= (remap[i] & 0b00000011) << 6;
|
||||
|
||||
packed.push_back(byte);
|
||||
i++; // Move to next group
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
else if (format == Format_INDEXED_16)
|
||||
{
|
||||
std::vector<uint8_t> packed;
|
||||
packed.reserve((remap.size() + 1) / 2); // Round up
|
||||
|
||||
for (int i = 0; i < remap.size(); ) {
|
||||
uint8_t byte = 0;
|
||||
byte |= (remap[i] & 0b00001111);
|
||||
|
||||
if (++i < remap.size()) {
|
||||
byte |= (remap[i] & 0b00001111) << 4;
|
||||
}
|
||||
|
||||
packed.push_back(byte);
|
||||
i++; // Move to next group
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
else if (format == Format_INDEXED_8A32)
|
||||
{
|
||||
std::vector<uint8_t> packed;
|
||||
packed.reserve(remap.size());
|
||||
|
||||
for (size_t i = 0; i < remap.size(); ++i) {
|
||||
uint8_t color_index = remap[i] & 0b00000111; // 3-bit mask
|
||||
uint8_t alpha = ((uint8_t)palette[remap[i]].a >> 3) & 0b00011111; // 5-bit mask
|
||||
|
||||
packed.push_back((alpha << 3) | color_index);
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
else if (format == Format_INDEXED_32A8)
|
||||
{
|
||||
std::vector<uint8_t> packed;
|
||||
packed.reserve(remap.size());
|
||||
|
||||
for (size_t i = 0; i < remap.size(); ++i) {
|
||||
uint8_t color_index = remap[i] & 0b00011111; // 5-bit mask
|
||||
uint8_t alpha = ((uint8_t)palette[remap[i]].a >> 5) & 0b00000111; // 3-bit mask
|
||||
|
||||
packed.push_back((alpha << 5) | color_index);
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
return remap;
|
||||
}
|
||||
|
||||
// // ----
|
||||
|
||||
void Image::Load(const std::filesystem::path &path)
|
||||
{
|
||||
int comp;
|
||||
stbi_uc *img = stbi_load(path.c_str(), &width, &height, &comp, 4);
|
||||
if (img == nullptr) throw std::runtime_error(stbi_failure_reason());
|
||||
stbi_uc *ptr_img = stbi_load(path.c_str(), &width, &height, &comp, 4);
|
||||
if (ptr_img == nullptr) throw std::runtime_error(stbi_failure_reason());
|
||||
|
||||
data.resize(width*height);
|
||||
for (int i=0; i<width*height; i++) {
|
||||
const int offset = i * 4;
|
||||
data[i].r = img[offset];
|
||||
data[i].g = img[offset+1];
|
||||
data[i].b = img[offset+2];
|
||||
data[i].a = img[offset+3];
|
||||
}
|
||||
int size = width*height*comp;
|
||||
data.assign(ptr_img, ptr_img+size);
|
||||
|
||||
stbi_image_free(img);
|
||||
stbi_image_free(ptr_img);
|
||||
}
|
139
src/quantizer.cpp
Normal file
139
src/quantizer.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#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)
|
||||
{
|
||||
liq_attr *attr = liq_attr_create();
|
||||
liq_histogram *hist = liq_histogram_create(attr);
|
||||
|
||||
liq_set_max_colors(attr, num_colors);
|
||||
|
||||
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_result *result;
|
||||
if (LIQ_OK != liq_histogram_quantize(hist, 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);
|
||||
|
||||
const liq_palette *pal = liq_get_palette(result);
|
||||
palette_output.resize(pal->count);
|
||||
for (int i=0; i<pal->count; i++)
|
||||
{
|
||||
palette_output[i].r = pal->entries[i].r;
|
||||
palette_output[i].g = pal->entries[i].g;
|
||||
palette_output[i].b = pal->entries[i].b;
|
||||
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_attr_destroy(attr);
|
||||
|
||||
// ---
|
||||
|
||||
if (format == Format_INDEXED_4)
|
||||
{
|
||||
std::vector<ImageMapped> packed(output.size());
|
||||
|
||||
for (int i=0; i<output.size(); i++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
else if (format == Format_INDEXED_16)
|
||||
{
|
||||
std::vector<ImageMapped> packed(output.size());
|
||||
|
||||
for (int i=0; i<output.size(); i++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
|
||||
for (int offset=0; offset<output[i].size(); offset+=2) {
|
||||
uint8_t current_byte = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
else if (format == Format_INDEXED_8A32)
|
||||
{
|
||||
std::vector<ImageMapped> packed(output.size());
|
||||
|
||||
for (int i=0; i<output.size(); i++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
else if (format == Format_INDEXED_32A8)
|
||||
{
|
||||
std::vector<ImageMapped> packed(output.size());
|
||||
|
||||
for (int i=0; i<output.size(); i++)
|
||||
{
|
||||
packed[i].reserve((output[i].size()+3) / 4);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user