Added gradient (charmap) module and other stuff

Like uh, UTF-8 encoding/decoding, some fixes in `calc_brightness`,
some changes in `c_fatal` (it now accepts formatting) and some other
things I of course forgot.
Oh yeah, and module now can signal that something went horrubly wrong
and die. Yay!
This commit is contained in:
Casey 2022-02-08 21:19:00 +03:00
parent 283422c62d
commit fa72c81f70
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
14 changed files with 423 additions and 27 deletions

View File

@ -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_braille.o
obj/mod_blocks.o obj/mod_braille.o obj/mod_charmap.o
VERSION := "v-git-$(shell git rev-parse --short HEAD)"
all: lib yaitaa

View File

@ -9,6 +9,7 @@
#include "commons.h"
#include "mod_blocks.h"
#include "mod_braille.h"
#include "mod_charmap.h"
typedef struct {
int value;
@ -32,7 +33,7 @@ const asc_handler_t asc_handlers[ASC_MOD_ENDL + 1] = {
{ ASC_MOD_GRADIENT,
{ "g", "grd", "gradient", NULL },
"Gradient of characters. No matching at all",
NULL, NULL },
mod_cmap_prepare, mod_cmap_main },
{ ASC_MOD_BRUTEFORCE,
{ "f", "guess", "bruteforce", NULL },
"Looking for best possible character",
@ -100,6 +101,7 @@ void usage(int argc, char **argv)
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, "-C CHARSET\tList of characters. Used in some modes\n");
fprintf(stderr, "\n");
fprintf(stderr, "-M MODE\t\tOutput mode\n");
fprintf(stderr, "-S STYLE\tStyle (palette)\n");
@ -147,9 +149,9 @@ int parse_args(int argc, char **argv, asc_args_t *args)
args->charset = " .'-*+$@";
int c;
#ifdef DISABLE_LOGGING
while ((c = getopt(argc, argv, "hdVW:H:M:S:F:P:O:")) != -1)
while ((c = getopt(argc, argv, "hdVC:W:H:M:S:F:P:O:")) != -1)
#else
while ((c = getopt(argc, argv, "vhdVW:H:M:S:F:P:O:")) != -1)
while ((c = getopt(argc, argv, "vhdVC:W:H:M:S:F:P:O:")) != -1)
#endif
{
switch (c)
@ -168,6 +170,9 @@ int parse_args(int argc, char **argv, asc_args_t *args)
case 'd':
args->dither = true;
break;
case 'C':
args->charset = optarg;
break;
case 'W':
if ((args->width = atoi(optarg)) < 0)
{
@ -282,6 +287,10 @@ int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state)
LOG("Image size: %dx%d",
state->source_image->width, state->source_image->height);
state->image = state->source_image;
state->userdata = NULL;
// Palette configuration
switch (args.out_style)
{

View File

@ -68,11 +68,12 @@ typedef struct {
image_t *source_image;
image_t *image;
palette_t *palette;
void *userdata;
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 bool (*asc_module_initializer_fn)(asc_state_t *state);
typedef bool (*asc_module_handler_fn)(asc_state_t state);
typedef struct {
int id;

View File

@ -141,7 +141,7 @@ void make_pal256(palette_t *dst, palette_t ansi)
float calc_brightness(rgba8 c)
{
return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
return (0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) / 255.0;
}
bool load_palette_gpl(palette_t *pal, FILE *fp)

View File

@ -44,9 +44,14 @@ void _log(const char *fmt, ...)
}
#endif
void c_fatal(int code, const char *reason)
void c_fatal(int code, const char *reason, ...)
{
fprintf(stderr, "Error: %s\n", reason);
fprintf(stderr, "Error: ");
va_list args;
va_start(args, reason);
vfprintf(stderr, reason, args);
va_end(args);
fprintf(stderr, "\n");
exit(code);
}
@ -64,3 +69,129 @@ palette_t *get_palette_by_id(asc_style_t stl)
}
return pal;
}
// From: https://github.com/raysan5/raylib/blob/e5ee69a/src/rtext.c#L1729
__attribute__((annotate("oclint:suppress[bitwise operator in conditional]")))
__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 get_codepoint(char *text, int *processed)
{
int code = 0;
int octet = (unsigned char)(text[0]);
*processed = 1;
if (octet <= 0x7f)
{
code = text[0];
}
else if ((octet & 0xe0) == 0xc0)
{
unsigned char octet1 = text[1];
if ((octet1 == '\0') || ((octet1 >> 6) != 2))
{ *processed = 2; return 0; }
if ((octet >= 0xc2) && (octet <= 0xdf))
{
code = ((octet & 0x1f) << 6) | (octet1 & 0x3f);
*processed = 2;
}
}
else if ((octet & 0xf0) == 0xe0)
{
unsigned char octet1 = text[1];
unsigned char octet2 = '\0';
if ((octet1 == '\0') || ((octet1 >> 6) != 2))
{ *processed = 2; return 0; }
octet2 = text[2];
if ((octet2 == '\0') || ((octet2 >> 6) != 2))
{ *processed = 3; return 0; }
if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 < 0xbf))) ||
((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f))))
{ *processed = 2; return 0; }
if ((octet >= 0xe0) && (octet <= 0xef))
{ code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f);
*processed = 3; }
}
else if ((octet & 0xf8) == 0xf0)
{
if (octet > 0xf4) return 0;
unsigned char octet1 = text[1];
unsigned char octet2 = '\0';
unsigned char octet3 = '\0';
if ((octet1 == '\0') || ((octet1 >> 6) != 2))
{ *processed = 2; return 0; }
octet2 = text[2];
if ((octet2 == '\0') || ((octet2 >> 6) != 2))
{ *processed = 3; return 0; }
if ((octet3 == '\0') || ((octet3 >> 6) != 2))
{ *processed = 4; return 0; }
if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) ||
((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f))))
{ *processed = 2; return 0; }
if (octet >= 0xf0)
{
code = (((octet & 0x7) << 18) |
((octet1 & 0x3f) << 12) |
((octet2 & 0x3f) << 6) |
(octet3 & 0x3f));
*processed = 4;
}
}
return code;
}
// From: https://gist.github.com/MightyPork/52eda3e5677b4b03524e40c9f0ab1da5
__attribute__((annotate("oclint:suppress[high ncss method]")))
int set_codepoint(char *buf, int codepoint)
{
if (codepoint <= 0x7F)
{
*buf++ = (char)codepoint;
*buf++ = '\0';
return 1;
}
else if (codepoint <= 0x07FF)
{
*buf++ = (char)(((codepoint >> 6) & 0x1F) | 0xC0);
*buf++ = (char)(((codepoint >> 0) & 0x3F) | 0x80);
*buf++ = '\0';
return 2;
}
else if (codepoint <= 0xFFFF)
{
*buf++ = (char)(((codepoint >> 12) & 0x0F) | 0xE0);
*buf++ = (char)(((codepoint >> 6) & 0x3F) | 0x80);
*buf++ = (char)(((codepoint >> 0) & 0x3F) | 0x80);
*buf++ = '\0';
return 3;
}
else if (codepoint <= 0x10FFFF)
{
*buf++ = (char)(((codepoint >> 18) & 0x07) | 0xF0);
*buf++ = (char)(((codepoint >> 12) & 0x3F) | 0x80);
*buf++ = (char)(((codepoint >> 6) & 0x3F) | 0x80);
*buf++ = (char)(((codepoint >> 0) & 0x3F) | 0x80);
*buf++ = '\0';
return 4;
}
*buf++ = '\xEF';
*buf++ = '\xBF';
*buf++ = '\xBD';
*buf++ = '\0';
return 0;
}
int n_codepoints(char *text)
{
int proc = 0, length = 0;
while (*text != '\0')
{
if (get_codepoint(text, &proc) == 0)
break;
length++;
text += proc;
}
return length;
}

