// x-run: ~/scripts/runc.sh % -Wall -Wextra -std=c99 -pedantic -lm --- ~/images/boykisser.png cpi-images/boykisser.cpi #define STB_IMAGE_IMPLEMENTATION #include #define STB_IMAGE_RESIZE_IMPLEMENTATION #include #include #include #include #include #include #include #include #include #define MAX_COLOR_DIFFERENCE 768 struct arguments { bool text_mode; int width, height; enum cpi_version { CPI_VERSION_AUTO, CPI_VERSION_0, CPI_VERSION_1, CPI_VERSION_2, } cpi_version; enum placement { PLACEMENT_CENTER, PLACEMENT_COVER, PLACEMENT_TILE, PLACEMENT_FULL, PLACEMENT_EXTEND, PLACEMENT_FILL } placement; char *input_path; char *output_path; } args = { .text_mode = false, .width = 4 * 8 - 1, .height = 3 * 6 - 2, .cpi_version = CPI_VERSION_AUTO, .placement = PLACEMENT_FULL, .input_path = NULL, .output_path = NULL }; struct image { int w, h; union color { // Alpha channel is not used, it's just there to align it all struct rgba { uint8_t r, g, b, a; } rgba; uint32_t v; } *pixels; }; bool parse_cmdline(int argc, char **argv); void show_help(const char *progname, bool show_all, FILE *fp); struct image *image_load(const char *fp); struct image *image_new(int w, int h); struct image *image_resize(struct image *original, int new_w, int new_h); struct image *image_dither(struct image *original, union color *colors, size_t n_colors); void image_unload(struct image *img); const char *known_file_extensions[] = { ".png", ".jpg", ".jpeg", ".jfif", ".jpg", ".gif", ".tga", ".bmp", ".hdr", ".pnm", 0 }; static const struct optiondocs { char shortopt; char *longopt; char *target; char *doc; struct optiondocs_choice { char *value; char *doc; } *choices; } optiondocs[] = { { 'h', "help", 0, "Show help", 0 }, { 't', "textmode", 0, "Output Lua script instead of binary", 0 }, { 'W', "width", "width", "Width in characters", 0 }, { 'h', "height", "height", "Height in characters", 0 }, { 'V', "cpi_version", "version", "Force specific version of CPI", (struct optiondocs_choice[]) { { "-1", "Choose best available" }, { "0", "OG CPI, 255x255, uncompressed" }, { "1", "CPIv1, huge images, uncompressed" }, { "255", "In-dev version, may not work" }, { 0, 0 } } }, { 'p', "placement", "placement", "Image placement mode (same as in hsetroot)", (struct optiondocs_choice[]){ { "center", "Render image centered on the canvas" }, { "cover", "Centered on screen, scaled to fill fully" }, { "tile", "Render image tiled" }, { "full", "Use maximum aspect ratio" }, { "extend", "Same as \"full\", but filling borders" }, { "fill", "Stretch to fill" }, { 0, 0 } } }, { 0, 0, "input.*", "Input file path", 0 }, { 0, 0, "output.cpi", "Output file path", 0 }, { 0 } }; int main(int argc, char **argv) { if (!parse_cmdline(argc, argv)) { show_help(argv[0], false, stderr); fprintf(stderr, "Fatal error occurred, exiting.\n"); return EXIT_FAILURE; } struct image *src_image = image_load(args.input_path); if (!src_image) { fprintf(stderr, "Error: failed to open the file\n"); return EXIT_FAILURE; } struct image *canvas = image_new(args.width * 2, args.height * 3); if (!canvas) { fprintf(stderr, "Error: failed to allocate second image buffer\n"); return EXIT_FAILURE; } // TODO: actually do stuff image_unload(src_image); image_unload(canvas); return EXIT_SUCCESS; } bool parse_cmdline(int argc, char **argv) { static struct option options[] = { { "help", no_argument, 0, 'h' }, { "textmode", no_argument, 0, 't' }, { "width", required_argument, 0, 'W' }, { "height", required_argument, 0, 'H' }, { "cpi_version", required_argument, 0, 'V' }, { "placement", required_argument, 0, 'p' }, { 0, 0, 0, 0 } }; while (true) { int option_index = 0; int c = getopt_long(argc, argv, "htW:H:V:p:", options, &option_index); if (c == -1) break; if (c == 0) c = options[option_index].val; if (c == '?') break; switch (c) { case 'h': show_help(argv[0], true, stdout); exit(EXIT_SUCCESS); break; case 't': args.text_mode = true; if (args.cpi_version != CPI_VERSION_AUTO) { fprintf(stderr, "Warning: text mode ignores version\n"); } break; case 'W': args.width = atoi(optarg); break; case 'H': args.height = atoi(optarg); break; case 'V': { int v = atoi(optarg); if ((v < -1 || v > 1) && v != 255) { fprintf(stderr, "Error: Invalid CPI version: %d\n", args.cpi_version); return false; } args.cpi_version = v == -1 ? CPI_VERSION_AUTO : v; } break; case 'p': if (0 == strcmp(optarg, "center")) { args.placement = PLACEMENT_CENTER; } else if (0 == strcmp(optarg, "cover")) { args.placement = PLACEMENT_COVER; } else if (0 == strcmp(optarg, "tile")) { args.placement = PLACEMENT_TILE; } else if (0 == strcmp(optarg, "full")) { args.placement = PLACEMENT_FULL; } else if (0 == strcmp(optarg, "extend")) { args.placement = PLACEMENT_EXTEND; } else if (0 == strcmp(optarg, "fill")) { args.placement = PLACEMENT_FILL; } else { fprintf(stderr, "Error: invaild placement %s\n", optarg); return false; } break; } } if (optind == argc) { fprintf(stderr, "Error: no input file provided\n"); return false; } else if (optind + 1 == argc) { fprintf(stderr, "Error: no output file provided\n"); return false; } else if ((argc - optind) != 2) { fprintf(stderr, "Error: too many arguments\n"); return false; } args.input_path = argv[optind]; args.output_path = argv[optind + 1]; const char *extension = strrchr(args.input_path, '.'); if (!extension) { fprintf(stderr, "Warning: no file extension, reading may fail!\n"); } else { bool known = false; for (int i = 0; known_file_extensions[i] != 0; i++) { if (0 == strcasecmp(known_file_extensions[i], extension)) { known = true; break; } } if (!known) { fprintf(stderr, "Warning: unknown file extension %s, reading may fail!\n", extension); } } return true; } void show_help(const char *progname, bool show_all, FILE *fp) { fprintf(fp, "usage: %s", progname); for (int i = 0; optiondocs[i].doc != 0; i++) { struct optiondocs doc = optiondocs[i]; fprintf(fp, " ["); if (doc.shortopt) fprintf(fp, "-%c", doc.shortopt); if (doc.shortopt && doc.longopt) fprintf(fp, "|"); if (doc.longopt) fprintf(fp, "--%s", doc.longopt); if (doc.target) { if (doc.shortopt || doc.longopt) fprintf(fp, " "); fprintf(fp, "%s", doc.target); } fprintf(fp, "]"); } fprintf(fp, "\n"); if (!show_all) return; fprintf(fp, "\n"); fprintf(fp, "ComputerCraft Palette Image converter\n"); fprintf(fp, "\n"); fprintf(fp, "positional arguments:\n"); for (int i = 0; optiondocs[i].doc != 0; i++) { struct optiondocs doc = optiondocs[i]; if (!doc.shortopt && !doc.longopt) { fprintf(fp, " %s\t%s\n", doc.target, doc.doc); } } fprintf(fp, "\n"); fprintf(fp, "options:\n"); for (int i = 0; optiondocs[i].doc != 0; i++) { struct optiondocs doc = optiondocs[i]; if (!doc.shortopt && !doc.longopt) { continue; } fprintf(fp, " "); int x = 2; if (doc.shortopt) { fprintf(fp, "-%c", doc.shortopt); x += 2; } if (doc.shortopt && doc.longopt) { fprintf(fp, ", "); x += 2; } if (doc.longopt) { fprintf(fp, "--%s", doc.longopt); x += strlen(doc.longopt) + 2; } if (doc.choices) { fprintf(fp, " {"); for (int j = 0; doc.choices[j].value != 0; j++) { if (j > 0) { fprintf(fp, ","); x += 1; } fprintf(fp, "%s", doc.choices[j].value); x += strlen(doc.choices[j].value); } fprintf(fp, "}"); x += 3; } else if (doc.target) { fprintf(fp, " "); fprintf(fp, "%s", doc.target); x += strlen(doc.target) + 1; } if (x > 24) fprintf(fp, "\n%24c", ' '); else fprintf(fp, "%*c", 24 - x, ' '); fprintf(fp, "%s\n", doc.doc); if (doc.choices) { for (int j = 0; doc.choices[j].value != 0; j++) { fprintf(fp, "%26c", ' '); if (doc.shortopt) fprintf(fp, "-%c ", doc.shortopt); else if (doc.longopt) fprintf(fp, "--%s", doc.longopt); fprintf(fp, "%-12s %s\n", doc.choices[j].value, doc.choices[j].doc); } } } } struct image *image_load(const char *fp) { struct image *img = calloc(1, sizeof(struct image)); if (!img) return NULL; img->pixels = (union color*)stbi_load(fp, &img->w, &img->h, 0, 4); if (!img->pixels) { free(img); return NULL; } return img; } struct image *image_new(int w, int h) { struct image *img = calloc(1, sizeof(struct image)); if (!img) return NULL; img->pixels = calloc(h, sizeof(union color) * w); img->w = w; img->h = h; if (!img->pixels) { free(img); return NULL; } return img; } struct image *image_resize(struct image *original, int new_w, int new_h) { struct image *resized = image_new(new_w, new_h); if (!resized) return NULL; stbir_resize_uint8_srgb((unsigned char *)original->pixels, original->w, original->h, 0, (unsigned char *)resized->pixels, resized->w, resized->h, 0, STBIR_RGBA); return resized; } void image_unload(struct image *img) { free(img->pixels); free(img); }