├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build/* 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(queercat) 2 | add_executable(queercat main.c) 3 | target_link_libraries(queercat 4 | m) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # queercat 2 | a version of lolcat with some lgbtq+ pride flags options 3 | 4 | ## Usage 5 | `$ queercat [-f flag_number][-h horizontal_speed] [-v vertical_speed] [--] [FILES...]` 6 | 7 | ``` 8 | Concatenate FILE(s), or standard input, to standard output. 9 | With no FILE, or when FILE is -, read standard input. 10 | 11 | --flag , -f : Choose colors to use: [rainbow: 0, trans: 1, NB: 2, lesbian: 3, gay: 4, pan: 5, bi: 6, genderfluid: 7, asexual: 8, unlabeled: 9, aromantic : 10, aroace: 11] default is rainbow(0) 12 | --horizontal-frequency , -h : Horizontal rainbow frequency (default: 0.23) 13 | --vertical-frequency , -v : Vertical rainbow frequency (default: 0.1) 14 | --force-color, -F: Force color even when stdout is not a tty 15 | --no-force-locale, -l: Use encoding from system locale instead of assuming UTF-8 16 | --random, -r: Random colors 17 | --24bit, -b: Output in 24-bit "true" RGB mode (slower and 18 | not supported by all terminals) 19 | --version: Print version and exit 20 | --help: Show this message 21 | ``` 22 | (Flag list above may not be up to date; run `queercat --help` to see which flags your version supports!) 23 | 24 | ## Adding a flag 25 | ### Step 1: Define the pattern 26 | To add a flag, first add an instance of `pattern_t` for it to the `flags` array in the `main.c` file. 27 | Look for the section `/* *** Flags *********************************************************/`, then find 28 | `/* Add new flags above this line. */`. The order is important! For the sake of simplicity, you should 29 | only add to the end. 30 | 31 | Example: 32 | ``` c 33 | }, 34 | 35 | { 36 | .name = "nonbinary", 37 | .ansii_pattern = { 38 | .codes_count = 8, 39 | .ansii_codes = {226, 226, 255, 255, 93, 93, 234, 234} 40 | }, 41 | .color_pattern = { 42 | .stripes_count = 4, 43 | .stripes_colors = { 44 | 0xffff00, /* #ffff00 - Yellow */ 45 | 0xb000ff, /* #b000ff - Purple */ 46 | 0xffffff, /* #ffffff - White */ 47 | 0x000000 /* #000000 - Black */ 48 | }, 49 | .factor = 4.0f 50 | }, 51 | .get_color = get_color_stripes 52 | }, 53 | /* Add new flags above this line. */ 54 | }; 55 | ``` 56 | 57 | ### Step 2 (optional): Update `README.md` to include your addition. 58 | **Pay attention to the number it gets from its position in the array!** 59 | 60 | Extend the line for `--flag` under `Usage`. 61 | 62 | *Note that in the readme it is a single line.* 63 | 64 | ### Step 3: Pull request :) 65 | 66 | ## Compiling 67 | to compile with gcc: `$ gcc main.c -lm -o queercat` 68 | 69 | add the binary to a directory in your `PATH` variable (`/bin` can work) to use from everywhere 70 | 71 | ## Credits 72 | base for code: 73 | Original idea: 74 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1689068808, 9 | "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1691990649, 24 | "narHash": "sha256-gMbKOiX1HwClRP9lADaaV/lnZr93NEaOFe4ApDx/zd8=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "6e287913f7b1ef537c97aa301b67c34ea46b640f", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "a version of lolcat with some lgbtq+ pride flags options"; 3 | 4 | inputs = { 5 | nixpkgs.url = github:nixos/nixpkgs/nixos-unstable; 6 | flake-utils.url = github:numtide/flake-utils; 7 | }; 8 | 9 | outputs = {self, nixpkgs, flake-utils, ...}@inputs: flake-utils.lib.eachDefaultSystem (system: 10 | let 11 | pkgs = import nixpkgs { 12 | inherit system; 13 | }; 14 | queercat = (with pkgs; stdenv.mkDerivation { 15 | name = "queercat"; 16 | src = ./.; 17 | nativeBuildInputs = [ cmake ]; 18 | buildPhase = "make -j $NIX_BUILD_CORES"; 19 | installPhase = '' 20 | mkdir -p $out/bin 21 | mv queercat $out/bin 22 | ''; 23 | } 24 | ); 25 | in rec { 26 | defaultApp = flake-utils.lib.mkApp { 27 | drv = defaultPackage; 28 | }; 29 | defaultPackage = queercat; 30 | devShell = pkgs.mkShell { 31 | buildInputs = [ 32 | queercat 33 | ]; 34 | }; 35 | } 36 | ); 37 | } -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define _XOPEN_SOURCE 2 | #define _GNU_SOURCE 3 | 4 | /* *** Includes ******************************************************/ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "math.h" 19 | 20 | 21 | /* *** Common ********************************************************/ 22 | /* Constants */ 23 | #define NEWLINE '\n' 24 | #define ESCAPE_CHAR '\033' 25 | 26 | /* Types */ 27 | typedef enum escape_state_e { 28 | ESCAPE_STATE_OUT = 0, 29 | ESCAPE_STATE_IN, 30 | ESCAPE_STATE_LAST 31 | } escape_state_t; 32 | 33 | /* Macros */ 34 | #define UNUSED(var) ((void)(var)) 35 | #define NEXT_CYCLIC_ELEMENT(array, index, array_size) \ 36 | (((index) + 1 == (array_size)) ? (array)[0] : (array)[((index) + 1)] ) 37 | #define IS_LETTER(c) (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) 38 | 39 | 40 | /* *** Constants *****************************************************/ 41 | 42 | #define MAX_FLAG_STRIPES (6) 43 | #define MAX_ANSII_CODES_PER_STRIPE (5) 44 | #define MAX_ANSII_CODES_COUNT (MAX_FLAG_STRIPES * MAX_ANSII_CODES_PER_STRIPE) 45 | #define MAX_FLAG_NAME_LENGTH (64) 46 | 47 | 48 | /* *** Types *********************************************************/ 49 | /* Colors. */ 50 | typedef uint32_t hex_color_t; 51 | typedef unsigned char ansii_code_t; 52 | typedef struct color_s { 53 | uint8_t red; 54 | uint8_t green; 55 | uint8_t blue; 56 | } color_t; 57 | 58 | /* Color type patterns. */ 59 | typedef enum color_type_e { 60 | COLOR_TYPE_INVALID = -1, 61 | COLOR_TYPE_ANSII = 0, 62 | COLOR_TYPE_24_BIT, 63 | COLOR_TYPE_COUNT 64 | } color_type_t; 65 | typedef struct ansii_pattern_s { 66 | const unsigned int codes_count; 67 | const unsigned char ansii_codes[MAX_ANSII_CODES_COUNT]; 68 | } ansii_pattern_t; 69 | typedef struct color_pattern_s { 70 | const uint8_t stripes_count; 71 | const uint32_t stripes_colors[MAX_FLAG_STRIPES]; 72 | const float factor; 73 | } color_pattern_t; 74 | 75 | /* Get color function. */ 76 | typedef void(get_color_f)(const color_pattern_t *color_pattern, float theta, color_t *color); 77 | 78 | /* Pattern. */ 79 | typedef struct pattern_s { 80 | const char name[MAX_FLAG_NAME_LENGTH]; 81 | const ansii_pattern_t ansii_pattern; 82 | const color_pattern_t color_pattern; 83 | get_color_f *get_color; 84 | } pattern_t; 85 | 86 | /* *** A Single Global ***********************************************/ 87 | char *helpstr; 88 | 89 | /* *** Pattern Functions *********************************************/ 90 | get_color_f get_color_rainbow; 91 | get_color_f get_color_stripes; 92 | 93 | 94 | /* *** Flags *********************************************************/ 95 | const pattern_t flags[] = { 96 | { 97 | .name = "rainbow", 98 | .ansii_pattern = { 99 | .codes_count = 30, 100 | .ansii_codes = { 39, 38, 44, 43, 49, 48, 84, 83, 119, 118, 154, 148, 184, 178, 101 | 214, 208, 209, 203, 204, 198, 199, 163, 164, 128, 129, 93, 99, 63, 69, 33 } 102 | }, 103 | .color_pattern = { 0 }, 104 | .get_color = get_color_rainbow 105 | }, 106 | 107 | { 108 | .name = "transgender", 109 | .ansii_pattern = { 110 | .codes_count = 10, 111 | .ansii_codes = {81, 81, 217, 217, 231, 231, 217, 217, 81, 81} 112 | }, 113 | .color_pattern = { 114 | .stripes_count = 5, 115 | .stripes_colors = { 116 | 0x55cdfc, /* #55cdfc - Blue */ 117 | 0xf7a8b8, /* #f7a8b8 - Pink */ 118 | 0xffffff, /* #ffffff - White */ 119 | 0xf7a8b8, /* #f7a8b8 - Pink */ 120 | 0x55cdfc /* #55cdfc - Blue */ 121 | }, 122 | .factor = 4.0f 123 | }, 124 | .get_color = get_color_stripes 125 | }, 126 | 127 | { 128 | .name = "nonbinary", 129 | .ansii_pattern = { 130 | .codes_count = 8, 131 | .ansii_codes = {226, 226, 255, 255, 93, 93, 234, 234} 132 | }, 133 | .color_pattern = { 134 | .stripes_count = 4, 135 | .stripes_colors = { 136 | 0xffff00, /* #ffff00 - Yellow */ 137 | 0xb000ff, /* #b000ff - Purple */ 138 | 0xffffff, /* #ffffff - White */ 139 | 0x000000 /* #000000 - Black */ 140 | }, 141 | .factor = 4.0f 142 | }, 143 | .get_color = get_color_stripes 144 | }, 145 | 146 | { 147 | .name = "lesbian", 148 | .ansii_pattern = { 149 | .codes_count = 5, 150 | .ansii_codes = {196, 208, 255, 170, 128} 151 | }, 152 | .color_pattern = { 153 | .stripes_count = 5, 154 | .stripes_colors = { 155 | 0xff0000, /* #ff0000 - Red */ 156 | 0xff993f, /* #ff993f - Orange */ 157 | 0xffffff, /* #ffffff - White */ 158 | 0xff8cbd, /* #ff8cbd - Pink */ 159 | 0xff4284 /* #ff4284 - Purple */ 160 | }, 161 | .factor = 2.0f 162 | }, 163 | .get_color = get_color_stripes 164 | }, 165 | 166 | { 167 | .name = "gay", 168 | .ansii_pattern = { 169 | .codes_count = 7, 170 | .ansii_codes = {36, 49, 121, 255, 117, 105, 92} 171 | }, 172 | .color_pattern = { 173 | .stripes_count = 5, 174 | .stripes_colors = { 175 | 0x00b685, /* #00b685 - Teal */ 176 | 0x6bffb6, /* #6bffb6 - Green */ 177 | 0xffffff, /* #ffffff - White */ 178 | 0x8be1ff, /* #8be1ff - Blue */ 179 | 0x8e1ae1 /* #8e1ae1 - Purple */ 180 | }, 181 | .factor = 6.0f 182 | }, 183 | .get_color = get_color_stripes 184 | }, 185 | 186 | { 187 | .name = "pansexual", 188 | .ansii_pattern = { 189 | .codes_count = 9, 190 | .ansii_codes = {200, 200, 200, 227, 227, 227, 45, 45, 45} 191 | }, 192 | .color_pattern = { 193 | .stripes_count = 3, 194 | .stripes_colors = { 195 | 0xff3388, /* #ff3388 - Pink */ 196 | 0xffea00, /* #ffea00 - Yellow */ 197 | 0x00dbff /* #00dbff - Cyan */ 198 | }, 199 | .factor = 8.0f 200 | }, 201 | .get_color = get_color_stripes 202 | }, 203 | 204 | { 205 | .name = "bisexual", 206 | .ansii_pattern = { 207 | .codes_count = 8, 208 | .ansii_codes = {162, 162, 162, 129, 129, 27, 27, 27} 209 | }, 210 | .color_pattern = { 211 | .stripes_count = 5, 212 | .stripes_colors = { 213 | 0xff3b7b, /* #ff3b7b - Pink */ 214 | 0xff3b7b, /* #ff3b7b - Pink */ 215 | 0xd06bcc, /* #d06bcc - Purple */ 216 | 0x3b72ff, /* #3b72ff - Blue */ 217 | 0x3b72ff /* #3b72ff - Blue */ 218 | }, 219 | .factor = 4.0f 220 | }, 221 | .get_color = get_color_stripes 222 | }, 223 | 224 | { 225 | .name = "gender_fluid", 226 | .ansii_pattern = { 227 | .codes_count = 10, 228 | .ansii_codes = {219, 219, 255, 255, 128, 128, 234, 234, 20, 20} 229 | }, 230 | .color_pattern = { 231 | .stripes_count = 5, 232 | .stripes_colors = { 233 | 0xffa0bc, /* #ffa0bc - Pink */ 234 | 0xffffff, /* #ffffff - White */ 235 | 0xc600e4, /* #c600e4 - Purple */ 236 | 0x000000, /* #000000 - Black */ 237 | 0x4e3cbb /* #4e3cbb - Blue */ 238 | }, 239 | .factor = 2.0f 240 | }, 241 | .get_color = get_color_stripes 242 | }, 243 | 244 | { 245 | .name = "asexual", 246 | .ansii_pattern = { 247 | .codes_count = 8, 248 | .ansii_codes = {233, 233, 247, 247, 255, 255, 5, 5} 249 | }, 250 | .color_pattern = { 251 | .stripes_count = 4, 252 | .stripes_colors = { 253 | 0x000000, /* #000000 - Black */ 254 | 0xa3a3a3, /* #a3a3a3 - Gray */ 255 | 0xffffff, /* #ffffff - White */ 256 | 0x800080 /* #800080 - Purple */ 257 | }, 258 | .factor = 4.0f 259 | }, 260 | .get_color = get_color_stripes 261 | }, 262 | 263 | { 264 | .name = "unlabeled", 265 | .ansii_pattern = { 266 | .codes_count = 8, 267 | .ansii_codes = {194, 194, 255, 255, 195, 195, 223, 223} 268 | }, 269 | .color_pattern = { 270 | .stripes_count = 4, 271 | .stripes_colors = { 272 | 0xe6f9e3, /* #e6f9e3 - Green */ 273 | 0xfdfdfb, /* #fdfdfb - White */ 274 | 0xdeeff9, /* #deeff9 - Blue */ 275 | 0xfae1c2 /* #fae1c2 - Orange */ 276 | }, 277 | .factor = 4.0f 278 | }, 279 | .get_color = get_color_stripes 280 | }, 281 | 282 | { 283 | .name = "aromantic", 284 | .ansii_pattern = { 285 | .codes_count = 10, 286 | .ansii_codes = { 287 | 34, 34, 288 | 120, 120, 289 | 255, 255, 290 | 247, 247, 291 | 233, 233 292 | } 293 | }, 294 | .color_pattern = { 295 | .stripes_count = 5, 296 | .stripes_colors = { 297 | 0x3da542, /* #3da542 - Green */ 298 | 0xa8d379, /* #a8d379 - Light green */ 299 | 0xffffff, /* #ffffff - White */ 300 | 0xa9a9a9, /* #a9a9a9 - Grey */ 301 | 0x000000 /* #000000 - Black */ 302 | }, 303 | .factor = 1.0f 304 | }, 305 | .get_color = get_color_stripes 306 | }, 307 | 308 | { 309 | .name = "aroace", 310 | .ansii_pattern = { 311 | .codes_count = 10, 312 | .ansii_codes = { 313 | 208, 208, 314 | 220, 220, 315 | 255, 255, 316 | 75, 75, 317 | 62, 62 318 | }, 319 | }, 320 | .color_pattern = { 321 | .stripes_count = 5, 322 | .stripes_colors = { 323 | 0xe28d00, /* #e28d00 - Orange */ 324 | 0xeccd00, /* #eccd00 - Yellow */ 325 | 0xffffff, /* #ffffff - White */ 326 | 0x62afdd, /* #62afdd - Light blue */ 327 | 0x203756 /* #203756 - Blue */ 328 | }, 329 | .factor = 1.0f 330 | }, 331 | .get_color = get_color_stripes 332 | }, 333 | /* Add new flags above this line. */ 334 | }; 335 | 336 | const int FLAG_COUNT = sizeof(flags)/sizeof(flags[0]); 337 | 338 | /* *** Functions Declarations ****************************************/ 339 | /* Info */ 340 | static void usage(void); 341 | static void version(void); 342 | 343 | /* Helpers */ 344 | static void build_helpstr(void); 345 | static void cleanup_helpstr(void); 346 | static void find_escape_sequences(wint_t current_char, escape_state_t *state); 347 | static wint_t helpstr_hack(FILE * _ignored); 348 | static const pattern_t * lookup_pattern(const char *name); 349 | 350 | /* Colors handling */ 351 | static void mix_colors(uint32_t color1, uint32_t color2, float balance, float factor, color_t *output_color); 352 | static void print_color(const pattern_t *pattern, color_type_t color_type, int char_index, int line_index, double freq_h, double freq_v, double offx, int rand_offset, int cc); 353 | 354 | /* *** Functions *****************************************************/ 355 | static void usage(void) 356 | { 357 | wprintf(L"Usage: queercat [-h horizontal_speed] [-v vertical_speed] [--] [FILES...]\n"); 358 | exit(1); 359 | } 360 | 361 | static void version(void) 362 | { 363 | wprintf(L"queercat version 2.0, (c) 2022 elsa002\n"); 364 | exit(0); 365 | } 366 | 367 | static void build_helpstr(void) 368 | { 369 | if(helpstr != NULL) 370 | return; 371 | 372 | static char helpstr_head[] = "\n" 373 | "Usage: queercat [-f flag_number][-h horizontal_speed] [-v vertical_speed] [--] [FILES...]\n" 374 | "\n" 375 | "Concatenate FILE(s), or standard input, to standard output.\n" 376 | "With no FILE, or when FILE is -, read standard input.\n" 377 | "\n" 378 | " --flag , -f : Choose colors to use (default: 0 (rainbow)):\n"; 379 | 380 | static char helpstr_indent[] = " "; 381 | 382 | static char helpstr_tail[] = 383 | "--horizontal-frequency , -h : Horizontal rainbow frequency (default: 0.23)\n" 384 | " --vertical-frequency , -v : Vertical rainbow frequency (default: 0.1)\n" 385 | " --offset , -o : Offset of the start of the flag\n" 386 | " --force-color, -F: Force color even when stdout is not a tty\n" 387 | " --no-force-locale, -l: Use encoding from system locale instead of\n" 388 | " assuming UTF-8\n" 389 | " --random, -r: Random colors\n" 390 | " --24bit, -b: Output in 24-bit \"true\" RGB mode (slower and\n" 391 | " not supported by all terminals)\n" 392 | " --version: Print version and exit\n" 393 | " --help: Show this message\n" 394 | "\n" 395 | "Examples:\n" 396 | " queercat f - g Output f's contents, then stdin, then g's contents.\n" 397 | " queercat Copy standard input to standard output.\n" 398 | " fortune | queercat Display a rainbow cookie.\n" 399 | "\n" 400 | "Report queercat bugs to \n" 401 | "queercat home page: \n" 402 | "base for code: \n" 403 | "Original idea: \n"; 404 | 405 | /* old version of what this generates, for reference: 406 | * " [rainbow: 0, trans: 1, NB: 2, lesbian: 3,\n" 407 | * " gay: 4, pan: 5, bi: 6, genderfluid: 7, asexual: 8,\n" 408 | * " unlabeled: 9, aromantic: 10, aroace: 11]\n" 409 | * would be nice to have the dynamic word-wrap back, but that's 410 | * more clever than I currently feel like trying to be 411 | */ 412 | const int line_max_len = strlen(helpstr_indent) + MAX_FLAG_NAME_LENGTH + strlen(": 000\n") ; 413 | char lines[FLAG_COUNT][line_max_len]; 414 | size_t lines_total_len = 0; 415 | 416 | for(int i = 0; i < FLAG_COUNT; ++i) { 417 | lines_total_len += snprintf(lines[i], line_max_len, "%s%s: %d\n", helpstr_indent, flags[i].name, i); 418 | } 419 | 420 | size_t helpstr_len = strlen(helpstr_head) + lines_total_len + strlen(helpstr_tail); 421 | 422 | char *out = malloc(helpstr_len); 423 | char *out_pos = out; 424 | 425 | out_pos = mempcpy(out, helpstr_head, strlen(helpstr_head)); 426 | 427 | for(int i = 0; i < FLAG_COUNT; ++i) { 428 | char* this_line = lines[i]; 429 | out_pos = mempcpy(out_pos, this_line, strlen(this_line)); 430 | } 431 | 432 | memcpy(out_pos, helpstr_tail, strlen(helpstr_tail)); 433 | 434 | // TODO? maybe refactor helpstr_hack to not rely on helpstr being global to avoid needing to use atexit 435 | helpstr = out; 436 | atexit(cleanup_helpstr); 437 | } 438 | 439 | static void cleanup_helpstr(void) 440 | { 441 | free(helpstr); 442 | } 443 | 444 | static void find_escape_sequences(wint_t current_char, escape_state_t *state) 445 | { 446 | if (current_char == '\033') { 447 | *state = ESCAPE_STATE_IN; 448 | } else if (*state == ESCAPE_STATE_IN) { 449 | *state = IS_LETTER(current_char) ? ESCAPE_STATE_LAST : ESCAPE_STATE_IN; 450 | } else { 451 | *state = ESCAPE_STATE_OUT; 452 | } 453 | } 454 | 455 | static wint_t helpstr_hack(FILE * _ignored) 456 | { 457 | (void)_ignored; 458 | static size_t idx = 0; 459 | char c = helpstr[idx++]; 460 | if (c) 461 | return c; 462 | idx = 0; 463 | return WEOF; 464 | } 465 | 466 | /* returns NULL on failure */ 467 | static const pattern_t * lookup_pattern(const char *name) 468 | { 469 | // check for name matches 470 | for(int i = 0; i < FLAG_COUNT; ++i) { 471 | if(!strcmp(name, flags[i].name)) 472 | return &flags[i]; 473 | } 474 | 475 | // try number matches 476 | char *endptr; 477 | int flag_num = (int)strtoul(name, &endptr, 10); 478 | 479 | // left-over charaters, or number out of range 480 | if(*endptr || (flag_num < 0 || flag_num >= FLAG_COUNT)) 481 | return NULL; 482 | 483 | return &flags[flag_num]; 484 | } 485 | 486 | static void mix_colors(uint32_t color1, uint32_t color2, float balance, float factor, color_t *output_color) 487 | { 488 | uint8_t red_1 = (color1 & 0xff0000) >> 16; 489 | uint8_t green_1 = (color1 & 0x00ff00) >> 8; 490 | uint8_t blue_1 = (color1 & 0x0000ff) >> 0; 491 | 492 | uint8_t red_2 = (color2 & 0xff0000) >> 16; 493 | uint8_t green_2 = (color2 & 0x00ff00) >> 8; 494 | uint8_t blue_2 = (color2 & 0x0000ff) >> 0; 495 | 496 | balance = pow(balance, factor); 497 | 498 | output_color->red = lrintf(red_1 * balance + red_2 * (1.0f - balance)); 499 | output_color->green = lrintf(green_1 * balance + green_2 * (1.0f - balance)); 500 | output_color->blue = lrintf(blue_1 * balance + blue_2 * (1.0f - balance)); 501 | } 502 | 503 | void get_color_rainbow (const color_pattern_t *color_pattern, float theta, color_t *color) 504 | { 505 | /* Unused variables. */ 506 | UNUSED(color_pattern); 507 | 508 | /* Get theta in range. */ 509 | while (theta < 0) theta += 2.0f * (float)M_PI; 510 | while (theta >= 2.0f * (float)M_PI) theta -= 2.0f * (float)M_PI; 511 | 512 | /* Generate the color. */ 513 | color->red = lrintf((1.0f * (0.5f + 0.5f * sin(theta + 0 ))) * 255.0f); 514 | color->green = lrintf((1.0f * (0.5f + 0.5f * sin(theta + 2 * M_PI / 3 ))) * 255.0f); 515 | color->blue = lrintf((1.0f * (0.5f + 0.5f * sin(theta + 4 * M_PI / 3 ))) * 255.0f); 516 | } 517 | 518 | void get_color_stripes (const color_pattern_t *color_pattern, float theta, color_t *color) 519 | { 520 | /* Get theta in range. */ 521 | while (theta < 0) theta += 2.0f * (float)M_PI; 522 | while (theta >= 2.0f * (float)M_PI) theta -= 2.0f * (float)M_PI; 523 | 524 | /* Find the stripe based on theta and generate the color. */ 525 | for (int i = 0; i < color_pattern->stripes_count; i++) { 526 | float stripe_size = (2.0f * M_PI) / color_pattern->stripes_count; 527 | float min_theta = i * stripe_size; 528 | float max_theta = (i + 1) * stripe_size; 529 | 530 | if (min_theta <= theta && max_theta > theta) { 531 | float balance = 1 - ((theta - min_theta) / stripe_size); 532 | mix_colors( 533 | color_pattern->stripes_colors[i], 534 | NEXT_CYCLIC_ELEMENT(color_pattern->stripes_colors, i, color_pattern->stripes_count), 535 | balance, 536 | color_pattern->factor, 537 | color); 538 | return; 539 | } 540 | } 541 | } 542 | 543 | static void print_color(const pattern_t *pattern, color_type_t color_type, int char_index, int line_index, double freq_h, double freq_v, double offx, int rand_offset, int cc) 544 | { 545 | float theta; 546 | color_t color = { 0 }; 547 | 548 | int ncc; 549 | 550 | switch (color_type) { 551 | case COLOR_TYPE_24_BIT: 552 | theta = char_index * freq_h / 5.0f + line_index * freq_v + (offx + 2.0f * rand_offset / (float)RAND_MAX) * M_PI; 553 | 554 | pattern->get_color(&pattern->color_pattern, theta, &color); 555 | wprintf(L"\033[38;2;%d;%d;%dm", color.red, color.green, color.blue); 556 | break; 557 | 558 | case COLOR_TYPE_ANSII: 559 | ncc = offx * pattern->ansii_pattern.codes_count + (int)(char_index * freq_h + line_index * freq_v); 560 | if (cc != ncc) 561 | wprintf(L"\033[38;5;%hhum", pattern->ansii_pattern.ansii_codes[(rand_offset + (cc = ncc)) % pattern->ansii_pattern.codes_count]); 562 | break; 563 | 564 | default: 565 | exit(1); 566 | } 567 | } 568 | 569 | int main(int argc, char** argv) 570 | { 571 | char* default_argv[] = { "-" }; 572 | int cc = -1; 573 | int i = 0; 574 | int char_index = 0; 575 | int line_index = 0; 576 | wint_t current_char = '\0'; 577 | bool print_colors = isatty(STDOUT_FILENO); 578 | bool force_locale = true; 579 | bool random = false; 580 | color_type_t color_type = COLOR_TYPE_ANSII; 581 | double freq_h = 0.23; 582 | double freq_v = 0.1; 583 | char* flag_type = "rainbow"; 584 | 585 | struct timeval tv; 586 | gettimeofday(&tv, NULL); 587 | double offx = (tv.tv_sec % 300) / 300.0; 588 | 589 | build_helpstr(); 590 | 591 | /* Handle flags. */ 592 | for (i = 1; i < argc; i++) { 593 | char* endptr; 594 | if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--flag")) { 595 | if ((++i) < argc) { 596 | flag_type = argv[i]; 597 | } else { 598 | usage(); 599 | } 600 | } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--horizontal-frequency")) { 601 | if ((++i) < argc) { 602 | freq_h = strtod(argv[i], &endptr); 603 | if (*endptr) 604 | usage(); 605 | } else { 606 | usage(); 607 | } 608 | } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--offset")) { 609 | if ((++i) < argc) { 610 | offx = strtod(argv[i], &endptr); 611 | if (*endptr) 612 | usage(); 613 | } else { 614 | usage(); 615 | } 616 | } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--vertical-frequency")) { 617 | if ((++i) < argc) { 618 | freq_v = strtod(argv[i], &endptr); 619 | if (*endptr) 620 | usage(); 621 | } else { 622 | usage(); 623 | } 624 | } else if (!strcmp(argv[i], "-F") || !strcmp(argv[i], "--force-color")) { 625 | print_colors = true; 626 | } else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--no-force-locale")) { 627 | force_locale = false; 628 | } else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--random")) { 629 | random = true; 630 | } else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--24bit")) { 631 | color_type = COLOR_TYPE_24_BIT; 632 | } else if (!strcmp(argv[i], "--version")) { 633 | version(); 634 | } else { 635 | if (!strcmp(argv[i], "--")) 636 | i++; 637 | break; 638 | } 639 | } 640 | 641 | /* Get pattern. */ 642 | const pattern_t *pattern = lookup_pattern(flag_type); 643 | if (pattern == NULL) { 644 | fprintf(stderr, "Invalid flag: %s\n", flag_type); 645 | exit(1); 646 | } 647 | 648 | /* Handle randomness. */ 649 | int rand_offset = 0; 650 | if (random) { 651 | srand(time(NULL)); 652 | rand_offset = rand(); 653 | } 654 | 655 | /* Get inputs. */ 656 | char** inputs = argv + i; 657 | char** inputs_end = argv + argc; 658 | if (inputs == inputs_end) { 659 | inputs = default_argv; 660 | inputs_end = inputs + 1; 661 | } 662 | 663 | /* Handle locale. */ 664 | char* env_lang = getenv("LANG"); 665 | if (force_locale && env_lang && !strstr(env_lang, "UTF-8")) { 666 | if (!setlocale(LC_ALL, "C.UTF-8")) { /* C.UTF-8 may not be available on all platforms */ 667 | setlocale(LC_ALL, ""); /* Let's hope for the best */ 668 | } 669 | } else { 670 | setlocale(LC_ALL, ""); 671 | } 672 | 673 | /* For file in inputs. */ 674 | for (char** filename = inputs; filename < inputs_end; filename++) { 675 | wint_t (*this_file_read_wchar)(FILE*); /* Used for --help because fmemopen is universally broken when used with fgetwc */ 676 | FILE* f; 677 | escape_state_t escape_state = ESCAPE_STATE_OUT; 678 | 679 | /* Handle "--help", "-" (STDIN) and file names. */ 680 | if (!strcmp(*filename, "--help")) { 681 | this_file_read_wchar = &helpstr_hack; 682 | f = 0; 683 | 684 | } else if (!strcmp(*filename, "-")) { 685 | this_file_read_wchar = &fgetwc; 686 | f = stdin; 687 | 688 | } else { 689 | this_file_read_wchar = &fgetwc; 690 | f = fopen(*filename, "r"); 691 | if (!f) { 692 | fwprintf(stderr, L"Cannot open input file \"%s\": %s\n", *filename, strerror(errno)); 693 | return 2; 694 | } 695 | } 696 | 697 | /* While there are chars to read. */ 698 | while ((current_char = this_file_read_wchar(f)) != WEOF) { 699 | 700 | /* If set to print colors, handle the colors. */ 701 | if (print_colors) { 702 | 703 | /* Skip escape sequences. */ 704 | find_escape_sequences(current_char, &escape_state); 705 | if (escape_state == ESCAPE_STATE_OUT) { 706 | 707 | /* Handle newlines. */ 708 | if (current_char == '\n') { 709 | line_index++; 710 | char_index = 0; 711 | } else { 712 | char_index += wcwidth(current_char); 713 | print_color(pattern, color_type, char_index, line_index, freq_h, freq_v, offx, rand_offset, cc); 714 | } 715 | } 716 | } 717 | 718 | /* Print the char. */ 719 | putwchar(current_char); 720 | 721 | if (escape_state == ESCAPE_STATE_LAST) { /* implies "print_colors" */ 722 | print_color(pattern, color_type, char_index, line_index, freq_h, freq_v, offx, rand_offset, cc); 723 | } 724 | } 725 | 726 | if (print_colors) 727 | wprintf(L"\033[0m"); 728 | 729 | cc = -1; 730 | 731 | if (f) { 732 | if (ferror(f)) { 733 | fwprintf(stderr, L"Error reading input file \"%s\": %s\n", *filename, strerror(errno)); 734 | fclose(f); 735 | return 2; 736 | } 737 | 738 | if (fclose(f)) { 739 | fwprintf(stderr, L"Error closing input file \"%s\": %s\n", *filename, strerror(errno)); 740 | return 2; 741 | } 742 | } 743 | } 744 | } 745 | --------------------------------------------------------------------------------