diff --git a/Makefile b/Makefile index 7f728ef..8b58fdb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ -CFLAGS := -Wall -Wextra -Werror -pedantic -std=c99 +CFLAGS := -Wall -Wextra -Werror -pedantic -std=c99 -ggdb CLIBS := -lm INCLUDES := -Isrc -OBJECTS := obj/stb_image.o +OBJECTS := obj/stb_image.o obj/stb_image_resize.o \ + obj/colors.o obj/args.o obj/image.o \ + obj/mod_blocks.o all: lib asciify diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd87899 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Why? +I just wasn't happy with state of old project, so I thought about recreating it. + +# Anything new? +Nothing. + +Oh wait. It's single binary now. Yay. diff --git a/src/args.c b/src/args.c new file mode 100644 index 0000000..09c7989 --- /dev/null +++ b/src/args.c @@ -0,0 +1,265 @@ +#include "args.h" +#include +#include +#include +#include +#include + +typedef struct { + int value; + char *strings[8]; + char *description; +} __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] = { + { ASC_MOD_BLOCKS, + { "b", "blk", "blocks", NULL }, + "Box-drawing characters (\342\226\204) (default)" }, + { ASC_MOD_BRAILLE, + { "r", "brl", "braille", NULL }, + "Braille characters (literally stolen from MineOS)" }, + { ASC_MOD_GRADIENT, + { "g", "grd", "gradient", NULL }, + "Gradient of characters. No matching at all" }, + { ASC_MOD_BRUTEFORCE, + { "f", "guess", "bruteforce", NULL }, + "Looking for best possible character" }, + { -1, { NULL }, NULL } +}; + +const __option_t __style_options[ASC_STL_ENDL + 1] = { + { ASC_STL_BLACKWHITE, + { "1", "bw", "black-white", "1bit", NULL }, + "1-bit black/white" }, + { ASC_STL_ANSI_VGA, + { "vga", "ansi-vga", NULL }, + "VGA palette" }, + { ASC_STL_ANSI_XTERM, + { "xterm", "ansi-xterm", NULL }, + "xTerm palette. A bit more rough, compared to VGA" }, + { ASC_STL_ANSI_DISCORD, + { "discord", "ansi-discord", NULL }, + "Palette in Discord ANSI highlight" }, + { ASC_STL_256COLOR, + { "256", "pal256", "8bit", NULL }, + "256-color palette (default)" }, + { ASC_STL_TRUECOLOR, + { "true", "truecolor", "24bit", NULL }, + "24-bit RGB (TrueColor)" }, + { ASC_STL_PALETTE, + { "pal", "palette", "custom", NULL }, + "Custom palette (specified via -P)" }, + { -1, { NULL }, NULL } +}; + +const __option_t __format_options[ASC_FMT_ENDL + 1] = { + { ASC_FMT_ANSI, + { "ansi", "raw", NULL }, + "Output, suitable for terminal (default)" }, + { ASC_FMT_HTML, + { "html", NULL }, + "Output as HTML table" }, + { ASC_FMT_JSON, + { "json", NULL }, + "Output as JSON 2D array of characters with properties" }, + { -1, { NULL }, NULL } +}; + +void usage(int argc, char **argv) +{ + (void)argc; + fprintf(stderr, "usage: %s ", *argv); + fprintf(stderr, "[-vhd] [-O FILE] [-W WIDTH] [-H HEIGHT] "); + fprintf(stderr, "[-M MODE] [-S STYLE] [-F FORMAT] [-P PALETTE] "); + fprintf(stderr, "FILE\n\n"); + fprintf(stderr, "-v\t\tEnable verbose mode\n"); + fprintf(stderr, "-h\t\tShow this help\n"); + fprintf(stderr, "-d\t\tEnable dithering\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "-O FILE\t\tOutput file. Default: - (stdout)\n"); + fprintf(stderr, "-W WIDTH\tOutput width (in characters)\n"); + fprintf(stderr, "-H HEIGHT\tOutput height (in characters)\n"); + fprintf(stderr, "-M MODE\t\tOutput mode\n"); + fprintf(stderr, "-S STYLE\tStyle (palette)\n"); + fprintf(stderr, "-F FORMAT\tOutput format\n"); + 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); + fprintf(stderr, "Options for STYLE:\n"); + __print_options(__style_options); + fprintf(stderr, "Options for FORMAT:\n"); + __print_options(__format_options); +} + +int parse_args(int argc, char **argv, asc_args_t *args) +{ + args->input_filename = NULL; + args->output_filename = "-"; + args->palette_filename = NULL; + args->width = 80; + args->height = 24; + args->out_format = ASC_FMT_ANSI; + args->out_style = ASC_STL_256COLOR; + args->mode = ASC_MOD_BLOCKS; + args->dither = false; + args->verbose = false; + args->charset = " .'-*+$@"; + int c; + while ((c = getopt(argc, argv, "vhdW:H:M:S:F:P:O:")) != -1) + { + switch (c) + { + case 'v': + args->verbose = true; + break; + case 'h': + usage(argc, argv); + return 1; + break; + case 'd': + args->dither = true; + break; + case 'W': + if ((args->width = atoi(optarg)) < 0) + { + fprintf(stderr, "Error: WIDTH is invalid\n"); + return -1; + } + break; + case 'H': + if ((args->height = atoi(optarg)) < 0) + { + fprintf(stderr, "Error: HEIGHT is invalid\n"); + return -1; + } + break; + case 'M': + { + int val = __find_value(__mode_options, optarg); + if (val < 0) + { + fprintf(stderr, "Error: invalid mode '%s'\n", optarg); + return -1; + } + args->mode = val; + } + break; + case 'S': + { + int val = __find_value(__style_options, optarg); + if (val < 0) + { + fprintf(stderr, "Error: invalid style '%s'\n", optarg); + return -1; + } + args->out_style = val; + } + break; + case 'F': + { + int val = __find_value(__format_options, optarg); + if (val < 0) + { + fprintf(stderr, "Error: invalid format '%s'\n", optarg); + return -1; + } + args->out_format = val; + } + break; + case 'P': + args->palette_filename = optarg; + break; + case 'O': + args->output_filename = optarg; + break; + case '?': + if (optopt == 'O' + || optopt == 'W' || optopt == 'H' + || optopt == 'S' || optopt == 'M' || optopt == 'F') + { + fprintf(stderr, "Error: missing argument for -%c\n", optopt); + } + else + { + fprintf(stderr, "Error: Unknown parameter -%c\n", optopt); + } + return -2; + } + } + if (args->out_style == ASC_STL_PALETTE && args->palette_filename == NULL) + { + fprintf(stderr, "Error: no palette file provided, but palette mode selected\n"); + return -3; + } + if (argc <= optind || argc < 2) + { + fprintf(stderr, "Error: no image provided\n"); + return -2; + } + args->input_filename = argv[optind]; + return 0; +} + +int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state) +{ + (void)argc; (void)argv; + state->args = args; + + FILE *image_file; + if ((image_file = fopen(args.input_filename, "rb")) == NULL + || ferror(image_file) != 0) + { + int err = errno; + fprintf(stderr, "Error: failed to open file %s for reading: %d: %s\n", + args.input_filename, err, strerror(err)); + return -100 - err; + } + + state->source_image = image_load(image_file); + fclose(image_file); + + state->out_file = stdout; + if (strcmp(args.output_filename, "-")) + state->out_file = fopen(args.output_filename, "wb"); + if (state->out_file == NULL) + { + int err = errno; + fprintf(stderr, "Error: failed to open file %s for writing: %d: %s\n", + args.output_filename, err, strerror(err)); + return -100 - err; + } + return 0; +} + +int __find_value(const __option_t *options, char *option) +{ + __option_t *opt; + while ((opt = (__option_t *)options++)->value >= 0) + { + char *str = opt->strings[0]; + for (int i = 0; str != NULL; i++, str = opt->strings[i]) + { + if (!strcmp(str, option)) return opt->value; + } + } + return -1; +} + +void __print_options(const __option_t *options) +{ + __option_t *opt; + while ((opt = (__option_t *)options++)->value >= 0) + { + fprintf(stderr, " - %s:\n\t", opt->description); + char *str = opt->strings[0]; + for (int i = 0; str != NULL; i++, str = opt->strings[i]) + { + fprintf(stderr, "%s ", str); + } + fprintf(stderr, "\n"); + } +} diff --git a/src/args.h b/src/args.h new file mode 100644 index 0000000..0dc02e7 --- /dev/null +++ b/src/args.h @@ -0,0 +1,59 @@ +#ifndef _ARGS_H_ +#define _ARGS_H_ +#include +#include +#include "image.h" + +typedef enum { + ASC_MOD_BLOCKS = 0, + ASC_MOD_BRAILLE = 1, + ASC_MOD_GRADIENT = 2, + ASC_MOD_BRUTEFORCE = 3, + ASC_MOD_ENDL = 4 +} asc_mode_t; + +typedef enum { + ASC_FMT_ANSI = 0, + ASC_FMT_HTML = 1, + ASC_FMT_JSON = 2, + ASC_FMT_ENDL = 3 +} asc_format_t; + +typedef enum { + ASC_STL_BLACKWHITE = 0, + ASC_STL_ANSI_VGA = 1, + ASC_STL_ANSI_XTERM = 2, + ASC_STL_ANSI_DISCORD = 3, + ASC_STL_256COLOR = 4, + ASC_STL_TRUECOLOR = 5, + ASC_STL_PALETTE = 6, + ASC_STL_ENDL = 7 +} asc_style_t; + +typedef struct { + char *input_filename; + char *output_filename; + char *palette_filename; + int width; + int height; + asc_format_t out_format; + asc_style_t out_style; + asc_mode_t mode; + bool dither; + bool verbose; + char *charset; +} asc_args_t; + +typedef struct { + asc_args_t args; + image_t *source_image; + image_t *image; + palette_t *palette; + FILE *out_file; +} asc_state_t; + +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); + +#endif diff --git a/src/colors.c b/src/colors.c new file mode 100644 index 0000000..08f430b --- /dev/null +++ b/src/colors.c @@ -0,0 +1,98 @@ +#include "colors.h" + +palette_t c_palette_ansi_discord = { + .n_colors = 8, + .palette = { + { 0x4f, 0x54, 0x5c, 0 }, + { 0xd1, 0x31, 0x35, 0 }, + { 0x85, 0x99, 0x00, 0 }, + { 0xb5, 0x89, 0x00, 0 }, + { 0x26, 0x8b, 0xd2, 0 }, + { 0xd3, 0x36, 0x82, 0 }, + { 0xd3, 0x36, 0x82, 0 }, + { 0xff, 0xff, 0xff, 0 }, + } +}; + +palette_t c_palette_ansi_vga = { + .n_colors = 16, + .palette = { + { 0, 0, 0, 0 }, + { 170, 0, 0, 0 }, + { 0, 170, 0, 0 }, + { 170, 85, 0, 0 }, + { 0, 0, 170, 0 }, + { 170, 0, 170, 0 }, + { 0, 170, 170, 0 }, + { 170, 170, 170, 0 }, + { 85, 85, 85, 0 }, + { 255, 85, 85, 0 }, + { 85, 255, 85, 0 }, + { 255, 255, 85, 0 }, + { 85, 85, 255, 0 }, + { 255, 85, 255, 0 }, + { 85, 255, 255, 0 }, + { 255, 255, 255, 0 } + } +}; + +palette_t c_palette_ansi_xterm = { + .n_colors = 16, + .palette = { + { 0, 0, 0, 0 }, + { 205, 0, 0, 0 }, + { 0, 205, 0, 0 }, + { 205, 205, 0, 0 }, + { 0, 0, 238, 0 }, + { 205, 0, 205, 0 }, + { 0, 205, 205, 0 }, + { 229, 229, 229, 0 }, + { 127, 127, 127, 0 }, + { 255, 0, 0, 0 }, + { 0, 255, 0, 0 }, + { 255, 255, 0, 0 }, + { 0, 0, 252, 0 }, + { 255, 0, 255, 0 }, + { 0, 255, 255, 0 }, + { 255, 255, 255, 0 }, + } +}; + +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++) + { + 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; + if (distance < min_distance) + { + min_distance = distance; + nearest = i; + } + } + return nearest; +} + +void load_palette_gpl(palette_t *pal, FILE *fp) +{ + (void)pal; (void)fp; + // TODO: load GNU palette file +} + +void load_palette_raw(palette_t *pal, FILE *fp) +{ + (void)pal; (void)fp; + // TODO: load raw palette file +} + +void load_palette(palette_t *pal, FILE *fp) +{ + (void)pal; (void)fp; + // TODO: guess palette file type and load it +} + diff --git a/src/colors.h b/src/colors.h new file mode 100644 index 0000000..ae85314 --- /dev/null +++ b/src/colors.h @@ -0,0 +1,24 @@ +#ifndef _COLORS_H_ +#define _COLORS_H_ +#include +#include + +typedef struct { + uint8_t r, g, b, a; +} rgba8; + +typedef struct { + int n_colors; + rgba8 palette[255]; +} palette_t; + +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); +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); + +#endif diff --git a/src/image.c b/src/image.c new file mode 100644 index 0000000..8e56cae --- /dev/null +++ b/src/image.c @@ -0,0 +1,66 @@ +#include "image.h" +#include "stb_image.h" +#include "stb_image_resize.h" +#include +#include +#include +#include + +image_t *image_load(FILE *file) +{ + image_t *img = calloc(1, sizeof(image_t)); + int n; + img->pixels = (rgba8 *)stbi_load_from_file(file, + &img->width, &img->height, + &n, STBI_rgb_alpha); + return img; +} + + +image_t *image_resize(image_t *img, int width, int height) +{ + image_t *res = calloc(1, sizeof(image_t)); + res->width = width; + res->height = height; + res->pixels = calloc(width * height, sizeof(rgba8)); + stbir_resize_uint8((const unsigned char *)img->pixels, + img->width, img->height, 0, + (unsigned char *)res->pixels, + res->width, res->height, 0, STBI_rgb_alpha); + return res; +} + +void image_unload(image_t *img) +{ + free(img->pixels); + free(img); +} + +void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh) +{ + *ow = dw; + *oh = dh; + float ratio = (float)w / (float)h; + float ratio_dst = (float)dw / (float)dh; + int tmp_1, tmp_2; + if (ratio_dst >= ratio) + { + tmp_1 = floor(dh * ratio); + tmp_2 = ceil(dh * ratio); + if (fabsf(ratio - (float)tmp_1 / dh) < fabsf(ratio - (float)tmp_1 / dh)) + *ow = tmp_1 < 1 ? 1 : tmp_1; + else + *ow = tmp_2 < 1 ? 1 : tmp_2; + } + else + { + tmp_1 = floor(dw / ratio); + tmp_2 = ceil(dw / ratio); + if (tmp_2 == 0 || + fabs(ratio - (float)dw / tmp_1) < fabs(ratio - (float)dw / tmp_2)) + (*oh) = tmp_1 < 1 ? 1 : tmp_1; + else + (*oh) = tmp_2 < 1 ? 1 : tmp_2; + } +} + diff --git a/src/image.h b/src/image.h new file mode 100644 index 0000000..e78dc18 --- /dev/null +++ b/src/image.h @@ -0,0 +1,19 @@ +#ifndef _IMAGE_H_ +#define _IMAGE_H_ +#include +#include "colors.h" + +typedef struct { + int width; + int height; + int errno; + rgba8 *pixels; +} image_t; + +image_t *image_load(FILE *file); +image_t *image_resize(image_t *img, int width, int height); +void image_unload(image_t *img); + +void get_size_keep_aspect(int w, int h, int dw, int dh, int *ow, int *oh); + +#endif diff --git a/src/main.c b/src/main.c index 8cb8cce..06b92f2 100644 --- a/src/main.c +++ b/src/main.c @@ -1,11 +1,20 @@ #include #include #include +#include +#include "args.h" int main(int argc, char **argv) { - (void)argc; - (void)argv; - printf("ah shit, here we go again\n"); + asc_args_t args; + int res = parse_args(argc, argv, &args); + if (res == 1) return 0; + if (res < 0) return -res; + + asc_state_t state; + res = prepare_state(argc, argv, args, &state); + if (res == 1) return 0; + if (res < 0) return -res; + return 0; } diff --git a/src/mod_blocks.c b/src/mod_blocks.c new file mode 100644 index 0000000..a76368d --- /dev/null +++ b/src/mod_blocks.c @@ -0,0 +1,11 @@ +#include "mod_blocks.h" + +void mod_blocks_prepare(asc_state_t *state) +{ + (void)state; +} + +void mod_blocks_main(asc_state_t state) +{ + (void)state; +} diff --git a/src/mod_blocks.h b/src/mod_blocks.h new file mode 100644 index 0000000..cdbcd0b --- /dev/null +++ b/src/mod_blocks.h @@ -0,0 +1,10 @@ +#ifndef _MOD_BLOCKS_ +#define _MOD_BLOCKS_ +#include +#include "colors.h" +#include "args.h" + +void mod_blocks_prepare(asc_state_t *state); +void mod_blocks_main(asc_state_t state); + +#endif diff --git a/src/stb_image_resize.c b/src/stb_image_resize.c new file mode 100644 index 0000000..b2d4775 --- /dev/null +++ b/src/stb_image_resize.c @@ -0,0 +1,2 @@ +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "stb_image_resize.h" diff --git a/src/stb_image_resize.h b/src/stb_image_resize.h new file mode 100644 index 0000000..d413205 --- /dev/null +++ b/src/stb_image_resize.h @@ -0,0 +1 @@ +#include "../thirdparty/stb/stb_image_resize.h"