forked from hkc/cc-stuff
Implement k-means palette generation
This commit is contained in:
parent
849563285c
commit
c5af524dac
113
img2cpi.c
113
img2cpi.c
|
@ -13,6 +13,7 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define MAX_COLOR_DIFFERENCE 768
|
#define MAX_COLOR_DIFFERENCE 768
|
||||||
|
#define K_MEANS_ITERATIONS 4
|
||||||
|
|
||||||
struct rgba { uint8_t r, g, b, a; };
|
struct rgba { uint8_t r, g, b, a; };
|
||||||
union color {
|
union color {
|
||||||
|
@ -85,6 +86,19 @@ struct image_pal {
|
||||||
const struct palette *palette;
|
const struct palette *palette;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct k_means_state {
|
||||||
|
const struct image *items;
|
||||||
|
struct palette *clusters;
|
||||||
|
uint8_t *predicted_cluster;
|
||||||
|
struct k_means_centroid_intermediate {
|
||||||
|
struct {
|
||||||
|
float r, g, b;
|
||||||
|
} sums;
|
||||||
|
size_t count;
|
||||||
|
} *centroid_intermediate;
|
||||||
|
size_t item_count;
|
||||||
|
};
|
||||||
|
|
||||||
bool parse_cmdline(int argc, char **argv);
|
bool parse_cmdline(int argc, char **argv);
|
||||||
void show_help(const char *progname, bool show_all, FILE *fp);
|
void show_help(const char *progname, bool show_all, FILE *fp);
|
||||||
struct image *image_load(const char *fp);
|
struct image *image_load(const char *fp);
|
||||||
|
@ -103,6 +117,11 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters);
|
||||||
struct palette *custom_palette_resize(uint8_t size);
|
struct palette *custom_palette_resize(uint8_t size);
|
||||||
struct palette *custom_palette_from(const struct palette *orig);
|
struct palette *custom_palette_from(const struct palette *orig);
|
||||||
|
|
||||||
|
struct k_means_state k_means_init(const struct image *image, struct palette *starting_palette);
|
||||||
|
bool k_means_iteration(struct k_means_state *state);
|
||||||
|
void k_means_end(struct k_means_state *state);
|
||||||
|
struct palette *palette_k_means(const struct image *image, const struct palette *prototype);
|
||||||
|
|
||||||
const char *known_file_extensions[] = {
|
const char *known_file_extensions[] = {
|
||||||
".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif",
|
".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif",
|
||||||
".tga", ".bmp", ".hdr", ".pnm", 0
|
".tga", ".bmp", ".hdr", ".pnm", 0
|
||||||
|
@ -176,7 +195,7 @@ int main(int argc, char **argv) {
|
||||||
switch (args.palette_type) {
|
switch (args.palette_type) {
|
||||||
case PALETTE_DEFAULT: palette = &DEFAULT_PALETTE; break;
|
case PALETTE_DEFAULT: palette = &DEFAULT_PALETTE; break;
|
||||||
case PALETTE_DEFAULT_GRAY: palette = &DEFAULT_GRAY_PALETTE; break;
|
case PALETTE_DEFAULT_GRAY: palette = &DEFAULT_GRAY_PALETTE; break;
|
||||||
case PALETTE_AUTO: assert(0 && "Not implemented"); break;
|
case PALETTE_AUTO: palette = palette_k_means(src_image, &DEFAULT_PALETTE); break;
|
||||||
case PALETTE_LIST: assert(0 && "Not implemented"); break;
|
case PALETTE_LIST: assert(0 && "Not implemented"); break;
|
||||||
case PALETTE_PATH: assert(0 && "Not implemented"); break;
|
case PALETTE_PATH: assert(0 && "Not implemented"); break;
|
||||||
default: assert(0 && "Unreachable");
|
default: assert(0 && "Unreachable");
|
||||||
|
@ -654,6 +673,98 @@ struct palette *custom_palette_from(const struct palette *orig) {
|
||||||
return (struct palette*)&custom_palette_data;
|
return (struct palette*)&custom_palette_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct k_means_state k_means_init(const struct image *image, struct palette *starting_palette) {
|
||||||
|
size_t item_count = image->w * image->h;
|
||||||
|
struct k_means_state state = {
|
||||||
|
.items = image,
|
||||||
|
.clusters = starting_palette,
|
||||||
|
.predicted_cluster = calloc(image->w, image->h),
|
||||||
|
.centroid_intermediate = calloc(item_count, sizeof(struct k_means_centroid_intermediate)),
|
||||||
|
.item_count = item_count,
|
||||||
|
};
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool k_means_iteration(struct k_means_state *state) {
|
||||||
|
if (!state->predicted_cluster || !state->centroid_intermediate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
// Find closest cluster
|
||||||
|
for (int i = 0; i < state->item_count; i++) {
|
||||||
|
int closest_cluster = 0;
|
||||||
|
float closest_distance = 1e20;
|
||||||
|
for (int cluster = 0; cluster < state->clusters->count; cluster++) {
|
||||||
|
float dist = get_color_difference(state->clusters->colors[cluster], state->items->pixels[i]);
|
||||||
|
if (dist <= closest_distance) {
|
||||||
|
closest_distance = dist;
|
||||||
|
closest_cluster = cluster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!changed) {
|
||||||
|
changed = state->predicted_cluster[i] != closest_cluster;
|
||||||
|
}
|
||||||
|
state->predicted_cluster[i] = closest_cluster;
|
||||||
|
state->centroid_intermediate[closest_cluster].count += 1;
|
||||||
|
state->centroid_intermediate[closest_cluster].sums.r += state->items->pixels[i].rgba.r;
|
||||||
|
state->centroid_intermediate[closest_cluster].sums.g += state->items->pixels[i].rgba.g;
|
||||||
|
state->centroid_intermediate[closest_cluster].sums.b += state->items->pixels[i].rgba.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update centroids
|
||||||
|
for (int i = 0; i < state->clusters->count; ++i) {
|
||||||
|
struct k_means_centroid_intermediate intermediate = state->centroid_intermediate[i];
|
||||||
|
if (intermediate.count) {
|
||||||
|
union color centroid = {
|
||||||
|
.rgba = {
|
||||||
|
.r = intermediate.sums.r / intermediate.count,
|
||||||
|
.g = intermediate.sums.g / intermediate.count,
|
||||||
|
.b = intermediate.sums.b / intermediate.count,
|
||||||
|
.a = 0xff,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!changed) {
|
||||||
|
changed = state->clusters->colors[i].v != centroid.v;
|
||||||
|
}
|
||||||
|
state->clusters->colors[i] = centroid;
|
||||||
|
} else {
|
||||||
|
// No pixels are closest to this color
|
||||||
|
// TODO: wiggle the centroid?
|
||||||
|
}
|
||||||
|
state->centroid_intermediate[i] = (struct k_means_centroid_intermediate) { .sums = {0, 0, 0}, .count = 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void k_means_end(struct k_means_state *state) {
|
||||||
|
if (state->predicted_cluster) {
|
||||||
|
free(state->predicted_cluster);
|
||||||
|
}
|
||||||
|
if (state->centroid_intermediate) {
|
||||||
|
free(state->centroid_intermediate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct palette *palette_k_means(const struct image *image, const struct palette *prototype) {
|
||||||
|
if (!prototype) {
|
||||||
|
prototype = &DEFAULT_PALETTE;
|
||||||
|
}
|
||||||
|
struct palette *palette = custom_palette_from(prototype);
|
||||||
|
|
||||||
|
struct k_means_state state = k_means_init(image, palette);
|
||||||
|
for (int i = 0; i < K_MEANS_ITERATIONS; i++) {
|
||||||
|
if (!k_means_iteration(&state)) {
|
||||||
|
fprintf(stderr, "early k-means stop at iteration %d\n", i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k_means_end(&state);
|
||||||
|
|
||||||
|
return palette;
|
||||||
|
}
|
||||||
|
|
||||||
const struct palette DEFAULT_PALETTE = PALETTE(
|
const struct palette DEFAULT_PALETTE = PALETTE(
|
||||||
{ { 0xf0, 0xf0, 0xf0, 0xff } },
|
{ { 0xf0, 0xf0, 0xf0, 0xff } },
|
||||||
{ { 0xf2, 0xb2, 0x33, 0xff } },
|
{ { 0xf2, 0xb2, 0x33, 0xff } },
|
||||||
|
|
Loading…
Reference in New Issue