View File

@ -32,8 +32,12 @@ extern bool b_logging;
void _log(const char *fmt, ...);
#endif
void c_fatal(int code, const char *reason);
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);
int get_codepoint(char *text, int *processed);
int set_codepoint(char *buf, int codepoint);
int n_codepoints(char *text);
#endif

View File

@ -22,6 +22,7 @@
#define S_JSON_LSTA " [\n"
#define S_JSON_PBBW " { \"char\": \"%s\", \"fg\": 16777215, \"bg\": 0 }"
#define S_JSON_PRGB " { \"char\": \"\\u%04x\", \"fg\": %d, \"bg\": %d }"
#define S_JSON_PUCC " { \"char\": \"\\u%04x\", \"fg\": %d, \"bg\": 0 }"
#define S_JSON_PBLK " { \"char\": \"%s\", \"fg\": %d, \"bg\": %d }"
#define S_JSON_LEND " ],\n"
#define S_JSON_LEND_FINAL " ]\n"
@ -33,13 +34,18 @@
"background: rgb(%d, %d, %d);\">&#%d;</td>"
#define S_HTML_PBLK "<td style=\"color: rgb(%d, %d, %d); "\
"background: rgb(%d, %d, %d);\">%s</td>"
#define S_HTML_PUCC "<td style=\"color: rgb(%d, %d, %d);\">&#%d;</td>"
#define S_HTML_PUBW "<td>&#%d;</td>"
#define S_HTML_PBBW "<td>%s</td>"
#define S_HTML_LEND "</tr>\n"
#define S_HTML_TAIL "</table>"
#define S_ANSI "\033[%d;%dm"
#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"
#define S_ANSI_RST "\033[0m"
#define S_ANSI "\033[%d;%dm"
#define S_ANSI_FG "\033[%dm"
#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"
#define S_ANSI_FG_256 "\033[38;5;%dm"
#define S_ANSI_FG_RGB "\033[38;2;%d;%d;%dm"
#define S_ANSI_RST "\033[0m"
#endif

