archive/art/p2u/p2u.c

619 lines
15 KiB
C

/* Copyright (c) 2018 Trollforge. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Trollforge's name may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize.h"
#define R 0
#define G 1
#define B 2
#define A 3
#define DIST(x, y) fabs(sqrtf((x[R] - y[R]) * (x[R] - y[R]) + \
(x[G] - y[G]) * (x[G] - y[G]) + \
(x[B] - y[B]) * (x[B] - y[B])))
#define ANSI_FMT 0
#define MIRC_FMT 1
#define EMOJI_FMT 2
#define VGA_PAL 0
#define MIRC_PAL 1
#define XIRC_PAL 2
typedef struct block_s {
int color;
} block_t;
void usage(void);
int nearestcolor(float *pixel, int palette, float tlevel);
double huetorgb(double p, double q, double t);
void tweak(float *pixel, float sat, float lum);
int
main(int argc, char *argv[])
{
int width = 0;
int height = 0;
int channels = 0;
block_t *block = NULL;
int format = ANSI_FMT;
int palette = VGA_PAL;
bool cp437 = false;
bool useice = false;
bool resize = false;
long resize_width = 0;
long resize_height = 0;
int ch = 0;
int fg = 0;
int bg = 0;
int lfg = 0;
int lbg = 0;
float *pixel = NULL;
float *resized = NULL;
float brightness = 100.0f;
float saturation = 100.0f;
float tlevel = 0.5f;
bool verbose = false;
while((ch = getopt(argc, argv, "b:f:p:s:t:w:v")) != -1) {
switch (ch) {
case 'b':
brightness = strtof(optarg, NULL);
break;
case 'f':
switch (optarg[0]) {
case 'a':
format = ANSI_FMT;
break;
case 'd':
format = ANSI_FMT;
cp437 = true;
useice = true;
break;
case 'm':
format = MIRC_FMT;
break;
case 'e':
format = EMOJI_FMT;
break;
default:
usage();
break;
}
break;
case 'p':
switch (optarg[0]) {
case 'm':
palette = MIRC_PAL;
break;
case 'v':
palette = VGA_PAL;
break;
case 'x':
palette = XIRC_PAL;
break;
default:
usage();
break;
}
break;
case 's':
saturation = strtof(optarg, NULL);
break;
case 't':
tlevel = strtof(optarg, NULL);
break;
case 'w':
resize_width = strtol(optarg, NULL, 10);
resize = true;
break;
case 'v':
verbose = true;
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
usage();
}
/* XXX handle alpha eventually */
/* channels is the number of channels in the original file, not our buffer) */
pixel = stbi_loadf(argv[0], &width, &height, &channels, STBI_rgb_alpha);
if (!pixel) {
fprintf(stderr, "Unable to read file: %s\n", argv[0]);
usage();
}
if (resize) {
resize_height = height * resize_width / width;
resized = malloc(sizeof(float) * resize_width * resize_height * STBI_rgb_alpha);
stbir_resize_float(pixel, width, height, 0,
resized, resize_width, resize_height, 0,
STBI_rgb_alpha);
free(pixel);
pixel = resized;
width = resize_width;
height = resize_height;
}
if (!pixel) {
usage();
}
if (verbose) {
fprintf(stderr, "file: %s\n", argv[0]);
fprintf(stderr, "format: %s\n", format == ANSI_FMT ? "ANSI" :
format == MIRC_FMT ? "mIRC" :
"emoji");
fprintf(stderr, "palette: %s\n", palette == VGA_PAL ? "VGA" : "mIRC");
if (format == ANSI_FMT) {
fprintf(stderr, "iCE: %s\n", useice ? "true" : "false");
fprintf(stderr, "CP437: %s\n", cp437 ? "true" : "false");
}
fprintf(stderr, "resized: %s\n", resize ? "true" : "false");
fprintf(stderr, "geometry: %dx%d\n", width, height);
fprintf(stderr, "channels: %d\n", STBI_rgb);
fprintf(stderr, "saturation: %f\n", saturation);
fprintf(stderr, "brightness: %f\n", brightness);
}
if (brightness != 100.0f || saturation != 100.0f) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
tweak(&pixel[((width * i) + j) * STBI_rgb_alpha],
saturation, brightness);
}
}
}
block = malloc(sizeof(block_t) * height * width);
if (format == EMOJI_FMT) {
palette = VGA_PAL;
}
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
block[(width * i) + j].color =
nearestcolor(&pixel[((width * i) + j) * STBI_rgb_alpha],
palette, tlevel);
}
}
free(pixel);
if (format == EMOJI_FMT) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
switch (block[(width * i) + j].color) {
case 0:
printf("");
break;
case 1:
printf("🔴");
break;
case 2:
printf("💚");
break;
case 3:
printf("💩");
break;
case 4:
printf("💙");
break;
case 5:
printf("💜");
break;
case 6:
printf("📫");
break;
case 7:
printf("👽");
break;
case 8:
printf("💣");
break;
case 9:
printf("🧠");
break;
case 10:
printf("🎾");
break;
case 11:
printf("🌞");
break;
case 12:
printf("");
break;
case 13:
printf("🐷");
break;
case 14:
printf("💦");
break;
case 15:
printf("💭");
break;
/* transparent */
case -1:
printf(" ");
break;
}
}
printf("\n");
}
return 0;
}
for (int i = 0; i + 1 < height; i += 2) {
for (int j = 0; j < width; j++) {
fg = block[(width * i) + j].color;
bg = block[(width * (i + 1)) + j].color;
/* dont print color codes if we dont have to */
if (j != 0 && lbg == bg && lfg == fg) {
/* try to save bytes */
if (bg == fg) {
printf(" ");
} else {
cp437 ? printf("\xdf") : printf("");
}
} else {
/* XXX we dont really have to print both attrs */
/* XXX not handling alpha here either */
if (format == ANSI_FMT) {
if (useice) {
printf("\x1b[%s%d;%dm%s",
/* bold and ice */
(fg > 7 && bg > 7) ? "1;5;" :
/* bold only */
(fg > 7 && bg < 8) ? "1;" :
/* ice only */
(fg < 8 && bg > 7) ? "5;" :
/* neither */
"",
fg > 7 ? fg - 8 + 30 : fg + 30,
bg > 7 ? bg - 8 + 40 : bg + 40,
bg == fg ? " " : cp437 ? "\xdf" : "");
} else {
/* XXX this doesnt work for extended colors */
printf("\x1b[%d;%dm%s",
fg < 8 ? fg + 30 : fg - 8 + 90,
bg < 8 ? bg + 40 : bg - 8 + 100,
bg == fg ? " " : cp437 ? "\xdf" : "");
}
} else {
if (bg == -1 && fg != -1) {
printf("\x03%d%s", fg, cp437 ? "\xdf" : "");
} else if (fg == -1 && bg != -1) {
printf("\x03%d%s", bg, cp437 ? "\xdc" : "");
} else if (fg == -1 && bg == -1) {
printf("\x03 ");
} else {
printf("\x03%d,%d%s", fg, bg,
bg == fg ? " " : cp437 ? "\xdf" : "");
}
}
}
lbg = bg;
lfg = fg;
}
/* reset to prevent line bleeding on terms */
if (format == ANSI_FMT) {
printf("\x1b[0m%s", cp437 && width == 80 ? "" : "\n");
} else {
printf("\n");
}
}
return 0;
}
int
nearestcolor(float *pixel, int palette, float tlevel)
{
if (pixel[A] < tlevel) {
return -1;
}
/* vga palette, maybe add more */
float vga_palette[16][3] = {{0.00f, 0.00f, 0.00f},
{0.66f, 0.00f, 0.00f},
{0.00f, 0.66f, 0.00f},
{0.66f, 0.33f, 0.00f},
{0.00f, 0.00f, 0.66f},
{0.66f, 0.00f, 0.66f},
{0.00f, 0.66f, 0.66f},
{0.66f, 0.66f, 0.66f},
{0.33f, 0.33f, 0.33f},
{1.00f, 0.85f, 0.85f},
{0.33f, 1.00f, 0.33f},
{1.00f, 1.00f, 0.33f},
{0.33f, 0.33f, 1.00f},
{1.00f, 0.33f, 1.00f},
{0.33f, 1.00f, 1.00f},
{1.00f, 1.00f, 1.00f}};
float mirc_palette[16][3] = {{1.00f, 1.00f, 1.00f},
{0.00f, 0.00f, 0.00f},
{0.00f, 0.00f, 0.50f},
{0.00f, 0.57f, 0.00f},
{1.00f, 0.00f, 0.00f},
{0.50f, 0.00f, 0.00f},
{0.61f, 0.00f, 0.61f},
{0.98f, 0.50f, 0.00f},
{1.00f, 1.00f, 0.00f},
{0.00f, 0.98f, 0.00f},
{0.00f, 0.57f, 0.57f},
{0.00f, 1.00f, 1.00f},
{0.00f, 0.33f, 0.98f},
{1.00f, 0.00f, 1.00f},
{0.50f, 0.50f, 0.50f},
{0.82f, 0.82f, 0.82f}};
float xirc_palette[99][3] = {{1.00f, 1.00f, 1.00f},
{0.00f, 0.00f, 0.00f},
{0.00f, 0.00f, 0.50f},
{0.00f, 0.57f, 0.00f},
{1.00f, 0.00f, 0.00f},
{0.50f, 0.00f, 0.00f},
{0.61f, 0.00f, 0.61f},
{0.98f, 0.50f, 0.00f},
{1.00f, 1.00f, 0.00f},
{0.00f, 0.98f, 0.00f},
{0.00f, 0.57f, 0.57f},
{0.00f, 1.00f, 1.00f},
{0.00f, 0.33f, 0.98f},
{1.00f, 0.00f, 1.00f},
{0.50f, 0.50f, 0.50f},
{0.82f, 0.82f, 0.82f},
{0.28f, 0.00f, 0.00f},
{0.28f, 0.13f, 0.00f},
{0.28f, 0.28f, 0.00f},
{0.20f, 0.28f, 0.00f},
{0.00f, 0.28f, 0.00f},
{0.00f, 0.28f, 0.17f},
{0.00f, 0.28f, 0.28f},
{0.00f, 0.15f, 0.28f},
{0.00f, 0.00f, 0.28f},
{0.18f, 0.00f, 0.28f},
{0.28f, 0.00f, 0.28f},
{0.28f, 0.00f, 0.16f},
{0.45f, 0.00f, 0.00f},
{0.45f, 0.23f, 0.00f},
{0.45f, 0.45f, 0.00f},
{0.32f, 0.45f, 0.00f},
{0.00f, 0.45f, 0.00f},
{0.00f, 0.45f, 0.29f},
{0.00f, 0.45f, 0.45f},
{0.00f, 0.25f, 0.45f},
{0.00f, 0.00f, 0.45f},
{0.29f, 0.00f, 0.45f},
{0.45f, 0.00f, 0.45f},
{0.45f, 0.00f, 0.27f},
{0.71f, 0.00f, 0.00f},
{0.71f, 0.39f, 0.00f},
{0.71f, 0.71f, 0.00f},
{0.49f, 0.71f, 0.00f},
{0.00f, 0.71f, 0.00f},
{0.00f, 0.71f, 0.44f},
{0.00f, 0.71f, 0.71f},
{0.00f, 0.39f, 0.71f},
{0.00f, 0.00f, 0.71f},
{0.46f, 0.00f, 0.71f},
{0.71f, 0.00f, 0.71f},
{0.71f, 0.00f, 0.42f},
{1.00f, 0.00f, 0.00f},
{1.00f, 0.55f, 0.00f},
{1.00f, 1.00f, 0.00f},
{0.70f, 1.00f, 0.00f},
{0.00f, 1.00f, 0.00f},
{0.00f, 1.00f, 0.63f},
{0.00f, 1.00f, 1.00f},
{0.00f, 0.55f, 1.00f},
{0.00f, 0.00f, 1.00f},
{0.65f, 0.00f, 1.00f},
{1.00f, 0.00f, 1.00f},
{1.00f, 0.00f, 0.60f},
{1.00f, 0.35f, 0.35f},
{1.00f, 0.71f, 0.35f},
{1.00f, 1.00f, 0.44f},
{0.81f, 1.00f, 0.38f},
{0.44f, 1.00f, 0.44f},
{0.40f, 1.00f, 0.79f},
{0.43f, 1.00f, 1.00f},
{0.35f, 0.71f, 1.00f},
{0.35f, 0.35f, 1.00f},
{0.77f, 0.35f, 1.00f},
{1.00f, 0.40f, 1.00f},
{1.00f, 0.35f, 0.74f},
{1.00f, 0.61f, 0.61f},
{1.00f, 0.83f, 0.61f},
{1.00f, 1.00f, 0.61f},
{0.89f, 1.00f, 0.61f},
{0.61f, 1.00f, 0.61f},
{0.61f, 1.00f, 0.86f},
{0.61f, 1.00f, 1.00f},
{0.61f, 0.83f, 1.00f},
{0.61f, 0.61f, 1.00f},
{0.86f, 0.61f, 1.00f},
{1.00f, 0.61f, 1.00f},
{1.00f, 0.58f, 0.83f},
{0.00f, 0.00f, 0.00f},
{0.07f, 0.07f, 0.07f},
{0.16f, 0.16f, 0.16f},
{0.21f, 0.21f, 0.21f},
{0.30f, 0.30f, 0.30f},
{0.40f, 0.40f, 0.40f},
{0.51f, 0.51f, 0.51f},
{0.62f, 0.62f, 0.62f},
{0.74f, 0.74f, 0.74f},
{0.89f, 0.89f, 0.89f},
{1.00f, 1.00f, 1.00f}};
float delta = 10;
int color = 0;
if (palette == MIRC_PAL) {
for (int i = 0; i < 16; i++) {
if (DIST(pixel, mirc_palette[i]) < delta) {
delta = DIST(pixel, mirc_palette[i]);
color = i;
}
}
} else if (palette == VGA_PAL) {
for (int i = 0; i < 16; i++) {
if (DIST(pixel, vga_palette[i]) < delta) {
delta = DIST(pixel, vga_palette[i]);
color = i;
}
}
} else { /* XIRC_PAL */
for (int i = 0; i < 99; i++) {
if (DIST(pixel, xirc_palette[i]) < delta) {
delta = DIST(pixel, xirc_palette[i]);
color = i;
}
}
}
return color;
}
double
huetorgb(double p, double q, double t)
{
if (t < 0.0f) {
t += 1.0f;
} else if (t > 1.0f) {
t -= 1.0f;
}
if (t < 1.0f/6.0f) {
return p + (q - p) * 6.0f * t;
}
if (t < 0.5f) {
return q;
}
if (t < 2.0f/3.0f) {
return p + (q - p) * ((2.0f/3.0f) - t) * 6.0f;
}
return p;
}
/* sat and lum are percentages */
void
tweak(float *pixel, float sat, float lum)
{
/* convert rgb to hsl */
float r = pixel[R];
float g = pixel[G];
float b = pixel[B];
float max = r > g ? r > b ? r : b : g > b ? g : b;
float min = r < g ? r < b ? r : b : g < b ? g : b;
float h = (min + max) / 2.0f;
float s = (min + max) / 2.0f;
float l = (min + max) / 2.0f;
float d = max - min;
float q = 0.0f;
float p = 0.0f;
if (max == min) {
s = l = 0.0f;
} else {
s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
if (max == r) {
h = (g - b) / d + (g < b ? 6.0f : 0.0f);
} else if (max == g) {
h = (b - r) / d + 2.0f;
} else { /* max == b */
h = (r - g) / d + 4.0f;
}
}
h /= 6.0f;
/* apply tweaks */
s *= sat * 0.01f;
l *= lum * 0.01f;
/* convert from hsl to rgb */
if (s == 0.0f) {
r = g = b = l;
} else {
q = l < 0.5f ? l * (1.0f + s) : l + s - l * s;
p = 2 * l - q;
r = huetorgb(p, q, h + 1.0f/3.0f);
g = huetorgb(p, q, h);
b = huetorgb(p, q, h - 1.0f/3.0f);
}
/* clamp values */
pixel[R] = r < 0.0f ? 0.0f : r > 1.0f ? 1.0f : r;
pixel[G] = g < 0.0f ? 0.0f : g > 1.0f ? 1.0f : g;
pixel[B] = b < 0.0f ? 0.0f : b > 1.0f ? 1.0f : b;
}
void
usage(void)
{
fprintf(stderr, "usage: p2u [options] input\n");
fprintf(stderr, "\n");
fprintf(stderr, "-b percent Adjust brightness levels, default is 100.\n");
fprintf(stderr, "-f a|d|e|m Specify output format ANSI, DOS (ANSI with\n");
fprintf(stderr, " CP437 characters), emoji or mirc. Default is ANSI.\n");
fprintf(stderr, "-p m|v|x Specify palette to use, mirc, VGA, or extended mirc,\n");
fprintf(stderr, " default is VGA.\n");
fprintf(stderr, "-s percent Adjust saturation levels, default is 100.\n");
fprintf(stderr, "-t percent Adjust transparency threshold of alpha channel,\n");
fprintf(stderr, " default is 50.\n");
fprintf(stderr, "-w width Specify output width, default is the image width.\n");
fprintf(stderr, "\n");
exit(1);
}