commit 3d94a3f9ea734691b2c71e2d13ea5bb7004b1b6f
parent 90d6c0c7d0a91c93849cca9423ea81904a179113
Author: Robert Russell <robertrussell.72001@gmail.com>
Date: Wed, 10 Apr 2024 17:09:48 -0700
Add ff2sp
Diffstat:
| M | .gitignore | | | 2 | +- |
| M | Makefile | | | 3 | +++ |
| M | common.c | | | 138 | +++++++++++++++++++++++++++++++++++++++++++++++-------------------------------- |
| M | common.h | | | 27 | +++++++++++++++++++++++++-- |
| A | ff2sp.c | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| D | sp2ff | | | 0 | |
| A | sp2ff.c | | | 85 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | spstabilize.c | | | 21 | +++++++++++++++------ |
8 files changed, 278 insertions(+), 64 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,3 +1,3 @@
-sandpiles
spstabilize
sp2ff
+ff2sp
diff --git a/Makefile b/Makefile
@@ -9,3 +9,6 @@ spstabilize: spstabilize.c common.c common.h
sp2ff: sp2ff.c common.c common.h
$(CC) -o $@ $(CFLAGS) sp2ff.c common.c -lrcx
+
+ff2sp: ff2sp.c common.c common.h
+ $(CC) -o $@ $(CFLAGS) ff2sp.c common.c -lrcx
diff --git a/common.c b/common.c
@@ -4,9 +4,85 @@
#include "common.h"
-u32 *
-sp_eallocz(u32 w, u32 h) {
- return r_eallocz((w + 2) * (h + 2) * sizeof(u32)); // TODO: align?
+static u8
+hex_value[] = {
+ ['0'] = 0x0, ['1'] = 0x1, ['2'] = 0x2, ['3'] = 0x3, ['4'] = 0x4,
+ ['5'] = 0x5, ['6'] = 0x6, ['7'] = 0x7, ['8'] = 0x8, ['9'] = 0x9,
+ ['A'] = 0xA, ['B'] = 0xB, ['C'] = 0xC, ['D'] = 0xD, ['E'] = 0xE, ['F'] = 0xF,
+ ['a'] = 0xA, ['b'] = 0xB, ['c'] = 0xC, ['d'] = 0xD, ['e'] = 0xE, ['f'] = 0xF,
+};
+
+static bool
+is_hex_numeral(char c) {
+ return ('0' <= c && c <= '9')
+ || ('A' <= c && c <= 'F')
+ || ('a' <= c && c <= 'f');
+}
+
+u16
+parse_rgba_channel(char *s, bool sixteen) {
+ u16 v = 0;
+ for (usize i = 0; i < (sixteen ? 4 : 2); i++) {
+ if (!is_hex_numeral(s[i]))
+ r_fatalf("expected hexadecimal numeral");
+ v = 16u * v + hex_value[(usize)s[i]];
+ }
+ return sixteen ? v : (v << 8) | v;
+}
+
+void
+ff_fwrite(FILE *f, Image img) {
+ usize w = img.w;
+ usize h = img.h;
+
+ char header[16];
+ memcpy(header, "farbfeld", 8);
+ r_writeb32(header + 8, w);
+ r_writeb32(header + 12, h);
+ if (!fwrite(header, sizeof header, 1, f))
+ r_fatalf("ff_fwrite: failed to write header");
+
+ for (usize y = 0; y < h; y++) {
+ for (usize x = 0; x < w; x++) {
+ Rgba p = img.pixels[y * w + x];
+ u16 buf[4] = {
+ r_htob16(p.r),
+ r_htob16(p.g),
+ r_htob16(p.b),
+ r_htob16(p.a),
+ };
+ if (!fwrite(buf, sizeof buf, 1, f))
+ r_fatalf("ff_fwrite: failed to write pixel data");
+ }
+ }
+}
+
+Image
+ff_fread(FILE *f) {
+ char header[16];
+ if (!fread(header, sizeof header, 1, f))
+ r_fatalf("ff_fread: failed to read header");
+ if (memcmp(header, "farbfeld", 8) != 0)
+ r_fatalf("ff_fread: invalid magic");
+ usize w = r_readb32(header + 8);
+ usize h = r_readb32(header + 12);
+
+ Rgba *pixels = r_ealloc(w * h * sizeof *pixels);
+ for (usize y = 0; y < h; y++) {
+ for (usize x = 0; x < w; x++) {
+ char buf[8];
+ if (!fread(buf, sizeof buf, 1, f))
+ r_fatalf("ff_fread: failed to read pixel data");
+ pixels[y * w + x] = (Rgba){
+ .r = r_readb16(buf + 0),
+ .g = r_readb16(buf + 2),
+ .b = r_readb16(buf + 4),
+ .a = r_readb16(buf + 6),
+ };
+ }
+ }
+
+ return (Image){.w = w, .h = h, .pixels = pixels};
}
void
@@ -24,7 +100,7 @@ sp_fwrite(FILE *f, Sandpile sp) {
for (usize y = 1; y <= h; y++) {
for (usize x = 1; x <= w; x++) {
char buf[4];
- r_writeb32(buf, sp.s[y * (w + 2) + x]);
+ r_writeb32(buf, sp.sand[y * (w + 2) + x]);
if (!fwrite(buf, sizeof buf, 1, f))
r_fatalf("sp_fwrite: failed to write sand data");
}
@@ -41,63 +117,15 @@ sp_fread(FILE *f) {
usize w = r_readb32(header + 8);
usize h = r_readb32(header + 12);
- u32 *s = sp_eallocz(w, h);
+ u32 *sand = r_eallocz((w + 2) * (h + 2) * sizeof(u32));
for (usize y = 1; y <= h; y++) {
for (usize x = 1; x <= w; x++) {
char buf[4];
if (!fread(buf, sizeof buf, 1, f))
r_fatalf("sp_fread: failed to read sand data");
- s[y * (w + 2) + x] = r_readb32(buf);
+ sand[y * (w + 2) + x] = r_readb32(buf);
}
}
- return (Sandpile){.w = w, .h = h, .s = s};
-}
-
-/*
-Image
-sp_encode(Sandpile sp) {
- usize w = sp.w;
- usize h = sp.h;
-
- usize size = 8 + 4 + 4 + w * h * 4;
- void *data = r_ealloc(size);
- u8 *cur = data;
-
- memcpy(cur, "sandpile", 8); cur += 8;
- r_writeb32(cur, w); cur += 4;
- r_writeb32(cur, h); cur += 4;
-
- for (usize y = 1; y <= h; y++) {
- for (usize x = 1; x <= w; x++) {
- r_writeb32(cur, sp.s[y * (w + 2) + x]);
- cur += 4;
- }
- }
-
- return (Image){.size = size, .data = data}
-}
-Sandpile
-sp_decode(Image img) {
- u8 *cur = img.data;
- if (img.size < 16)
- r_fatalf("sp_decode: invalid header");
-
- if (memcmp(cur, "sandpile", 8) != 0)
- r_fatalf("sp_decode: invalid magic");
- cur += 8;
-
- usize w = r_readb32(cur); cur += 4;
- usize h = r_readb32(cur); cur += 4;
- if (img.size != 16 + w * h * 4)
- r_fatalf("sp_decode: invalid image size");
-
- u32 *s = sp_eallocz(w, h);
- for (usize y = 1; y <= h; y++) {
- memcpy(&s[y * (w + 2) + 1], cur, w * 4);
- cur += w * 4;
- }
-
- return (Sandpile){.w = w, .h = h, .s = s};
+ return (Sandpile){.w = w, .h = h, .sand = sand};
}
-*/
diff --git a/common.h b/common.h
@@ -1,15 +1,38 @@
#pragma once
#include <rcx/def.h>
+#include <stdbool.h>
#include <stdio.h>
+#define CHECK_FOR_HELP_OPTION(usage_format, argc, argv) \
+ if ((argc) > 1 && (strcmp((argv)[1], "-h") == 0 \
+ || strcmp((argv)[1], "--help") == 0)) { \
+ printf(usage_format, argv[0]); \
+ return 0; \
+ }
+
+typedef struct rgba Rgba;
+typedef struct image Image;
typedef struct sandpile Sandpile;
+struct rgba {
+ u16 r, g, b, a;
+};
+
+struct image {
+ u32 w, h;
+ Rgba *pixels;
+};
+
struct sandpile {
u32 w, h;
- u32 *s;
+ u32 *sand;
};
-u32 *sp_eallocz(u32 w, u32 h);
+u16 parse_rgba_channel(char *s, bool sixteen);
+
+void ff_fwrite(FILE *f, Image img);
+Image ff_fread(FILE *f);
+
void sp_fwrite(FILE *f, Sandpile sp);
Sandpile sp_fread(FILE *f);
diff --git a/ff2sp.c b/ff2sp.c
@@ -0,0 +1,66 @@
+#include <rcx/all.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+
+#define USAGE \
+ "usage: %s SAND [THRESHOLD]\n" \
+ "where SAND is decimal and THRESHOLD is 8 or 16 bit grayscale hex\n"
+
+u32
+parse_dec(char *s) {
+ u32 v = 0;
+ for (; *s; s++) {
+ if (*s < '0' || *s > '9')
+ r_fatalf("expected decimal numeral");
+ u32 d = *s - '0';
+ if (v > U32_MAX / 10 || 10 * v > U32_MAX - d)
+ r_fatalf("overflow");
+ v = 10 * v + d;
+ }
+ return v;
+}
+
+Sandpile
+sandpilify(Image img, u32 amount, u16 threshold) {
+ usize w = img.w;
+ usize h = img.h;
+
+ u32 *sand = r_eallocz((w + 2) * (h + 2) * sizeof(u32));
+ for (usize y = 0; y < h; y++) {
+ for (usize x = 0; x < w; x++) {
+ Rgba p = img.pixels[y * w + x];
+ if (p.r != p.g || p.g != p.b)
+ r_fatalf("expected grayscale");
+ // XXX: Error/warn if alpha != 0xffff ?
+ sand[(y + 1) * (w + 2) + (x + 1)] = p.r >= threshold ? amount : 0;
+ }
+ }
+
+ return (Sandpile){.w = w, .h = h, .sand = sand};
+}
+
+int
+main(int argc, char **argv) {
+ CHECK_FOR_HELP_OPTION(USAGE, argc, argv);
+
+ if (argc < 2 || argc > 3) {
+ fprintf(stderr, USAGE, argv[0]);
+ return 1;
+ }
+ u32 amount = parse_dec(argv[1]);
+ u16 threshold;
+ if (argc == 2) {
+ threshold = 0x8888;
+ } else {
+ usize len = strlen(argv[2]);
+ if (len != 2 && len != 4)
+ r_fatalf("expected 2 or 4 hexadecimal numerals");
+ threshold = parse_rgba_channel(argv[2], len == 4);
+ }
+
+ Image img = ff_fread(stdin);
+ Sandpile sp = sandpilify(img, amount, threshold);
+ sp_fwrite(stdout, sp);
+}
diff --git a/sp2ff b/sp2ff
Binary files differ.
diff --git a/sp2ff.c b/sp2ff.c
@@ -0,0 +1,85 @@
+#include <rcx/all.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+
+#define USAGE \
+ "usage: %s [COLOR0 COLOR1 COLOR2 COLOR3]\n" \
+ "where COLORi is a 24 or 48 (32 or 64) bit RGB (RGBA) hex value\n"
+
+Rgba
+parse_rgba(char *s) {
+ switch (strlen(s)) {
+ case 6: // 8-bit RGB
+ return (Rgba){
+ parse_rgba_channel(s + 0, false),
+ parse_rgba_channel(s + 2, false),
+ parse_rgba_channel(s + 4, false),
+ 0xffff,
+ };
+ case 8: // 8-bit RGBA
+ return (Rgba){
+ parse_rgba_channel(s + 0, false),
+ parse_rgba_channel(s + 2, false),
+ parse_rgba_channel(s + 4, false),
+ parse_rgba_channel(s + 6, false),
+ };
+ case 12: // 16-bit RGB
+ return (Rgba){
+ parse_rgba_channel(s + 0, true),
+ parse_rgba_channel(s + 2, true),
+ parse_rgba_channel(s + 4, true),
+ 0xffff,
+ };
+ case 16: // 16-bit RGBA
+ return (Rgba){
+ parse_rgba_channel(s + 0, true),
+ parse_rgba_channel(s + 2, true),
+ parse_rgba_channel(s + 4, true),
+ parse_rgba_channel(s + 6, true),
+ };
+ default:
+ r_fatalf("expected 6, 8, 12, or 16 hexadecimal numerals");
+ }
+ unreachable;
+}
+
+Image
+draw(Sandpile sp, Rgba (*palette)[4]) {
+ usize w = sp.w;
+ usize h = sp.h;
+
+ Rgba *pixels = r_ealloc(w * h * sizeof *pixels);
+ for (usize y = 0; y < h; y++) {
+ for (usize x = 0; x < w; x++) {
+ u32 s = sp.sand[(y + 1) * (w + 2) + (x + 1)];
+ pixels[y * w + x] = (*palette)[MIN(s, 3)];
+ }
+ }
+
+ return (Image){.w = w, .h = h, .pixels = pixels};
+}
+
+int
+main(int argc, char **argv) {
+ CHECK_FOR_HELP_OPTION(USAGE, argc, argv);
+
+ Rgba palette[4];
+ if (argc == 1) { // Default palette
+ palette[0] = (Rgba){ 0x2222, 0x2727, 0x2525, 0xffff }; // "eerie black"
+ palette[1] = (Rgba){ 0x8989, 0x9898, 0x7878, 0xffff }; // "moss green"
+ palette[2] = (Rgba){ 0xe4e4, 0xe6e6, 0xc3c3, 0xffff }; // "beige"
+ palette[3] = (Rgba){ 0xf7f7, 0xf7f7, 0xf2f2, 0xffff }; // "baby powder"
+ } else if (argc == 5) { // Custom palette
+ for (usize i = 0; i < 4; i++)
+ palette[i] = parse_rgba(argv[i+1]);
+ } else {
+ fprintf(stderr, USAGE, argv[0]);
+ return 1;
+ }
+
+ Sandpile sp = sp_fread(stdin);
+ Image img = draw(sp, &palette);
+ ff_fwrite(stdout, img);
+}
diff --git a/spstabilize.c b/spstabilize.c
@@ -8,14 +8,16 @@
#error "AVX2 support required"
#endif
+#define USAGE "usage: %s\n"
+
Sandpile
-sp_stabilize(Sandpile sp) {
+stabilize(Sandpile sp) {
usize w = sp.w;
usize h = sp.h;
u32 *sand[2];
- sand[0] = sp.s;
- sand[1] = sp_eallocz(w, h);
+ sand[0] = sp.sand;
+ sand[1] = r_eallocz((w + 2) * (h + 2) * sizeof(u32));
isize nxv = (isize)w / 8; // Number of x vectors that fit in w
v8u32 v3 = v8u32_fill(3);
@@ -75,14 +77,21 @@ sp_stabilize(Sandpile sp) {
if (!unstable) {
free(sand[i]);
- return (Sandpile){.w = w, .h = h, .s = sand[!i]};
+ return (Sandpile){.w = w, .h = h, .sand = sand[!i]};
}
}
}
int
-main(void) {
+main(int argc, char **argv) {
+ CHECK_FOR_HELP_OPTION(USAGE, argc, argv);
+
+ if (argc != 1) {
+ fprintf(stderr, USAGE, argv[0]);
+ return 1;
+ }
+
Sandpile sp = sp_fread(stdin);
- sp = sp_stabilize(sp);
+ sp = stabilize(sp);
sp_fwrite(stdout, sp);
}