View File

@ -20,10 +20,9 @@
#include <stdint.h>
#include <string.h>
#include "args.h"
#include "mod_blocks.h"
#include "mod_braille.h"
#include "commons.h"
__attribute__((annotate("oclint:suppress[high npath complexity]")))
int main(int argc, char **argv)
{
asc_args_t args;
@ -40,10 +39,14 @@ int main(int argc, char **argv)
if (handler.prepare == NULL)
c_fatal(12, "this mode is not implemented yet");
handler.prepare(&state);
handler.main(state);
if (!handler.prepare(&state))
c_fatal(15, "handler.prepare() said a big no-no");
if (!handler.main(state))
c_fatal(16, "handler.main() said a big no-no");
image_unload(state.image);
if (state.image != state.source_image)
image_unload(state.image);
image_unload(state.source_image);
if (args.out_style == ASC_STL_PALETTE)

View File

@ -19,7 +19,7 @@ void __blk_end_line(asc_state_t s, bool final);
void __blk_end_output(asc_state_t s);
void mod_blocks_prepare(asc_state_t *state)
bool mod_blocks_prepare(asc_state_t *state)
{
int width, height;
get_size_keep_aspect(
@ -33,9 +33,10 @@ void mod_blocks_prepare(asc_state_t *state)
state->image = image_resize(state->source_image, width, height);
if (state->args.dither)
m_prepare_dither(state);
return true;
}
void mod_blocks_main(asc_state_t state)
bool mod_blocks_main(asc_state_t state)
{
image_t *img = state.image;
__blk_start_output(state);
@ -52,6 +53,7 @@ void mod_blocks_main(asc_state_t state)
__blk_end_line(state, final);
}
__blk_end_output(state);
return true;
}
void __blk_start_output(asc_state_t state)

View File

@ -21,7 +21,7 @@
#include "colors.h"
#include "args.h"
void mod_blocks_prepare(asc_state_t *state);
void mod_blocks_main(asc_state_t state);
bool mod_blocks_prepare(asc_state_t *state);
bool mod_blocks_main(asc_state_t state);
#endif

View File

@ -30,7 +30,7 @@ void __bra_update2x4(image_t *img, rgba8 block[8], int x, int y)
}
void mod_braille_prepare(asc_state_t *state)
bool mod_braille_prepare(asc_state_t *state)
{
int width, height;
get_size_keep_aspect(
@ -44,9 +44,10 @@ void mod_braille_prepare(asc_state_t *state)
state->image = image_resize(state->source_image, width, height);
if (state->args.dither)
m_prepare_dither(state);
return true;
}
void mod_braille_main(asc_state_t state)
bool mod_braille_main(asc_state_t state)
{
image_t *img = state.image;
@ -92,6 +93,7 @@ void mod_braille_main(asc_state_t state)
__bra_end_line(state, final);
}
__bra_end_output(state);
return true;
}
int __bra_best_match_i(rgba8 a, rgba8 b, rgba8 t)

