Compare commits
8 Commits
master
...
optimized-
Author | SHA1 | Date |
---|---|---|
Vftdan | 91fc5098eb | |
Vftdan | fd810cafea | |
Vftdan | 30b6534fc1 | |
Vftdan | 53c9ba3db5 | |
Vftdan | bedc6d9166 | |
Vftdan | 135c3642b0 | |
Vftdan | 6ebe98e94c | |
Vftdan | 46f8ecd68e |
6
Makefile
6
Makefile
|
@ -7,13 +7,13 @@ test-cpi2png: cpi2png
|
||||||
./cpi2png ./cpi-images/rat.cpi /tmp/rat.png
|
./cpi2png ./cpi-images/rat.cpi /tmp/rat.png
|
||||||
|
|
||||||
img2cpi: img2cpi.c cc-common.o dependencies/stb/stb_image.o dependencies/stb/stb_image_resize2.o
|
img2cpi: img2cpi.c cc-common.o dependencies/stb/stb_image.o dependencies/stb/stb_image_resize2.o
|
||||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS)
|
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS) $(CFLAGS) $(INCPATH)
|
||||||
|
|
||||||
cpi2png: cpi2png.c cc-common.o dependencies/stb/stb_image_write.o
|
cpi2png: cpi2png.c cc-common.o dependencies/stb/stb_image_write.o
|
||||||
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS)
|
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS) $(CFLAGS) $(INCPATH)
|
||||||
|
|
||||||
dependencies/stb/%.o: dependencies/stb/%.h
|
dependencies/stb/%.o: dependencies/stb/%.h
|
||||||
$(CC) -DSTB_IMAGE_IMPLEMENTATION -DSTB_IMAGE_RESIZE_IMPLEMENTATION -DSTB_IMAGE_WRITE_IMPLEMENTATION -x c $^ -c -o "$@"
|
$(CC) -DSTB_IMAGE_IMPLEMENTATION -DSTB_IMAGE_RESIZE_IMPLEMENTATION -DSTB_IMAGE_WRITE_IMPLEMENTATION -x c $^ -c -o "$@" $(CFLAGS)
|
||||||
|
|
||||||
wsvpn: wsvpn.o dependencies/mongoose/mongoose.o
|
wsvpn: wsvpn.o dependencies/mongoose/mongoose.o
|
||||||
$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o "$@"
|
$(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o "$@"
|
||||||
|
|
170
img2cpi.c
170
img2cpi.c
|
@ -16,6 +16,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 cc_char {
|
struct cc_char {
|
||||||
unsigned char character;
|
unsigned char character;
|
||||||
|
@ -23,8 +26,12 @@ struct cc_char {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct arguments {
|
struct arguments {
|
||||||
bool fast_mode;
|
|
||||||
int width, height;
|
int width, height;
|
||||||
|
enum conversion_mode {
|
||||||
|
CONVERSION_BLOCK,
|
||||||
|
CONVERSION_CHAR_PRECISE,
|
||||||
|
CONVERSION_CHAR_FAST,
|
||||||
|
} conversion_mode;
|
||||||
enum cpi_version {
|
enum cpi_version {
|
||||||
CPI_VERSION_AUTO,
|
CPI_VERSION_AUTO,
|
||||||
CPI_VERSION_RAW,
|
CPI_VERSION_RAW,
|
||||||
|
@ -51,9 +58,9 @@ struct arguments {
|
||||||
char *input_path;
|
char *input_path;
|
||||||
char *output_path;
|
char *output_path;
|
||||||
} args = {
|
} args = {
|
||||||
.fast_mode = false,
|
|
||||||
.width = 4 * 8 - 1, // 4x3 blocks screen
|
.width = 4 * 8 - 1, // 4x3 blocks screen
|
||||||
.height = 3 * 6 - 2,
|
.height = 3 * 6 - 2,
|
||||||
|
.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,
|
||||||
|
@ -101,7 +108,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);
|
||||||
|
@ -112,6 +119,14 @@ 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 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",
|
||||||
".tga", ".bmp", ".hdr", ".pnm", 0
|
".tga", ".bmp", ".hdr", ".pnm", 0
|
||||||
|
@ -125,7 +140,14 @@ static const struct optiondocs {
|
||||||
struct optiondocs_choice { char *value; char *doc; } *choices;
|
struct optiondocs_choice { char *value; char *doc; } *choices;
|
||||||
} optiondocs[] = {
|
} optiondocs[] = {
|
||||||
{ 'h', "help", 0, "Show help", 0 },
|
{ 'h', "help", 0, "Show help", 0 },
|
||||||
{ 'f', "fast", 0, "Use fast (old) method for picking characters and colors", 0 },
|
{ 'f', "fast", 0, "Use fast (old) method for picking characters and colors\n"
|
||||||
|
" DEPRECATED: use `--mode block` instead\n", 0 },
|
||||||
|
{ 'm', "mode", "mode", "Set conversion mode",
|
||||||
|
(struct optiondocs_choice[]) {
|
||||||
|
{ "block", "Use fast (old) method for picking characters and colors" },
|
||||||
|
{ "char-precise", "Select among all characters with maximum precision" },
|
||||||
|
{ "char-fast", "Select among all characters with reduced precision" },
|
||||||
|
{ 0, 0 } } },
|
||||||
{ 'W', "width", "width", "Width in characters", 0 },
|
{ 'W', "width", "width", "Width in characters", 0 },
|
||||||
{ 'h', "height", "height", "Height in characters", 0 },
|
{ 'h', "height", "height", "Height in characters", 0 },
|
||||||
{ 'P', "palette", "palette", "Use specific palette.\n"
|
{ 'P', "palette", "palette", "Use specific palette.\n"
|
||||||
|
@ -169,7 +191,7 @@ int main(int argc, char **argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct image *canvas;
|
struct image *canvas;
|
||||||
if (args.fast_mode) {
|
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);
|
||||||
|
@ -222,10 +244,19 @@ int main(int argc, char **argv) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.fast_mode) {
|
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
|
||||||
|
@ -254,6 +285,7 @@ bool parse_cmdline(int argc, char **argv) {
|
||||||
static struct option options[] = {
|
static struct option options[] = {
|
||||||
{ "help", no_argument, 0, 'h' },
|
{ "help", no_argument, 0, 'h' },
|
||||||
{ "fast", no_argument, 0, 'f' },
|
{ "fast", no_argument, 0, 'f' },
|
||||||
|
{ "mode", required_argument, 0, 'm' },
|
||||||
{ "width", required_argument, 0, 'W' },
|
{ "width", required_argument, 0, 'W' },
|
||||||
{ "height", required_argument, 0, 'H' },
|
{ "height", required_argument, 0, 'H' },
|
||||||
{ "cpi_version", required_argument, 0, 'V' },
|
{ "cpi_version", required_argument, 0, 'V' },
|
||||||
|
@ -264,7 +296,7 @@ bool parse_cmdline(int argc, char **argv) {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
int c = getopt_long(argc, argv, "hfW:H:V:p:P:", options, &option_index);
|
int c = getopt_long(argc, argv, "hfm:W:H:V:p:P:", options, &option_index);
|
||||||
if (c == -1) break;
|
if (c == -1) break;
|
||||||
if (c == 0) c = options[option_index].val;
|
if (c == 0) c = options[option_index].val;
|
||||||
if (c == '?') break;
|
if (c == '?') break;
|
||||||
|
@ -275,11 +307,23 @@ bool parse_cmdline(int argc, char **argv) {
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
break;
|
break;
|
||||||
case 'f': // --fast
|
case 'f': // --fast
|
||||||
args.fast_mode = true;
|
args.conversion_mode = CONVERSION_BLOCK;
|
||||||
|
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");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'm': // --mode
|
||||||
|
{
|
||||||
|
if (0 == strcmp(optarg, "block") || 0 == strcmp(optarg, "fast") || 0 == strcmp(optarg, "2x3")) {
|
||||||
|
args.conversion_mode = CONVERSION_BLOCK;
|
||||||
|
} else if (0 == strcmp(optarg, "char") || 0 == strcmp(optarg, "char-precise") || 0 == strcmp(optarg, "8x11") || 0 == strcmp(optarg, "6x9")) {
|
||||||
|
args.conversion_mode = CONVERSION_CHAR_PRECISE;
|
||||||
|
} else if (0 == strcmp(optarg, "char-fast") || 0 == strcmp(optarg, "8x11-fast") || 0 == strcmp(optarg, "6x9-fast")) {
|
||||||
|
args.conversion_mode = CONVERSION_CHAR_FAST;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'W': // --width
|
case 'W': // --width
|
||||||
args.width = atoi(optarg);
|
args.width = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
|
@ -593,7 +637,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++) {
|
||||||
|
@ -618,11 +662,9 @@ void convert_8x11(const struct image_pal *img, struct cc_char *characters) {
|
||||||
|
|
||||||
float min_diff = 0xffffff;
|
float min_diff = 0xffffff;
|
||||||
char closest_sym = 0x00, closest_color = 0xae;
|
char closest_sym = 0x00, closest_color = 0xae;
|
||||||
for (int sym = 0x01; sym <= 0xFF; sym++) {
|
for (int color = 0x00; color <= 0xff; color++) {
|
||||||
if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') {
|
{
|
||||||
continue;
|
const int sym = closest_chunk_color_symbol(&chunk_palette_diffs, color, precise);
|
||||||
}
|
|
||||||
for (int color = 0x00; color <= 0xff; color++) {
|
|
||||||
float difference = 0;
|
float difference = 0;
|
||||||
for (int oy = 0; oy < 11; oy++) {
|
for (int oy = 0; oy < 11; oy++) {
|
||||||
unsigned char sym_line = cc_font_atlas[sym][oy];
|
unsigned char sym_line = cc_font_atlas[sym][oy];
|
||||||
|
@ -779,3 +821,99 @@ struct palette *palette_k_means(const struct image *image, const struct palette
|
||||||
|
|
||||||
return palette;
|
return palette;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
for (int ox = 0; ox < 8; ox++) {
|
||||||
|
bool lit = sym_line & (0x80 >> ox);
|
||||||
|
if (lit) {
|
||||||
|
dist += (*weights)[oy][ox];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t closest_glyph_symbol_fast(const GlyphBitmap *target) {
|
||||||
|
uint8_t best = 0x01;
|
||||||
|
int best_dist = glyph_hamming_distance(target, &cc_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, &cc_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, &cc_font_atlas[best], weights);
|
||||||
|
for (int sym = 0x02; sym <= 0xFF; sym++) {
|
||||||
|
if (sym == '\t' || sym == '\n' || sym == '\r' || sym == '\x0e') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float dist = weighted_glyph_hamming_distance(target, &cc_font_atlas[sym], weights);
|
||||||
|
if (dist <= best_dist) {
|
||||||
|
best_dist = dist;
|
||||||
|
best = sym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
uint8_t fg = color_pair >> 4,
|
||||||
|
bg = color_pair & 0xF;
|
||||||
|
for (int oy = 0; oy < 11; oy++) {
|
||||||
|
uint8_t sym_line = 0;
|
||||||
|
for (int ox = 0; ox < 8; ox++) {
|
||||||
|
// We want lit to minimize distance, so lit should be trueish when background color is further from the pixel than foreground color
|
||||||
|
float dist_diff = (*chunk_palette_diffs)[ox][oy][bg] - (*chunk_palette_diffs)[ox][oy][fg];
|
||||||
|
uint8_t lit = dist_diff > 0;
|
||||||
|
sym_line |= lit << (7 - ox);
|
||||||
|
if (weights) {
|
||||||
|
(*weights)[oy][ox] = lit ? dist_diff : -dist_diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*result)[oy] = sym_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, precise ? &weights : NULL, chunk_palette_diffs, color_pair);
|
||||||
|
if (precise) {
|
||||||
|
return closest_glyph_symbol_precise(&glyph, &weights);
|
||||||
|
} else {
|
||||||
|
return closest_glyph_symbol_fast(&glyph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue