├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .vscode 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler" 5 | version = "1.0.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 8 | 9 | [[package]] 10 | name = "adler32" 11 | version = "1.2.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" 14 | 15 | [[package]] 16 | name = "ansi_term" 17 | version = "0.11.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 20 | dependencies = [ 21 | "winapi", 22 | ] 23 | 24 | [[package]] 25 | name = "atty" 26 | version = "0.2.14" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 29 | dependencies = [ 30 | "hermit-abi", 31 | "libc", 32 | "winapi", 33 | ] 34 | 35 | [[package]] 36 | name = "autocfg" 37 | version = "1.0.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "1.2.1" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 46 | 47 | [[package]] 48 | name = "bytemuck" 49 | version = "1.5.1" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" 52 | 53 | [[package]] 54 | name = "byteorder" 55 | version = "1.4.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 58 | 59 | [[package]] 60 | name = "cfg-if" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 64 | 65 | [[package]] 66 | name = "clap" 67 | version = "2.33.3" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 70 | dependencies = [ 71 | "ansi_term", 72 | "atty", 73 | "bitflags", 74 | "strsim", 75 | "textwrap", 76 | "unicode-width", 77 | "vec_map", 78 | ] 79 | 80 | [[package]] 81 | name = "color_quant" 82 | version = "1.1.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 85 | 86 | [[package]] 87 | name = "crc32fast" 88 | version = "1.2.1" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 91 | dependencies = [ 92 | "cfg-if", 93 | ] 94 | 95 | [[package]] 96 | name = "crossbeam-channel" 97 | version = "0.5.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 100 | dependencies = [ 101 | "cfg-if", 102 | "crossbeam-utils", 103 | ] 104 | 105 | [[package]] 106 | name = "crossbeam-deque" 107 | version = "0.8.0" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" 110 | dependencies = [ 111 | "cfg-if", 112 | "crossbeam-epoch", 113 | "crossbeam-utils", 114 | ] 115 | 116 | [[package]] 117 | name = "crossbeam-epoch" 118 | version = "0.9.4" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94" 121 | dependencies = [ 122 | "cfg-if", 123 | "crossbeam-utils", 124 | "lazy_static", 125 | "memoffset", 126 | "scopeguard", 127 | ] 128 | 129 | [[package]] 130 | name = "crossbeam-utils" 131 | version = "0.8.4" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" 134 | dependencies = [ 135 | "autocfg", 136 | "cfg-if", 137 | "lazy_static", 138 | ] 139 | 140 | [[package]] 141 | name = "deflate" 142 | version = "0.8.6" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" 145 | dependencies = [ 146 | "adler32", 147 | "byteorder", 148 | ] 149 | 150 | [[package]] 151 | name = "either" 152 | version = "1.6.1" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 155 | 156 | [[package]] 157 | name = "getrandom" 158 | version = "0.2.2" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" 161 | dependencies = [ 162 | "cfg-if", 163 | "libc", 164 | "wasi", 165 | ] 166 | 167 | [[package]] 168 | name = "gif" 169 | version = "0.11.2" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de" 172 | dependencies = [ 173 | "color_quant", 174 | "weezl", 175 | ] 176 | 177 | [[package]] 178 | name = "hermit-abi" 179 | version = "0.1.18" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" 182 | dependencies = [ 183 | "libc", 184 | ] 185 | 186 | [[package]] 187 | name = "image" 188 | version = "0.23.14" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" 191 | dependencies = [ 192 | "bytemuck", 193 | "byteorder", 194 | "color_quant", 195 | "gif", 196 | "jpeg-decoder", 197 | "num-iter", 198 | "num-rational", 199 | "num-traits", 200 | "png", 201 | "scoped_threadpool", 202 | "tiff", 203 | ] 204 | 205 | [[package]] 206 | name = "jpeg-decoder" 207 | version = "0.1.22" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" 210 | dependencies = [ 211 | "rayon", 212 | ] 213 | 214 | [[package]] 215 | name = "lazy_static" 216 | version = "1.4.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 219 | 220 | [[package]] 221 | name = "libc" 222 | version = "0.2.94" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" 225 | 226 | [[package]] 227 | name = "memmap" 228 | version = "0.7.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" 231 | dependencies = [ 232 | "libc", 233 | "winapi", 234 | ] 235 | 236 | [[package]] 237 | name = "memoffset" 238 | version = "0.6.3" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" 241 | dependencies = [ 242 | "autocfg", 243 | ] 244 | 245 | [[package]] 246 | name = "miniz_oxide" 247 | version = "0.3.7" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 250 | dependencies = [ 251 | "adler32", 252 | ] 253 | 254 | [[package]] 255 | name = "miniz_oxide" 256 | version = "0.4.4" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 259 | dependencies = [ 260 | "adler", 261 | "autocfg", 262 | ] 263 | 264 | [[package]] 265 | name = "num-integer" 266 | version = "0.1.44" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 269 | dependencies = [ 270 | "autocfg", 271 | "num-traits", 272 | ] 273 | 274 | [[package]] 275 | name = "num-iter" 276 | version = "0.1.42" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 279 | dependencies = [ 280 | "autocfg", 281 | "num-integer", 282 | "num-traits", 283 | ] 284 | 285 | [[package]] 286 | name = "num-rational" 287 | version = "0.3.2" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" 290 | dependencies = [ 291 | "autocfg", 292 | "num-integer", 293 | "num-traits", 294 | ] 295 | 296 | [[package]] 297 | name = "num-traits" 298 | version = "0.2.14" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 301 | dependencies = [ 302 | "autocfg", 303 | ] 304 | 305 | [[package]] 306 | name = "num_cpus" 307 | version = "1.13.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 310 | dependencies = [ 311 | "hermit-abi", 312 | "libc", 313 | ] 314 | 315 | [[package]] 316 | name = "pbr" 317 | version = "1.0.4" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "ff5751d87f7c00ae6403eb1fcbba229b9c76c9a30de8c1cf87182177b168cea2" 320 | dependencies = [ 321 | "crossbeam-channel", 322 | "libc", 323 | "time", 324 | "winapi", 325 | ] 326 | 327 | [[package]] 328 | name = "png" 329 | version = "0.16.8" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" 332 | dependencies = [ 333 | "bitflags", 334 | "crc32fast", 335 | "deflate", 336 | "miniz_oxide 0.3.7", 337 | ] 338 | 339 | [[package]] 340 | name = "ppv-lite86" 341 | version = "0.2.10" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 344 | 345 | [[package]] 346 | name = "rand" 347 | version = "0.8.3" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" 350 | dependencies = [ 351 | "libc", 352 | "rand_chacha", 353 | "rand_core", 354 | "rand_hc", 355 | ] 356 | 357 | [[package]] 358 | name = "rand_chacha" 359 | version = "0.3.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" 362 | dependencies = [ 363 | "ppv-lite86", 364 | "rand_core", 365 | ] 366 | 367 | [[package]] 368 | name = "rand_core" 369 | version = "0.6.2" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" 372 | dependencies = [ 373 | "getrandom", 374 | ] 375 | 376 | [[package]] 377 | name = "rand_hc" 378 | version = "0.3.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" 381 | dependencies = [ 382 | "rand_core", 383 | ] 384 | 385 | [[package]] 386 | name = "rayon" 387 | version = "1.5.0" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" 390 | dependencies = [ 391 | "autocfg", 392 | "crossbeam-deque", 393 | "either", 394 | "rayon-core", 395 | ] 396 | 397 | [[package]] 398 | name = "rayon-core" 399 | version = "1.9.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" 402 | dependencies = [ 403 | "crossbeam-channel", 404 | "crossbeam-deque", 405 | "crossbeam-utils", 406 | "lazy_static", 407 | "num_cpus", 408 | ] 409 | 410 | [[package]] 411 | name = "redox_syscall" 412 | version = "0.2.8" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" 415 | dependencies = [ 416 | "bitflags", 417 | ] 418 | 419 | [[package]] 420 | name = "remove_dir_all" 421 | version = "0.5.3" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 424 | dependencies = [ 425 | "winapi", 426 | ] 427 | 428 | [[package]] 429 | name = "scoped_threadpool" 430 | version = "0.1.9" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" 433 | 434 | [[package]] 435 | name = "scopeguard" 436 | version = "1.1.0" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 439 | 440 | [[package]] 441 | name = "spiralizer" 442 | version = "1.0.0" 443 | dependencies = [ 444 | "clap", 445 | "image", 446 | "memmap", 447 | "pbr", 448 | "tempfile", 449 | ] 450 | 451 | [[package]] 452 | name = "strsim" 453 | version = "0.8.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 456 | 457 | [[package]] 458 | name = "tempfile" 459 | version = "3.2.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 462 | dependencies = [ 463 | "cfg-if", 464 | "libc", 465 | "rand", 466 | "redox_syscall", 467 | "remove_dir_all", 468 | "winapi", 469 | ] 470 | 471 | [[package]] 472 | name = "textwrap" 473 | version = "0.11.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 476 | dependencies = [ 477 | "unicode-width", 478 | ] 479 | 480 | [[package]] 481 | name = "tiff" 482 | version = "0.6.1" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" 485 | dependencies = [ 486 | "jpeg-decoder", 487 | "miniz_oxide 0.4.4", 488 | "weezl", 489 | ] 490 | 491 | [[package]] 492 | name = "time" 493 | version = "0.1.43" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 496 | dependencies = [ 497 | "libc", 498 | "winapi", 499 | ] 500 | 501 | [[package]] 502 | name = "unicode-width" 503 | version = "0.1.8" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 506 | 507 | [[package]] 508 | name = "vec_map" 509 | version = "0.8.2" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 512 | 513 | [[package]] 514 | name = "wasi" 515 | version = "0.10.2+wasi-snapshot-preview1" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 518 | 519 | [[package]] 520 | name = "weezl" 521 | version = "0.1.5" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" 524 | 525 | [[package]] 526 | name = "winapi" 527 | version = "0.3.9" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 530 | dependencies = [ 531 | "winapi-i686-pc-windows-gnu", 532 | "winapi-x86_64-pc-windows-gnu", 533 | ] 534 | 535 | [[package]] 536 | name = "winapi-i686-pc-windows-gnu" 537 | version = "0.4.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 540 | 541 | [[package]] 542 | name = "winapi-x86_64-pc-windows-gnu" 543 | version = "0.4.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 546 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "spiralizer" 3 | version = "1.0.0" 4 | authors = ["Matt Ickstadt "] 5 | description = "Helps create a swirly timelapse gif" 6 | repository = "https://github.com/mattico/spiralizer" 7 | keywords = ["image", "gif"] 8 | license = "MIT" 9 | edition = "2018" 10 | 11 | [dependencies] 12 | clap = "2" 13 | image = "0.23" 14 | memmap = "0.7" 15 | pbr = "1" 16 | tempfile = "3" 17 | 18 | [profile.dev] 19 | debug = true 20 | 21 | [profile.release] 22 | debug = true 23 | lto = true 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Matt Ickstadt 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![example](https://thumbs.gfycat.com/AnimatedRadiantGhostshrimp-size_restricted.gif) 2 | > *[high quality](https://gfycat.com/AnimatedRadiantGhostshrimp)* 3 | 4 | # Spiralizer 5 | 6 | Helps to create gifs like [this](https://www.reddit.com/r/gifs/comments/4xdfa9/timescape_halls_harbour_nova_scotia/). 7 | 8 | Inspired by [this program from the Reddit thread](https://github.com/lgommans/TimeCircle). 9 | 10 | ## Limitations 11 | - Doesn't currently handle video frame extraction or creation 12 | - Animated GIFs don't work as expected 13 | - Blending between frames is weird 14 | - No macOS build 15 | 16 | ## Usage 17 | ``` 18 | Spiralizer 0.1.0 19 | Matt Ickstadt 20 | Helps create a swirly timelapse gif 21 | 22 | USAGE: 23 | spiralizer.exe 24 | 25 | FLAGS: 26 | -h, --help Prints help information 27 | -V, --version Prints version information 28 | 29 | ARGS: 30 | A directory full of images to use 31 | A directory to output the images to 32 | 33 | Supported input formats: PNG JPG GIF ICO BMP 34 | Output images will be saved in PNG format. 35 | ``` 36 | 37 | ## Video Conversion With FFMpeg 38 | 39 | Video to images: 40 | `fmpeg -i video.mp4 -vf fps=1 frame_%04d.png` 41 | Adjust `fps=` to change how often frames are saved from the video. 42 | 43 | Basic images to video: 44 | `ffmpeg -i frame%04d.png out.mp4` 45 | 46 | Recommended images to video settings: 47 | `ffmpeg -i frame%04d.png -framerate 60 -c:v libx264 -r 60 -pix_fmt yuv420p -preset slow -crf 19 out.mp4` 48 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | use std::fs::File; 3 | use std::io::prelude::*; 4 | use std::path::PathBuf; 5 | use std::process::exit; 6 | 7 | use image::{ImageBuffer, Pixel, Rgb, RgbImage}; 8 | use memmap::Mmap; 9 | use tempfile::TempDir; 10 | 11 | const APP_VERSION: &'static str = env!("CARGO_PKG_VERSION"); 12 | const APP_AUTHORS: &'static str = env!("CARGO_PKG_AUTHORS"); 13 | 14 | fn main() { 15 | let matches = clap::App::new("Spiralizer") 16 | .version(APP_VERSION) 17 | .author(APP_AUTHORS) 18 | .about("Helps create a swirly timelapse gif") 19 | .after_help( 20 | "Supported input formats: PNG JPG GIF ICO BMP\n\ 21 | Output images will be saved in PNG format.", 22 | ) 23 | .arg( 24 | clap::Arg::with_name("INPUT") 25 | .help("A directory full of images to use") 26 | .required(true), 27 | ) 28 | .arg( 29 | clap::Arg::with_name("OUTPUT") 30 | .help("A directory to output the images to") 31 | .required(true), 32 | ) 33 | .get_matches(); 34 | 35 | let assert_is_dir = |d: &PathBuf| { 36 | if !d.is_dir() { 37 | println!( 38 | "ERROR: Arguments must be directories.\n{} is not a directory", 39 | d.to_string_lossy() 40 | ); 41 | exit(0); 42 | } 43 | }; 44 | 45 | let mut input_files: Vec = { 46 | let dir = matches.value_of("INPUT").unwrap(); 47 | let d = PathBuf::from(dir); 48 | assert_is_dir(&d); 49 | d.read_dir().unwrap().map(|x| x.unwrap().path()).collect() 50 | }; 51 | input_files.sort(); 52 | 53 | let out_dir: PathBuf = { 54 | let dir = matches.value_of("OUTPUT").unwrap(); 55 | let d = PathBuf::from(dir); 56 | assert_is_dir(&d); 57 | d 58 | }; 59 | 60 | if input_files.len() > 1 { 61 | spiralize(&input_files, &out_dir); 62 | } else { 63 | println!("ERROR: Not enough valid frames provided. Need at least 2."); 64 | exit(0); 65 | } 66 | } 67 | 68 | fn spiralize(input_files: &Vec, out_dir: &PathBuf) { 69 | let mut width = 0; 70 | let mut height = 0; 71 | let temp_dir = TempDir::new_in("spiralizer").unwrap(); 72 | 73 | println!("= Loading images ="); 74 | 75 | let mut load_progress_bar = pbr::ProgressBar::new(input_files.len() as u64); 76 | 77 | let mmaps: Vec = input_files 78 | .iter() 79 | .filter_map(|file| { 80 | let rgb_image = match image::open(file) { 81 | Ok(img) => img.to_rgb8(), 82 | Err(_) => return None, 83 | }; 84 | if width == 0 && height == 0 { 85 | width = rgb_image.width(); 86 | height = rgb_image.height(); 87 | } else if width != rgb_image.width() || height != rgb_image.height() { 88 | println!("ERROR: Images must all be the same size."); 89 | exit(0); 90 | } 91 | let mut mmap_file_path = temp_dir.path().join(file.file_name().unwrap()); 92 | mmap_file_path.set_extension("bin"); 93 | let mut mmap_file = File::create(&mmap_file_path).unwrap(); 94 | mmap_file.write_all(&*rgb_image.into_raw()).unwrap(); 95 | load_progress_bar.inc(); 96 | Some(unsafe { Mmap::map(&mmap_file).unwrap() }) 97 | }) 98 | .collect(); 99 | 100 | load_progress_bar.finish_print(&format!( 101 | "Loaded {} images. {} files ignored.\n", 102 | mmaps.len(), 103 | input_files.len() - mmaps.len() 104 | )); 105 | 106 | let frames: Vec, &[u8]>> = mmaps 107 | .iter() 108 | .filter_map(|m| ImageBuffer::from_raw(width, height, m.as_ref())) 109 | .collect(); 110 | 111 | let mut output = RgbImage::new(width, height); 112 | let mut save_progress_bar = pbr::ProgressBar::new(frames.len() as u64); 113 | 114 | println!("= Saving images ="); 115 | 116 | for i in 0..frames.len() { 117 | for (x, y, pixel) in output.enumerate_pixels_mut() { 118 | let two_pi = 2f64 * PI; 119 | let c_x = (height / 2) as i32 - y as i32; 120 | let c_y = (width / 2) as i32 - x as i32; 121 | let mut pixel_angle = f64::atan2(c_x as f64, c_y as f64); 122 | if pixel_angle < 0.0 { 123 | pixel_angle += two_pi; 124 | } 125 | let angle_modifier = i as f64 / frames.len() as f64 * two_pi; 126 | let time_of_day = ((pixel_angle + angle_modifier) % two_pi) / two_pi; 127 | let source_frame_frac = time_of_day * frames.len() as f64; 128 | let source_frame_round = source_frame_frac.round(); 129 | let source_frame = source_frame_frac.floor() as isize; 130 | 131 | let blend_source_frame = if source_frame_frac > source_frame_round { 132 | source_frame - 1 133 | } else { 134 | source_frame + 1 135 | }; 136 | 137 | if blend_source_frame < 0 || blend_source_frame >= frames.len() as isize { 138 | *pixel = *frames[source_frame as usize].get_pixel(x, y); 139 | continue; 140 | } 141 | 142 | let blend_val = (source_frame_frac - source_frame_round).abs(); 143 | 144 | let mut main_pixel = frames[source_frame as usize].get_pixel(x, y).to_rgba(); 145 | let mut blend_pixel = frames[blend_source_frame as usize] 146 | .get_pixel(x, y) 147 | .to_rgba(); 148 | 149 | blend_pixel[3] = (blend_val * 256.0) as u8; 150 | main_pixel.blend(&blend_pixel); 151 | 152 | *pixel = main_pixel.to_rgb(); 153 | } 154 | output 155 | .save( 156 | out_dir 157 | .join(format!("frame_{:04}.png", i)) 158 | .to_str() 159 | .unwrap(), 160 | ) 161 | .unwrap(); 162 | save_progress_bar.inc(); 163 | } 164 | 165 | save_progress_bar.finish_print(&format!( 166 | "Saved {} images to {}\n", 167 | frames.len(), 168 | out_dir.to_string_lossy() 169 | )); 170 | } 171 | --------------------------------------------------------------------------------