Dithering and palette support

TODO: fix some weird issues with dithering
This commit is contained in:
Casey 2022-02-03 00:02:54 +03:00
parent c2b3a157c4
commit 44418ac242
6 changed files with 209 additions and 42 deletions

View File

@ -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");

View File

@ -1,5 +1,15 @@
#include "colors.h"
#include <math.h>
#include <stdbool.h>
#include <string.h>
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);
}
}

View File

@ -2,6 +2,7 @@
#define _COLORS_H_
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
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

View File

@ -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);

View File

@ -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);

View File

@ -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, "<table>\n");
fprintf(state.out_file, "<table style=\"border-collapse: collapse;\">\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, "<td>%s</td>", px);
fprintf(fp, "<td>%s</td>", 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, "<td style=\"color: rgb(%d, %d, %d); background: rgb(%d, %d, %d);\">%s</td>",
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, "<td style=\"color: rgb(%d, %d, %d); background: rgb(%d, %d, %d);\">%s</td>",
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, "<td style=\"color: rgb(%d, %d, %d); background: rgb(%d, %d, %d);\">%s</td>",
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;
}
}