forked from hkc/cc-stuff
1
0
Fork 0

Compare commits

...

2 Commits

Author SHA1 Message Date
Vftdan 30b6534fc1 Rename character_mode to conversion_mode 2024-10-03 22:40:17 +02:00
Vftdan 53c9ba3db5 Implemented char-fast conversion mode 2024-10-03 22:37:32 +02:00
1 changed files with 82 additions and 23 deletions

105
img2cpi.c
View File

@ -14,6 +14,9 @@
#define MAX_COLOR_DIFFERENCE 768 #define MAX_COLOR_DIFFERENCE 768
#define K_MEANS_ITERATIONS 4 #define K_MEANS_ITERATIONS 4
#ifndef HAS_POPCNT
# define HAS_POPCNT 1
#endif
struct rgba { uint8_t r, g, b, a; }; struct rgba { uint8_t r, g, b, a; };
union color { union color {
@ -37,11 +40,11 @@ const extern struct palette DEFAULT_PALETTE, DEFAULT_GRAY_PALETTE;
struct arguments { struct arguments {
int width, height; int width, height;
enum character_mode { enum conversion_mode {
CHARACTER_BLOCK, CONVERSION_BLOCK,
CHARACTER_CHAR_PRECISE, CONVERSION_CHAR_PRECISE,
CHARACTER_CHAR_FAST, CONVERSION_CHAR_FAST,
} character_mode; } conversion_mode;
enum cpi_version { enum cpi_version {
CPI_VERSION_AUTO, CPI_VERSION_AUTO,
CPI_VERSION_RAW, CPI_VERSION_RAW,
@ -70,7 +73,7 @@ struct arguments {
} args = { } args = {
.width = 4 * 8 - 1, // 4x3 blocks screen .width = 4 * 8 - 1, // 4x3 blocks screen
.height = 3 * 6 - 2, .height = 3 * 6 - 2,
.character_mode = CHARACTER_CHAR_PRECISE, .conversion_mode = CONVERSION_CHAR_PRECISE,
.cpi_version = CPI_VERSION_AUTO, .cpi_version = CPI_VERSION_AUTO,
.placement = PLACEMENT_FULL, .placement = PLACEMENT_FULL,
.input_path = NULL, .input_path = NULL,
@ -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 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_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 // Only one global custom palette is maintained
struct palette *custom_palette_resize(uint8_t size); 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); void k_means_end(struct k_means_state *state);
struct palette *palette_k_means(const struct image *image, const struct palette *prototype); 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[] = { const char *known_file_extensions[] = {
".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif", ".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif",
@ -195,7 +204,7 @@ int main(int argc, char **argv) {
} }
struct image *canvas; struct image *canvas;
if (args.character_mode == CHARACTER_BLOCK) { if (args.conversion_mode == CONVERSION_BLOCK) {
canvas = image_new(args.width * 2, args.height * 3); canvas = image_new(args.width * 2, args.height * 3);
} else { } else {
canvas = image_new(args.width * 8, args.height * 11); canvas = image_new(args.width * 8, args.height * 11);
@ -248,10 +257,19 @@ int main(int argc, char **argv) {
return EXIT_FAILURE; return EXIT_FAILURE;
} }
if (args.character_mode == CHARACTER_BLOCK) { switch (args.conversion_mode) {
case CONVERSION_BLOCK:
convert_2x3(quantized_image, characters); convert_2x3(quantized_image, characters);
} else { break;
convert_8x11(quantized_image, characters); case CONVERSION_CHAR_PRECISE:
convert_8x11(quantized_image, characters, true);
break;
case CONVERSION_CHAR_FAST:
convert_8x11(quantized_image, characters, false);
break;
default:
fprintf(stderr, "BUG: invalid args.conversion_mode\n");
return EXIT_FAILURE;
} }
// TODO: implement something other than CPIv0 // TODO: implement something other than CPIv0
@ -302,7 +320,7 @@ bool parse_cmdline(int argc, char **argv) {
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
break; break;
case 'f': // --fast case 'f': // --fast
args.character_mode = CHARACTER_BLOCK; args.conversion_mode = CONVERSION_BLOCK;
fprintf(stderr, "Warning: `--fast` is deprecated, use `--mode block` instead\n"); fprintf(stderr, "Warning: `--fast` is deprecated, use `--mode block` instead\n");
if (args.cpi_version != CPI_VERSION_AUTO) { if (args.cpi_version != CPI_VERSION_AUTO) {
fprintf(stderr, "Warning: text mode ignores version\n"); fprintf(stderr, "Warning: text mode ignores version\n");
@ -311,11 +329,11 @@ bool parse_cmdline(int argc, char **argv) {
case 'm': // --mode case 'm': // --mode
{ {
if (0 == strcmp(optarg, "block") || 0 == strcmp(optarg, "fast") || 0 == strcmp(optarg, "2x3")) { if (0 == strcmp(optarg, "block") || 0 == strcmp(optarg, "fast") || 0 == strcmp(optarg, "2x3")) {
args.character_mode = CHARACTER_BLOCK; args.conversion_mode = CONVERSION_BLOCK;
} else if (0 == strcmp(optarg, "char") || 0 == strcmp(optarg, "char-precise") || 0 == strcmp(optarg, "8x11") || 0 == strcmp(optarg, "6x9")) { } else if (0 == strcmp(optarg, "char") || 0 == strcmp(optarg, "char-precise") || 0 == strcmp(optarg, "8x11") || 0 == strcmp(optarg, "6x9")) {
args.character_mode = CHARACTER_CHAR_PRECISE; args.conversion_mode = CONVERSION_CHAR_PRECISE;
} else if (0 == strcmp(optarg, "char-fast") || 0 == strcmp(optarg, "8x11-fast") || 0 == strcmp(optarg, "6x9-fast")) { } else if (0 == strcmp(optarg, "char-fast") || 0 == strcmp(optarg, "8x11-fast") || 0 == strcmp(optarg, "6x9-fast")) {
args.character_mode = CHARACTER_CHAR_FAST; args.conversion_mode = CONVERSION_CHAR_FAST;
} }
} }
break; break;
@ -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; int w = img->w / 8, h = img->h / 11;
float palette_self_diffs[0x100][0x10] = {{(float) 0xffffff}}; 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 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; char closest_sym = 0x00, closest_color = 0xae;
for (int color = 0x00; color <= 0xff; color++) { 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; float difference = 0;
for (int oy = 0; oy < 11; oy++) { for (int oy = 0; oy < 11; oy++) {
unsigned char sym_line = font_atlas[sym][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; 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; float dist = 0;
for (int oy = 0; oy < 11; oy++) { for (int oy = 0; oy < 11; oy++) {
uint8_t sym_line = (*lhs)[oy] ^ (*rhs)[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; 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; uint8_t best = 0x01;
float best_dist = weighted_glyph_hamming_distance(target, &font_atlas[best], weights); float best_dist = weighted_glyph_hamming_distance(target, &font_atlas[best], weights);
for (int sym = 0x02; sym <= 0xFF; sym++) { 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; GlyphBitmap glyph;
float weights[11][8]; float weights[11][8];
construct_chunk_color_glyph(&glyph, &weights, chunk_palette_diffs, color_pair); construct_chunk_color_glyph(&glyph, precise ? &weights : NULL, chunk_palette_diffs, color_pair);
return closest_glyph_symbol(&glyph, &weights); if (precise) {
return closest_glyph_symbol_precise(&glyph, &weights);
} else {
return closest_glyph_symbol_fast(&glyph);
}
} }
const struct palette DEFAULT_PALETTE = PALETTE( const struct palette DEFAULT_PALETTE = PALETTE(