commit 6b90f1b054bf1b8f63de31c6987454cd3f0ab83a Author: Sander Hautvast Date: Mon Feb 21 18:17:48 2022 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2329446 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/dist +*.iml +.idea/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b1f2f33 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,978 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "bytemuck" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +dependencies = [ + "custom_derive", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gloo" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23947965eee55e3e97a5cd142dd4c10631cc349b48cecca0ed230fd296f568cd" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", +] + +[[package]] +name = "gloo-console" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3907f786f65bbb4f419e918b0c5674175ef1c231ecda93b2dbd65fd1e8882637" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffb557a2ea2ed283f1334423d303a336fad55fb8572d51ae488f828b1464b40" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5d6084efa4a2b182ef3a8649cb6506cb4843f22cf907c6e0a799944248ae90" +dependencies = [ + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b4cda6e149df3bb4a3c6a343873903e5bcc2448a9877d61bb8274806ad67f6e" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5057761927af1b1929d02b1f49cf83553dd347a473ee7c8bb08420f2673ffc" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c77af6f96a4f9e27c8ac23a88407381a31f4a74c3fb985c85aa79b8d898136" +dependencies = [ + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "imageproc" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7923654f3ce7cb6849d5dc9e544aaeab49c508a90b56c721b046e7234c74ab53" +dependencies = [ + "conv", + "image", + "itertools", + "num 0.3.1", + "rand", + "rand_distr", + "rayon", + "rulinalg", + "rusttype", +] + +[[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matrixmultiply" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +dependencies = [ + "num-integer", + "num-iter", + "num-traits", +] + +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f923fb806c46266c02ab4a5b239735c144bdeda724a50ed058e5226f594cde3" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_distr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" +dependencies = [ + "rand", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rawpointer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" + +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rulinalg" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ada202c9685e1d72a7420c578e92b358dbf807d3dfabb676a3dab9cc3bb12f" +dependencies = [ + "matrixmultiply", + "num 0.1.42", +] + +[[package]] +name = "rusttype" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7c727aded0be18c5b80c1640eae0ac8e396abf6fa8477d96cb37d18ee5ec59" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scoped-tls-hkt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + +[[package]] +name = "ttf-parser" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5d7cd7ab3e47dda6e56542f4bbf3824c15234958c6e1bd6aaa347e93499fdc" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e" + +[[package]] +name = "yew" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1ccb53e57d3f7d847338cf5758befa811cabe207df07f543c06f502f9998cd" +dependencies = [ + "console_error_panic_hook", + "gloo", + "gloo-utils", + "indexmap", + "js-sys", + "scoped-tls-hkt", + "slab", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-app" +version = "0.1.0" +dependencies = [ + "gloo-utils", + "image", + "imageproc", + "log", + "wasm-bindgen", + "wasm-logger", + "web-sys", + "yew", +] + +[[package]] +name = "yew-macro" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fab79082b556d768d6e21811869c761893f0450e1d550a67892b9bce303b7bb" +dependencies = [ + "boolinator", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b2bccab --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "yew-app" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +gloo-utils = "0.1.2" +log = "0.4.6" +wasm-bindgen = "0.2.79" +wasm-logger = "0.2.0" +web-sys = {version = "0.3.56", features = [ + "DataTransfer", + "DataTransferItemList", + "DataTransferItem", + "Document", + "Element", + "Node", + "HtmlElement", + "HtmlImageElement", + "Url", + 'Blob', + 'HtmlCanvasElement', + 'ImageData', + 'CanvasRenderingContext2d' +]} +yew = "0.19" +image = "0.23.14" +imageproc="0.22.0" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3a457a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +work in progress +Goal: image filters, running clientside +![](source.jpg) => ![](destination.jpg) \ No newline at end of file diff --git a/destination.jpg b/destination.jpg new file mode 100644 index 0000000..5d608a6 Binary files /dev/null and b/destination.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..d553e16 --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + + + + + Spiegel + + + \ No newline at end of file diff --git a/source.jpg b/source.jpg new file mode 100644 index 0000000..25f1a53 Binary files /dev/null and b/source.jpg differ diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..0f41c61 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,113 @@ +use gloo_utils::document; +use wasm_bindgen::JsCast; +use web_sys::{DragEvent, HtmlImageElement}; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData}; +use web_sys::Url; +use yew::{Component, Context, html, Html}; + +pub enum Msg { + Dropped(DragEvent), + Dragged(DragEvent), + ImageLoaded, +} + +pub struct DropPhoto {} + +impl Component for DropPhoto { + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self {} + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::Dragged(event) => { + event.prevent_default(); + false + } + Msg::Dropped(event) => { + event.prevent_default(); + let data_transfer = event + .data_transfer() + .expect("Event should have DataTransfer"); + let item_list = data_transfer.items(); + for i in 0..item_list.length() { + let item = item_list.get(i).expect("Should find an item"); + if item.kind() == "file" { + let file = item + .get_as_file() + .expect("Should find a file here") + .unwrap(); + let url = Url::create_object_url_with_blob(&file).expect("Cannot create url"); + let img = document().get_element_by_id("source-image").expect("cannot get #source-image").dyn_into::().unwrap(); + img.set_src(&url); + } + + } + true + } + Msg::ImageLoaded => { + if let Some(canvas) = document().get_element_by_id("source").and_then(|e| e.dyn_into::().ok()) { + let ctx: CanvasRenderingContext2d = canvas + .get_context("2d") + .unwrap() + .unwrap() + .dyn_into::() + .unwrap(); + if let Some(img) = document().get_element_by_id("source-image").and_then(|e| e.dyn_into::().ok()) { + canvas.set_width(img.width()); + canvas.set_height(img.height()); + ctx.draw_image_with_html_image_element(&img, 0.0, 0.0).expect("Cannot draw image on canvas"); + } + if let Some(drop_zone) = document().get_element_by_id("drop-zone") { + drop_zone.set_attribute("style", "display:none").expect("Cannot update attribute"); + } + } + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + html! { + <> +
+

{ "drag your photos here" }

+
+ + + + + } + } +} + +//make generic for element type +// fn with_element(id: &str, action: F) +// where +// F: Fn(Element), +// { +// if let Some(element) = document() +// .get_element_by_id(id) { +// action(element); +// } +// } + +fn create_element<'a, T>(element_type: &str) -> T + where + T: JsCast, +{ + let element = document().create_element(element_type).unwrap(); + element + .dyn_into::() + .expect(&format!("Cannot create element {}", element_type)) +} + +pub fn _get_image_data(canvas: &HtmlCanvasElement, ctx: &CanvasRenderingContext2d) -> ImageData { + ctx.get_image_data(0.0, 0.0, canvas.width() as f64, canvas.height() as f64).unwrap() +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7909bb2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,8 @@ +mod app; +mod transform; +mod quantizer; + +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + yew::start_app::(); +} \ No newline at end of file diff --git a/src/quantizer.rs b/src/quantizer.rs new file mode 100644 index 0000000..8156e10 --- /dev/null +++ b/src/quantizer.rs @@ -0,0 +1,347 @@ +use std::{cell::RefCell, rc::Rc}; + +use image::{Pixel, Rgb, RgbImage}; + +const MAX_LEVEL: usize = 5; + +pub(crate) fn quantize(image: &RgbImage, num_colors: usize) -> RgbImage { + let mut quantizer = OctTreeQuantizer::new(num_colors); + quantizer.quantize(image) +} + +struct OctTreeQuantizer { + root: Rc>, + reduce_colors: usize, + maximum_colors: usize, + colors: usize, + color_list: Vec>>>>, +} + +impl OctTreeQuantizer { + fn new(num_colors: usize) -> Self { + let mut new_quantizer = Self { + root: Rc::new(RefCell::new(OctTreeNode::new())), + reduce_colors: usize::max(512, num_colors * 2), + maximum_colors: num_colors, + color_list: vec![], + colors: 0, + }; + + for _ in 0..=MAX_LEVEL { + new_quantizer.color_list.push(Vec::new()); + } + new_quantizer + } + + pub fn quantize(&mut self, image: &RgbImage) -> RgbImage { + for pixel in image.pixels() { + self.insert_color(pixel, Rc::clone(&self.root)); + + if self.colors > self.reduce_colors { + self.reduce_tree(self.reduce_colors); + //reduce sets to None and the code below actually removes nodes from the list + for level in &mut self.color_list { + level.retain(|c| c.is_some()); + } + } + } + let table = self.build_color_table(); + + let mut imgbuf = RgbImage::new(image.width(), image.height()); + for (x, y, pixel) in image.enumerate_pixels() { + if let Some(index) = self.get_index_for_color(pixel, &self.root) { + let color = &table[index]; + if let Some(color) = color { + imgbuf.put_pixel(x, y, *color); + } + } + } + + imgbuf + } + + fn get_index_for_color

(&self, color: &P, node: &Rc>) -> Option + where + P: Pixel + 'static, + { + fn get_index_for_color

( + quantizer: &OctTreeQuantizer, + color: &P, + level: usize, + node: &Rc>, + ) -> Option + where + P: Pixel + 'static, + { + if level > MAX_LEVEL { + return None; + } + let node = Rc::clone(node); + let index = get_bitmask(color, &level); + + let node_b = node.borrow(); + let child = &node_b.leaf[index]; + + if let Some(child) = child { + let child_b = child.borrow(); + if child_b.is_leaf { + return Some(child_b.index); + } else { + return get_index_for_color(quantizer, color, level + 1, child); + } + } else { + return Some(node_b.index); + } + } + + get_index_for_color(&self, color, 0, node) + } + + fn build_color_table(&mut self) -> Vec>> { + //nested function that is called recursively + fn build_color_table( + quantizer: &mut OctTreeQuantizer, + node: &Rc>, + table: &mut Vec>>, + index: usize, + ) -> usize { + if quantizer.colors > quantizer.maximum_colors { + quantizer.reduce_tree(quantizer.maximum_colors); + } + if node.borrow().is_leaf { + { + let node = node.borrow(); + let count = node.count; + table[index] = Some(Rgb::from([ + (node.total_red / count as u32) as u8, + (node.total_green / count as u32) as u8, + (node.total_blue / count as u32) as u8, + ])); + } + node.borrow_mut().index = index; + + index + 1 + } else { + let mut result = index; + for i in 0..8 { + // cannot iterate leaf, because that widens the scope of the borrow (of node) + let mut node = node.borrow_mut(); + if let Some(leaf) = &node.leaf[i] { + //could be immutable borrow + let new_index = build_color_table(quantizer, leaf, table, result); + node.index = index; //but also need mutable borrow here + result = new_index; + } + } + + result + } + } + + let mut table: Vec>> = vec![None; self.colors]; + let node = Rc::clone(&self.root); + build_color_table(self, &node, &mut table, 0); + table + } + + fn insert_color

(&mut self, rgb: &P, node: Rc>) + where + P: Pixel + 'static, + { + //nested function that is called recursively + fn insert_color

( + quantizer: &mut OctTreeQuantizer, + color: &P, + level: usize, + node: Rc>, + ) where + P: Pixel + 'static, + { + if level > MAX_LEVEL { + return; + } + + let index = get_bitmask(color, &level); + + if node.borrow().leaf[index].is_none() { + let mut child = OctTreeNode::new(); + child.parent = Some(Rc::clone(&node)); + child.p_index = quantizer.color_list[level].len(); + + if level == MAX_LEVEL { + child.is_leaf = true; + child.count = 1; + child.total_red = color.channels()[0] as u32; + child.total_green = color.channels()[1] as u32; + child.total_blue = color.channels()[2] as u32; + child.level = level; + quantizer.colors += 1; + } + + let child = Rc::new(RefCell::new(child)); + quantizer.color_list[level].push(Some(Rc::clone(&child))); + let clone = Rc::clone(&child); + + { + let mut mutnode = node.borrow_mut(); + mutnode.children += 1; + mutnode.is_leaf = false; + mutnode.leaf[index] = Some(child); + } + + if level < MAX_LEVEL { + insert_color(quantizer, color, level + 1, clone); + } else { + return; + } + } else { + if node + .borrow() + .leaf + .get(index) + .unwrap() + .as_ref() + .unwrap() + .borrow() + .is_leaf + { + let mut node = node.borrow_mut(); + let mut child = node + .leaf + .get_mut(index) + .unwrap() + .as_ref() + .unwrap() + .borrow_mut(); + child.count += 1; + child.total_red += color.channels()[0] as u32; + child.total_green += color.channels()[1] as u32; + child.total_blue += color.channels()[2] as u32; + return; + } else { + insert_color( + quantizer, + color, + level + 1, + Rc::clone(&(node.borrow().leaf[index]).as_ref().unwrap()), + ); + } + } + } + + insert_color(self, rgb, 0, node); + } + + fn reduce_tree(&mut self, num_colors: usize) { + // Nested function that is called recursively + fn reduce_tree(quantizer: &mut OctTreeQuantizer, num_colors: usize, level: isize) { + if level < 0 { + return; + } else { + let mut removals = Vec::new(); + let list = &quantizer.color_list[level as usize]; + for node in list { + if let Some(node) = node { + if node.borrow().children > 0 { + for i in 0..8 { + let mut color: Option<(usize, u32, u32, u32, usize)> = None; + + if let Some(child) = node.borrow().leaf.get(i) { + if let Some(child) = child { + let child = child.borrow(); + color = Some(( + child.count, + child.total_red, + child.total_green, + child.total_blue, + child.p_index, + )); + } + } + + // need to mutate node, which conflicts with previous borrow to retrieve the child + if let Some(color) = color { + let mut node = node.borrow_mut(); + node.count += color.0; + node.total_red += color.1; + node.total_green += color.2; + node.total_blue += color.3; + node.leaf[i] = None; + node.children -= 1; + quantizer.colors -= 1; + + removals.push(color.4); //save for further processing outside loop (and borrow of colorlist) + } + } + node.borrow_mut().is_leaf = true; + quantizer.colors += 1; + if quantizer.colors <= num_colors { + return; + } + } + } + } + let color_list = &mut quantizer.color_list[(level as usize + 1)]; + for index in removals { + color_list[index] = None; //set to None here, Option removed later + } + + reduce_tree(quantizer, num_colors, level - 1); + } + } + + // call to nested function + reduce_tree(self, num_colors, (MAX_LEVEL - 1) as isize); + } +} + +struct OctTreeNode { + children: usize, + level: usize, + parent: Option>>, + leaf: Vec>>>, + is_leaf: bool, + count: usize, + total_red: u32, + total_green: u32, + total_blue: u32, + index: usize, + p_index: usize, +} + +impl OctTreeNode { + fn new() -> Self { + Self { + children: 0, + level: 0, + parent: None, + leaf: vec![None; 8], + is_leaf: false, + count: 0, + total_red: 0, + total_green: 0, + total_blue: 0, + index: 0, + p_index: 0, + } + } +} + +fn get_bitmask

(color: &P, level: &usize) -> usize + where + P: Pixel + 'static, +{ + let bit = 0x80 >> level; + + let mut index = 0; + if (color.channels()[0] & bit) != 0 { + index += 4; + } + if (color.channels()[1] & bit) != 0 { + index += 2; + } + if (color.channels()[2] & bit) != 0 { + index += 1; + } + index +} diff --git a/src/transform.rs b/src/transform.rs new file mode 100644 index 0000000..40b85ec --- /dev/null +++ b/src/transform.rs @@ -0,0 +1,212 @@ +use std::{error::Error, fs, result::Result}; + +use image::{GenericImageView, ImageBuffer, Pixel, Rgb, RgbImage}; +use imageproc::point::Point; +use crate::quantizer; + +pub fn run(image_filename: &String) -> Result<(), Box> { + println!("reading image samples"); + let color_samples = read_color_samples()?; + + let src: RgbImage = image::open(image_filename).unwrap().into_rgb8(); + + println!("applying gaussian blur filter"); + let gauss = imageproc::filter::gaussian_blur_f32(&src, 2.0); + println!("applying median filter"); + let median = imageproc::filter::median_filter(&gauss, 2, 2); + println!("applying color quantization filter"); + let quantized = quantizer::quantize(&median, 256); + + println!("applying samples"); + let out = apply_samples_to_image(quantized, &color_samples); + out.save_with_format("output.jpg", image::ImageFormat::Jpeg)?; + + Ok(()) +} + +fn apply_samples_to_image(mut src: RgbImage, color_samples: &Vec) -> RgbImage{ + let mut imgbuf = RgbImage::new(src.width(), src.height()); + unsafe { + for y in 0..src.height() { + for x in 0..src.width() { + let pixel = &src.unsafe_get_pixel(x, y); + if imgbuf.unsafe_get_pixel(x, y).channels() == [0, 0, 0] { + if let Some(sample) = get_closest(&color_samples, pixel) { + fill(&mut src, sample, &mut imgbuf, pixel, x, y); + } + } + } + } + } + imgbuf +} + +fn fill( + src: &mut ImageBuffer, Vec>, + sample: &ColorSample, + dest: &mut ImageBuffer, Vec>, + color: &Rgb, + px: u32, + py: u32, +) { + if color.channels() == [0, 0, 0] { + return; + } + let height = sample.image.height(); + let width = sample.image.width(); + let mut points = List::new(); + if is_same(src.get_pixel(px, py), &color) { + points.push(Point { x: px, y: py }); + } + + while !points.is_empty() { + if let Some(point) = points.pop() { + let orig_pixel = src.get_pixel(point.x, point.y); + let x = point.x; + let y = point.y; + if src.get_pixel(x, y).channels() != [0, 0, 0] { + if is_same(orig_pixel, &color) { + let mut xx = x; + let mut yy = y; + while xx >= width { + xx -= width; + } + while yy >= height { + yy -= height; + } + dest.put_pixel(x, y, *sample.image.get_pixel(xx, yy)); + src.put_pixel(x, y, Rgb([0, 0, 0])); + if x > 1 { + points.push(Point::new(x - 1, y)); + } + if y > 1 { + points.push(Point::new(x, y - 1)); + } + if x < src.width() - 1 { + points.push(Point::new(x + 1, y)); + } + if y < src.height() - 1 { + points.push(Point::new(x, y + 1)); + } + } + } + } else { + println!("break"); + break; + } + } +} + +fn is_same(p1: &Rgb, p2: &Rgb) -> bool { + let p1 = p1.channels(); + let p2 = p2.channels(); + i16::abs(p1[0] as i16 - p2[0] as i16) < 4 + && i16::abs(p1[1] as i16 - p2[1] as i16) < 4 + && i16::abs(p1[2] as i16 - p2[2] as i16) < 4 +} + +fn get_closest<'a>( + color_samples: &'a Vec, + pixel: &Rgb, +) -> Option<&'a ColorSample> { + let mut closest = None; + let mut min_diff: f32 = 4294967295.0; //0xFFFFFFFF + for sample in color_samples { + let diff = get_distance(sample.r, sample.g, sample.b, pixel); + if diff < min_diff { + closest = Some(sample); + min_diff = diff; + } + } + + closest +} + +fn get_distance(r: u8, g: u8, b: u8, c2: &Rgb) -> f32 { + let red_dif = r as f32 - c2.channels()[0] as f32; + let green_dif = g as f32 - c2.channels()[1] as f32; + let blue_dif = b as f32 - c2.channels()[2] as f32; + return f32::sqrt(red_dif * red_dif + green_dif * green_dif + blue_dif * blue_dif); +} + +fn read_color_samples() -> Result, Box> { + let paths = fs::read_dir("samples")?; + let mut color_samples: Vec = Vec::new(); + for path in paths { + let path = path?.path(); + let filename = path.to_str().unwrap().to_owned(); + + if filename.ends_with(".jpg") { + let sample_image: RgbImage = image::open(&filename).unwrap().into_rgb8(); + let hex_r = &filename[8..10]; + let hex_g = &filename[10..12]; + let hex_b = &filename[12..14]; + color_samples.push(ColorSample { + r: u8::from_str_radix(&hex_r, 16).unwrap(), + g: u8::from_str_radix(&hex_g, 16).unwrap(), + b: u8::from_str_radix(&hex_b, 16).unwrap(), + image: sample_image, + }); + } + } + Ok(color_samples) +} + +struct ColorSample { + r: u8, + g: u8, + b: u8, + image: RgbImage, +} + +#[derive(Debug)] +struct List { + head: Option>, +} + +impl List { + fn new() -> Self { + Self { head: None } + } + fn push(&mut self, point: Point) { + let new_node = Box::new(Node { + value: point, + next: self.head.take(), + }); + + self.head = Some(new_node); + } + + fn pop(&mut self) -> Option> { + self.head.take().map(|node| { + self.head = node.next; + node.value + }) + } + + fn is_empty(&self) -> bool { + self.head.is_none() + } +} + +#[derive(Debug)] +struct Node { + value: Point, + next: Option>, +} + +#[cfg(test)] +mod test { + + use super::*; + + #[test] + fn test() { + let mut list = List::new(); + + list.push(Point::new(1, 1)); + list.push(Point::new(2, 2)); + assert_eq!(2, list.pop().unwrap().x); + assert_eq!(1, list.pop().unwrap().x); + } +} diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..4b9aed2 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,27 @@ +html { + font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; + color: rgb(20, 20, 104); +} + +.drop-zone { + border: 4px dashed rgb(17, 122, 184); + width: 200px; + height: 100px; + background-color: rgb(202, 202, 238); +} + +p { + display: table; + margin: 0 auto; + transform: translateY(50%); +} + +.body > img { + width: 25%; + border: rgb(17, 122, 184) solid 3px; +} + +.photo > canvas { + width: 25%; + border: rgb(17, 122, 184) solid 3px; +} \ No newline at end of file