diff --git a/src/colors.c b/src/colors.c index 08f430b..8d2a648 100644 --- a/src/colors.c +++ b/src/colors.c @@ -1,4 +1,5 @@ #include "colors.h" +#include palette_t c_palette_ansi_discord = { .n_colors = 8, @@ -58,13 +59,13 @@ palette_t c_palette_ansi_xterm = { } }; -int closest_color(palette_t *pal, rgba8 color) +int closest_color(palette_t pal, rgba8 color) { int nearest = -1; int32_t min_distance = 0x0fffffff; - for (int i = 0; i < pal->n_colors; i++) + for (int i = 0; i < pal.n_colors; i++) { - rgba8 pal_color = pal->palette[i]; + rgba8 pal_color = pal.palette[i]; int16_t dr = pal_color.r - color.r; int16_t dg = pal_color.g - color.g; int16_t db = pal_color.b - color.b; @@ -78,10 +79,55 @@ int closest_color(palette_t *pal, rgba8 color) return nearest; } +int closest_256(palette_t pal, rgba8 color) +{ + (void)pal; (void)color; + if (color.r == color.g && color.g == color.b) + { + if (color.r < 8) return 16; + if (color.r > 248) return 231; + return 232 + ceil((color.r - 8.0) / 247.0 * 24.0); + } + uint8_t oc = 16; + oc += 36 * ceil(color.r / 255.0 * 5.0); + oc += 6 * ceil(color.g / 255.0 * 5.0); + oc += ceil(color.b / 255.0 * 5.0); + return oc; +} + +rgba8 pal256_to_rgb(palette_t pal, int ndx) +{ + rgba8 out = { 0, 0, 0, 255 }; + if (ndx < 16) + { + return pal.palette[ndx]; + } + else if (ndx >= 232) + { + int l = (ndx - 232) * 255 / 24; + out.r = out.g = out.b = l; + } + else + { + ndx -= 16; + out.b = (ndx % 6) * 42; + ndx /= 6; + out.g = (ndx % 6) * 42; + ndx /= 6; + out.r = (ndx % 6) * 42; + } + return out; +} + +float calc_brightness(rgba8 c) +{ + return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b; +} + void load_palette_gpl(palette_t *pal, FILE *fp) { (void)pal; (void)fp; - // TODO: load GNU palette file + // TODO: load GIMP palette file } void load_palette_raw(palette_t *pal, FILE *fp) diff --git a/src/colors.h b/src/colors.h index ae85314..8d44927 100644 --- a/src/colors.h +++ b/src/colors.h @@ -16,9 +16,12 @@ extern palette_t c_palette_ansi_discord; extern palette_t c_palette_ansi_vga; extern palette_t c_palette_ansi_xterm; -int closest_color(palette_t *pal, rgba8 color); +int closest_color(palette_t pal, rgba8 color); +int closest_256(palette_t pal, rgba8 color); +rgba8 pal256_to_rgb(palette_t pal, int ndx); void load_palette_gpl(palette_t *pal, FILE *fp); void load_palette_raw(palette_t *pal, FILE *fp); void load_palette(palette_t *pal, FILE *fp); +float calc_brightness(rgba8 color); #endif diff --git a/src/main.c b/src/main.c index 06b92f2..e83ba0f 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,7 @@ #include #include #include "args.h" +#include "mod_blocks.h" int main(int argc, char **argv) { @@ -15,6 +16,16 @@ int main(int argc, char **argv) res = prepare_state(argc, argv, args, &state); if (res == 1) return 0; if (res < 0) return -res; + + switch (args.mode) + { + case ASC_MOD_BLOCKS: + mod_blocks_prepare(&state); + mod_blocks_main(state); + break; + default: + break; + } return 0; } diff --git a/src/mod_blocks.c b/src/mod_blocks.c index a76368d..06bb03d 100644 --- a/src/mod_blocks.c +++ b/src/mod_blocks.c @@ -1,11 +1,253 @@ +#include #include "mod_blocks.h" +#include "image.h" +#include "colors.h" + +#define BLOCK_TOP "\u2580" +#define BLOCK_BOT "\u2584" +#define BLOCK_FUL "\u2588" +#define BLOCK_NUL " " +#define BLOCK_TOP_ESC "\\u2580" +#define BLOCK_BOT_ESC "\\u2584" +#define BLOCK_FUL_ESC "\\u2588" +#define BLOCK_NUL_ESC " " void mod_blocks_prepare(asc_state_t *state) { - (void)state; + int w, h; + get_size_keep_aspect( + state->source_image->width, state->source_image->height, + state->args.width, state->args.height * 2, &w, &h); + h = (h / 2) * 2; + if (state->args.verbose) + { + fprintf(stderr, "src: %dx%d\n", + state->source_image->width, + state->source_image->height); + fprintf(stderr, "tgt: %dx%d\n", state->args.width, state->args.height * 2); + fprintf(stderr, "dst: %dx%d\n", w, h); + } + state->image = image_resize(state->source_image, w, h); + // TODO: dither +} + +void __start_output(asc_state_t state) +{ + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(state.out_file, "{\n"); + fprintf(state.out_file, " \"width\": %d,\n", state.image->width); + fprintf(state.out_file, " \"height\": %d,\n", state.image->height); + fprintf(state.out_file, " \"data\": ["); + break; + case ASC_FMT_HTML: + fprintf(state.out_file, "\n"); + break; + default: + break; + } +} + +void __end_output(asc_state_t state) +{ + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(state.out_file, " ]\n}"); + break; + case ASC_FMT_HTML: + fprintf(state.out_file, "
\n"); + break; + default: + break; + } +} + +void __start_line(FILE *fp, asc_format_t fmt, bool final) +{ + (void)final; + switch (fmt) + { + case ASC_FMT_JSON: + fprintf(fp, " [\n"); + break; + case ASC_FMT_HTML: + fprintf(fp, ""); + default: + break; + } +} + +void __end_line(FILE *fp, asc_format_t fmt, asc_style_t stl, bool final) +{ + switch (fmt) + { + case ASC_FMT_JSON: + fprintf(fp, final ? " ]\n" : " ],\n"); + break; + case ASC_FMT_HTML: + fprintf(fp, "\n"); + break; + default: + if (stl != ASC_STL_BLACKWHITE) fprintf(fp, "\033[0m"); + fprintf(fp, "\n"); + break; + } +} + +void __putc_bw(char *px, FILE *fp, asc_format_t fmt, bool final) +{ + switch (fmt) + { + case ASC_FMT_JSON: + fprintf(fp, final ? "\"%s\"" : "\"%s\", ", px); + break; + case ASC_FMT_HTML: + fprintf(fp, "%s", px); + break; + default: + fprintf(fp, "%s", px); + break; + } +} + +void __putc_ansi(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette_t pal) +{ + rgba8 top_rgb = pal.palette[ct], bot_rgb = pal.palette[cb]; + int top_int = top_rgb.r << 16 | top_rgb.g << 8 | top_rgb.b; + int bot_int = bot_rgb.r << 16 | bot_rgb.g << 8 | bot_rgb.b; + switch (fmt) + { + case ASC_FMT_JSON: + fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", + BLOCK_TOP_ESC, top_int, bot_int); + if (!final) fprintf(fp, ", "); + break; + case ASC_FMT_HTML: + fprintf(fp, "%s", + top_rgb.r, top_rgb.g, top_rgb.b, + bot_rgb.r, bot_rgb.g, bot_rgb.b, + BLOCK_TOP); + break; + default: + fprintf(fp, "\033[%d;%dm%s", + ct + (ct >= 8 ? 82 : 30), + cb + (cb >= 8 ? 92 : 40), + BLOCK_TOP); + break; + } +} + +void __putc_256(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette_t pal) +{ + rgba8 top_rgb = pal256_to_rgb(pal, ct), bot_rgb = pal256_to_rgb(pal, cb); + int top_int = top_rgb.r << 16 | top_rgb.g << 8 | top_rgb.b; + int bot_int = bot_rgb.r << 16 | bot_rgb.g << 8 | bot_rgb.b; + switch (fmt) + { + case ASC_FMT_JSON: + fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", + BLOCK_TOP_ESC, top_int, bot_int); + if (!final) fprintf(fp, ", "); + break; + case ASC_FMT_HTML: + fprintf(fp, "%s", + top_rgb.r, top_rgb.g, top_rgb.b, + bot_rgb.r, bot_rgb.g, bot_rgb.b, + BLOCK_TOP); + break; + default: + fprintf(fp, "\033[38;5;%d;48;5;%dm%s", ct, cb, BLOCK_TOP); + break; + } +} + +void __putc_truecolor(FILE *fp, asc_format_t fmt, bool final, rgba8 top, rgba8 bot) +{ + int top_int = top.r << 16 | top.g << 8 | top.b; + int bot_int = bot.r << 16 | bot.g << 8 | bot.b; + switch (fmt) + { + case ASC_FMT_JSON: + fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", + BLOCK_TOP_ESC, top_int, bot_int); + if (!final) fprintf(fp, ", "); + break; + case ASC_FMT_HTML: + fprintf(fp, "%s", + top.r, top.g, top.b, bot.r, bot.g, bot.b, BLOCK_TOP); + break; + default: + fprintf(fp, "\033[38;2;%d;%d;%d;48;2;%d;%d;%dm%s", + top.r, top.g, top.b, bot.r, bot.g, bot.b, BLOCK_TOP); + break; + } +} + +void __put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) +{ + asc_format_t fmt = state.args.out_format; + FILE *fp = state.out_file; + switch (state.args.out_style) + { + case ASC_STL_BLACKWHITE: + { + bool bri_top = calc_brightness(top) > 0.5; + bool bri_bot = calc_brightness(bot) > 0.5; + if ( bri_top && bri_bot) __putc_bw(BLOCK_FUL_ESC, fp, fmt, final); + if (!bri_top && bri_bot) __putc_bw(BLOCK_BOT_ESC, fp, fmt, final); + if ( bri_top && !bri_bot) __putc_bw(BLOCK_TOP_ESC, fp, fmt, final); + if (!bri_top && !bri_bot) __putc_bw(BLOCK_NUL_ESC, fp, fmt, final); + } + break; + case ASC_STL_ANSI_VGA: + case ASC_STL_ANSI_XTERM: + case ASC_STL_ANSI_DISCORD: + { + palette_t pal; + if(state.args.out_style == ASC_STL_ANSI_VGA) + pal = c_palette_ansi_vga; + if(state.args.out_style == ASC_STL_ANSI_XTERM) + pal = c_palette_ansi_xterm; + if(state.args.out_style == ASC_STL_ANSI_DISCORD) + pal = c_palette_ansi_discord; + int index_top = closest_color(pal, top), + index_bot = closest_color(pal, bot); + __putc_ansi(fp, fmt, final, index_top, index_bot, pal); + } + break; + case ASC_STL_256COLOR: + { + int index_top = closest_256(c_palette_ansi_vga, top), + index_bot = closest_256(c_palette_ansi_vga, bot); + __putc_256(fp, fmt, final, index_top, index_bot, c_palette_ansi_vga); + } + break; + case ASC_STL_TRUECOLOR: + { + __putc_truecolor(fp, fmt, final, top, bot); + } + default: + break; + } } void mod_blocks_main(asc_state_t state) { - (void)state; + image_t *img = state.image; + __start_output(state); + for (int y = 0; y < img->height; y += 2) + { + bool final = y >= (img->height - 2); + __start_line(state.out_file, state.args.out_format, final); + for (int x = 0; x < img->width; x++) + { + rgba8 top = img->pixels[x + y * img->width]; + rgba8 bot = img->pixels[x + (y + 1) * img->width]; + __put_pixel(state, top, bot, x >= (img->width - 1)); + } + __end_line(state.out_file, state.args.out_format, state.args.out_style, final); + } + __end_output(state); }