diff --git a/.oclint b/.oclint new file mode 100644 index 0000000..6f4b45b --- /dev/null +++ b/.oclint @@ -0,0 +1,7 @@ +rule-configurations: + - key: SHORT_VARIABLE_NAME + value: 1 + - key: LONG_VARIABLE_NAME + value: 25 + - key: LONG_LINE + value: 80 diff --git a/Makefile b/Makefile index 00403fe..a80e449 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -CFLAGS := -Wall -Wextra -Werror -pedantic -std=c99 +CFLAGS := -Wall -Wextra -Werror -pedantic -std=c99 -Wno-attributes CLIBS := -lm INCLUDES := -Isrc OBJECTS := obj/stb_image.o obj/stb_image_resize.o \ diff --git a/README.md b/README.md index cf53755..3fbff5e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# YAITAA - Yet Another Image To A(NSI) Art (converter). +# YAITAA - Yet Another Image To A(NSI) Art (converter) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/9fdbd9f8bc7843df9a6715b72b4da2fd)](https://www.codacy.com/gh/hatkidchan/yaitaa/dashboard?utm_source=github.com&utm_medium=referral&utm_content=hatkidchan/yaitaa&utm_campaign=Badge_Grade) @@ -17,7 +17,6 @@ recap: 5. Separate binaries for each mode 6. Git submodules. Yay. - ## Build instructions ### 1. Make sure that you have C compiler and make diff --git a/src/args.c b/src/args.c index 4d52284..7a78543 100644 --- a/src/args.c +++ b/src/args.c @@ -1,73 +1,85 @@ -#include "version.h" -#include "args.h" -#include "colors.h" #include #include #include #include #include +#include "version.h" +#include "args.h" +#include "colors.h" +#include "commons.h" +#include "mod_blocks.h" +#include "mod_braille.h" typedef struct { int value; char *strings[8]; char *description; + void *unused[2]; } __option_t; int __find_value(const __option_t *options, char *option); void __print_options(const __option_t *options); -const __option_t __mode_options[ASC_MOD_ENDL + 1] = { +const asc_handler_t asc_handlers[ASC_MOD_ENDL + 1] = { { ASC_MOD_BLOCKS, { "b", "blk", "blocks", NULL }, - "Box-drawing characters (\342\226\204) (default)" }, + "Box-drawing characters (\342\226\204) (default)", + mod_blocks_prepare, mod_blocks_main }, { ASC_MOD_BRAILLE, { "r", "brl", "braille", NULL }, - "Braille characters (literally stolen from MineOS)" }, + "Braille characters (literally stolen from MineOS)", + mod_braille_prepare, mod_braille_main }, { ASC_MOD_GRADIENT, { "g", "grd", "gradient", NULL }, - "Gradient of characters. No matching at all" }, + "Gradient of characters. No matching at all", + NULL, NULL }, { ASC_MOD_BRUTEFORCE, { "f", "guess", "bruteforce", NULL }, - "Looking for best possible character" }, - { -1, { NULL }, NULL } + "Looking for best possible character", + NULL, NULL }, + { -1, { NULL }, NULL, NULL, NULL } }; const __option_t __style_options[ASC_STL_ENDL + 1] = { { ASC_STL_BLACKWHITE, { "1", "bw", "black-white", "1bit", NULL }, - "1-bit black/white" }, + "1-bit black/white", { NULL, NULL } }, { ASC_STL_ANSI_VGA, - { "vga", "ansi-vga", NULL }, - "VGA palette" }, + { "vga", "ansi-vga" }, + "VGA palette", { NULL, NULL } }, { ASC_STL_ANSI_XTERM, { "xterm", "ansi-xterm", NULL }, - "xTerm palette. A bit more rough, compared to VGA" }, + "xTerm palette. A bit more rough, compared to VGA", { NULL, NULL } }, { ASC_STL_ANSI_DISCORD, { "discord", "ansi-discord", NULL }, - "Palette in Discord ANSI highlight" }, + "Palette in Discord ANSI highlight", { NULL, NULL } }, { ASC_STL_256COLOR, { "256", "pal256", "8bit", NULL }, - "256-color palette (default)" }, + "256-color palette (default)", { NULL, NULL } }, { ASC_STL_TRUECOLOR, { "true", "truecolor", "24bit", NULL }, - "24-bit RGB (TrueColor)" }, + "24-bit RGB (TrueColor)", { NULL, NULL } }, { ASC_STL_PALETTE, { "pal", "palette", "custom", NULL }, - "Custom palette (specified via -P). Either GIMP palette file or N*3 RGB pixels" }, - { -1, { NULL }, NULL } + "Custom palette (specified via -P). "\ + "Either GIMP palette file or N*3 RGB pixels", { NULL, NULL } }, + { -1, { NULL }, NULL, { NULL, NULL } } }; const __option_t __format_options[ASC_FMT_ENDL + 1] = { { ASC_FMT_ANSI, { "ansi", "raw", NULL }, - "Output, suitable for terminal (default)" }, + "Output, suitable for terminal (default)", + { NULL, NULL } }, { ASC_FMT_HTML, { "html", NULL }, - "Output as HTML table" }, + "Output as HTML table", + { NULL, NULL } }, { ASC_FMT_JSON, { "json", NULL }, - "Output as JSON 2D array of characters with properties" }, - { -1, { NULL }, NULL } + "Output as JSON 2D array of characters with properties", + { NULL, NULL } }, + { -1, { NULL }, NULL, { NULL, NULL } } }; void usage(int argc, char **argv) @@ -93,7 +105,7 @@ void usage(int argc, char **argv) fprintf(stderr, "-P PALETTE\tPath to palette file (when -S pal)\n"); fprintf(stderr, "\n\n"); fprintf(stderr, "Options for MODE:\n"); - __print_options(__mode_options); + __print_options((const __option_t*)asc_handlers); fprintf(stderr, "Options for STYLE:\n"); __print_options(__style_options); fprintf(stderr, "Options for FORMAT:\n"); @@ -112,6 +124,10 @@ void version(int argc, char **argv) fprintf(stderr, "See LICENSE file for details\n"); } +__attribute__((annotate("oclint:suppress[high cyclomatic complexity]"))) +__attribute__((annotate("oclint:suppress[high npath complexity]"))) +__attribute__((annotate("oclint:suppress[high ncss method]"))) +__attribute__((annotate("oclint:suppress[long method]"))) int parse_args(int argc, char **argv, asc_args_t *args) { args->input_filename = NULL; @@ -136,11 +152,9 @@ int parse_args(int argc, char **argv, asc_args_t *args) case 'h': usage(argc, argv); return 1; - break; case 'V': version(argc, argv); return 1; - break; case 'd': args->dither = true; break; @@ -160,7 +174,7 @@ int parse_args(int argc, char **argv, asc_args_t *args) break; case 'M': { - int val = __find_value(__mode_options, optarg); + int val = __find_value((const __option_t*)asc_handlers, optarg); if (val < 0) { fprintf(stderr, "Error: invalid mode '%s'\n", optarg); @@ -209,11 +223,14 @@ int parse_args(int argc, char **argv, asc_args_t *args) fprintf(stderr, "Error: Unknown parameter -%c\n", optopt); } return -2; + default: + fprintf(stderr, "Error: UNREACHABLE: getopt switch gone wrong\n"); + break; } } if (args->out_style == ASC_STL_PALETTE && args->palette_filename == NULL) { - fprintf(stderr, "Error: no palette file provided, but palette mode selected\n"); + fprintf(stderr, "Error: no palette file provided in palette mode\n"); return -3; } if (argc <= optind || argc < 2) @@ -225,11 +242,16 @@ int parse_args(int argc, char **argv, asc_args_t *args) return 0; } +__attribute__((annotate("oclint:suppress[high cyclomatic complexity]"))) +__attribute__((annotate("oclint:suppress[high npath complexity]"))) +__attribute__((annotate("oclint:suppress[high ncss method]"))) +__attribute__((annotate("oclint:suppress[long method]"))) int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state) { (void)argc; (void)argv; state->args = args; + // Loading image FILE *image_file; if ((image_file = fopen(args.input_filename, "rb")) == NULL || ferror(image_file) != 0) @@ -239,33 +261,48 @@ int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state) args.input_filename, err, strerror(err)); return -100 - err; } - state->source_image = image_load(image_file); fclose(image_file); - if (args.out_style == ASC_STL_PALETTE) + // Palette configuration + switch (args.out_style) { - FILE *fp = fopen(args.palette_filename, "rb"); - if (fp == NULL) - { - int err = errno; - fprintf(stderr, "Error: failed to open file %s for reading: %d: %s\n", - args.palette_filename, err, strerror(err)); - return -100 - err; - } - state->palette = calloc(1, sizeof(palette_t)); - if (!load_palette(state->palette, fp)) - { - fprintf(stderr, "Error: failed to read palette\n"); - fclose(fp); - return -7; - } - fclose(fp); + case ASC_STL_PALETTE: + { + FILE *fp = fopen(args.palette_filename, "rb"); + if (fp == NULL) + { + int err = errno; + fprintf(stderr, "Error: failed to open file %s for reading: %d: %s\n", + args.palette_filename, err, strerror(err)); + return -100 - err; + } + state->palette = calloc(1, sizeof(palette_t)); + if (!load_palette(state->palette, fp)) + { + fprintf(stderr, "Error: failed to read palette\n"); + fclose(fp); + return -7; + } + fclose(fp); + } + break; + case ASC_STL_256COLOR: + make_pal256(&c_palette_256, c_palette_ansi_vga); + state->palette = &c_palette_256; + break; + case ASC_STL_ANSI_VGA: + case ASC_STL_ANSI_XTERM: + case ASC_STL_ANSI_DISCORD: + case ASC_STL_BLACKWHITE: + state->palette = get_palette_by_id(args.out_style); + break; + case ASC_STL_TRUECOLOR: + case ASC_STL_ENDL: + break; } - if (args.out_style == ASC_STL_256COLOR) - make_pal256(&c_palette_256, c_palette_ansi_vga); - + // Output file configuration state->out_file = stdout; if (strcmp(args.output_filename, "-")) state->out_file = fopen(args.output_filename, "wb"); @@ -276,6 +313,7 @@ int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state) args.output_filename, err, strerror(err)); return -100 - err; } + return 0; } diff --git a/src/args.h b/src/args.h index 56ee7b3..c6490d4 100644 --- a/src/args.h +++ b/src/args.h @@ -69,6 +69,19 @@ typedef struct { FILE *out_file; } asc_state_t; +typedef void (*asc_module_initializer_fn)(asc_state_t *state); +typedef void (*asc_module_handler_fn)(asc_state_t state); + +typedef struct { + int id; + char *strings[8]; + char *description; + asc_module_initializer_fn prepare; + asc_module_handler_fn main; +} asc_handler_t; + +extern const asc_handler_t asc_handlers[ASC_MOD_ENDL + 1]; + void usage(int argc, char **argv); int parse_args(int argc, char **argv, asc_args_t *args); int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state); diff --git a/src/colors.c b/src/colors.c index 0858637..7c446e8 100644 --- a/src/colors.c +++ b/src/colors.c @@ -119,6 +119,11 @@ rgba8 pal256_to_rgb(palette_t pal, int ndx) return out; } +rgba8 clamp_to_pal(palette_t pal, rgba8 color) +{ + return pal.palette[closest_color(pal, color)]; +} + void make_pal256(palette_t *dst, palette_t ansi) { if (dst->n_colors == 256) return; @@ -175,12 +180,7 @@ bool load_palette(palette_t *pal, FILE *fp) if (fread(head, sizeof(char), 12, fp) < 12) return false; if (fseek(fp, 0, SEEK_SET) != 0) return false; if (!strncmp(head, "GIMP Palette", 12)) - { return load_palette_gpl(pal, fp); - } - else - { - return load_palette_raw(pal, fp); - } + return load_palette_raw(pal, fp); } diff --git a/src/colors.h b/src/colors.h index 9ba5d68..07a2c44 100644 --- a/src/colors.h +++ b/src/colors.h @@ -22,6 +22,7 @@ #include #define PURE_BLACK ((rgba8){ 0, 0, 0, 0 }) +#define RGBN(c) (c.r << 16 | c.g << 8 | c.b) typedef struct { uint8_t r, g, b, a; @@ -41,6 +42,7 @@ 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); +rgba8 clamp_to_pal(palette_t pal, rgba8 color); void make_pal256(palette_t *dst, palette_t ansi); bool load_palette_gpl(palette_t *pal, FILE *fp); bool load_palette_raw(palette_t *pal, FILE *fp); diff --git a/src/commons.c b/src/commons.c index 4934930..740e7ae 100644 --- a/src/commons.c +++ b/src/commons.c @@ -3,41 +3,27 @@ #include #include -void m_prepare_dither(asc_state_t *state) +void m_prepare_dither(asc_state_t *sta) { - if (state->args.dither) + image_t *res = NULL; + switch (sta->args.out_style) { - image_t *res = NULL; - switch (state->args.out_style) - { - case ASC_STL_BLACKWHITE: - res = image_dither(state->image, c_palette_bw); - break; - case ASC_STL_ANSI_VGA: - res = image_dither(state->image, c_palette_ansi_vga); - break; - case ASC_STL_ANSI_XTERM: - res = image_dither(state->image, c_palette_ansi_xterm); - break; - case ASC_STL_ANSI_DISCORD: - res = image_dither(state->image, c_palette_ansi_discord); - break; - case ASC_STL_256COLOR: - res = image_dither(state->image, c_palette_256); - break; - case ASC_STL_PALETTE: - res = image_dither(state->image, *state->palette); - break; - case ASC_STL_TRUECOLOR: - case ASC_STL_ENDL: - break; - } - if (res != NULL) - { - image_unload(state->image); - state->image = res; - } + case ASC_STL_BLACKWHITE: + case ASC_STL_ANSI_VGA: + case ASC_STL_ANSI_XTERM: + case ASC_STL_ANSI_DISCORD: + case ASC_STL_256COLOR: + res = image_dither(sta->image, *get_palette_by_id(sta->args.out_style)); + break; + case ASC_STL_PALETTE: + res = image_dither(sta->image, *sta->palette); + break; + case ASC_STL_TRUECOLOR: + case ASC_STL_ENDL: + return; } + image_unload(sta->image); + sta->image = res; } void c_fatal(int code, const char *reason) @@ -45,3 +31,18 @@ void c_fatal(int code, const char *reason) fprintf(stderr, "Error: %s\n", reason); exit(code); } + +palette_t *get_palette_by_id(asc_style_t stl) +{ + palette_t *pal = &c_palette_bw; + switch (stl) + { + case ASC_STL_BLACKWHITE: pal = &c_palette_bw; break; + 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; + case ASC_STL_256COLOR: pal = &c_palette_256; break; + default: c_fatal(9, "[UNREACH] Palette is unset"); break; + } + return pal; +} diff --git a/src/commons.h b/src/commons.h index 962808e..9062693 100644 --- a/src/commons.h +++ b/src/commons.h @@ -20,7 +20,10 @@ #include "args.h" +#define CLAMP(min, val, max) ((val)>(max)?(max):((val)<(min)?(min):(val))) + void c_fatal(int code, const char *reason); void m_prepare_dither(asc_state_t *state); +palette_t *get_palette_by_id(asc_style_t stl); #endif diff --git a/src/fmt_strings.h b/src/fmt_strings.h new file mode 100644 index 0000000..881e63a --- /dev/null +++ b/src/fmt_strings.h @@ -0,0 +1,26 @@ +#ifndef _FMT_STRINGS_H +#define _FMT_STRINGS_H + +#define S_JSON_HEAD "{\n \"width\": %d,\n \"height\": %d,\n \"data\": [\n" +#define S_JSON_LSTA " [\n" +#define S_JSON_PRGB "{ \"char\": \"\\u%04x\", \"fg\": %d, \"bg\": %d }" +#define S_JSON_PBLK "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }" +#define S_JSON_LEND " ],\n" +#define S_JSON_LEND_FINAL " ]\n" +#define S_JSON_TAIL "}" + +#define S_HTML_HEAD "\n" +#define S_HTML_LSTA "" +#define S_HTML_PCBR "" +#define S_HTML_PBLK "" +#define S_HTML_LEND "\n" +#define S_HTML_TAIL "
&#%d;%s
" + +#define S_ANSI "\033[%d;%dm" +#define S_ANSI_S "\033[%d;%dm%s" +#define S_ANSI_RGB "\033[38;2;%d;%d;%d;48;2;%d;%d;%dm" +#define S_ANSI_256 "\033[38;5;%d;48;5;%dm" + +#endif diff --git a/src/image.c b/src/image.c index 450e6bd..ac36ef9 100644 --- a/src/image.c +++ b/src/image.c @@ -1,3 +1,4 @@ +#include "commons.h" #include "image.h" #include "stb_image.h" #include "stb_image_resize.h" @@ -29,22 +30,19 @@ image_t *image_resize(image_t *img, int width, int height) return res; } +__attribute__((annotate("oclint:suppress[high cyclomatic complexity]"))) void __dither_update_pixel(image_t *img, int x, int y, int err[3], float bias) { if (x < 0 || x >= img->width || y < 0 || y >= img->height) return; int i = x + y * img->width; rgba8 pix = img->pixels[i]; int dst[3] = { pix.r, pix.g, pix.b }; - dst[0] += (int)(((float)err[0]) * bias); - dst[1] += (int)(((float)err[1]) * bias); - dst[2] += (int)(((float)err[2]) * bias); - img->pixels[i].r = (dst[0] > 255 ? 255 : (dst[0] < 0 ? 0 : dst[0])); - img->pixels[i].g = (dst[1] > 255 ? 255 : (dst[1] < 0 ? 0 : dst[1])); - img->pixels[i].b = (dst[2] > 255 ? 255 : (dst[2] < 0 ? 0 : dst[2])); + img->pixels[i].r = CLAMP(0, dst[0] + (int)((float)err[0] * bias), 255); + img->pixels[i].g = CLAMP(0, dst[1] + (int)((float)err[1] * bias), 255); + img->pixels[i].b = CLAMP(0, dst[2] + (int)((float)err[2] * bias), 255); } -// TODO: make it work better sometime in future (for some reason it sucks rn) image_t *image_dither_fn(image_t *img, dither_quantizer_t quantize, void *param) { image_t *res = calloc(1, sizeof(image_t)); diff --git a/src/image.h b/src/image.h index 3d2f127..3e28073 100644 --- a/src/image.h +++ b/src/image.h @@ -23,7 +23,7 @@ typedef struct { int width; int height; - int errno; + int err; rgba8 *pixels; } image_t; diff --git a/src/main.c b/src/main.c index 552e5ae..5c379ac 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,7 @@ #include "args.h" #include "mod_blocks.h" #include "mod_braille.h" +#include "commons.h" int main(int argc, char **argv) { @@ -35,25 +36,11 @@ int main(int argc, char **argv) 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; - case ASC_MOD_BRAILLE: - mod_braille_prepare(&state); - mod_braille_main(state); - break; - case ASC_MOD_GRADIENT: - fprintf(stderr, "Error: ASC_MOD_GRADIENT is not implemented yet\n"); - break; - case ASC_MOD_BRUTEFORCE: - fprintf(stderr, "Error: ASC_MOD_BRUTEFORCE is not implemented yet\n"); - break; - case ASC_MOD_ENDL: - break; - } - + asc_handler_t handler = asc_handlers[args.mode]; + if (handler.prepare == NULL) + c_fatal(12, "this mode is not implemented yet"); + + handler.prepare(&state); + handler.main(state); return 0; } diff --git a/src/mod_blocks.c b/src/mod_blocks.c index 133ce41..f63b86a 100644 --- a/src/mod_blocks.c +++ b/src/mod_blocks.c @@ -3,6 +3,7 @@ #include "image.h" #include "colors.h" #include "commons.h" +#include "fmt_strings.h" const char *BLOCKS[4] = { " ", "\xe2\x96\x80", "\xe2\x96\x84", "\xe2\x96\x88" }; const char *BLOCKS_ESC[4] = { " ", "\\u2580", "\\u2584", "\\u2588" }; @@ -16,7 +17,8 @@ void mod_blocks_prepare(asc_state_t *state) state->args.width, state->args.height * 2, &w, &h); h = (h / 2) * 2; state->image = image_resize(state->source_image, w, h); - m_prepare_dither(state); + if (state->args.dither) + m_prepare_dither(state); } void __blk_start_output(asc_state_t state) @@ -55,16 +57,10 @@ void __blk_end_output(asc_state_t state) void __blk_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; - } + if (fmt == ASC_FMT_JSON) + fprintf(fp, S_JSON_LSTA); + else if (fmt == ASC_FMT_HTML) + fprintf(fp, S_HTML_LSTA); } void __blk_end_line(FILE *fp, asc_format_t fmt, asc_style_t stl, bool final) @@ -108,20 +104,15 @@ void __blk_putc_ansi(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, pal switch (fmt) { case ASC_FMT_JSON: - fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", - BLOCKS_ESC[1], top_int, bot_int); + fprintf(fp, S_JSON_PBLK, BLOCKS_ESC[1], 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, - BLOCKS[1]); + fprintf(fp, S_HTML_PBLK, top_rgb.r, top_rgb.g, top_rgb.b, + bot_rgb.r, bot_rgb.g, bot_rgb.b, BLOCKS[1]); break; default: - fprintf(fp, "\033[%d;%dm%s", - ct + (ct >= 8 ? 82 : 30), - cb + (cb >= 8 ? 92 : 40), + fprintf(fp, S_ANSI_S, ct + (ct >= 8 ? 82 : 30), cb + (cb >= 8 ? 92 : 40), BLOCKS[1]); break; } @@ -193,17 +184,10 @@ void __blk_put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) 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: c_fatal(9, "[UNREACH] Palette is unset"); return; - } - int index_top = closest_color(pal, top), - index_bot = closest_color(pal, bot); - __blk_putc_ansi(fp, fmt, final, index_top, index_bot, pal); + palette_t *pal = get_palette_by_id(state.args.out_style); + int index_top = closest_color(*pal, top), + index_bot = closest_color(*pal, bot); + __blk_putc_ansi(fp, fmt, final, index_top, index_bot, *pal); } break; case ASC_STL_256COLOR: @@ -219,10 +203,10 @@ void __blk_put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) 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)]; - __blk_putc_truecolor(fp, fmt, final, pal_top, pal_bot); + __blk_putc_truecolor(fp, fmt, final, + clamp_to_pal(*pal, top), clamp_to_pal(*pal, bot)); } + break; case ASC_STL_ENDL: break; } diff --git a/src/mod_braille.c b/src/mod_braille.c index e950b4f..fac8f81 100644 --- a/src/mod_braille.c +++ b/src/mod_braille.c @@ -3,18 +3,31 @@ #include "image.h" #include "colors.h" #include "commons.h" +#include "fmt_strings.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 __bra_putc_raw(asc_state_t s, uint8_t ch); +void __bra_putc_esc(asc_state_t s, uint8_t ch); +void __bra_start_output(asc_state_t s); +void __bra_start_line(asc_state_t s, bool final); +void __bra_put_pixel(asc_state_t s, rgba8 bg, rgba8 fg, uint8_t ch, bool final); +void __bra_putc_ansi(asc_state_t s, int bg, int fg, uint8_t ch, palette_t pal, bool final); +void __bra_putc_256(asc_state_t s, int bg, int fg, uint8_t ch, bool final); +void __bra_putc_true(asc_state_t s, rgba8 bg, rgba8 fg, uint8_t ch, bool final); +void __bra_end_line(asc_state_t s, bool final); +void __bra_end_output(asc_state_t s); + +void __bra_update2x4(image_t *img, rgba8 block[8], int x, int y) +{ + block[0] = img->pixels[(x + 0) + (y + 0) * img->width]; + block[3] = img->pixels[(x + 1) + (y + 0) * img->width]; + block[1] = img->pixels[(x + 0) + (y + 1) * img->width]; + block[4] = img->pixels[(x + 1) + (y + 1) * img->width]; + block[2] = img->pixels[(x + 0) + (y + 2) * img->width]; + block[5] = img->pixels[(x + 1) + (y + 2) * img->width]; + block[6] = img->pixels[(x + 0) + (y + 3) * img->width]; + block[7] = img->pixels[(x + 1) + (y + 3) * img->width]; +} void mod_braille_prepare(asc_state_t *state) @@ -25,7 +38,8 @@ void mod_braille_prepare(asc_state_t *state) 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); + if (state->args.dither) + m_prepare_dither(state); } void mod_braille_main(asc_state_t state) @@ -33,7 +47,7 @@ void mod_braille_main(asc_state_t state) image_t *img = state.image; uint8_t braille_char = 0x00; - rgba8 pix2x4[8]; + rgba8 block[8]; rgba8 color_max, color_min; int dist_max, dist_min, dist_min_d = 0xffffff, dist; @@ -45,37 +59,30 @@ void mod_braille_main(asc_state_t state) __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]; + __bra_update2x4(img, block, x, y); + color_max = color_min = block[0]; dist_max = 0; dist_min = dist_min_d; for (int i = 0; i < 8; i++) { - dist = color_difference(pix2x4[i], PURE_BLACK); + dist = color_difference(block[i], PURE_BLACK); if (dist < dist_min) { dist_min = dist; - color_min = pix2x4[i]; + color_min = block[i]; } if (dist > dist_max) { dist_max = dist; - color_max = pix2x4[i]; + color_max = block[i]; } } braille_char = 0x00; for (int i = 0; i < 8; i++) { - if (__bra_best_match_i(color_min, color_max, pix2x4[i]) != 0) + if (__bra_best_match_i(color_min, color_max, block[i]) != 0) { braille_char |= (1 << i); } @@ -107,72 +114,45 @@ void __bra_putc_esc(asc_state_t state, uint8_t 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 / 2); - fprintf(state.out_file, " \"height\": %d,\n", state.image->height / 4); - fprintf(state.out_file, " \"data\": ["); - break; - case ASC_FMT_HTML: - fprintf(state.out_file, "\n"); - break; - default: - break; - } + int w = state.image->width / 2, h = state.image->height / 4; + if (state.args.out_format == ASC_FMT_JSON) + fprintf(state.out_file, S_JSON_HEAD, w, h); + else if (state.args.out_format == ASC_FMT_HTML) + fprintf(state.out_file, S_HTML_HEAD); } 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; - } + if (state.args.out_format == ASC_FMT_JSON) + fprintf(state.out_file, S_JSON_LSTA); + else if (state.args.out_format == ASC_FMT_HTML) + fprintf(state.out_file, S_HTML_LSTA); } -void __bra_put_pixel(asc_state_t state, rgba8 min, rgba8 max, uint8_t ch, bool final) +void __bra_put_pixel(asc_state_t s, rgba8 min, rgba8 max, uint8_t ch, bool fin) { - switch (state.args.out_style) + switch (s.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: c_fatal(9, "[UNREACH] Palette is unset"); return; - } - __bra_putc_ansi(state, - closest_color(pal, min), closest_color(pal, max), ch, pal, final); + palette_t pal = *get_palette_by_id(s.args.out_style); + __bra_putc_ansi(s, + closest_color(pal, min), closest_color(pal, max), ch, pal, fin); } break; case ASC_STL_256COLOR: - { - __bra_putc_256(state, closest_color(c_palette_256, min), - closest_color(c_palette_256, max), ch, final); - } + __bra_putc_256(s, closest_color(c_palette_256, min), + closest_color(c_palette_256, max), ch, fin); break; case ASC_STL_TRUECOLOR: - __bra_putc_true(state, min, max, ch, final); + __bra_putc_true(s, min, max, ch, fin); 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); - } + __bra_putc_true(s, clamp_to_pal(*s.palette, min), + clamp_to_pal(*s.palette, max), ch, fin); break; case ASC_STL_BLACKWHITE: case ASC_STL_ENDL: @@ -180,115 +160,93 @@ void __bra_put_pixel(asc_state_t state, rgba8 min, rgba8 max, uint8_t ch, bool f } } -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_ansi +(asc_state_t s, int bgi, int fgi, uint8_t ch, palette_t pal, bool fin) { - 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) + rgba8 bg = pal.palette[bgi], fg = pal.palette[fgi]; + FILE *fp = s.out_file; + switch (s.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, ", "); + fprintf(fp, S_JSON_PRGB, ch, RGBN(fg), RGBN(bg)); + if (!fin) 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); + fprintf(fp, S_HTML_PCBR, fg.r, fg.g, fg.b, + bg.r, bg.g, bg.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); + fprintf(fp, S_ANSI, fgi + (fgi > 8 ? 82 : 30), bgi + (bgi > 8 ? 82 : 30)); + __bra_putc_raw(s, 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) +void __bra_putc_256(asc_state_t s, int bgi, int fgi, 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) + rgba8 bg = c_palette_256.palette[bgi]; + rgba8 fg = c_palette_256.palette[fgi]; + FILE *fp = s.out_file; + switch (s.args.out_format) { case ASC_FMT_JSON: - fprintf(fp, "{ \"char\": \"\\u28%d\", \"fg\": %d, \"bg\": %d }", - ch, max_int, min_int); + fprintf(fp, S_JSON_PRGB, 0x2800 | ch, RGBN(fg), RGBN(bg)); 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); + fprintf(fp, S_HTML_PCBR, fg.r, fg.g, fg.b, bg.r, bg.g, bg.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); + fprintf(fp, S_ANSI_256, fgi, bgi); + __bra_putc_raw(s, ch); break; case ASC_FMT_ENDL: break; } } -void __bra_putc_true(asc_state_t state, rgba8 min, rgba8 max, uint8_t ch, bool final) +void __bra_putc_true(asc_state_t s, rgba8 bg, rgba8 fg, uint8_t ch, bool fin) { - 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) + int max_int = fg.r << 16 | fg.g << 8 | fg.b; + int min_int = bg.r << 16 | bg.g << 8 | bg.b; + FILE *fp = s.out_file; + if (s.args.out_format == ASC_FMT_JSON) { - 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; + fprintf(fp, S_JSON_PRGB, 0x2800 | ch, max_int, min_int); + if (!fin) fprintf(fp, ", "); + } + else if (s.args.out_format == ASC_FMT_HTML) + { + fprintf(fp, S_HTML_PCBR, fg.r, fg.g, fg.b, bg.r, bg.g, bg.b, 0x2800 | ch); + } + else + { + fprintf(fp, S_ANSI_RGB, fg.r, fg.g, fg.b, bg.r, bg.g, bg.b); + __bra_putc_raw(s, ch); } } void __bra_end_line(asc_state_t state, bool final) { - switch (state.args.out_format) + if (state.args.out_format == ASC_FMT_JSON) + fprintf(state.out_file, final ? S_JSON_LEND_FINAL : S_JSON_LEND); + else if (state.args.out_format == ASC_FMT_HTML) + fprintf(state.out_file, S_HTML_LEND); + else { - 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; + if (state.args.out_style != ASC_STL_BLACKWHITE) + fprintf(state.out_file, "\033[0m"); + fprintf(state.out_file, "\n"); } } 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; - } + if (state.args.out_format == ASC_FMT_JSON) + fprintf(state.out_file, " ]\n}"); + else if (state.args.out_format == ASC_FMT_HTML) + fprintf(state.out_file, "\n"); }