View File

@ -21,7 +21,7 @@
#include "colors.h"
#include "args.h"
void mod_braille_prepare(asc_state_t *state);
void mod_braille_main(asc_state_t state);
bool mod_braille_prepare(asc_state_t *state);
bool mod_braille_main(asc_state_t state);
#endif

209
src/mod_charmap.c Normal file
View File

@ -0,0 +1,209 @@
#include <stdio.h>
#include <math.h>
#include "mod_charmap.h"
#include "image.h"
#include "colors.h"
#include "commons.h"
#include "fmt_strings.h"
void __chr_start_output(asc_state_t s);
void __chr_start_line(asc_state_t s, bool final);
void __chr_put_pixel(asc_state_t s, rgba8 fg, bool final);
void __chr_putc_bw(asc_state_t s, int cc, bool final);
void __chr_putc_ansi(asc_state_t s, int fg, int cc, bool final);
void __chr_putc_256(asc_state_t s, int fg, int cc, bool final);
void __chr_putc_true(asc_state_t s, rgba8 fg, int cc, bool final);
void __chr_end_line(asc_state_t s, bool final);
void __chr_end_output(asc_state_t s);
typedef struct {
int n_codepoints;
int *codepoints;
} __m_cmap_userdata_t;
bool mod_cmap_prepare(asc_state_t *state)
{
static __m_cmap_userdata_t userdata;
static int codepoints[M_CMAP_MAX_CODEPOINTS];
userdata.codepoints = codepoints;
int n_cp = 0, cp, proc;
char *charset = state->args.charset;
while ((cp = get_codepoint(charset, &proc)) != 0)
{ codepoints[n_cp++] = cp; charset += proc; }
if (n_cp > M_CMAP_MAX_CODEPOINTS)
{
fprintf(stderr, "Error: maximum of %d codepoints is allowed, got %d\n",
M_CMAP_MAX_CODEPOINTS, n_cp);
return false;
}
userdata.n_codepoints = n_cp;
state->userdata = (void *)&userdata;
int width, height;
get_size_keep_aspect(
state->source_image->width * 2, state->source_image->height,
state->args.width, state->args.height, &width, &height);
LOG("Source size: %dx%d", state->source_image->width,
state->source_image->height);
LOG("Requested size: %dx%d", state->args.width, state->args.height);
LOG("Resizing image to %dx%d", width, height);
state->image = image_resize(state->source_image, width, height);
if (state->args.dither)
m_prepare_dither(state);
return true;
}
bool mod_cmap_main(asc_state_t state)
{
image_t *img = state.image;
__chr_start_output(state);
for (int y = 0; y < img->height; y++)
{
bool final = y >= img->height;
__chr_start_line(state, final);
for (int x = 0; x < img->width; x++)
{
__chr_put_pixel(state, img->pixels[x + y * img->width], x >= img->width);
}
__chr_end_line(state, final);
}
__chr_end_output(state);
return true;
}
void __chr_start_output(asc_state_t state)
{
if (state.args.out_format == ASC_FMT_JSON)
fprintf(state.out_file, S_JSON_HEAD,
state.image->width, state.image->height / 2);
else if (state.args.out_format == ASC_FMT_HTML)
fprintf(state.out_file, S_HTML_HEAD);
}
void __chr_start_line(asc_state_t state, bool final)
{
(void)final;
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 __chr_put_pixel(asc_state_t s, rgba8 fg, bool final)
{
float brightness = calc_brightness(fg);
__m_cmap_userdata_t data = *(__m_cmap_userdata_t *)s.userdata;
int ndx = floorf(brightness * data.n_codepoints);
if (ndx >= data.n_codepoints) ndx = data.n_codepoints - 1;
int codepoint = data.codepoints[ndx];
switch (s.args.out_style)
{
case ASC_STL_BLACKWHITE:
__chr_putc_bw(s, codepoint, final);
break;
case ASC_STL_ANSI_VGA:
case ASC_STL_ANSI_XTERM:
case ASC_STL_ANSI_DISCORD:
__chr_putc_ansi(s, closest_color(*s.palette, fg), codepoint, final);
break;
case ASC_STL_256COLOR:
__chr_putc_256(s, closest_color(c_palette_256, fg), codepoint, final);
break;
case ASC_STL_PALETTE:
__chr_putc_true(s, clamp_to_pal(*s.palette, fg), codepoint, final);
break;
case ASC_STL_TRUECOLOR:
__chr_putc_true(s, fg, codepoint, final);
break;
case ASC_STL_ENDL:
break;
}
}
void __chr_putc_bw(asc_state_t s, int cc, bool final)
{
static char buf[8];
set_codepoint(buf, cc);
if (s.args.out_format == ASC_FMT_JSON)
{
fprintf(s.out_file, S_JSON_PUCC, cc, 0xFFFFFF);
if (!final) fprintf(s.out_file, ",");
fprintf(s.out_file, "\n");
}
else if (s.args.out_format == ASC_FMT_HTML)
fprintf(s.out_file, S_HTML_PUBW, cc);
else
fprintf(s.out_file, "%s", buf);
}
void __chr_putc_ansi(asc_state_t s, int fg, int cc, bool final)
{
static char buf[8];
rgba8 fgc = s.palette->palette[fg];
set_codepoint(buf, cc);
if (s.args.out_format == ASC_FMT_JSON)
{
fprintf(s.out_file, S_JSON_PUCC, cc, RGBN(fgc));
if (!final) fprintf(s.out_file, ",");
fprintf(s.out_file, "\n");
}
else if (s.args.out_format == ASC_FMT_HTML)
fprintf(s.out_file, S_HTML_PUCC, fgc.r, fgc.g, fgc.b, cc);
else
fprintf(s.out_file, S_ANSI_FG"%s", fg + (fg >= 8 ? 82 : 30), buf);
}
void __chr_putc_256(asc_state_t s, int fg, int cc, bool final)
{
static char buf[8];
rgba8 fgc = pal256_to_rgb(*s.palette, fg);
set_codepoint(buf, cc);
if (s.args.out_format == ASC_FMT_JSON)
{
fprintf(s.out_file, S_JSON_PUCC, cc, RGBN(fgc));
if (!final) fprintf(s.out_file, ",");
fprintf(s.out_file, "\n");
}
else if (s.args.out_format == ASC_FMT_HTML)
fprintf(s.out_file, S_HTML_PUCC, fgc.r, fgc.g, fgc.b, cc);
else
fprintf(s.out_file, S_ANSI_FG_256"%s", fg, buf);
}
void __chr_putc_true(asc_state_t s, rgba8 fg, int cc, bool final)
{
static char buf[8];
set_codepoint(buf, cc);
if (s.args.out_format == ASC_FMT_JSON)
{
fprintf(s.out_file, S_JSON_PUCC, cc, RGBN(fg));
if (!final) fprintf(s.out_file, ",");
fprintf(s.out_file, "\n");
}
else if (s.args.out_format == ASC_FMT_HTML)
fprintf(s.out_file, S_HTML_PUCC, fg.r, fg.g, fg.b, cc);
else
fprintf(s.out_file, S_ANSI_FG_RGB"%s", fg.r, fg.g, fg.b, buf);
}
void __chr_end_line(asc_state_t state, bool final)
{
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
{
if (state.args.out_style != ASC_STL_BLACKWHITE)
fprintf(state.out_file, S_ANSI_RST);
fprintf(state.out_file, "\n");
}
}
void __chr_end_output(asc_state_t state)
{
if (state.args.out_format == ASC_FMT_JSON)
fprintf(state.out_file, S_JSON_TAIL);
else if (state.args.out_format == ASC_FMT_HTML)
fprintf(state.out_file, S_HTML_TAIL);
}

29
src/mod_charmap.h Normal file
View File

@ -0,0 +1,29 @@
/*
* yaitaa - yet another image to ascii art converter
* Copyright (C) 2022 hatkidchan <hatkidchan at gmail dot com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _MOD_CHARMAP_
#define _MOD_CHARMAP_
#include <stdio.h>
#include "colors.h"
#include "args.h"
#define M_CMAP_MAX_CODEPOINTS 256
bool mod_cmap_prepare(asc_state_t *state);
bool mod_cmap_main(asc_state_t state);
#endif