back using libimagequant

This commit is contained in:
sillysagiri 2025-05-21 03:06:29 +07:00
parent 82cc0b60b7
commit 77b0fb85bd
5 changed files with 185 additions and 528 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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 MacQueens 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;
}

View File

@ -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;
auto remap = Quantize(images, palette, numcol, false, format);
// pre-calclate size
for (auto &image : images)
combined_size += image.data.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);
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
View 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;
}