diff --git a/src/args.c b/src/args.c index 09c7989..be6309b 100644 --- a/src/args.c +++ b/src/args.c @@ -222,6 +222,26 @@ int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state) state->source_image = image_load(image_file); fclose(image_file); + if (args.out_style == 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); + } + state->out_file = stdout; if (strcmp(args.output_filename, "-")) state->out_file = fopen(args.output_filename, "wb"); diff --git a/src/colors.c b/src/colors.c index 8d2a648..c88646e 100644 --- a/src/colors.c +++ b/src/colors.c @@ -1,5 +1,15 @@ #include "colors.h" #include +#include +#include + +palette_t c_palette_bw = { + .n_colors = 2, + .palette = { + { 0, 0, 0, 0 }, + { 255, 255, 255, 0 } + } +}; palette_t c_palette_ansi_discord = { .n_colors = 8, @@ -124,21 +134,52 @@ 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) +bool load_palette_gpl(palette_t *pal, FILE *fp) { - (void)pal; (void)fp; - // TODO: load GIMP palette file + static char buf[8192]; + fgets(buf, 8192, fp); // GIMP Palette + fgets(buf, 8192, fp); // Name: %s + fgets(buf, 8192, fp); // Columns: %d + + pal->n_colors = 0; + while (!feof(fp) && pal->n_colors < 256) + { + fgets(buf, 8192, fp); + int r, g, b; + if (sscanf(buf, "%d %d %d", &r, &g, &b) == 3) + { + pal->palette[pal->n_colors].r = r; + pal->palette[pal->n_colors].g = g; + pal->palette[pal->n_colors].b = b; + pal->n_colors++; + } + } + return true; } -void load_palette_raw(palette_t *pal, FILE *fp) +bool load_palette_raw(palette_t *pal, FILE *fp) { - (void)pal; (void)fp; - // TODO: load raw palette file + while (!feof(fp)) + { + size_t sz = fread(&pal->palette[pal->n_colors++], 1, sizeof(rgba8), fp); + if (sz == 0 && feof(fp)) break; + if (sz != sizeof(rgba8)) return false; + } + return true; } -void load_palette(palette_t *pal, FILE *fp) +bool load_palette(palette_t *pal, FILE *fp) { - (void)pal; (void)fp; - // TODO: guess palette file type and load it + static char head[16]; + 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); + } } diff --git a/src/colors.h b/src/colors.h index 8d44927..c6968b0 100644 --- a/src/colors.h +++ b/src/colors.h @@ -2,6 +2,7 @@ #define _COLORS_H_ #include #include +#include typedef struct { uint8_t r, g, b, a; @@ -9,9 +10,10 @@ typedef struct { typedef struct { int n_colors; - rgba8 palette[255]; + rgba8 palette[256]; } palette_t; +extern palette_t c_palette_bw; extern palette_t c_palette_ansi_discord; extern palette_t c_palette_ansi_vga; extern palette_t c_palette_ansi_xterm; @@ -19,9 +21,9 @@ extern palette_t c_palette_ansi_xterm; 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); +bool load_palette_gpl(palette_t *pal, FILE *fp); +bool load_palette_raw(palette_t *pal, FILE *fp); +bool load_palette(palette_t *pal, FILE *fp); float calc_brightness(rgba8 color); #endif diff --git a/src/image.c b/src/image.c index 6a9f33b..e9b696d 100644 --- a/src/image.c +++ b/src/image.c @@ -16,7 +16,6 @@ image_t *image_load(FILE *file) return img; } - image_t *image_resize(image_t *img, int width, int height) { image_t *res = calloc(1, sizeof(image_t)); @@ -30,6 +29,64 @@ image_t *image_resize(image_t *img, int width, int height) return res; } +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; + rgba8 pix = img->pixels[x + y * img->width]; + 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); + pix.r = (dst[0] > 255 ? 255 : (dst[0] < 0 ? 0 : dst[0])); + pix.g = (dst[1] > 255 ? 255 : (dst[1] < 0 ? 0 : dst[1])); + pix.b = (dst[2] > 255 ? 255 : (dst[2] < 0 ? 0 : dst[2])); + memcpy(&img->pixels[x + y * img->width], &pix, sizeof(rgba8)); +} + + +// 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)); + int w = res->width = img->width; + int h = res->height = img->height; + res->pixels = calloc(img->width * img->height, sizeof(rgba8)); + memcpy(res->pixels, img->pixels, img->width * img->height * sizeof(rgba8)); + for (int y = 0; y < h; y++) + { + for (int x = 0; x < w; x++) + { + rgba8 old = res->pixels[x + y * w]; + rgba8 new = quantize(old, param); + res->pixels[x + y * w].r = new.r; + res->pixels[x + y * w].g = new.g; + res->pixels[x + y * w].b = new.b; + int err[3] = { + (int)old.r - (int)new.r, + (int)old.g - (int)new.g, + (int)old.b - (int)new.b + }; + __dither_update_pixel(res, x + 1, y , err, 7.0f / 16.0f); + __dither_update_pixel(res, x - 1, y + 1, err, 3.0f / 16.0f); + __dither_update_pixel(res, x , y + 1, err, 5.0f / 16.0f); + __dither_update_pixel(res, x + 1, y + 1, err, 1.0f / 16.0f); + } + } + return res; +} + +rgba8 __image_quantize_pal(rgba8 clr, void *param) +{ + palette_t palette = *(palette_t *)param; + int ndx = closest_color(palette, clr); + return palette.palette[ndx]; +} + +image_t *image_dither(image_t *img, palette_t pal) +{ + return image_dither_fn(img, __image_quantize_pal, &pal); +} + void image_unload(image_t *img) { free(img->pixels); diff --git a/src/image.h b/src/image.h index e78dc18..a11111c 100644 --- a/src/image.h +++ b/src/image.h @@ -10,8 +10,13 @@ typedef struct { rgba8 *pixels; } image_t; +typedef rgba8 (*dither_quantizer_t)(rgba8 clr, void *param); + + image_t *image_load(FILE *file); image_t *image_resize(image_t *img, int width, int height); +image_t *image_dither(image_t *img, palette_t palette); +image_t *image_dither_fn(image_t *img, dither_quantizer_t quantize, void *p); void image_unload(image_t *img); void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh); diff --git a/src/mod_blocks.c b/src/mod_blocks.c index 06bb03d..baa31fc 100644 --- a/src/mod_blocks.c +++ b/src/mod_blocks.c @@ -3,14 +3,16 @@ #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 " " +const char *BLOCKS[4] = { " ", "\xe2\x96\x80", "\xe2\x96\x84", "\xe2\x96\x88" }; +const char *BLOCKS_ESC[4] = { " ", "\\u2580", "\\u2584", "\\u2588" }; + + +rgba8 __to_256(rgba8 c, void *p) +{ + (void)p; + return pal256_to_rgb(c_palette_ansi_vga, closest_256(c_palette_ansi_vga, c)); +} + void mod_blocks_prepare(asc_state_t *state) { @@ -29,6 +31,40 @@ void mod_blocks_prepare(asc_state_t *state) } state->image = image_resize(state->source_image, w, h); // TODO: dither + if (state->args.dither) + { + 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_fn(state->image, __to_256, NULL); + 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; + } + } } void __start_output(asc_state_t state) @@ -42,7 +78,7 @@ void __start_output(asc_state_t state) fprintf(state.out_file, " \"data\": ["); break; case ASC_FMT_HTML: - fprintf(state.out_file, "\n"); + fprintf(state.out_file, "
\n"); break; default: break; @@ -96,18 +132,18 @@ void __end_line(FILE *fp, asc_format_t fmt, asc_style_t stl, bool final) } } -void __putc_bw(char *px, FILE *fp, asc_format_t fmt, bool final) +void __putc_bw(int ndx, FILE *fp, asc_format_t fmt, bool final) { switch (fmt) { case ASC_FMT_JSON: - fprintf(fp, final ? "\"%s\"" : "\"%s\", ", px); + fprintf(fp, final ? "\"%s\"" : "\"%s\", ", BLOCKS_ESC[ndx]); break; case ASC_FMT_HTML: - fprintf(fp, "", px); + fprintf(fp, "", BLOCKS[ndx]); break; default: - fprintf(fp, "%s", px); + fprintf(fp, "%s", BLOCKS[ndx]); break; } } @@ -121,20 +157,20 @@ void __putc_ansi(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette { case ASC_FMT_JSON: fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", - BLOCK_TOP_ESC, top_int, bot_int); + BLOCKS_ESC[1], top_int, bot_int); if (!final) fprintf(fp, ", "); break; case ASC_FMT_HTML: fprintf(fp, "", top_rgb.r, top_rgb.g, top_rgb.b, bot_rgb.r, bot_rgb.g, bot_rgb.b, - BLOCK_TOP); + BLOCKS[1]); break; default: fprintf(fp, "\033[%d;%dm%s", ct + (ct >= 8 ? 82 : 30), cb + (cb >= 8 ? 92 : 40), - BLOCK_TOP); + BLOCKS[1]); break; } } @@ -148,17 +184,17 @@ void __putc_256(FILE *fp, asc_format_t fmt, bool final, int ct, int cb, palette_ { case ASC_FMT_JSON: fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", - BLOCK_TOP_ESC, top_int, bot_int); + BLOCKS_ESC[1], top_int, bot_int); if (!final) fprintf(fp, ", "); break; case ASC_FMT_HTML: fprintf(fp, "", top_rgb.r, top_rgb.g, top_rgb.b, bot_rgb.r, bot_rgb.g, bot_rgb.b, - BLOCK_TOP); + BLOCKS[1]); break; default: - fprintf(fp, "\033[38;5;%d;48;5;%dm%s", ct, cb, BLOCK_TOP); + fprintf(fp, "\033[38;5;%d;48;5;%dm%s", ct, cb, BLOCKS[1]); break; } } @@ -171,16 +207,16 @@ void __putc_truecolor(FILE *fp, asc_format_t fmt, bool final, rgba8 top, rgba8 b { case ASC_FMT_JSON: fprintf(fp, "{ \"char\": \"%s\", \"fg\": %d, \"bg\": %d }", - BLOCK_TOP_ESC, top_int, bot_int); + BLOCKS_ESC[1], top_int, bot_int); if (!final) fprintf(fp, ", "); break; case ASC_FMT_HTML: fprintf(fp, "", - top.r, top.g, top.b, bot.r, bot.g, bot.b, BLOCK_TOP); + top.r, top.g, top.b, bot.r, bot.g, bot.b, BLOCKS[1]); 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); + top.r, top.g, top.b, bot.r, bot.g, bot.b, BLOCKS[1]); break; } } @@ -195,10 +231,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(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); + 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); } break; case ASC_STL_ANSI_VGA: @@ -225,10 +261,16 @@ void __put_pixel(asc_state_t state, rgba8 top, rgba8 bot, bool final) } break; case ASC_STL_TRUECOLOR: + __putc_truecolor(fp, fmt, final, top, bot); + break; + case ASC_STL_PALETTE: { - __putc_truecolor(fp, fmt, final, top, bot); + 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); } - default: + case ASC_STL_ENDL: break; } }
%s%s%s%s%s