/* http://sedcore.eu.org/small/spiralish.tar.gz */ /* version 2026-07-17 */ /* gcc -Wall -o spiralish spiralish.c -lpng -lm -g -O3 */ /* ./spiralish | ./gui -w 1040 -h 140 -f 30 -rgba */ /* to generate video: * ./spiralish -no-loop| ffmpeg -f rawvideo -s 1040x140 -r 30 -pix_fmt rgba -i - -pix_fmt yuv420p -c:v libsvtav1 -crf 50 /tmp/spiralish.webm */ #include #include #include #include #include #include typedef struct { float x; float y; } point_t; typedef struct { int r; int g; int b; } color_t; typedef point_t vec_t; float length(vec_t v) { return sqrt(v.x * v.x + v.y * v.y); } vec_t vec_from_pts(point_t a, point_t b) { return (vec_t){ .x = b.x - a.x, .y = b.y - a.y }; } /* v must not be 0 */ vec_t normalize(vec_t v) { float l = length(v); return (vec_t){ .x = v.x / l, .y = v.y / l }; } point_t rotate(point_t p, float angle) { float c = cos(angle); float s = sin(angle); /* (p.x + i p.y) * (cos + i sin) */ return (point_t){ .x = p.x * c - p.y * s, .y = p.x * s + p.y * c }; } int is_same(float a, float b) { return fabs(a-b) < 1e-10; } int is_zero(float a) { return is_same(a, 0); } int eq(vec_t v1, vec_t v2) { return is_same(v1.x, v2.x) && is_same(v1.y, v2.y); } /* returns in *p the intersection of lines (os) and (t1t2) * returns -1 if no intersection or if intersection in not between t1 and t2 * returns k in [0..1] if intersection between t1 and t2 * if intersection is t1 return 0, if it's t2 return 1, otherwise return * in between value * o, s, t1 and t2 must be distinct * t1 and t2 must have different x */ float intersect(point_t *p, point_t o, point_t s, point_t t1, point_t t2) { vec_t os = { .x = s.x - o.x, .y = s.y - o.y }; vec_t t1t2 = { .x = t2.x - t1.x, .y = t2.y - t1.y }; if (eq(normalize(os), normalize(t1t2))) return -1; /* linear system, l1 for x, l2 for y (may be reversed below if l2[2] is 0) * P = T1 + k * T1T2 * P = O + l * OS * => T1 - O + k * T1T2 - l * OS = 0 */ float _l1[3]; float _l2[3]; float *l1 = _l1; float *l2 = _l2; l1[0] = t1.x - o.x; l2[0] = t1.y - o.y; l1[1] = t1t2.x; l2[1] = t1t2.y; l1[2] = -os.x; l2[2] = -os.y; /* now find k and l, the system to solve is: * l1[0] + k * l1[1] + l * l1[2] = 0 * l2[0] + k * l2[1] + l * l2[2] = 0 * we want to transform to: * a0 + k b0 = 0 * a1 + k b1 + l c1 = 0 * with c1 != 0 */ if (is_zero(l2[2])) { l2 = _l1; l1 = _l2; } float c0 = l1[2]; float c1 = l2[2]; /* update l1 to have l1[2] == 0 by "merging" with l2 (I forgot how it's called) * we do l1 = c1 * l1 + c0 * l2 */ for (int i = 0; i < 3; i++) { l1[i] = l1[i] * c1 - l2[i] * c0; } float k = -l1[0] / l1[1]; float l = -(l2[0] + k * l2[1]) / l2[2]; if (k < 0 || k > 1) return -1; *p = (point_t) { .x = o.x + l * os.x, .y = o.y + l * os.y }; return k; } #define COLOR_0 (color_t){ 255, 255, 0 } #if 0 #define COLOR_1 (color_t){ 255, 0, 0 } #define COLOR_2 (color_t){ 0, 255, 0 } #define COLOR_3 (color_t){ 0, 0, 255 } #define COLOR_4 (color_t){ 255, 255, 255 } #endif #if 0 #define COLOR_1 (color_t){ 255, 255, 255 } #define COLOR_2 (color_t){ 2*255/3, 2*255/3, 2*255/3 } #define COLOR_3 (color_t){ 255/3, 255/3, 255/3 } #define COLOR_4 (color_t){ 2*255/3, 2*255/3, 2*255/3 } #endif #if 0 #undef COLOR_0 #define COLOR_0 (color_t){ 'a', 255, 255 } #define COLOR_1 (color_t){ 'b', 255, 255 } #define COLOR_2 (color_t){ 'c', 2*255/3, 2*255/3 } #define COLOR_3 (color_t){ 'd', 255/3, 255/3 } #define COLOR_4 (color_t){ 'c', 2*255/3, 2*255/3 } #endif float range(float a, float b, float pos) { return a + (b-a) * pos; } #if 1 #define WIDTH (52 * 20) #define HEIGHT (7 * 20) #endif #if 0 #define WIDTH (52) #define HEIGHT (7) #endif unsigned char *textures[4]; color_t color_from_texture(int texture_id, int column, int line) { int offset = line * WIDTH + column; unsigned char *b = textures[texture_id] + offset * 4; return (color_t){ b[0], b[1], b[2] }; } struct png_reader { unsigned char *data; int size; int pos; }; static void png_readfn(png_structp png_ptr, png_bytep data, png_size_t length) { struct png_reader *r = png_get_io_ptr(png_ptr); if (length > r->size - r->pos) png_error(png_ptr, "bad png image"); memcpy(data, r->data + r->pos, length); r->pos += length; } static void load_image(int texture_id, unsigned char *data, int length) { png_structp png_ptr; png_infop info_ptr; png_bytepp image; int width, height, bit_depth, color_type, channels; unsigned char *img_data; struct png_reader r; int i; /* unpack PNG data */ r.data = data; r.size = length; r.pos = 0; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (png_ptr == NULL) abort(); info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) abort(); if (setjmp(png_jmpbuf(png_ptr))) abort(); png_set_read_fn(png_ptr, &r, png_readfn); png_read_png(png_ptr, info_ptr, /*PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_PACKING |*/ 0/*PNG_TRANSFORM_GRAY_TO_RGB | PNG_TRANSFORM_BGR*/, NULL); image = png_get_rows(png_ptr, info_ptr); width = png_get_image_width(png_ptr, info_ptr); height = png_get_image_height(png_ptr, info_ptr); bit_depth = png_get_bit_depth(png_ptr, info_ptr); color_type = png_get_color_type(png_ptr, info_ptr); channels = png_get_channels(png_ptr, info_ptr); if (width < 1 || width > 2000 || height < 1 || height > 1000 || bit_depth != 8 || color_type != PNG_COLOR_TYPE_RGBA || channels != 4) { printf("bad image\n"); abort(); } if (width != WIDTH || height != HEIGHT) { printf("bad image\n"); exit(1); } img_data = malloc(4 * width * height); if (img_data == NULL) abort(); for (i = 0; i < height; i++) memcpy(img_data+i*4*width, image[i], width*4); png_destroy_read_struct(&png_ptr, &info_ptr, NULL); textures[texture_id] = img_data; } void load_textures(void) { char *texture_names[] = { "systemd.png", "sucks.png", "lf.png", "is-shit.png" }; for (int i = 0; i < 4; i++) { FILE *f = fopen(texture_names[i], "r"); if (!f) { perror(texture_names[i]); exit(1); } struct stat statbuf; if (fstat(fileno(f), &statbuf)) { perror(texture_names[i]); exit(1); } int length = statbuf.st_size; unsigned char *data = malloc(length); if (!data) exit(1); if (fread(data, length, 1, f) != 1) exit(1); fclose(f); load_image(i, data, length); free(data); } } color_t get_color(int col, int line, int time) { time %= 200; //time = 454; float line_width = 1; point_t o = { .x = 0, .y = -9.4 }; point_t s = { .x = (line / (float)(HEIGHT-1) - 0.5) * line_width, .y = -14 }; point_t t[5] = { { 1, 0 }, { 0, 1 }, { -1, 0 }, { 0, -1 }, }; /* t[4] is same as t[0], to simplify the loop */ t[4] = t[0]; float angle = 0; //2 * M_PI * time / 10 / 4; //angle += col / 599. * 2 * M_PI * range(.5, 2, (cos(2 * M_PI * time / 100) + 1) / 2); //angle = sin(M_PI * col / 599.) * range(.5, 2, (cos(2 * M_PI * time / 100) + 1) * 20); angle = time / 10. + col / 599. * 3; float tt = time / 20.; float yy = col / (4000. / (52*20) * WIDTH); /* angle formula from https://trasevol.dog/2018/01/24/di20/ */ angle = cos(2 * M_PI * (0.2 * sin(2 * M_PI * (tt*0.1+yy*2)))) + 0.5 * cos(2 * M_PI * (-0.2*tt+yy/2)); angle *= 2 * M_PI; //angle = time * 0.2; /* add a slow rotation */ angle -= tt * M_PI; o = rotate(o, angle); s = rotate(s, angle); color_t color = COLOR_0; float mindist = 1000; #if 0 /* color for each line */ color_t colors[4] = { COLOR_1, COLOR_2, COLOR_3, COLOR_4, }; #endif for (int l = 0; l < 4; l++) { point_t t1 = t[l]; point_t t2 = t[l + 1]; point_t p; float v = intersect(&p, o, s, t1, t2); if (v < 0 || v > 1) continue; int v_in_texture = (1-v) * HEIGHT; if (v_in_texture > HEIGHT-1) v_in_texture = HEIGHT - 1; color_t texture_color = color_from_texture(l, col, v_in_texture); float dist = length(vec_from_pts(o, p)); if (dist < mindist) { mindist = dist; color = texture_color; } } return color; } void usage(void) { printf("options:\n"); printf(" -no-loop\n"); printf(" don't loop, compute only 200 frames\n"); exit(0); } int main(int n, char **v) { int loop_forever = 1; for (int i = 1; i < n; i++) { if (!strcmp(v[i], "-no-loop")) { loop_forever = 0; continue; } usage(); } int width = WIDTH; int height = HEIGHT; load_textures(); for (int t = 0; loop_forever || t < 200; t++) { //for (int t = 0; t < 1; t++) { fprintf(stderr, "%d\n", t); for (int j = 0; j < height; j++) for (int i = 0; i < width; i++) { color_t c = get_color(i, j, t); #if 0 putchar(c.r); #endif #if 1 putchar(c.r); putchar(c.g); putchar(c.b); putchar(0); #endif } } return 0; #if 0 point_t t1 = { 0, -1 }; point_t t2 = { -1, 0 }; point_t o = { 0, -5 }; point_t s = { -1, 0 }; point_t p; int ret = intersect(&p, o, s, t1, t2); #endif return 0; }