sandpiles

sandpile art
git clone git://git.rr3.xyz/sandpiles
Log | Files | Refs | README | LICENSE

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+-
MMakefile | 3+++
Mcommon.c | 138+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mcommon.h | 27+++++++++++++++++++++++++--
Aff2sp.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsp2ff | 0
Asp2ff.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mspstabilize.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); }