forked from hkc/cc-stuff
C version of cc-pic (in progress)
This commit is contained in:
parent
4922c5550f
commit
bf3c5e6b1c
|
@ -0,0 +1,327 @@
|
|||
// x-run: ~/scripts/runc.sh % -Wall -Wextra -std=c99 -pedantic -lm --- ~/images/boykisser.png cpi-images/boykisser.cpi
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include <stb/stb_image_resize2.h>
|
||||
#include <argp.h>
|
||||
#include <stdio.h>
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue