From 53c9ba3db5e726a0f07149f40eb231a2b964fcc5 Mon Sep 17 00:00:00 2001 From: Vftdan Date: Thu, 3 Oct 2024 22:37:32 +0200 Subject: [PATCH] Implemented char-fast conversion mode --- img2cpi.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/img2cpi.c b/img2cpi.c index 4b0623a..f3ee294 100644 --- a/img2cpi.c +++ b/img2cpi.c @@ -14,6 +14,9 @@ #define MAX_COLOR_DIFFERENCE 768 #define K_MEANS_ITERATIONS 4 +#ifndef HAS_POPCNT +# define HAS_POPCNT 1 +#endif struct rgba { uint8_t r, g, b, a; }; union color { @@ -118,7 +121,7 @@ void image_unload(struct image *img); 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); +void convert_8x11(const struct image_pal *img, struct cc_char *characters, bool precise); // Only one global custom palette is maintained struct palette *custom_palette_resize(uint8_t size); @@ -129,7 +132,13 @@ 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); -inline static uint8_t closest_chunk_color_symbol(const typeof(float[8][11][0x10]) *chunk_palette_diffs, uint8_t color_pair); +inline static int popcnt32(uint32_t mask); +inline static int glyph_hamming_distance(const GlyphBitmap *lhs, const GlyphBitmap *rhs); +inline static float weighted_glyph_hamming_distance(const GlyphBitmap *lhs, const GlyphBitmap *rhs, const typeof(float[11][8]) *weights); +uint8_t closest_glyph_symbol_fast(const GlyphBitmap *target); +uint8_t closest_glyph_symbol_precise(const GlyphBitmap *target, const typeof(float[11][8]) *weights); +void construct_chunk_color_glyph(GlyphBitmap *result, typeof(float[11][8]) *weights, const typeof(float[8][11][0x10]) *chunk_palette_diffs, uint8_t color_pair); +inline static uint8_t closest_chunk_color_symbol(const typeof(float[8][11][0x10]) *chunk_palette_diffs, uint8_t color_pair, bool precise); const char *known_file_extensions[] = { ".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif", @@ -248,10 +257,19 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - if (args.character_mode == CHARACTER_BLOCK) { + switch (args.character_mode) { + case CHARACTER_BLOCK: convert_2x3(quantized_image, characters); - } else { - convert_8x11(quantized_image, characters); + break; + case CHARACTER_CHAR_PRECISE: + convert_8x11(quantized_image, characters, true); + break; + case CHARACTER_CHAR_FAST: + convert_8x11(quantized_image, characters, false); + break; + default: + fprintf(stderr, "BUG: invalid args.character_mode\n"); + return EXIT_FAILURE; } // TODO: implement something other than CPIv0 @@ -632,7 +650,7 @@ void convert_2x3(const struct image_pal *img, struct cc_char *characters) { } } -void convert_8x11(const struct image_pal *img, struct cc_char *characters) { +void convert_8x11(const struct image_pal *img, struct cc_char *characters, bool precise) { 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++) { @@ -659,7 +677,7 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) { char closest_sym = 0x00, closest_color = 0xae; for (int color = 0x00; color <= 0xff; color++) { { - const int sym = closest_chunk_color_symbol(&chunk_palette_diffs, color); + const int sym = closest_chunk_color_symbol(&chunk_palette_diffs, color, precise); float difference = 0; for (int oy = 0; oy < 11; oy++) { unsigned char sym_line = font_atlas[sym][oy]; @@ -817,7 +835,28 @@ struct palette *palette_k_means(const struct image *image, const struct palette return palette; } -inline static float weighted_glyph_hamming_distance(const GlyphBitmap *lhs, const GlyphBitmap *rhs, const typeof(float[11][8]) *weights) { +int popcnt32(uint32_t mask) { +#if HAS_POPCNT + return __builtin_popcount(mask); +#else + int res = 0; + for (; mask; mask >>= 1) { + res += mask & 1; + } + return res; +#endif +} + +int glyph_hamming_distance(const GlyphBitmap *lhs, const GlyphBitmap *rhs) { + int dist = 0; + dist += popcnt32(*((uint32_t*)&((*lhs)[0])) ^ *((uint32_t*)&((*rhs)[0]))); + dist += popcnt32(*((uint32_t*)&((*lhs)[4])) ^ *((uint32_t*)&((*rhs)[4]))); + dist += popcnt32(*((uint16_t*)&((*lhs)[8])) ^ *((uint16_t*)&((*rhs)[8]))); + dist += popcnt32( ( ((*lhs)[10])) ^ ( ((*rhs)[10]))); + return dist; +} + +float weighted_glyph_hamming_distance(const GlyphBitmap *lhs, const GlyphBitmap *rhs, const typeof(float[11][8]) *weights) { float dist = 0; for (int oy = 0; oy < 11; oy++) { uint8_t sym_line = (*lhs)[oy] ^ (*rhs)[oy]; @@ -831,7 +870,23 @@ inline static float weighted_glyph_hamming_distance(const GlyphBitmap *lhs, cons return dist; } -uint8_t closest_glyph_symbol(const GlyphBitmap *target, const typeof(float[11][8]) *weights) { +uint8_t closest_glyph_symbol_fast(const GlyphBitmap *target) { + uint8_t best = 0x01; + int best_dist = glyph_hamming_distance(target, &font_atlas[best]); + for (int sym = 0x02; sym <= 0xFF; sym++) { + if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') { + continue; + } + int dist = glyph_hamming_distance(target, &font_atlas[sym]); + if (dist <= best_dist) { + best_dist = dist; + best = sym; + } + } + return best; +} + +uint8_t closest_glyph_symbol_precise(const GlyphBitmap *target, const typeof(float[11][8]) *weights) { uint8_t best = 0x01; float best_dist = weighted_glyph_hamming_distance(target, &font_atlas[best], weights); for (int sym = 0x02; sym <= 0xFF; sym++) { @@ -865,11 +920,15 @@ void construct_chunk_color_glyph(GlyphBitmap *result, typeof(float[11][8]) *weig } } -uint8_t closest_chunk_color_symbol(const typeof(float[8][11][0x10]) *chunk_palette_diffs, uint8_t color_pair) { +uint8_t closest_chunk_color_symbol(const typeof(float[8][11][0x10]) *chunk_palette_diffs, uint8_t color_pair, bool precise) { GlyphBitmap glyph; float weights[11][8]; - construct_chunk_color_glyph(&glyph, &weights, chunk_palette_diffs, color_pair); - return closest_glyph_symbol(&glyph, &weights); + construct_chunk_color_glyph(&glyph, precise ? &weights : NULL, chunk_palette_diffs, color_pair); + if (precise) { + return closest_glyph_symbol_precise(&glyph, &weights); + } else { + return closest_glyph_symbol_fast(&glyph); + } } const struct palette DEFAULT_PALETTE = PALETTE(