diff --git a/Makefile b/Makefile index 294fbd7..48b502e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ CLIBS := -lm INCLUDES := -Isrc OBJECTS := obj/stb_image.o obj/stb_image_resize.o \ obj/colors.o obj/args.o obj/image.o obj/commons.o \ - obj/mod_blocks.o + obj/mod_blocks.o obj/mod_braille.o all: lib asciify diff --git a/src/colors.c b/src/colors.c index c5caf71..0858637 100644 --- a/src/colors.c +++ b/src/colors.c @@ -73,6 +73,14 @@ palette_t c_palette_ansi_xterm = { } }; +int color_difference(rgba8 a, rgba8 b) +{ + int16_t dr = (int16_t)a.r - (int16_t)b.r; + int16_t dg = (int16_t)a.g - (int16_t)b.g; + int16_t db = (int16_t)a.b - (int16_t)b.b; + return dr * dr + dg * dg + db * db; +} + int closest_color(palette_t pal, rgba8 color) { int nearest = -1; @@ -80,10 +88,7 @@ int closest_color(palette_t pal, rgba8 color) for (int i = 0; i < pal.n_colors; 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; - int32_t distance = dr * dr + dg * dg + db * db; + int32_t distance = color_difference(pal_color, color); if (distance < min_distance) { min_distance = distance; diff --git a/src/colors.h b/src/colors.h index 0cdca34..ba64849 100644 --- a/src/colors.h +++ b/src/colors.h @@ -4,6 +4,8 @@ #include #include +#define PURE_BLACK ((rgba8){ 0, 0, 0, 0 }) + typedef struct { uint8_t r, g, b, a; } rgba8; @@ -19,6 +21,7 @@ extern palette_t c_palette_ansi_discord; extern palette_t c_palette_ansi_vga; extern palette_t c_palette_ansi_xterm; +int color_difference(rgba8 a, rgba8 b); int closest_color(palette_t pal, rgba8 color); rgba8 pal256_to_rgb(palette_t pal, int ndx); void make_pal256(palette_t *dst, palette_t ansi); diff --git a/src/main.c b/src/main.c index e83ba0f..e5ffc3b 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include #include "args.h" #include "mod_blocks.h" +#include "mod_braille.h" int main(int argc, char **argv) { @@ -23,6 +24,10 @@ int main(int argc, char **argv) mod_blocks_prepare(&state); mod_blocks_main(state); break; + case ASC_MOD_BRAILLE: + mod_braille_prepare(&state); + mod_braille_main(state); + break; default: break; } diff --git a/src/mod_blocks.c b/src/mod_blocks.c index 2eda702..4594c6c 100644 --- a/src/mod_blocks.c +++ b/src/mod_blocks.c @@ -19,7 +19,7 @@ void mod_blocks_prepare(asc_state_t *state) m_prepare_dither(state); } -void __start_output(asc_state_t state) +void __blk_start_output(asc_state_t state) { switch (state.args.out_format) { @@ -37,7 +37,7 @@ void __start_output(asc_state_t state) } } -void __end_output(asc_state_t state) +void __blk_end_output(asc_state_t state) { switch (state.args.out_format) { @@ -52,7 +52,7 @@ void __end_output(asc_state_t state) } } -void __start_line(FILE *fp, asc_format_t fmt, bool final) +void __blk_start_line(FILE *fp, asc_format_t fmt, bool final) { (void)final; switch (fmt) @@ -67,7 +67,7 @@ void __start_line(FILE *fp, asc_format_t fmt, bool final) } } -void __end_line(FILE *fp, asc_format_t fmt, asc_style_t stl, bool final) +void __blk_end_line(FILE *fp, asc_format_t fmt, asc_style_t stl, bool final) { switch (fmt) { @@ -84,7 +84,7 @@ void __end_line(FILE *fp, asc_format_t fmt, asc_style_t stl, bool final) } } -void __putc_bw(int ndx, FILE *fp, asc_format_t fmt, bool final) +void __blk_putc_bw(int ndx, FILE *fp, asc_format_t fmt, bool final) { switch (fmt) { @@ -100,7 +100,7 @@ void __putc_bw(int ndx, FILE *fp, asc_format_t fmt, bool final) } } -void __putc_ansi(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette_t pal) +void __blk_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; @@ -127,7 +127,7 @@ void __putc_ansi(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette } } -void __putc_256(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette_t pal) +void __blk_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; @@ -151,7 +151,7 @@ void __putc_256(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette_ } } -void __putc_truecolor(FILE *fp, asc_format_t fmt, bool final, rgba8 top, rgba8 bot) +void __blk_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; @@ -173,7 +173,7 @@ void __putc_truecolor(FILE *fp, asc_format_t fmt, bool final, rgba8 top, rgba8 b } } -void __put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) +void __blk_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; @@ -183,10 +183,10 @@ void __put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) { bool bri_top = calc_brightness(top) > 0.5; bool bri_bot = calc_brightness(bot) > 0.5; - if ( bri_top && bri_bot) __putc_bw(3, fp, fmt, final); - if (!bri_top && bri_bot) __putc_bw(2, fp, fmt, final); - if ( bri_top && !bri_bot) __putc_bw(1, fp, fmt, final); - if (!bri_top && !bri_bot) __putc_bw(0, fp, fmt, final); + if ( bri_top && bri_bot) __blk_putc_bw(3, fp, fmt, final); + if (!bri_top && bri_bot) __blk_putc_bw(2, fp, fmt, final); + if ( bri_top && !bri_bot) __blk_putc_bw(1, fp, fmt, final); + if (!bri_top && !bri_bot) __blk_putc_bw(0, fp, fmt, final); } break; case ASC_STL_ANSI_VGA: @@ -194,33 +194,34 @@ void __put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) 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; + switch (state.args.out_style) + { + case ASC_STL_ANSI_VGA: pal = c_palette_ansi_vga; break; + case ASC_STL_ANSI_XTERM: pal = c_palette_ansi_xterm; break; + case ASC_STL_ANSI_DISCORD: pal = c_palette_ansi_discord; break; + default: break; + } int index_top = closest_color(pal, top), index_bot = closest_color(pal, bot); - __putc_ansi(fp, fmt, final, index_top, index_bot, pal); + __blk_putc_ansi(fp, fmt, final, index_top, index_bot, pal); } break; case ASC_STL_256COLOR: { int index_top = closest_color(c_palette_256, top), index_bot = closest_color(c_palette_256, bot); - __putc_256(fp, fmt, final, index_top, index_bot, c_palette_ansi_vga); + __blk_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); + __blk_putc_truecolor(fp, fmt, final, top, bot); break; case ASC_STL_PALETTE: { palette_t *pal = state.palette; rgba8 pal_top = pal->palette[closest_color(*pal, top)]; rgba8 pal_bot = pal->palette[closest_color(*pal, bot)]; - __putc_truecolor(fp, fmt, final, pal_top, pal_bot); + __blk_putc_truecolor(fp, fmt, final, pal_top, pal_bot); } case ASC_STL_ENDL: break; @@ -230,18 +231,18 @@ void __put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) void mod_blocks_main(asc_state_t state) { image_t *img = state.image; - __start_output(state); + __blk_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); + __blk_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)); + __blk_put_pixel(state, top, bot, x >= (img->width - 1)); } - __end_line(state.out_file, state.args.out_format, state.args.out_style, final); + __blk_end_line(state.out_file, state.args.out_format, state.args.out_style, final); } - __end_output(state); + __blk_end_output(state); } diff --git a/src/mod_braille.c b/src/mod_braille.c new file mode 100644 index 0000000..710f4ad --- /dev/null +++ b/src/mod_braille.c @@ -0,0 +1,294 @@ +#include +#include "mod_braille.h" +#include "image.h" +#include "colors.h" +#include "commons.h" + +int __bra_best_match_i(rgba8 a, rgba8 b, rgba8 t); +void __bra_putc_raw(asc_state_t state, uint8_t ch); +void __bra_putc_esc(asc_state_t state, uint8_t ch); +void __bra_start_output(asc_state_t state); +void __bra_start_line(asc_state_t state, bool final); +void __bra_put_pixel(asc_state_t sta, rgba8 min, rgba8 max, uint8_t ch, bool final); +void __bra_putc_ansi(asc_state_t state, int i_min, int i_max, uint8_t ch, palette_t pal, bool final); +void __bra_putc_256(asc_state_t state, int i_min, int i_max, uint8_t ch, bool final); +void __bra_putc_true(asc_state_t state, rgba8 min, rgba8 max, uint8_t ch, bool final); +void __bra_end_line(asc_state_t state, bool final); +void __bra_end_output(asc_state_t state); + + +void mod_braille_prepare(asc_state_t *state) +{ + int w, h; + get_size_keep_aspect( + state->source_image->width, state->source_image->height, + state->args.width * 2, state->args.height * 4, &w, &h); + w = (w / 2) * 2; h = (h / 4) * 4; + state->image = image_resize(state->source_image, w, h); + m_prepare_dither(state); +} + +void mod_braille_main(asc_state_t state) +{ + image_t *img = state.image; + + uint8_t braille_char = 0x00; + rgba8 pix2x4[8]; + + rgba8 color_max, color_min; + int dist_max, dist_min, dist_min_d = 0xffffff, dist; + + __bra_start_output(state); + for (int y = 0; y < img->height; y += 4) + { + bool final = y >= (img->height - 4); + __bra_start_line(state, final); + for (int x = 0; x < img->width; x += 2) + { + pix2x4[0] = img->pixels[(x + 0) + (y + 0) * img->width]; + pix2x4[3] = img->pixels[(x + 1) + (y + 0) * img->width]; + pix2x4[1] = img->pixels[(x + 0) + (y + 1) * img->width]; + pix2x4[4] = img->pixels[(x + 1) + (y + 1) * img->width]; + pix2x4[2] = img->pixels[(x + 0) + (y + 2) * img->width]; + pix2x4[5] = img->pixels[(x + 1) + (y + 2) * img->width]; + pix2x4[6] = img->pixels[(x + 0) + (y + 3) * img->width]; + pix2x4[7] = img->pixels[(x + 1) + (y + 3) * img->width]; + color_max = color_min = pix2x4[0]; + + dist_max = 0; + dist_min = dist_min_d; + for (int i = 0; i < 8; i++) + { + dist = color_difference(pix2x4[i], PURE_BLACK); + if (dist < dist_min) + { + dist_min = dist; + color_min = pix2x4[i]; + } + if (dist > dist_max) + { + dist_max = dist; + color_max = pix2x4[i]; + } + } + + braille_char = 0x00; + for (int i = 0; i < 8; i++) + { + if (__bra_best_match_i(color_min, color_max, pix2x4[i]) != 0) + { + braille_char |= (1 << i); + } + } + __bra_put_pixel(state, color_min, color_max, braille_char, final); + } + __bra_end_line(state, final); + } + __bra_end_output(state); +} + +int __bra_best_match_i(rgba8 a, rgba8 b, rgba8 t) +{ + return color_difference(a, t) < color_difference(b, t) ? 0 : 1; +} + +void __bra_putc_raw(asc_state_t state, uint8_t ch) +{ + int ccode = 0x2800 | ch; + fputc(0xe2, state.out_file); + fputc(0x80 | ((ccode >> 6) & 0x3f), state.out_file); + fputc(0x80 | ((ccode >> 0) & 0x3f), state.out_file); +} + +void __bra_putc_esc(asc_state_t state, uint8_t ch) +{ + fprintf(state.out_file, "\\u28%02x", ch); +} + +void __bra_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 __bra_start_line(asc_state_t state, bool final) +{ + (void)final; + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(state.out_file, " [\n"); + break; + case ASC_FMT_HTML: + fprintf(state.out_file, ""); + default: + break; + } +} + +void __bra_put_pixel(asc_state_t state, rgba8 min, rgba8 max, uint8_t ch, bool final) +{ + switch (state.args.out_style) + { + case ASC_STL_ANSI_VGA: + case ASC_STL_ANSI_XTERM: + case ASC_STL_ANSI_DISCORD: + { + palette_t pal; + switch (state.args.out_style) + { + case ASC_STL_ANSI_VGA: pal = c_palette_ansi_vga; break; + case ASC_STL_ANSI_XTERM: pal = c_palette_ansi_xterm; break; + case ASC_STL_ANSI_DISCORD: pal = c_palette_ansi_discord; break; + default: break; + } + __bra_putc_ansi(state, + closest_color(pal, min), closest_color(pal, max), ch, pal, final); + } + break; + case ASC_STL_256COLOR: + { + __bra_putc_256(state, closest_color(c_palette_256, min), + closest_color(c_palette_256, max), ch, final); + } + break; + case ASC_STL_TRUECOLOR: + __bra_putc_true(state, min, max, ch, final); + break; + case ASC_STL_PALETTE: + { + palette_t *pal = state.palette; + __bra_putc_true(state, pal->palette[closest_color(*pal, min)], + pal->palette[closest_color(*pal, max)], ch, final); + } + break; + case ASC_STL_BLACKWHITE: + case ASC_STL_ENDL: + break; + } +} + +void __bra_putc_ansi(asc_state_t state, int i_min, int i_max, uint8_t ch, palette_t pal, bool final) +{ + rgba8 min_rgb = pal.palette[i_min], max_rgb = pal.palette[i_max]; + int min_int = min_rgb.r << 16 | min_rgb.g << 8 | min_rgb.b; + int max_int = max_rgb.r << 16 | max_rgb.g << 8 | max_rgb.b; + FILE *fp = state.out_file; + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(fp, "{ \"char\": \"\\u28%d\", \"fg\": %d, \"bg\": %d }", + ch, max_int, min_int); + if (!final) fprintf(fp, ", "); + break; + case ASC_FMT_HTML: + fprintf(fp, "", + max_rgb.r, max_rgb.g, max_rgb.b, min_rgb.r, min_rgb.g, min_rgb.b, + 0x2800 | ch); + break; + case ASC_FMT_ANSI: + fprintf(fp, "\033[%d;%dm", i_max + (i_max > 8 ? 82 : 30), i_min + (i_min > 8 ? 82 : 30)); + __bra_putc_raw(state, ch); + break; + case ASC_FMT_ENDL: + break; + } +} + +void __bra_putc_256(asc_state_t state, int i_min, int i_max, uint8_t ch, bool final) +{ + rgba8 min_rgb = c_palette_256.palette[i_min]; + rgba8 max_rgb = c_palette_256.palette[i_max]; + int min_int = min_rgb.r << 16 | min_rgb.g << 8 | min_rgb.b; + int max_int = max_rgb.r << 16 | max_rgb.g << 8 | max_rgb.b; + FILE *fp = state.out_file; + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(fp, "{ \"char\": \"\\u28%d\", \"fg\": %d, \"bg\": %d }", + ch, max_int, min_int); + if (!final) fprintf(fp, ", "); + break; + case ASC_FMT_HTML: + fprintf(fp, "", + max_rgb.r, max_rgb.g, max_rgb.b, min_rgb.r, min_rgb.g, min_rgb.b, + 0x2800 | ch); + break; + case ASC_FMT_ANSI: + fprintf(fp, "\033[38;5;%d;48;5;%dm", i_max, i_min); + __bra_putc_raw(state, ch); + break; + case ASC_FMT_ENDL: + break; + } +} + +void __bra_putc_true(asc_state_t state, rgba8 min, rgba8 max, uint8_t ch, bool final) +{ + int max_int = max.r << 16 | max.g << 8 | max.b; + int min_int = min.r << 16 | min.g << 8 | min.b; + FILE *fp = state.out_file; + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(fp, "{ \"char\": \"\\u%d\", \"fg\": %d, \"bg\": %d }", + 0x2800 | ch, max_int, min_int); + if (!final) fprintf(fp, ", "); + break; + case ASC_FMT_HTML: + fprintf(fp, "", + max.r, max.g, max.b, min.r, min.g, min.b, 0x2800 | ch); + break; + default: + fprintf(fp, "\033[38;2;%d;%d;%d;48;2;%d;%d;%dm", + max.r, max.g, max.b, min.r, min.g, min.b); + __bra_putc_raw(state, ch); + break; + } +} + +void __bra_end_line(asc_state_t state, bool final) +{ + switch (state.args.out_format) + { + case ASC_FMT_JSON: + fprintf(state.out_file, final ? " ]\n" : " ],\n"); + break; + case ASC_FMT_HTML: + fprintf(state.out_file, "\n"); + break; + default: + if (state.args.out_style != ASC_STL_BLACKWHITE) + fprintf(state.out_file, "\033[0m"); + fprintf(state.out_file, "\n"); + break; + } +} + +void __bra_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, "
&#%d;&#%d;&#%d;
\n"); + break; + default: + break; + } +} + diff --git a/src/mod_braille.h b/src/mod_braille.h new file mode 100644 index 0000000..9615640 --- /dev/null +++ b/src/mod_braille.h @@ -0,0 +1,10 @@ +#ifndef _MOD_BRAILLE_ +#define _MOD_BRAILLE_ +#include +#include "colors.h" +#include "args.h" + +void mod_braille_prepare(asc_state_t *state); +void mod_braille_main(asc_state_t state); + +#endif