forked from hkc/cc-stuff
Merge branch 'palette-k-means' of https://git.being.pet/vftdan/cc-stuff into vftdan-palette-k-means
This commit is contained in:
commit
93205ec237
|
@ -0,0 +1,4 @@
|
|||
img2cpi
|
||||
img2cpi.o
|
||||
wsvpn
|
||||
wsvpn.o
|
|
@ -0,0 +1,6 @@
|
|||
[submodule "dependencies/stb"]
|
||||
path = dependencies/stb
|
||||
url = https://github.com/nothings/stb
|
||||
[submodule "dependencies/mongoose"]
|
||||
path = dependencies/mongoose
|
||||
url = https://github.com/cesanta/mongoose
|
|
@ -0,0 +1,9 @@
|
|||
CPPFLAGS += -Idependencies -Idependencies/mongoose
|
||||
LDLIBS += -lm
|
||||
|
||||
all: img2cpi wsvpn
|
||||
|
||||
wsvpn: wsvpn.o dependencies/mongoose/mongoose.o
|
||||
$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o "$@"
|
||||
|
||||
.PHONY: all
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3525f044f551816dc1469f445fc16b94d51a1e78
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f75e8d1cad7d90d72ef7a4661f1b994ef78b4e31
|
240
img2cpi.c
240
img2cpi.c
|
@ -13,6 +13,7 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#define MAX_COLOR_DIFFERENCE 768
|
||||
#define K_MEANS_ITERATIONS 4
|
||||
|
||||
struct rgba { uint8_t r, g, b, a; };
|
||||
union color {
|
||||
|
@ -23,9 +24,15 @@ struct cc_char {
|
|||
unsigned char character;
|
||||
unsigned char bg, fg;
|
||||
};
|
||||
struct palette {
|
||||
const uint8_t count;
|
||||
union color colors[] __attribute__((counted_by(count)));
|
||||
};
|
||||
#define LENGTHOF(...) (sizeof(__VA_ARGS__) / sizeof(*(__VA_ARGS__)))
|
||||
#define PALETTE(...) { .count = LENGTHOF((union color[]){__VA_ARGS__}), .colors = {__VA_ARGS__} }
|
||||
|
||||
const extern char font_atlas[256][11];
|
||||
const extern union color DEFAULT_PALETTE[16], DEFAULT_GRAY_PALETTE[16];
|
||||
const extern struct palette DEFAULT_PALETTE, DEFAULT_GRAY_PALETTE;
|
||||
|
||||
struct arguments {
|
||||
bool fast_mode;
|
||||
|
@ -76,8 +83,22 @@ struct image {
|
|||
struct image_pal {
|
||||
int w, h;
|
||||
uint8_t *pixels;
|
||||
const union color *palette;
|
||||
size_t palette_size;
|
||||
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;
|
||||
union color closest_present_item;
|
||||
float closest_present_distance;
|
||||
} *centroid_intermediate;
|
||||
size_t item_count;
|
||||
};
|
||||
|
||||
bool parse_cmdline(int argc, char **argv);
|
||||
|
@ -85,7 +106,7 @@ void show_help(const char *progname, bool show_all, FILE *fp);
|
|||
struct image *image_load(const char *fp);
|
||||
struct image *image_new(int w, int h);
|
||||
struct image *image_resize(struct image *original, int new_w, int new_h);
|
||||
struct image_pal *image_quantize(struct image *original, const union color *colors, size_t n_colors);
|
||||
struct image_pal *image_quantize(struct image *original, const struct palette *palette);
|
||||
float get_color_difference(union color a, union color b);
|
||||
float get_color_brightness(union color clr);
|
||||
void image_unload(struct image *img);
|
||||
|
@ -94,6 +115,15 @@ void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh);
|
|||
void convert_2x3(const struct image_pal *img, struct cc_char *characters);
|
||||
void convert_8x11(const struct image_pal *img, struct cc_char *characters);
|
||||
|
||||
// Only one global custom palette is maintained
|
||||
struct palette *custom_palette_resize(uint8_t size);
|
||||
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[] = {
|
||||
".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif",
|
||||
".tga", ".bmp", ".hdr", ".pnm", 0
|
||||
|
@ -163,11 +193,11 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
|
||||
// TODO: load palette, maybe calculate it too? k-means?
|
||||
const union color *palette = DEFAULT_PALETTE;
|
||||
const struct palette *palette = &DEFAULT_PALETTE;
|
||||
switch (args.palette_type) {
|
||||
case PALETTE_DEFAULT: palette = DEFAULT_PALETTE; break;
|
||||
case PALETTE_DEFAULT_GRAY: palette = DEFAULT_GRAY_PALETTE; break;
|
||||
case PALETTE_AUTO: assert(0 && "Not implemented"); break;
|
||||
case PALETTE_DEFAULT: palette = &DEFAULT_PALETTE; break;
|
||||
case PALETTE_DEFAULT_GRAY: palette = &DEFAULT_GRAY_PALETTE; break;
|
||||
case PALETTE_AUTO: palette = palette_k_means(src_image, &DEFAULT_PALETTE); break;
|
||||
case PALETTE_LIST: assert(0 && "Not implemented"); break;
|
||||
case PALETTE_PATH: assert(0 && "Not implemented"); break;
|
||||
default: assert(0 && "Unreachable");
|
||||
|
@ -198,7 +228,7 @@ int main(int argc, char **argv) {
|
|||
// TODO: actually do stuff
|
||||
struct cc_char *characters = calloc(args.width * args.height, sizeof(struct cc_char));
|
||||
|
||||
struct image_pal *quantized_image = image_quantize(canvas, palette, 16);
|
||||
struct image_pal *quantized_image = image_quantize(canvas, palette);
|
||||
if (!quantized_image) {
|
||||
fprintf(stderr, "Error: failed to open the file\n");
|
||||
return EXIT_FAILURE;
|
||||
|
@ -217,9 +247,9 @@ int main(int argc, char **argv) {
|
|||
fputc(args.height, fp);
|
||||
fputc(0x00, fp);
|
||||
for (int i = 0; i < 16; i++) {
|
||||
fputc(palette[i].rgba.r, fp);
|
||||
fputc(palette[i].rgba.g, fp);
|
||||
fputc(palette[i].rgba.b, fp);
|
||||
fputc(palette->colors[i].rgba.r, fp);
|
||||
fputc(palette->colors[i].rgba.g, fp);
|
||||
fputc(palette->colors[i].rgba.b, fp);
|
||||
}
|
||||
for (int i = 0; i < args.width * args.height; i++) {
|
||||
fputc(characters[i].character, fp);
|
||||
|
@ -486,19 +516,18 @@ void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh)
|
|||
}
|
||||
}
|
||||
|
||||
struct image_pal *image_quantize(struct image *original, const union color *colors, size_t n_colors) {
|
||||
struct image_pal *image_quantize(struct image *original, const struct palette *palette) {
|
||||
struct image_pal *out = calloc(1, sizeof(struct image_pal));
|
||||
out->w = original->w;
|
||||
out->h = original->h;
|
||||
out->pixels = calloc(original->w, original->h);
|
||||
out->palette = colors;
|
||||
out->palette_size = n_colors;
|
||||
out->palette = palette;
|
||||
|
||||
for (int i = 0; i < out->w * out->h; i++) {
|
||||
int closest_color = 0;
|
||||
float closest_distance = 1e20;
|
||||
for (int color = 0; color < n_colors; color++) {
|
||||
float dist = get_color_difference(colors[color], original->pixels[i]);
|
||||
for (int color = 0; color < palette->count; color++) {
|
||||
float dist = get_color_difference(palette->colors[color], original->pixels[i]);
|
||||
if (dist <= closest_distance) {
|
||||
closest_distance = dist;
|
||||
closest_color = color;
|
||||
|
@ -531,7 +560,7 @@ void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
|
|||
for (int oy = 0; oy < 3; oy++) {
|
||||
for (int ox = 0; ox < 2; ox++) {
|
||||
unsigned char pix = img->pixels[ox + (x + (y * 3 + oy) * w) * 2];
|
||||
float brightness = get_color_brightness(img->palette[pix]);
|
||||
float brightness = get_color_brightness(img->palette->colors[pix]);
|
||||
if (brightness >= brightest_diff) {
|
||||
brightest_i = pix;
|
||||
brightest_diff = brightness;
|
||||
|
@ -549,8 +578,8 @@ void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
|
|||
for (int ox = 0; ox < 2; ox++) {
|
||||
if (ox == 1 && oy == 2) continue;
|
||||
unsigned char pix = img->pixels[ox + (x + (y * 3 + oy) * w) * 2];
|
||||
float diff_bg = get_color_difference(img->palette[darkest_i], img->palette[pix]);
|
||||
float diff_fg = get_color_difference(img->palette[brightest_i], img->palette[pix]);
|
||||
float diff_bg = get_color_difference(img->palette->colors[darkest_i], img->palette->colors[pix]);
|
||||
float diff_fg = get_color_difference(img->palette->colors[brightest_i], img->palette->colors[pix]);
|
||||
if (diff_fg < diff_bg) {
|
||||
bitmap |= pixel_bits[oy][ox];
|
||||
}
|
||||
|
@ -559,8 +588,8 @@ void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
|
|||
|
||||
{
|
||||
unsigned char pix = img->pixels[1 + (x + (y * 3 + 2) * w) * 2];
|
||||
float diff_bg = get_color_difference(img->palette[darkest_i], img->palette[pix]);
|
||||
float diff_fg = get_color_difference(img->palette[brightest_i], img->palette[pix]);
|
||||
float diff_bg = get_color_difference(img->palette->colors[darkest_i], img->palette->colors[pix]);
|
||||
float diff_fg = get_color_difference(img->palette->colors[brightest_i], img->palette->colors[pix]);
|
||||
if (diff_fg < diff_bg) {
|
||||
bitmap ^= 31;
|
||||
unsigned char tmp = darkest_i;
|
||||
|
@ -578,8 +607,27 @@ void convert_2x3(const struct image_pal *img, struct cc_char *characters) {
|
|||
|
||||
void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
|
||||
int w = img->w / 8, h = img->h / 11;
|
||||
float palette_self_diffs[0x100][0x10] = {{(float) 0xffffff}};
|
||||
for (int input_color = 0x0; input_color < 0x100 && input_color < img->palette->count; input_color++) {
|
||||
for (int output_color = 0x0; output_color < 0x10 && output_color < img->palette->count; output_color++) {
|
||||
palette_self_diffs[input_color][output_color] = get_color_difference(img->palette->colors[input_color], img->palette->colors[output_color]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
float chunk_palette_diffs[8][11][0x10] = {{{(float) 0xffffff}}};
|
||||
for (int ox = 0; ox < 8; ox++) {
|
||||
for (int oy = 0; oy < 11; oy++) {
|
||||
uint8_t pixel_unresolved = img->pixels[
|
||||
ox + (x + (y * 11 + oy) * w) * 8
|
||||
];
|
||||
for (int color = 0x0; color < 0x10 && color < img->palette->count; color++) {
|
||||
chunk_palette_diffs[ox][oy][color] = palette_self_diffs[pixel_unresolved][color];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float min_diff = 0xffffff;
|
||||
char closest_sym = 0x00, closest_color = 0xae;
|
||||
for (int sym = 0x01; sym <= 0xFF; sym++) {
|
||||
|
@ -587,17 +635,12 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
|
|||
continue;
|
||||
}
|
||||
for (int color = 0x00; color <= 0xff; color++) {
|
||||
union color cell_bg = img->palette[color & 0xF],
|
||||
cell_fg = img->palette[color >> 4];
|
||||
float difference = 0;
|
||||
for (int oy = 0; oy < 11; oy++) {
|
||||
unsigned char sym_line = font_atlas[sym][oy];
|
||||
for (int ox = 0; ox < 8; ox++) {
|
||||
bool lit = sym_line & (0x80 >> ox);
|
||||
union color pixel = img->palette[img->pixels[
|
||||
ox + (x + (y * 11 + oy) * w) * 8
|
||||
]];
|
||||
difference += get_color_difference(pixel, lit ? cell_fg : cell_bg);
|
||||
difference += chunk_palette_diffs[ox][oy][lit ? color >> 4 : color & 0xF];
|
||||
}
|
||||
}
|
||||
if (difference <= min_diff) {
|
||||
|
@ -614,7 +657,142 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
|
|||
}
|
||||
}
|
||||
|
||||
const union color DEFAULT_PALETTE[16] = {
|
||||
struct {
|
||||
uint8_t count;
|
||||
union color colors[256];
|
||||
} custom_palette_data;
|
||||
|
||||
struct palette *custom_palette_resize(uint8_t size) {
|
||||
custom_palette_data.count = size;
|
||||
return (struct palette*)&custom_palette_data;
|
||||
}
|
||||
|
||||
struct palette *custom_palette_from(const struct palette *orig) {
|
||||
custom_palette_data.count = orig->count;
|
||||
for (int i = 0; i < custom_palette_data.count; i++) {
|
||||
custom_palette_data.colors[i] = orig->colors[i];
|
||||
}
|
||||
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;
|
||||
uint8_t cluster_count = starting_palette->count;
|
||||
struct k_means_state state = {
|
||||
.items = image,
|
||||
.clusters = starting_palette,
|
||||
.predicted_cluster = calloc(image->w, image->h),
|
||||
.centroid_intermediate = calloc(cluster_count, sizeof(struct k_means_centroid_intermediate)),
|
||||
.item_count = item_count,
|
||||
};
|
||||
if (state.centroid_intermediate) {
|
||||
for (size_t i = 0; i < cluster_count; i++) {
|
||||
state.centroid_intermediate[i].closest_present_item = starting_palette->colors[i];
|
||||
state.centroid_intermediate[i].closest_present_distance = 1e20;
|
||||
}
|
||||
}
|
||||
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++) {
|
||||
union color item = state->items->pixels[i];
|
||||
float dist = get_color_difference(state->clusters->colors[cluster], item);
|
||||
if (dist <= closest_distance) {
|
||||
closest_distance = dist;
|
||||
closest_cluster = cluster;
|
||||
}
|
||||
if (dist < state->centroid_intermediate[cluster].closest_present_distance) {
|
||||
bool can_update = true;
|
||||
for (int other_cluster = 0; other_cluster < state->clusters->count; other_cluster++) {
|
||||
if (other_cluster == cluster) {
|
||||
continue;
|
||||
}
|
||||
if (state->centroid_intermediate[other_cluster].closest_present_item.v == item.v) {
|
||||
can_update = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (can_update) {
|
||||
state->centroid_intermediate[cluster].closest_present_item = item;
|
||||
state->centroid_intermediate[cluster].closest_present_distance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
// Warp the centroid onto the closest item
|
||||
state->clusters->colors[i] = intermediate.closest_present_item;
|
||||
}
|
||||
state->centroid_intermediate[i] = (struct k_means_centroid_intermediate) { .sums = {0, 0, 0}, .count = 0, .closest_present_item = state->clusters->colors[i], .closest_present_distance = 1e20 };
|
||||
}
|
||||
|
||||
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(
|
||||
{ { 0xf0, 0xf0, 0xf0, 0xff } },
|
||||
{ { 0xf2, 0xb2, 0x33, 0xff } },
|
||||
{ { 0xe5, 0x7f, 0xd8, 0xff } },
|
||||
|
@ -631,7 +809,7 @@ const union color DEFAULT_PALETTE[16] = {
|
|||
{ { 0x57, 0xa6, 0x4e, 0xff } },
|
||||
{ { 0xcc, 0x4c, 0x4c, 0xff } },
|
||||
{ { 0x11, 0x11, 0x11, 0xff } }
|
||||
}, DEFAULT_GRAY_PALETTE[16] = {
|
||||
), DEFAULT_GRAY_PALETTE = PALETTE(
|
||||
{ { 0xf0, 0xf0, 0xf0, 0xff } },
|
||||
{ { 0x9d, 0x9d, 0x9d, 0xff } },
|
||||
{ { 0xbe, 0xbe, 0xbe, 0xff } },
|
||||
|
@ -648,7 +826,7 @@ const union color DEFAULT_PALETTE[16] = {
|
|||
{ { 0x6e, 0x6e, 0x6e, 0xff } },
|
||||
{ { 0x76, 0x76, 0x76, 0xff } },
|
||||
{ { 0x11, 0x11, 0x11, 0xff } }
|
||||
};
|
||||
);
|
||||
|
||||
const char font_atlas[256][11] = {
|
||||
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
|
||||
|
|
Loading…
Reference in New Issue