diff --git a/Cargo.lock b/Cargo.lock index 0effe9b..66a23d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,91 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_colours" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80" +dependencies = [ + "rgb", +] + +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bit_field" version = "0.10.1" @@ -26,6 +105,21 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bktree" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb1e744816f6a3b9e962186091867f3e5959d4dac995777ec254631cb00b21c" +dependencies = [ + "num", +] + [[package]] name = "bumpalo" version = "3.11.1" @@ -50,12 +144,70 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.64", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -108,6 +260,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -120,6 +297,45 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "exr" version = "1.5.2" @@ -135,6 +351,12 @@ dependencies = [ "threadpool", ] +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "flate2" version = "1.0.24" @@ -202,6 +424,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -211,6 +439,12 @@ dependencies = [ "libc", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "image" version = "0.24.4" @@ -231,14 +465,26 @@ dependencies = [ ] [[package]] -name = "image-similarity" +name = "image_similarity" version = "0.1.0" dependencies = [ + "base64 0.22.1", + "bktree", + "clap", + "env_logger", "image", + "log", "rmp-serde", "serde", + "viuer", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "jpeg-decoder" version = "0.2.6" @@ -257,6 +503,12 @@ 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 = "lebe" version = "0.5.2" @@ -265,9 +517,15 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -281,12 +539,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memoffset" @@ -315,6 +576,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "nanorand" version = "0.7.0" @@ -325,20 +598,52 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ - "autocfg", + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", "num-traits", ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "num-bigint" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -346,10 +651,21 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "num-rational" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -370,6 +686,29 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "parking_lot" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.5", +] + [[package]] name = "paste" version = "1.0.9" @@ -393,7 +732,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.103", ] [[package]] @@ -402,7 +741,7 @@ version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "flate2", "miniz_oxide 0.6.2", @@ -410,18 +749,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -450,6 +789,53 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + [[package]] name = "rmp" version = "0.8.11" @@ -472,6 +858,19 @@ dependencies = [ "serde", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -490,6 +889,36 @@ version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -505,6 +934,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.103" @@ -516,6 +951,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + [[package]] name = "threadpool" version = "1.8.1" @@ -542,6 +1009,28 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "viuer" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2ede5c8814363f92f862892dfe71a266f6816b649ca435aed1ff5e2cf3454e" +dependencies = [ + "ansi_colours", + "base64 0.21.7", + "console", + "crossterm", + "image", + "lazy_static", + "tempfile", + "termcolor", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -569,7 +1058,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.103", "wasm-bindgen-shared", ] @@ -591,7 +1080,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.103", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -607,3 +1096,173 @@ name = "weezl" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 05b1879..9d7e515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "image-similarity" +name = "image_similarity" version = "0.1.0" edition = "2021" @@ -9,3 +9,9 @@ edition = "2021" image = "0.24.4" serde = "1.0.147" rmp-serde = "1.1.1" +base64 = "0.22.1" +log = "0.4.21" +env_logger = "0.11.3" +bktree = "1.0.1" +viuer = "0.7.1" +clap = { version = "4.5.4", features = ["derive"] } diff --git a/src/bin/dct.rs b/src/bin/dct.rs new file mode 100644 index 0000000..266f324 --- /dev/null +++ b/src/bin/dct.rs @@ -0,0 +1,66 @@ +use std::env; +use std::f64::consts::{PI, SQRT_2}; +use image::{save_buffer, GenericImageView, GrayImage}; + +fn dct(img: &image::DynamicImage) -> GrayImage { + //let mut dct_values: = [0.0; img.width() * img.height()]; + let w = img.width(); + let h = img.height(); + let mut dct_img = GrayImage::new(w, h); + + //let mut dct_values: Vec = Vec::new(); + for u in 0..w { + for v in 0..h { + let w = w as f64; + let h = h as f64; + //let k = (v*img.width())+u; + let mut alpha = 0.25; + if u == 0 { + alpha = alpha / SQRT_2 + } + if v == 0 { + alpha = alpha / SQRT_2 + } + let v: f64 = v as f64; + let u: f64 = u as f64; + let mut sum: f64 = 0.0; + for (x, y, pix) in img.pixels() { + let x: f64 = x as f64; + let y: f64 = y as f64; + let pixel = pix[0] as f64; + // sum += + // pixel * + // (x*u*PI/16.0).cos() * + // (y*v*PI/16.0).cos() + sum += + pixel * + ( + PI/w* + (x+0.5)*u + ).cos() + * + ( + PI/h* + (y+0.5)*v + ).cos() + } + //dct_values[k] = alpha * sum; + //dct_values.push(alpha * sum); + print!("{sum}\t"); + //sum = (sum + 200000.0)/784.0; + print!("{}\n", sum * alpha); + let pixel = (sum).abs() as u8; + println!("{u}, {v}:\t {sum}"); + dct_img.put_pixel(u as u32, v as u32, image::Luma([pixel])); + } + } + dct_img +} + +fn main() { + let args: Vec = env::args().collect(); + let path = args[1].clone(); + let img = image::open(&path).unwrap(); + let dct = dct(&img); + dct.save("dct.png").unwrap(); +} \ No newline at end of file diff --git a/src/bin/dctfilename.rs b/src/bin/dctfilename.rs new file mode 100644 index 0000000..b81c50e --- /dev/null +++ b/src/bin/dctfilename.rs @@ -0,0 +1,27 @@ +//use image_similarity::descriptors::dct::DCT; +use image_similarity::descriptors::{DCT, Descriptor}; +use std::path::PathBuf; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; +use log::debug; +use clap::Parser; + +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Cfg { + path: PathBuf +} + +fn main() { + env_logger::init(); + let cfg = Cfg::parse(); + + let desc = DCT::new().with_quality(50); + + let img = image::open(cfg.path) + .expect("Unable to open file"); + let phash: u64 = desc.describe(&img); + debug!("Phash integer:\n{phash}"); + debug!("Phash binary:\n{phash:064b}"); + let output = URL_SAFE_NO_PAD.encode(phash.to_be_bytes()); + println!("{output}") +} diff --git a/src/bin/dctquery.rs b/src/bin/dctquery.rs new file mode 100644 index 0000000..d8a74f8 --- /dev/null +++ b/src/bin/dctquery.rs @@ -0,0 +1,50 @@ +//use image_similarity::descriptors::dct::DCT; +use image_similarity::descriptors::{DCT, Descriptor}; +use image_similarity::store::DescriptorStore; +use std::env; +use log::{info, debug, error}; + +struct Cfg { + path: String +} + +impl Cfg{ + fn load(args: &[String]) -> Result { + if args.len() < 2 { + return Err("Filename argument required"); + } + let path = args[1].clone(); + Ok(Cfg { path }) + } +} + +fn main() { + env_logger::init(); + + //Init all descriptors: + let desc = DCT::new().with_quality(50); + + let args: Vec = env::args().collect(); + + let cfg = Cfg::load(&args) + .expect("Error loading config"); + + let img = image::open(cfg.path) + .expect("Unable to open file"); + println!("Iamge"); + let conf = viuer::Config { + width: Some(16), + height: Some(8), + ..Default::default() + }; + viuer::print(&img, &conf).expect("Image printing failed."); + println!("Iamge"); + let store = + DescriptorStore::new() + .with_file("store.messagepack"); + //info!("{}", store); + let phash: u64 = desc.describe(&img); + info!("Phash integer:\n{phash}"); + info!("Phash binary:\n{phash:064b}"); + store.knn(phash, 10); +} \ No newline at end of file diff --git a/src/bin/experiment.rs b/src/bin/experiment.rs new file mode 100644 index 0000000..ab2a847 --- /dev/null +++ b/src/bin/experiment.rs @@ -0,0 +1,36 @@ +use image_similarity::descriptors::{Descriptor, DCT}; +use std::collections::HashMap; +use std::fs; + +fn main() { + env_logger::init(); + + let desc = DCT::new(); + + //Initialize hashmap, empty or from cached file: + let count = fs::read_dir("img").unwrap().count(); + let map_file = fs::read("map.messagepack"); + let mut map: HashMap = match map_file { + Ok(f) => rmp_serde::from_slice(&f).unwrap(), + Err(_e) => HashMap::with_capacity(count), + }; + + //Calculate phashes for all images + for node in fs::read_dir("img").unwrap() { + let file = node.expect("Error walking directory"); + let name = file.file_name().into_string().expect("Issue with filename"); + if !map.contains_key(&name) { + let img = image::open(file.path()).expect("Unable to open file"); + let phash = desc.describe(&img); + map.insert(name, phash); + } + } + + for (key, val) in map.iter() { + println!("{key: >15}: {val}\t {val:b}"); + } + + //Serialize hashmap with MessagePack: + let _serialized: Vec = rmp_serde::to_vec(&map).unwrap(); + //fs::write("map.messagepack",&serialized).unwrap(); +} diff --git a/src/descriptors.rs b/src/descriptors.rs deleted file mode 100644 index d4b0e02..0000000 --- a/src/descriptors.rs +++ /dev/null @@ -1,34 +0,0 @@ -use image::GenericImageView; - -pub trait Descriptor { - fn info(&self) -> String { - "Descriptor".to_string() - } - fn describe(&self, img: image::DynamicImage) -> u64; - fn distance(&self, a: u64, b: u64) -> u64 { - (a^b).count_ones().into() - } -} - -pub struct Median; - -impl Descriptor for Median { - fn describe(&self, img: image::DynamicImage) -> u64 { - let mut median: u64 = 0; //Average for now while i figure out sorting in rust - let img = img.grayscale().thumbnail_exact(8, 8); - for (_, _, pix) in img.pixels() { - median += pix[0] as u64; - } - let median: u8 = (median/64) as u8; - let mut mask: u64 = 0; - img.save("debug.png").unwrap(); - for (_, _, pix) in img.pixels() { - if pix[0] > median { - mask += 1; - } - mask = mask << 1; - } - mask - } -} - diff --git a/src/descriptors/dct.rs b/src/descriptors/dct.rs new file mode 100644 index 0000000..62df718 --- /dev/null +++ b/src/descriptors/dct.rs @@ -0,0 +1,202 @@ +use image::{save_buffer, GenericImageView}; +use std::f64::consts::{PI, SQRT_2}; +use crate::descriptors::{Descriptor, DCT, print_matrix}; +use log::{debug, log_enabled, Level}; + + +impl DCT { + /// Returns a new DCT instance with a base quality DCT matrix. + pub fn new() -> DCT { + let quantization_matrix: [u8; 64] = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 6, 10, 103, 77, + 24, 35, 55, 64, 8, 10, 113, 92, + 49, 64, 78, 8, 10, 12, 12, 101, + 72, 92, 95, 9, 11, 10, 103, 99, + ]; + DCT { quantization_matrix } + } + + /// Builds DCT with given quality value + /// quality + pub fn with_quality(mut self, quality: u8) -> Self { + let mut quantization_matrix: [u8; 64] = [0; 64]; + let scalar: f32 = match quality { + 1..=49 => 5000.0/quality as f32, + 50..=100 => 200.0 - 2.0*quality as f32, + _ => 100.0 // Invalid input: set to base quality + }; + for i in 0..64 { + quantization_matrix[i] = ((scalar * self.quantization_matrix[i] as f32 + 50.0) / 100.0).floor() as u8; + if quantization_matrix[i] == 0 { + quantization_matrix[i] = 1; + } + } + self.quantization_matrix = quantization_matrix; + self + } + + /// + fn dct(&self, img: &image::DynamicImage) -> [f64; 64] { + let mut dct_values: [f64; 64] = [0.0; 64]; + for u in 0..8 { + for v in 0..8 { + let k = (v*8)+u; + let mut alpha = 0.25; + if u == 0 { + alpha = alpha / SQRT_2 + } + if v == 0 { + alpha = alpha / SQRT_2 + } + let v: f64 = v as f64; + let u: f64 = u as f64; + let mut sum: f64 = 0.0; + for (x, y, pix) in img.pixels() { + let x: f64 = 1.0 + 2.0 * x as f64; + let y: f64 = 1.0 + 2.0 * y as f64; + let pixel = (pix[0] as i16 - 127) as f64; + sum += + pixel * + (x*u*PI/16.0).cos() * + (y*v*PI/16.0).cos() + } + dct_values[k] = alpha * sum; + } + } + dct_values + } + + fn idct(&self, dct_values: [f64; 64]) -> [u8; 64] { + let mut reconstructed: [u8; 64] = [0; 64]; + for k in 0..64 { + let x = (k%8) as f64; + let y = (k/8) as f64; + let mut sum = 0.0; + for u in 0..8 { + for v in 0..8 { + let mut alpha = 1.0; + if u == 0 { + alpha = alpha / SQRT_2 + } + if v == 0 { + alpha = alpha / SQRT_2 + } + let uv = (v*8)+u; + let v = v as f64; + let u = u as f64; + sum += + alpha * + dct_values[uv] * + ((2.0 * x + 1.0) * u * PI / 16.0).cos() * + ((2.0 * y + 1.0) * v * PI / 16.0).cos(); + } + } + sum = 127.0 + (0.25 * sum).round(); + reconstructed[k] = std::cmp::min(255_u8, sum as u8); + } + reconstructed + } +} + +impl Descriptor for DCT { + fn info(&self) -> String { + "DCT".to_string() + } + fn describe(&self, img: &image::DynamicImage) -> u64 { + let resized = self.resize(img); + let mut dct_values = self.dct(&resized); + debug!("DCT-coefficients:\n {}", print_matrix(dct_values)); + + // Quantization: + //debug!("Using quantization matrix:\n{}", print_matrix(self.quantization_matrix)); + for i in 0..64 { + dct_values[i] = (dct_values[i] / self.quantization_matrix[i] as f64).round(); + } + debug!("DCT-coefficients, quantized:\n {}", print_matrix(dct_values)); + + if log_enabled!(Level::Debug) { + resized.save("resize.png").expect("Error saving file"); + // De-quantization: + for i in 0..64 { + dct_values[i] = dct_values[i] * self.quantization_matrix[i] as f64; + } + //debug!("DCT-coefficients, de-quantized:\n {}", print_matrix(dct_values)); + + // Reconstruction original pixel values: + let reconstructed = self.idct(dct_values); + + save_buffer( + "reconstructed.png", + &reconstructed, + 8, + 8, + image::ColorType::L8 + ).expect("Error saving buffer"); + } + + // Calculating descriptor from dct values: + + // Zigzag order for our flattened array, + // first 28 elements only + let zigzag: [usize; 28] = [ + 0, + 1, 8, + 16, 9, 2, + 3, 10, 17, 24, + 32, 25, 18, 11, 4, + 5, 12, 19, 26, 33, 40, + 48, 41, 34, 27, 20, 13, 6, + ]; + + // Mask that indicates if a dct coefficient is positive or negative + // By convention, when sign bit is 1, number is negative + let mut sign_mask: u64 = 0; + // If first horizontal AC coefficient is negative + // This might account for horizontal flips when applied to all horizontal coefficients. + let sign_mult = dct_values[1].signum(); + + // Mask that indicates if a coefficient is bigger or smaller than previous in order + let mut pearson_mask: u64 = 0; + let mut prev = dct_values[0]; + + + for i in zigzag { + let cur = dct_values[i]; + if cur > prev { + pearson_mask += 1; + } + prev = cur; + + let signum = dct_values[i].signum(); + // Only multiply sign if dct-coefficient contains a horizontal component. + if + //i % 8 != 0 && sign_mult * signum < 0.0 + // || + signum < 0.0 + { + sign_mask += 1; + } + + // Shift masks + sign_mask = sign_mask << 1; + pearson_mask = pearson_mask << 1; + } + debug!("Sign mask: {:028b}", sign_mask); + debug!("Pearson mask: {:028b}", pearson_mask); + + let mut mask = sign_mask; + debug!("Mask: {:064b}", mask); + mask = mask << 28; + debug!("Mask: {:064b}", mask); + mask += pearson_mask; + debug!("Mask: {:064b}", mask); + mask = mask << 8; + debug!("Mask: {:064b}", mask); + // TODO: Do something with these last 8 bits. + mask + } +} \ No newline at end of file diff --git a/src/descriptors/mod.rs b/src/descriptors/mod.rs new file mode 100644 index 0000000..8d5806c --- /dev/null +++ b/src/descriptors/mod.rs @@ -0,0 +1,69 @@ +//! Descriptors that can be used to describe an image. +//! If two images are (almost) the same, their descriptions will be the same. + +pub trait Descriptor { + /// Print the name of the descriptor, + fn info(&self) -> String; + fn resize(&self, img: &image::DynamicImage) -> image::DynamicImage { + img.grayscale().thumbnail_exact(8, 8) + } + fn describe(&self, img: &image::DynamicImage) -> u64; + fn distance(&self, a: u64, b: u64) -> u64 { + (a^b).count_ones().into() + } +} + +/// Interprets a 64-element array as an 8x8 matrix +/// returns a nicely printable string +fn print_matrix(array: [T; 64]) -> String { + let mut output = String::new(); + for x in 0..8 { + for y in 0..8 { + let element = format!("{:.2}", &array[x*8+y]); + let element = format!("{:8}\t", element); + output.push_str(&element); + } + output.push('\n'); + } + output +} + +/// A struct representing a Discrete Cosine Transform descriptor +/// Transforms an image into an 8x8 grayscale version, and transforms it +/// into the frequency domain +pub struct DCT { + quantization_matrix: [u8; 64] +} +pub mod dct; + +// fn median64(values: &[T]) -> T { +// let mut sorted_values = values.to_vec(); +// sorted_values.sort(); +// let len = sorted_values.len(); +// sorted_values[len/2] +// } + + +// pub struct Median; + +// impl Descriptor for Median { +// fn describe(&self, img: image::DynamicImage) -> u64 { +// let img = self.resize(img); +// let mut values: [u8; 64] = [0; 64]; +// let mut i: usize = 0; +// for (_, _, pix) in img.pixels() { +// values[i] = pix[0]; +// i = i+1; +// } +// let median = median64(&values); +// let mut mask: u64 = 0; +// img.save("debug.png").unwrap(); +// for (_, _, pix) in img.pixels() { +// if pix[0] > median { +// mask += 1; +// } +// mask = mask << 1; +// } +// mask +// } +// } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ea0f982 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +//! Near-copy image similarity detection. + +//! Descriptors that can be used to describe an image. +//! If two images are (almost) the same, their descriptions will be the same. +pub mod descriptors; +pub mod store; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5bd9dc0..5b6f311 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,37 +1,59 @@ -use descriptors::{Descriptor, Median}; -use std::collections::HashMap; -use std::fs; +use image_similarity::store::DescriptorStore; +use image_similarity::descriptors::{Descriptor, DCT}; +use log::info; +use std::path::PathBuf; +use std::thread; +use clap::Parser; -pub mod descriptors; +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Cfg { + /// Path that contains the input images. Will not traverse directories. + path: PathBuf +} + +fn make_store(descriptor: T, input_path: PathBuf, save_path: PathBuf) -> () { + let mut store = + DescriptorStore::new() + .with_file(save_path); + info!("{}", store); + store.insert_directory(input_path, descriptor); + info!("{}", store); + store.save().expect("Error saving"); +} fn main() { - //Init all descriptors: - let desc = Median; + env_logger::init(); + let cfg = Cfg::parse(); - //Initialize hashmap, empty or from cached file: - let count = fs::read_dir("img").unwrap().count(); - let map_file = fs::read("map.messagepack"); - let mut map: HashMap = match map_file { - Ok(f) => rmp_serde::from_slice(&f).unwrap(), - Err(_e) => HashMap::with_capacity(count), - }; + let mut threads = vec![]; - //Calculate phashes for all images - for node in fs::read_dir("img").unwrap() { - let file = node.expect("Error walking directory"); - let name = file.file_name().into_string().expect("Issue with filename"); - if !map.contains_key(&name) { - let img = image::open(file.path()).expect("Unable to open file"); - let phash = desc.describe(img); - map.insert(name, phash); - } + let dct_50 = + DCT::new() + .with_quality(50); + let dct_30 = + DCT::new() + .with_quality(30); + let dct_10 = + DCT::new() + .with_quality(10); + + threads.push( + thread::spawn(move || { + make_store(dct_50, "data".into(), "dct50.messagepack".into()); + }) + ); + threads.push( + thread::spawn(move || { + make_store(dct_30, "data".into(), "dct30.messagepack".into()); + }) + ); + threads.push( + thread::spawn(move || { + make_store(dct_10, "data".into(), "dct10.messagepack".into()); + }) + ); + for thread in threads { + let _ = thread.join(); } - - for (key, val) in map.iter() { - println!("{key}: {val}"); - } - - //Serialize hashmap with MessagePack: - let serialized: Vec = rmp_serde::to_vec(&map).unwrap(); - fs::write("map.messagepack",&serialized).unwrap(); } diff --git a/src/store.rs b/src/store.rs new file mode 100644 index 0000000..c5c7ef9 --- /dev/null +++ b/src/store.rs @@ -0,0 +1,146 @@ +//! Persistent store of all calculated image descriptions. +//! Can be queried to find identical or similar images. + +use std::collections::HashMap; +use crate::descriptors::Descriptor; +use std::fs; +use std::path::Path; +use std::fmt; +use log::{info, debug, error}; +use bktree::*; + +#[derive(Debug)] +pub enum SaveError { + Serialization, + File, +} + +pub struct DescriptorStore { + map: HashMap, + save_location: std::path::PathBuf, + bktree: BkTree, +} + +impl DescriptorStore { + /// Makes a new empty DescriptorStore with default settings + pub fn new() -> Self { + let map: HashMap = HashMap::new(); + DescriptorStore { + map, + save_location: std::path::PathBuf::from("store.messagepack"), + bktree: BkTree::new(hamming_distance) + } + } + + /// Sets the file location and loads data from file (if available) + pub fn with_file>(mut self, path: P) -> Self { + self.save_location = std::path::PathBuf::from(path.as_ref()); + let map_file = fs::read(&self.save_location); + match map_file { + Ok(f) => self.map = rmp_serde::from_slice(&f).unwrap(), + Err(_) => info!("{} not found, starting from empty store.", self.save_location.display()), + }; + for i in self.map.keys() { + self.bktree.insert(*i); + } + self + } + + pub fn save(&self) -> Result<(), SaveError> { + let serialized: Vec = match rmp_serde::to_vec(&self.map) { + Ok(value) => value, + Err(_e) => return Err(SaveError::Serialization), + }; + match fs::write(&self.save_location, &serialized) { + Ok(_) => Ok(()), + Err(_e) => Err(SaveError::File), + } + } + + /// Returns true iff the store already contains the key + pub fn contains(&self, key: u64) -> bool { + self.map.contains_key(&key) + } + + /// Inserts a single value into the store + pub fn insert(&mut self, key: u64, value: String) { + self.map.insert(key, value); + self.bktree.insert(key); + } + + /// Calculates all descriptions with a given descriptor + pub fn insert_directory>(&mut self, dir: U, desc: T) { + for node in fs::read_dir(dir).unwrap() { + let file = node.expect("Error walking directory"); + let name = match file.file_name().into_string() { + Ok(v) => v, + Err(e) => { + error!("Error reading {}: {:?}:", file.path().to_string_lossy(), e); + continue + } + }; + if true { // !self.contains(name.to_string()) { + info!("Processing {}", name); + let img = match image::open(file.path()) { + Ok(v) => v, + Err(e) => { + error!("Failed to process {}: {}", name, e); + continue + } + }; + let phash = desc.describe(&img); + if self.contains(phash) { + println!("{} duplicate of {:?}", name, self.map.get(&phash)); + } + self.map.insert(phash, name); + } else { + debug!("{} already known, skipping.", name); + } + self.save().expect("error"); + } + } + + /// K-nearest neighbors + pub fn knn(&self, from: u64, k: isize) { + println!("{from:b}"); + let mut x = 0; + let mut y = 8; + let (term_width, _) = viuer::terminal_size(); + println!("\t\t\t\t\tTERMWIDTH {term_width}"); + for (element, distance) in self.bktree.find(from, k) { + let name = self.map.get(element); + match name { + Some(n) => { + println!("{element:b}: {n} (distance: {distance})"); + if x+16 >= term_width { + x = 0; + y += 8; + } + let conf = viuer::Config { + width: Some(16), + height: Some(8), + x: x, + y: y, + ..Default::default() + }; + x += 16; + let path = "data/".to_string() + &n; + let img = image::open(&path).unwrap(); + viuer::print(&img, &conf).expect("Image printing failed."); + }, + None => () + }; + } + } +} + +impl fmt::Display for DescriptorStore { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut output = String::new(); + for (key, val) in self.map.iter() { + let entry = format!("{key:064b}: {val}\t {val:020}\n"); + output.push_str(&entry); + } + write!(f, "DescriptorStore:\n{}Total: {}", output, self.map.len()) + } +} \ No newline at end of file