archive

- Random tools & helpful resources for IRC
git clone git://git.acid.vegas/archive.git
Log | Files | Refs | Archive

p2u.c (15067B)

      1 /* Copyright (c) 2018 Trollforge. All rights reserved.
      2  *
      3  * Redistribution and use in source and binary forms, with or without
      4  * modification, are permitted provided that the following conditions
      5  * are met:
      6  * 1. Redistributions of source code must retain the above copyright
      7  *    notice, this list of conditions and the following disclaimer.
      8  * 2. Redistributions in binary form must reproduce the above copyright
      9  *    notice, this list of conditions and the following disclaimer in the
     10  *    documentation and/or other materials provided with the distribution.
     11  * 3. Trollforge's name may not be used to endorse or promote products
     12  *    derived from this software without specific prior written permission.
     13  */
     14 
     15 #include <math.h>
     16 #include <stdio.h>
     17 #include <string.h>
     18 #include <stdlib.h>
     19 #include <unistd.h>
     20 #include <getopt.h>
     21 #include <stdbool.h>
     22 
     23 #define STB_IMAGE_IMPLEMENTATION
     24 #include "stb_image.h"
     25 
     26 #define STB_IMAGE_RESIZE_IMPLEMENTATION
     27 #include "stb_image_resize.h"
     28 
     29 #define R	0
     30 #define G	1
     31 #define B	2
     32 #define A	3
     33 
     34 #define DIST(x, y)	fabs(sqrtf((x[R] - y[R]) * (x[R] - y[R]) + \
     35 				   (x[G] - y[G]) * (x[G] - y[G]) + \
     36 				   (x[B] - y[B]) * (x[B] - y[B])))
     37 
     38 #define ANSI_FMT	0
     39 #define MIRC_FMT	1
     40 #define EMOJI_FMT	2
     41 
     42 #define VGA_PAL		0
     43 #define MIRC_PAL	1
     44 #define XIRC_PAL	2
     45 
     46 typedef struct block_s {
     47 	int color;
     48 } block_t;
     49 
     50 void usage(void);
     51 int nearestcolor(float *pixel, int palette, float tlevel);
     52 double huetorgb(double p, double q, double t);
     53 void tweak(float *pixel, float sat, float lum);
     54 
     55 int
     56 main(int argc, char *argv[])
     57 {
     58 	int width = 0;
     59 	int height = 0;
     60 	int channels = 0;
     61 	block_t *block = NULL;
     62 
     63 	int format = ANSI_FMT;
     64 	int palette = VGA_PAL;
     65 
     66 	bool cp437 = false;
     67 	bool useice = false;
     68 	bool resize = false;
     69 
     70 	long resize_width = 0;
     71 	long resize_height = 0;
     72 
     73 	int ch = 0;
     74 	int fg = 0;
     75 	int bg = 0;
     76 	int lfg = 0;
     77 	int lbg = 0;
     78 
     79 	float *pixel = NULL;
     80 	float *resized = NULL;
     81 
     82 	float brightness = 100.0f;
     83 	float saturation = 100.0f;
     84 	float tlevel = 0.5f;
     85 
     86 	bool verbose = false;
     87 
     88 	while((ch = getopt(argc, argv, "b:f:p:s:t:w:v")) != -1) {
     89 		switch (ch) {
     90 			case 'b':
     91 				brightness = strtof(optarg, NULL);
     92 				break;
     93 			case 'f':
     94 				switch (optarg[0]) {
     95 					case 'a':
     96 						format = ANSI_FMT;
     97 						break;
     98 					case 'd':
     99 						format = ANSI_FMT;
    100 						cp437 = true;
    101 						useice = true;
    102 						break;
    103 					case 'm':
    104 						format = MIRC_FMT;
    105 						break;
    106 					case 'e':
    107 						format = EMOJI_FMT;
    108 						break;
    109 					default:
    110 						usage();
    111 						break;
    112 				}
    113 				break;
    114 			case 'p':
    115 				switch (optarg[0]) {
    116 					case 'm':
    117 						palette = MIRC_PAL;
    118 						break;
    119 					case 'v':
    120 						palette = VGA_PAL;
    121 						break;
    122 					case 'x':
    123 						palette = XIRC_PAL;
    124 						break;
    125 					default:
    126 						usage();
    127 						break;
    128 				}
    129 				break;
    130 			case 's':
    131 				saturation = strtof(optarg, NULL);
    132 				break;
    133 			case 't':
    134 				tlevel = strtof(optarg, NULL);
    135 				break;
    136 			case 'w':
    137 				resize_width = strtol(optarg, NULL, 10);
    138 				resize = true;
    139 				break;
    140 			case 'v':
    141 				verbose = true;
    142 				break;
    143 			default:
    144 				usage();
    145 		}
    146 	}
    147 	argc -= optind;
    148 	argv += optind;
    149 
    150 	if (argc < 1) {
    151 		usage();
    152 	}
    153 
    154 	/* XXX handle alpha eventually */
    155 	/* channels is the number of channels in the original file, not our buffer) */
    156 	pixel = stbi_loadf(argv[0], &width, &height, &channels, STBI_rgb_alpha);
    157 
    158 	if (!pixel) {
    159 		fprintf(stderr, "Unable to read file: %s\n", argv[0]);
    160 		usage();
    161 	}
    162 
    163 	if (resize) {
    164 		resize_height = height * resize_width / width;
    165 
    166 		resized = malloc(sizeof(float) * resize_width * resize_height * STBI_rgb_alpha);
    167 
    168 		stbir_resize_float(pixel, width, height, 0,
    169 				   resized, resize_width, resize_height, 0,
    170 				   STBI_rgb_alpha);
    171 
    172 		free(pixel);
    173 
    174 		pixel = resized;
    175 		width = resize_width;
    176 		height = resize_height;
    177 	}
    178 
    179 	if (!pixel) {
    180 		usage();
    181 	}
    182 
    183 	if (verbose) {
    184 		fprintf(stderr, "file: %s\n", argv[0]);
    185 		fprintf(stderr, "format: %s\n", format == ANSI_FMT ? "ANSI" :
    186 						format == MIRC_FMT ? "mIRC" :
    187 						"emoji");
    188 		fprintf(stderr, "palette: %s\n", palette == VGA_PAL ? "VGA" : "mIRC");
    189 
    190 		if (format == ANSI_FMT) {
    191 			fprintf(stderr, "iCE: %s\n", useice ? "true" : "false");
    192 			fprintf(stderr, "CP437: %s\n", cp437 ? "true" : "false");
    193 		}
    194 
    195 		fprintf(stderr, "resized: %s\n", resize ? "true" : "false");
    196 		fprintf(stderr, "geometry: %dx%d\n", width, height);
    197 		fprintf(stderr, "channels: %d\n", STBI_rgb);
    198 
    199 		fprintf(stderr, "saturation: %f\n", saturation);
    200 		fprintf(stderr, "brightness: %f\n", brightness);
    201 	}
    202 
    203 	if (brightness != 100.0f || saturation != 100.0f) {
    204 		for (int i = 0; i < height; i++) {
    205 			for (int j = 0; j < width; j++) {
    206 				tweak(&pixel[((width * i) + j) * STBI_rgb_alpha],
    207 				      saturation, brightness);
    208 			}
    209 		}
    210 	}
    211 
    212 	block = malloc(sizeof(block_t) * height * width);
    213 
    214 	if (format == EMOJI_FMT) {
    215 		palette = VGA_PAL;
    216 	}
    217 
    218 	for (int i = 0; i < height; i++) {
    219 		for (int j = 0; j < width; j++) {
    220 			block[(width * i) + j].color =
    221 			nearestcolor(&pixel[((width * i) + j) * STBI_rgb_alpha],
    222 				     palette, tlevel);
    223 		}
    224 	}
    225 	free(pixel);
    226 
    227 	if (format == EMOJI_FMT) {
    228 		for (int i = 0; i < height; i++) {
    229 			for (int j = 0; j < width; j++) {
    230 				switch (block[(width * i) + j].color) {
    231 					case 0:
    232 						printf("⬛");
    233 						break;
    234 					case 1:
    235 						printf("🔴");
    236 						break;
    237 					case 2:
    238 						printf("💚");
    239 						break;
    240 					case 3:
    241 						printf("💩");
    242 						break;
    243 					case 4:
    244 						printf("💙");
    245 						break;
    246 					case 5:
    247 						printf("💜");
    248 						break;
    249 					case 6:
    250 						printf("📫");
    251 						break;
    252 					case 7:
    253 						printf("👽");
    254 						break;
    255 					case 8:
    256 						printf("💣");
    257 						break;
    258 					case 9:
    259 						printf("🧠");
    260 						break;
    261 					case 10:
    262 						printf("🎾");
    263 						break;
    264 					case 11:
    265 						printf("🌞");
    266 						break;
    267 					case 12:
    268 						printf("♿");
    269 						break;
    270 					case 13:
    271 						printf("🐷");
    272 						break;
    273 					case 14:
    274 						printf("💦");
    275 						break;
    276 					case 15:
    277 						printf("💭");
    278 						break;
    279 					/* transparent */
    280 					case -1:
    281 						printf(" ");
    282 						break;
    283 				}
    284 			}
    285 			printf("\n");
    286 		}
    287 		return 0;
    288 	}
    289 
    290 	for (int i = 0; i + 1 < height; i += 2) {
    291 		for (int j = 0; j < width; j++) {
    292 			fg = block[(width * i) + j].color;
    293 			bg = block[(width * (i + 1)) + j].color;
    294 
    295 			/* dont print color codes if we dont have to */
    296 			if (j != 0 && lbg == bg && lfg == fg) {
    297 				/* try to save bytes */
    298 				if (bg == fg) {
    299 					printf(" ");
    300 				} else {
    301 					cp437 ? printf("\xdf") : printf("▀");
    302 				}
    303 			} else {
    304 				/* XXX we dont really have to print both attrs */
    305 				/* XXX not handling alpha here either */
    306 				if (format == ANSI_FMT) {
    307 					if (useice) {
    308 						printf("\x1b[%s%d;%dm%s",
    309 							/* bold and ice */
    310 							(fg > 7 && bg > 7) ? "1;5;" :
    311 							/* bold only */
    312 							(fg > 7 && bg < 8) ? "1;" :
    313 							/* ice only */
    314 							(fg < 8 && bg > 7) ? "5;" :
    315 							/* neither */
    316 							"",
    317 							fg > 7 ? fg - 8 + 30 : fg + 30,
    318 							bg > 7 ? bg - 8 + 40 : bg + 40,
    319 							bg == fg ? " " : cp437 ? "\xdf" : "▀");
    320 					} else {
    321 					/* XXX this doesnt work for extended colors */
    322 					printf("\x1b[%d;%dm%s",
    323 						fg < 8 ? fg + 30 : fg - 8 + 90,
    324 						bg < 8 ? bg + 40 : bg - 8 + 100,
    325 						bg == fg ? " " : cp437 ? "\xdf" : "▀");
    326 					}
    327 				} else {
    328 					if (bg == -1 && fg != -1) {
    329 						printf("\x03%d%s", fg, cp437 ? "\xdf" : "▀");
    330 					} else if (fg == -1 && bg != -1) {
    331 						printf("\x03%d%s", bg, cp437 ? "\xdc" : "▄");
    332 					} else if (fg == -1 && bg == -1) {
    333 						printf("\x03 ");
    334 					} else {
    335 						printf("\x03%d,%d%s", fg, bg,
    336 						       bg == fg ? " " : cp437 ? "\xdf" : "▀");
    337 					}
    338 				}
    339 			}
    340 			lbg = bg;
    341 			lfg = fg;
    342 		}
    343 		/* reset to prevent line bleeding on terms */
    344 		if (format == ANSI_FMT) {
    345 			printf("\x1b[0m%s", cp437 && width == 80 ? "" : "\n");
    346 		} else {
    347 			printf("\n");
    348 		}
    349 	}
    350 	return 0;
    351 }
    352 
    353 int
    354 nearestcolor(float *pixel, int palette, float tlevel)
    355 {
    356 
    357 	if (pixel[A] < tlevel) {
    358 		return -1;
    359 	}
    360 	/* vga palette, maybe add more */
    361 	float vga_palette[16][3] = {{0.00f, 0.00f, 0.00f},
    362 				    {0.66f, 0.00f, 0.00f},
    363 				    {0.00f, 0.66f, 0.00f},
    364 				    {0.66f, 0.33f, 0.00f},
    365 				    {0.00f, 0.00f, 0.66f},
    366 				    {0.66f, 0.00f, 0.66f},
    367 				    {0.00f, 0.66f, 0.66f},
    368 				    {0.66f, 0.66f, 0.66f},
    369 				    {0.33f, 0.33f, 0.33f},
    370 				    {1.00f, 0.85f, 0.85f},
    371 				    {0.33f, 1.00f, 0.33f},
    372 				    {1.00f, 1.00f, 0.33f},
    373 				    {0.33f, 0.33f, 1.00f},
    374 				    {1.00f, 0.33f, 1.00f},
    375 				    {0.33f, 1.00f, 1.00f},
    376 				    {1.00f, 1.00f, 1.00f}};
    377 
    378 	float mirc_palette[16][3] = {{1.00f, 1.00f, 1.00f},
    379 				     {0.00f, 0.00f, 0.00f},
    380 				     {0.00f, 0.00f, 0.50f},
    381 				     {0.00f, 0.57f, 0.00f},
    382 				     {1.00f, 0.00f, 0.00f},
    383 				     {0.50f, 0.00f, 0.00f},
    384 				     {0.61f, 0.00f, 0.61f},
    385 				     {0.98f, 0.50f, 0.00f},
    386 				     {1.00f, 1.00f, 0.00f},
    387 				     {0.00f, 0.98f, 0.00f},
    388 				     {0.00f, 0.57f, 0.57f},
    389 				     {0.00f, 1.00f, 1.00f},
    390 				     {0.00f, 0.33f, 0.98f},
    391 				     {1.00f, 0.00f, 1.00f},
    392 				     {0.50f, 0.50f, 0.50f},
    393 				     {0.82f, 0.82f, 0.82f}};
    394 
    395 	float xirc_palette[99][3] = {{1.00f, 1.00f, 1.00f},
    396 				     {0.00f, 0.00f, 0.00f},
    397 				     {0.00f, 0.00f, 0.50f},
    398 				     {0.00f, 0.57f, 0.00f},
    399 				     {1.00f, 0.00f, 0.00f},
    400 				     {0.50f, 0.00f, 0.00f},
    401 				     {0.61f, 0.00f, 0.61f},
    402 				     {0.98f, 0.50f, 0.00f},
    403 				     {1.00f, 1.00f, 0.00f},
    404 				     {0.00f, 0.98f, 0.00f},
    405 				     {0.00f, 0.57f, 0.57f},
    406 				     {0.00f, 1.00f, 1.00f},
    407 				     {0.00f, 0.33f, 0.98f},
    408 				     {1.00f, 0.00f, 1.00f},
    409 				     {0.50f, 0.50f, 0.50f},
    410 				     {0.82f, 0.82f, 0.82f},
    411 				     {0.28f, 0.00f, 0.00f},
    412 				     {0.28f, 0.13f, 0.00f},
    413 				     {0.28f, 0.28f, 0.00f},
    414 				     {0.20f, 0.28f, 0.00f},
    415 				     {0.00f, 0.28f, 0.00f},
    416 				     {0.00f, 0.28f, 0.17f},
    417 				     {0.00f, 0.28f, 0.28f},
    418 				     {0.00f, 0.15f, 0.28f},
    419 				     {0.00f, 0.00f, 0.28f},
    420 				     {0.18f, 0.00f, 0.28f},
    421 				     {0.28f, 0.00f, 0.28f},
    422 				     {0.28f, 0.00f, 0.16f},
    423 				     {0.45f, 0.00f, 0.00f},
    424 				     {0.45f, 0.23f, 0.00f},
    425 				     {0.45f, 0.45f, 0.00f},
    426 				     {0.32f, 0.45f, 0.00f},
    427 				     {0.00f, 0.45f, 0.00f},
    428 				     {0.00f, 0.45f, 0.29f},
    429 				     {0.00f, 0.45f, 0.45f},
    430 				     {0.00f, 0.25f, 0.45f},
    431 				     {0.00f, 0.00f, 0.45f},
    432 				     {0.29f, 0.00f, 0.45f},
    433 				     {0.45f, 0.00f, 0.45f},
    434 				     {0.45f, 0.00f, 0.27f},
    435 				     {0.71f, 0.00f, 0.00f},
    436 				     {0.71f, 0.39f, 0.00f},
    437 				     {0.71f, 0.71f, 0.00f},
    438 				     {0.49f, 0.71f, 0.00f},
    439 				     {0.00f, 0.71f, 0.00f},
    440 				     {0.00f, 0.71f, 0.44f},
    441 				     {0.00f, 0.71f, 0.71f},
    442 				     {0.00f, 0.39f, 0.71f},
    443 				     {0.00f, 0.00f, 0.71f},
    444 				     {0.46f, 0.00f, 0.71f},
    445 				     {0.71f, 0.00f, 0.71f},
    446 				     {0.71f, 0.00f, 0.42f},
    447 				     {1.00f, 0.00f, 0.00f},
    448 				     {1.00f, 0.55f, 0.00f},
    449 				     {1.00f, 1.00f, 0.00f},
    450 				     {0.70f, 1.00f, 0.00f},
    451 				     {0.00f, 1.00f, 0.00f},
    452 				     {0.00f, 1.00f, 0.63f},
    453 				     {0.00f, 1.00f, 1.00f},
    454 				     {0.00f, 0.55f, 1.00f},
    455 				     {0.00f, 0.00f, 1.00f},
    456 				     {0.65f, 0.00f, 1.00f},
    457 				     {1.00f, 0.00f, 1.00f},
    458 				     {1.00f, 0.00f, 0.60f},
    459 				     {1.00f, 0.35f, 0.35f},
    460 				     {1.00f, 0.71f, 0.35f},
    461 				     {1.00f, 1.00f, 0.44f},
    462 				     {0.81f, 1.00f, 0.38f},
    463 				     {0.44f, 1.00f, 0.44f},
    464 				     {0.40f, 1.00f, 0.79f},
    465 				     {0.43f, 1.00f, 1.00f},
    466 				     {0.35f, 0.71f, 1.00f},
    467 				     {0.35f, 0.35f, 1.00f},
    468 				     {0.77f, 0.35f, 1.00f},
    469 				     {1.00f, 0.40f, 1.00f},
    470 				     {1.00f, 0.35f, 0.74f},
    471 				     {1.00f, 0.61f, 0.61f},
    472 				     {1.00f, 0.83f, 0.61f},
    473 				     {1.00f, 1.00f, 0.61f},
    474 				     {0.89f, 1.00f, 0.61f},
    475 				     {0.61f, 1.00f, 0.61f},
    476 				     {0.61f, 1.00f, 0.86f},
    477 				     {0.61f, 1.00f, 1.00f},
    478 				     {0.61f, 0.83f, 1.00f},
    479 				     {0.61f, 0.61f, 1.00f},
    480 				     {0.86f, 0.61f, 1.00f},
    481 				     {1.00f, 0.61f, 1.00f},
    482 				     {1.00f, 0.58f, 0.83f},
    483 				     {0.00f, 0.00f, 0.00f},
    484 				     {0.07f, 0.07f, 0.07f},
    485 				     {0.16f, 0.16f, 0.16f},
    486 				     {0.21f, 0.21f, 0.21f},
    487 				     {0.30f, 0.30f, 0.30f},
    488 				     {0.40f, 0.40f, 0.40f},
    489 				     {0.51f, 0.51f, 0.51f},
    490 				     {0.62f, 0.62f, 0.62f},
    491 				     {0.74f, 0.74f, 0.74f},
    492 				     {0.89f, 0.89f, 0.89f},
    493 				     {1.00f, 1.00f, 1.00f}};
    494 
    495 	float delta = 10;
    496 	int color = 0;
    497 
    498 	if (palette == MIRC_PAL) {
    499 		for (int i = 0; i < 16; i++) {
    500 			if (DIST(pixel, mirc_palette[i]) < delta) {
    501 				delta = DIST(pixel, mirc_palette[i]);
    502 				color = i;
    503 			}
    504 		}
    505 	} else if (palette == VGA_PAL) {
    506 		for (int i = 0; i < 16; i++) {
    507 			if (DIST(pixel, vga_palette[i]) < delta) {
    508 				delta = DIST(pixel, vga_palette[i]);
    509 				color = i;
    510 			}
    511 		}
    512 	} else { /* XIRC_PAL */
    513 		for (int i = 0; i < 99; i++) {
    514 			if (DIST(pixel, xirc_palette[i]) < delta) {
    515 				delta = DIST(pixel, xirc_palette[i]);
    516 				color = i;
    517 			}
    518 		}
    519 	}
    520 	return color;
    521 }
    522 
    523 double
    524 huetorgb(double p, double q, double t)
    525 {
    526 	if (t < 0.0f) {
    527 		t += 1.0f;
    528 	} else if (t > 1.0f) {
    529 		t -= 1.0f;
    530 	}
    531 
    532 	if (t < 1.0f/6.0f) {
    533 		return p + (q - p) * 6.0f * t;
    534 	}
    535 
    536 	if (t < 0.5f) {
    537 		return q;
    538 	}
    539 
    540 	if (t < 2.0f/3.0f) {
    541 		return p + (q - p) * ((2.0f/3.0f) - t) * 6.0f;
    542 	}
    543 	return p;
    544 }
    545 
    546 /* sat and lum are percentages */
    547 void
    548 tweak(float *pixel, float sat, float lum)
    549 {
    550 	/* convert rgb to hsl */
    551 	float r = pixel[R];
    552 	float g = pixel[G];
    553 	float b = pixel[B];
    554 
    555 	float max = r > g ? r > b ? r : b : g > b ? g : b;
    556 	float min = r < g ? r < b ? r : b : g < b ? g : b;
    557 
    558 	float h = (min + max) / 2.0f;
    559 	float s = (min + max) / 2.0f;
    560 	float l = (min + max) / 2.0f;
    561 
    562 	float d = max - min;
    563 
    564 	float q = 0.0f;
    565 	float p = 0.0f;
    566 
    567 	if (max == min) {
    568 		s = l = 0.0f;
    569 	} else {
    570 		s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min);
    571 		if (max == r) {
    572 			h = (g - b) / d + (g < b ? 6.0f : 0.0f);
    573 		} else if (max == g) {
    574 			h = (b - r) / d + 2.0f;
    575 		} else { /* max == b */
    576 			h = (r - g) / d + 4.0f;
    577 		}
    578 	}
    579 	h /= 6.0f;
    580 
    581 	/* apply tweaks */
    582 	s *= sat * 0.01f;
    583 	l *= lum * 0.01f;
    584 
    585 	/* convert from hsl to rgb */
    586 	if (s == 0.0f) {
    587 		r = g = b = l;
    588 	} else {
    589 		q = l < 0.5f ? l * (1.0f + s) : l + s - l * s;
    590 		p = 2 * l - q;
    591 		r = huetorgb(p, q, h + 1.0f/3.0f);
    592 		g = huetorgb(p, q, h);
    593 		b = huetorgb(p, q, h - 1.0f/3.0f);
    594 	}
    595 
    596 	/* clamp values */
    597 	pixel[R] = r < 0.0f ? 0.0f : r > 1.0f ? 1.0f : r;
    598 	pixel[G] = g < 0.0f ? 0.0f : g > 1.0f ? 1.0f : g;
    599 	pixel[B] = b < 0.0f ? 0.0f : b > 1.0f ? 1.0f : b;
    600 }
    601 
    602 void
    603 usage(void)
    604 {
    605 	fprintf(stderr, "usage: p2u [options] input\n");
    606 	fprintf(stderr, "\n");
    607 	fprintf(stderr, "-b percent     Adjust brightness levels, default is 100.\n");
    608 	fprintf(stderr, "-f a|d|e|m     Specify output format ANSI, DOS (ANSI with\n");
    609 	fprintf(stderr, "               CP437 characters), emoji or mirc.  Default is ANSI.\n");
    610 	fprintf(stderr, "-p m|v|x       Specify palette to use, mirc, VGA, or extended mirc,\n");
    611 	fprintf(stderr, "               default is VGA.\n");
    612 	fprintf(stderr, "-s percent     Adjust saturation levels, default is 100.\n");
    613 	fprintf(stderr, "-t percent     Adjust transparency threshold of alpha channel,\n");
    614 	fprintf(stderr, "               default is 50.\n");
    615 	fprintf(stderr, "-w width       Specify output width, default is the image width.\n");
    616 	fprintf(stderr, "\n");
    617 	exit(1);
    618 }