├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── img ├── ger.gif └── logo.png └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | weaver 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Gustav Louw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN = weaver 2 | 3 | CFLAGS = -std=c99 -Wall -Wextra -pedantic -Ofast -flto -march=native 4 | 5 | LDFLAGS = -lm -lSDL2 -lSDL2_image 6 | 7 | CC = gcc 8 | 9 | SRC = main.c 10 | 11 | all: 12 | $(CC) $(CFLAGS) $(SRC) $(LDFLAGS) -o $(BIN) 13 | 14 | run: 15 | ./$(BIN) 16 | 17 | clean: 18 | rm -f $(BIN) 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![screenshot](img/logo.png) 2 | 3 | Weaver weaves a spider tapestry of your favorite image. 4 | 5 | make; ./weaver path/to/image.png threshold 6 | 7 | Dependencies: 8 | 9 | SDL2-devel 10 | 11 | SDL2_image-devel 12 | 13 | Play with threshold (0-255) for weave intensity. Intense weaves take a long time. 14 | 15 | ![screenshot](img/ger.gif) 16 | -------------------------------------------------------------------------------- /img/ger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/weaver/efd316f46bb3f499fbc503fefe21cc900ea4ab3d/img/ger.gif -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glouw/weaver/efd316f46bb3f499fbc503fefe21cc900ea4ab3d/img/logo.png -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct 10 | { 11 | float x; 12 | float y; 13 | } 14 | Point; 15 | 16 | typedef struct 17 | { 18 | Point a; 19 | Point b; 20 | Point c; 21 | } 22 | Tri; 23 | 24 | typedef struct 25 | { 26 | Tri* tri; 27 | int count; 28 | int max; 29 | } 30 | Tris; 31 | 32 | typedef struct 33 | { 34 | Point* point; 35 | int count; 36 | int max; 37 | } 38 | Points; 39 | 40 | const Point zer = { 0.0f, 0.0f }; 41 | 42 | const Point one = { 1.0f, 1.0f }; 43 | 44 | static Tris tsnew(const int max) 45 | { 46 | const Tris ts = { (Tri*) malloc(sizeof(Tri) * max), 0, max }; 47 | return ts; 48 | } 49 | 50 | static Tris tsadd(Tris tris, const Tri tri) 51 | { 52 | if(tris.count == tris.max) 53 | { 54 | puts("size limitation reached"); 55 | exit(1); 56 | } 57 | tris.tri[tris.count++] = tri; 58 | return tris; 59 | } 60 | 61 | static int peql(const Point a, const Point b) 62 | { 63 | return a.x == b.x && a.y == b.y; 64 | } 65 | 66 | static int incircum(const Tri t, const Point p) 67 | { 68 | const float ax = t.a.x - p.x; 69 | const float ay = t.a.y - p.y; 70 | const float bx = t.b.x - p.x; 71 | const float by = t.b.y - p.y; 72 | const float cx = t.c.x - p.x; 73 | const float cy = t.c.y - p.y; 74 | const float det = 75 | (ax * ax + ay * ay) * (bx * cy - cx * by) - 76 | (bx * bx + by * by) * (ax * cy - cx * ay) + 77 | (cx * cx + cy * cy) * (ax * by - bx * ay); 78 | return det > 0.0f; 79 | } 80 | 81 | // Collects all edges from given triangles. 82 | static Tris ecollect(Tris edges, const Tris in) 83 | { 84 | for(int i = 0; i < in.count; i++) 85 | { 86 | const Tri tri = in.tri[i]; 87 | const Tri ab = { tri.a, tri.b, zer }; 88 | const Tri bc = { tri.b, tri.c, zer }; 89 | const Tri ca = { tri.c, tri.a, zer }; 90 | edges = tsadd(edges, ab); 91 | edges = tsadd(edges, bc); 92 | edges = tsadd(edges, ca); 93 | } 94 | return edges; 95 | } 96 | 97 | // Returns true if edge ab of two triangles are alligned. 98 | static int alligned(const Tri a, const Tri b) 99 | { 100 | return (peql(a.a, b.a) && peql(a.b, b.b)) || (peql(a.a, b.b) && peql(a.b, b.a)); 101 | } 102 | 103 | // Flags alligned edges. 104 | static Tris emark(Tris edges) 105 | { 106 | for(int i = 0; i < edges.count; i++) 107 | { 108 | const Tri edge = edges.tri[i]; 109 | for(int j = 0; j < edges.count; j++) 110 | { 111 | if(i == j) 112 | continue; 113 | const Tri other = edges.tri[j]; 114 | if(alligned(edge, other)) 115 | edges.tri[j].c = one; 116 | } 117 | } 118 | return edges; 119 | } 120 | 121 | // Creates new triangles from unique edges and appends to tris. 122 | static Tris ejoin(Tris tris, const Tris edges, const Point p) 123 | { 124 | for(int j = 0; j < edges.count; j++) 125 | { 126 | const Tri edge = edges.tri[j]; 127 | if(peql(edge.c, zer)) 128 | { 129 | const Tri tri = { edge.a, edge.b, p }; 130 | tris = tsadd(tris, tri); 131 | } 132 | } 133 | return tris; 134 | } 135 | 136 | static SDL_Surface* load(const char* const path) 137 | { 138 | SDL_Surface* const img = IMG_Load(path); 139 | if(!img) 140 | { 141 | puts(SDL_GetError()); 142 | exit(1); 143 | } 144 | SDL_PixelFormat* const allocation = SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888); 145 | SDL_Surface* const converted = SDL_ConvertSurface(img, allocation, 0); 146 | return converted; 147 | } 148 | 149 | // Convolution - requires a 3x3 kernel. 150 | static uint32_t conv(uint32_t* p, const int x, const int y, const int w, const int s, const int k[3][3]) 151 | { 152 | return 153 | (k[0][0] * (0xFF & (p[(x - 1) + (y - 1) * w] >> s))) + 154 | (k[0][1] * (0xFF & (p[(x - 0) + (y - 1) * w] >> s))) + 155 | (k[0][2] * (0xFF & (p[(x + 1) + (y - 1) * w] >> s))) + 156 | (k[1][0] * (0xFF & (p[(x - 1) + (y - 0) * w] >> s))) + 157 | (k[1][1] * (0xFF & (p[(x - 0) + (y - 0) * w] >> s))) + 158 | (k[1][2] * (0xFF & (p[(x + 1) + (y - 0) * w] >> s))) + 159 | (k[2][0] * (0xFF & (p[(x - 1) + (y + 1) * w] >> s))) + 160 | (k[2][1] * (0xFF & (p[(x - 0) + (y + 1) * w] >> s))) + 161 | (k[2][2] * (0xFF & (p[(x + 1) + (y + 1) * w] >> s))); 162 | } 163 | 164 | static int weight(const int k[3][3]) 165 | { 166 | return 167 | k[0][0] + k[0][1] + k[0][2] + 168 | k[1][0] + k[1][1] + k[1][2] + 169 | k[2][0] + k[2][1] + k[2][2]; 170 | } 171 | 172 | static uint32_t* blur(uint32_t* const in, const int w, const int h) 173 | { 174 | const int k[3][3] = { 175 | { 1, 2, 1 }, 176 | { 2, 4, 2 }, 177 | { 1, 2, 1 }, 178 | }; 179 | const int bytes = sizeof(*in) * w * h; 180 | uint32_t* const out = (uint32_t*) memcpy(malloc(bytes), in, bytes); 181 | for(int x = 1; x < w - 1; x++) 182 | for(int y = 1; y < h - 1; y++) 183 | { 184 | const uint32_t b = conv(in, x, y, w, 0x10, k) / weight(k); 185 | const uint32_t g = conv(in, x, y, w, 0x08, k) / weight(k); 186 | const uint32_t r = conv(in, x, y, w, 0x00, k) / weight(k); 187 | out[x + y * w] = (b << 0x10) | (g << 0x08) | (r << 0x00); 188 | } 189 | return out; 190 | } 191 | 192 | static uint32_t* grey(uint32_t* const in, const int w, const int h) 193 | { 194 | const int bytes = sizeof(*in) * w * h; 195 | uint32_t* const out = (uint32_t*) memcpy(malloc(bytes), in, bytes); 196 | for(int x = 1; x < w - 1; x++) 197 | for(int y = 1; y < h - 1; y++) 198 | { 199 | const uint32_t lb = 0.21 * (0xFF & (in[x + y * w] >> 0x10)); 200 | const uint32_t lg = 0.72 * (0xFF & (in[x + y * w] >> 0x08)); 201 | const uint32_t lr = 0.07 * (0xFF & (in[x + y * w] >> 0x00)); 202 | const uint32_t lum = lb + lg + lr; 203 | out[x + y * w] = (lum << 0x10) | (lum << 0x08) | (lum << 0x00); 204 | } 205 | return out; 206 | } 207 | 208 | static uint32_t max(uint32_t* out, const int w, const int h) 209 | { 210 | uint32_t max = 0; 211 | for(int x = 1; x < w - 1; x++) 212 | for(int y = 1; y < h - 1; y++) 213 | if(out[x + y * w] > max) 214 | max = out[x + y * w]; 215 | return max; 216 | } 217 | 218 | static void normalize(uint32_t* const in, const int w, const int h) 219 | { 220 | const uint32_t mx = max(in, w, h); 221 | for(int x = 1; x < w - 1; x++) 222 | for(int y = 1; y < h - 1; y++) 223 | in[x + y * w] = 255 * in[x + y * w] / (float) mx; 224 | } 225 | 226 | static uint32_t* sobl(uint32_t* const in, const int w, const int h) 227 | { 228 | const int kx[3][3] = { 229 | { -1, 0, 1 }, 230 | { -2, 0, 2 }, 231 | { -1, 0, 1 }, 232 | }; 233 | const int ky[3][3] = { 234 | { 1, 2, 1 }, 235 | { 0, 0, 0 }, 236 | { -1, -2, -1 }, 237 | }; 238 | const int bytes = sizeof(*in) * w * h; 239 | uint32_t* const out = (uint32_t*) memcpy(malloc(bytes), in, bytes); 240 | for(int x = 1; x < w - 1; x++) 241 | for(int y = 1; y < h - 1; y++) 242 | { 243 | const int vx = conv(in, x, y, w, 0x00, kx); 244 | const int vy = conv(in, x, y, w, 0x00, ky); 245 | out[x + y * w] = (uint32_t) sqrtf(vx * vx + vy * vy); 246 | } 247 | normalize(out, w, h); 248 | return out; 249 | } 250 | 251 | static int outob(const int x, const int y, const int w, const int h) 252 | { 253 | return x < 0 || y < 0 || x >= w || y >= h; 254 | } 255 | 256 | static void draw(SDL_Renderer* const renderer, const int w, const int h, const Tris tris, uint32_t* regular) 257 | { 258 | SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0x0); 259 | SDL_RenderClear(renderer); 260 | for(int i = 0; i < tris.count; i++) 261 | { 262 | const Tri t = tris.tri[i]; 263 | const int x = (t.a.x + t.b.x + t.c.x) / 3.0f; 264 | const int y = (t.a.y + t.b.y + t.c.y) / 3.0f; 265 | const uint32_t color = outob(x, y, w, h) ? 0x00 : regular[x + y * w]; 266 | const uint32_t r = (color >> 0x00) & 0xFF; 267 | const uint32_t g = (color >> 0x08) & 0xFF; 268 | const uint32_t b = (color >> 0x10) & 0xFF; 269 | const uint32_t a = 0xFF; 270 | SDL_SetRenderDrawColor(renderer, r, g, b, a); 271 | const SDL_Point points[] = { 272 | { (int) t.a.x, (int) t.a.y }, 273 | { (int) t.b.x, (int) t.b.y }, 274 | { (int) t.c.x, (int) t.c.y }, 275 | { (int) t.a.x, (int) t.a.y }, 276 | }; 277 | SDL_RenderDrawLines(renderer, points, sizeof(points) / sizeof(*points)); 278 | } 279 | SDL_RenderPresent(renderer); 280 | } 281 | 282 | static void deltri(SDL_Renderer* const renderer, const Points ps, const int w, const int h, uint32_t* regular) 283 | { 284 | const int size = w * h; 285 | Tris in = tsnew(size); 286 | Tris out = tsnew(size); 287 | Tris tris = tsnew(size); 288 | Tris edges = tsnew(size); 289 | // The super triangle will snuggley fit over the screen. 290 | const Tri super = { { (float) -w, 0.0f }, { 2.0f * w, 0.0f }, { w / 2.0f, 2.0f * h } }; 291 | tris = tsadd(tris, super); 292 | for(int j = 0; j < ps.count; j++) 293 | { 294 | SDL_Event event; 295 | SDL_PollEvent(&event); 296 | if(event.type == SDL_QUIT || event.key.keysym.sym == SDLK_ESCAPE) 297 | break; 298 | in.count = out.count = edges.count = 0; 299 | const Point p = ps.point[j]; 300 | // For all triangles... 301 | for(int i = 0; i < tris.count; i++) 302 | { 303 | const Tri tri = tris.tri[i]; 304 | // Get triangles where point lies inside their circumcenter... 305 | if(incircum(tri, p)) 306 | in = tsadd(in, tri); 307 | // And get triangles where point lies outside of their circumcenter. 308 | else out = tsadd(out, tri); 309 | } 310 | // Collect all triangle edges where point was inside circumcenter. 311 | edges = ecollect(edges, in); 312 | // Flag edges that are non-unique. 313 | edges = emark(edges); 314 | // Construct new triangles with unique edges. 315 | out = ejoin(out, edges, p); 316 | // Update triangle list. 317 | tris = out; 318 | // Loading bar. 319 | if(j % 100 == 0) 320 | draw(renderer, w, h, tris, regular); 321 | } 322 | // Flush. 323 | draw(renderer, w, h, tris, regular); 324 | } 325 | 326 | static Points psnew(const int max) 327 | { 328 | const Points ps = { (Point*) malloc(sizeof(Point) * max), 0, max }; 329 | return ps; 330 | } 331 | 332 | static Points pcollect(uint32_t* in, const int w, const int h, const uint32_t thresh) 333 | { 334 | const int max = w * h; 335 | Points ps = psnew(max); 336 | for(int y = 1; y < h - 1; y++) 337 | for(int x = 1; x < w - 1; x++) 338 | if(in[x + y * w] > thresh) 339 | { 340 | const Point p = { 341 | (float) x, 342 | (float) y, 343 | }; 344 | ps.point[ps.count++] = p; 345 | } 346 | return ps; 347 | } 348 | 349 | int main(int argc, char* argv[]) 350 | { 351 | if(argc != 3) 352 | { 353 | puts("use: path/to/image threshold"); 354 | return 1; 355 | } 356 | SDL_Surface* surface = load(argv[1]); 357 | const uint32_t thresh = atoi(argv[2]); 358 | SDL_Window* window; 359 | SDL_Renderer* renderer; 360 | const int w = surface->w; 361 | const int h = surface->h; 362 | SDL_CreateWindowAndRenderer(w, h, 0, &window, &renderer); 363 | SDL_SetWindowTitle(window, "Weaver-1.3"); 364 | // The image is first blurred, then grey scaled, then sobel filtered for edge detection. 365 | uint32_t* const a = (uint32_t*) surface->pixels; 366 | uint32_t* const b = blur(a, w, h); 367 | uint32_t* const c = grey(b, w, h); 368 | uint32_t* const d = sobl(c, w, h); 369 | // Collect all points - Higher thresholds yield fewer points. 370 | const Points ps = pcollect(d, w, h, thresh); 371 | // Note that the original image is used for coloring delaunay triangles. 372 | deltri(renderer, ps, w, h, a); 373 | puts("done"); 374 | // Present and wait. 375 | SDL_Event event; 376 | do 377 | { 378 | SDL_PollEvent(&event); 379 | SDL_Delay(10); 380 | } 381 | while(event.type != SDL_KEYUP && event.type != SDL_QUIT); 382 | // No need to free hoisted memory - gives a fast exit. 383 | return 0; 384 | } 385 | --------------------------------------------------------------------------------