diff options
-rw-r--r-- | imageutil.cpp | 16 | ||||
-rw-r--r-- | imageutil.hpp | 15 | ||||
-rw-r--r-- | mingui/main.cpp | 32 | ||||
-rw-r--r-- | mingui/mingui.cpp | 47 | ||||
-rw-r--r-- | mingui/mingui.hpp | 13 | ||||
-rw-r--r-- | signature.cpp | 7 | ||||
-rw-r--r-- | signature.hpp | 3 | ||||
-rw-r--r-- | tests/testdrive.cpp | 160 |
8 files changed, 183 insertions, 110 deletions
diff --git a/imageutil.cpp b/imageutil.cpp index b3609d4..fd78ccf 100644 --- a/imageutil.cpp +++ b/imageutil.cpp @@ -1,8 +1,12 @@ #include <algorithm> #include <cmath> +#include <fstream> #include <iterator> #include <limits> //#include <cstdio> + +#include <opencv2/imgcodecs.hpp> + #include "imageutil.hpp" //libpuzzle uses a contrast-based cropping, and clamps the cropped area to a given percentage. @@ -111,3 +115,15 @@ cv::Mat image_util::blend_white(cv::Mat m) } return ret; } + +cv::Mat image_util::imread_path(const std::filesystem::path &p, int flags) +{ + auto size = std::filesystem::file_size(p); + std::fstream fst(p, std::ios::binary | std::ios::in); + std::vector<char> dat; + dat.resize(size); + fst.read(dat.data(), size); + fst.close(); + cv::Mat img = cv::imdecode(dat, flags); + return img; +} diff --git a/imageutil.hpp b/imageutil.hpp index 15be98b..d453311 100644 --- a/imageutil.hpp +++ b/imageutil.hpp @@ -2,6 +2,7 @@ #define IMAGEUTIL_HPP #include <cstdlib> +#include <filesystem> #include <vector> #include <opencv2/core.hpp> @@ -9,13 +10,15 @@ #define sqr(x) ((x) * (x)) -class image_util + + +namespace image_util { -public: - static cv::Mat crop(cv::InputArray s, double contrast_threshold, double max_crop_ratio); - static cv::Range crop_axis(cv::InputArray s, int axis, double contrast_threshold, double max_crop_ratio); - static double median(std::vector<double> &v); - static cv::Mat blend_white(cv::Mat m); + cv::Mat crop(cv::InputArray s, double contrast_threshold, double max_crop_ratio); + cv::Range crop_axis(cv::InputArray s, int axis, double contrast_threshold, double max_crop_ratio); + double median(std::vector<double> &v); + cv::Mat blend_white(cv::Mat m); + cv::Mat imread_path(const std::filesystem::path &p, int flags); template<class T, int B> static double length(const compressed_vector<T, B> &v, T center) diff --git a/mingui/main.cpp b/mingui/main.cpp index 8274b6f..6856cad 100644 --- a/mingui/main.cpp +++ b/mingui/main.cpp @@ -1,5 +1,6 @@ #include <cstdio> #include <algorithm> +#include <filesystem> #include <map> #include <string> #include <vector> @@ -11,9 +12,10 @@ #include "mingui.hpp" using std::size_t; +namespace fs = std::filesystem; -std::unordered_map<std::string, size_t> p; -std::vector<std::string> fns; +std::unordered_map<fs::path, size_t> fnmap; +std::vector<fs::path> fns; std::map<std::pair<size_t, size_t>, double> dist; std::vector<size_t> par; std::vector<std::vector<size_t>> lists; @@ -42,25 +44,25 @@ void load_result(const char* rp) { int l; double d; - std::string s1, s2; + fs::path::string_type s1, s2; if (feof(f)) break; fread(&l, sizeof(int), 1, f); s1.resize(l); - fread(s1.data(), 1, l, f); - p.try_emplace(s1, p.size() + 1); + fread(s1.data(), sizeof(fs::path::value_type), l, f); + fnmap.try_emplace(s1, fnmap.size() + 1); fread(&l, sizeof(int), 1, f); s2.resize(l); - fread(s2.data(), 1, l, f); - p.try_emplace(s2, p.size() + 1); + fread(s2.data(), sizeof(fs::path::value_type), l, f); + fnmap.try_emplace(s2, fnmap.size() + 1); fread(&d, sizeof(double), 1, f); - dist[std::make_pair(p[s1], p[s2])] = d; + dist[std::make_pair(fnmap[s1], fnmap[s2])] = d; } fclose(f); } -std::vector<std::string> build_list(const std::vector<size_t> &l) +std::vector<fs::path> build_list(const std::vector<size_t> &l) { - std::vector<std::string> ret; + std::vector<fs::path> ret; for (auto &x : l) ret.push_back(fns[x]); return ret; @@ -88,12 +90,12 @@ int main(int argc, char **argv) if (argc < 2) return 1; load_result(argv[1]); - printf("%lu known files\n", p.size()); + printf("%lu known files\n", fnmap.size()); - par.resize(p.size() + 1); - fns.resize(p.size() + 1); - lists.resize(p.size() + 1); - for (auto &kp : p) + par.resize(fnmap.size() + 1); + fns.resize(fnmap.size() + 1); + lists.resize(fnmap.size() + 1); + for (auto &kp : fnmap) fns[kp.second] = kp.first; for (size_t i = 1; i < par.size(); ++i) diff --git a/mingui/mingui.cpp b/mingui/mingui.cpp index b421512..58a6ca5 100644 --- a/mingui/mingui.cpp +++ b/mingui/mingui.cpp @@ -1,7 +1,8 @@ #include "mingui.hpp" #include <cstdio> -#include <filesystem> +#include <cwchar> +#include <type_traits> #include <QDebug> #include <QKeyEvent> @@ -28,6 +29,15 @@ const std::vector<int> keys = { Qt::Key::Key_U, Qt::Key::Key_I, Qt::Key::Key_O, Qt::Key::Key_P }; +QString fsstr_to_qstring(const fs::path::string_type &s) +{ +#ifdef _WIN32 //the degenerate platform + return QString::fromStdWString(s); +#else + return QString::fromStdString(s); +#endif +} + MinGuiWidget::MinGuiWidget() { this->setFont(QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont)); @@ -52,7 +62,7 @@ MinGuiWidget::MinGuiWidget() infopanel->setSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Minimum); } -void MinGuiWidget::show_images(const std::vector<std::string> &fns) +void MinGuiWidget::show_images(const std::vector<fs::path> &fns) { current_set = fns; marks.clear(); @@ -61,14 +71,14 @@ void MinGuiWidget::show_images(const std::vector<std::string> &fns) imgcontainer->setLayout(new QVBoxLayout(imgcontainer)); int max_height = (this->screen()->size().height() / fns.size() * 0.75 - 24) * this->screen()->devicePixelRatio(); int max_width = this->screen()->size().width() * 0.8 * this->screen()->devicePixelRatio(); - std::string common_pfx = common_prefix(fns); + fs::path::string_type common_pfx = common_prefix(fns); size_t idx = 0; if (fns.size() > keys.size()) QMessageBox::warning(this, "Too many duplicates", "Too many duplicates found. Some couldn't be assigned a hotkey."); for (auto &f : fns) { marks.push_back(marked.find(f) != marked.end()); - ImageWidget *tw = new ImageWidget(f, f.substr(common_pfx.length()), idx, max_width, max_height, this); + ImageWidget *tw = new ImageWidget(f, f.native().substr(common_pfx.length()), idx, max_width, max_height, this); QObject::connect(tw, &ImageWidget::clicked, [this, idx] { this->mark_toggle(idx); }); imgw.push_back(tw); imgcontainer->layout()->addWidget(tw); @@ -103,7 +113,11 @@ void MinGuiWidget::save_list() QString fn = QFileDialog::getSaveFileName(this, "Save list", QString(), "*.txt"); FILE *f = fopen(fn.toStdString().c_str(), "w"); for (auto &x : this->marked) +#ifdef _WIN32 + fwprintf(f, L"%ls\n", x.c_str()); +#else fprintf(f, "%s\n", x.c_str()); +#endif fclose(f); } @@ -180,23 +194,24 @@ void MinGuiWidget::mark_view_update(bool update_msg) sb->showMessage(QString("%1 of %2 marked for deletion").arg(m).arg(current_set.size()), 1000); } -std::string MinGuiWidget::common_prefix(const std::vector<std::string> &fns) +fs::path::string_type MinGuiWidget::common_prefix(const std::vector<fs::path> &fns) { - std::string ret; - std::string shortest = *std::min_element(fns.begin(), fns.end(), [](auto &a, auto &b){return a.length() < b.length();}); + using fsstr = fs::path::string_type; + fsstr ret; + fsstr shortest = *std::min_element(fns.begin(), fns.end(), [](auto &a, auto &b){return a.native().length() < b.native().length();}); for (size_t i = 0; i < shortest.length(); ++i) { - char c = shortest[i]; + fs::path::value_type c = shortest[i]; bool t = true; - for (auto &s : fns) if (s[i] != c) {t = false; break;} + for (auto &s : fns) if (s.c_str()[i] != c) {t = false; break;} if (!t) break; ret.push_back(c); } if (!ret.empty()) { - auto p = ret.rfind((char)std::filesystem::path::preferred_separator); - if (p != std::string::npos) - return ret.substr(0, p + 1); + auto p = ret.rfind(std::filesystem::path::preferred_separator); + if (p != fsstr::npos) + return fs::path(ret.substr(0, p + 1)); } return ret; } @@ -227,15 +242,15 @@ void MinGuiWidget::keyReleaseEvent(QKeyEvent *e) } } -ImageWidget::ImageWidget(std::string f, std::string dispf, size_t _idx, int max_width, int max_height, QWidget *par) +ImageWidget::ImageWidget(fs::path f, fs::path::string_type dispf, size_t _idx, int max_width, int max_height, QWidget *par) : QWidget(par), fn(QString::fromStdString(f)), idx(_idx) { this->setLayout(new QVBoxLayout(this)); this->layout()->setMargin(10); im = new QLabel(this); this->layout()->addWidget(im); - QFile imgf(QString::fromStdString(f)); - QPixmap pm(QString::fromStdString(f)); + QFile imgf(fsstr_to_qstring(f.native())); + QPixmap pm(fsstr_to_qstring(f.native())); int imw = pm.width(); int imh = pm.height(); pm.setDevicePixelRatio(this->screen()->devicePixelRatio()); @@ -247,7 +262,7 @@ ImageWidget::ImageWidget(std::string f, std::string dispf, size_t _idx, int max_ this->layout()->addWidget(lb); QString s = QString("<%1>: %2, %3 x %4, %5") .arg(idx < keys.size() ? QKeySequence(keys[idx]).toString(): QString("(No hotkey available)")) - .arg(QString::fromStdString(dispf/*f.substr(common_pfx.length())*/)) + .arg(fsstr_to_qstring(dispf)) .arg(imw).arg(imh) .arg(QLocale::system().formattedDataSize(imgf.size(), 3)); lb->setTextFormat(Qt::TextFormat::PlainText); diff --git a/mingui/mingui.hpp b/mingui/mingui.hpp index 7ed0eb1..3a5dddc 100644 --- a/mingui/mingui.hpp +++ b/mingui/mingui.hpp @@ -1,6 +1,7 @@ #ifndef MINGUI_HPP #define MINGUI_HPP +#include <filesystem> #include <vector> #include <string> #include <unordered_set> @@ -11,6 +12,8 @@ class QHBoxLayout; class QLabel; class QStatusBar; +namespace fs = std::filesystem; + class MinGuiWidget : public QWidget { Q_OBJECT @@ -25,17 +28,17 @@ private: void mark_all(); void mark_none(); void mark_view_update(bool update_msg = true); - std::string common_prefix(const std::vector<std::string> &fns); + fs::path::string_type common_prefix(const std::vector<fs::path> &fns); std::vector<QWidget*> imgw; std::vector<bool> marks; - std::unordered_set<std::string> marked; - std::vector<std::string> current_set; + std::unordered_set<fs::path> marked; + std::vector<fs::path> current_set; protected: void keyPressEvent(QKeyEvent *e) override; void keyReleaseEvent(QKeyEvent *e) override; public: MinGuiWidget(); - void show_images(const std::vector<std::string> &fns); + void show_images(const std::vector<std::filesystem::path> &fns); void update_distances(const std::map<std::pair<std::size_t, std::size_t>, double> &d); void update_permamsg(std::size_t cur, std::size_t size); void save_list(); @@ -55,7 +58,7 @@ private: protected: void mouseReleaseEvent(QMouseEvent *event) override; public: - ImageWidget(std::string f, std::string dispfn, std::size_t _idx, int max_width, int max_height, QWidget *par); + ImageWidget(fs::path f, fs::path::string_type dispfn, std::size_t _idx, int max_width, int max_height, QWidget *par); void set_marked(bool marked); Q_SIGNALS: void clicked(); diff --git a/signature.cpp b/signature.cpp index fb2e757..8a5579e 100644 --- a/signature.cpp +++ b/signature.cpp @@ -8,6 +8,7 @@ */ #include <algorithm> +#include <fstream> #include <cmath> #include <cstdint> #include <vector> @@ -281,6 +282,12 @@ signature signature::from_file(const char *fn, const signature_config &cfg) return signature::from_cvmatrix(img, cfg); } +signature signature::from_path(const std::filesystem::path &path, const signature_config &cfg) +{ + cv::Mat img = image_util::imread_path(path, cv::IMREAD_UNCHANGED); + return signature::from_cvmatrix(img, cfg); +} + signature_config signature::default_cfg() { return _default_cfg; diff --git a/signature.hpp b/signature.hpp index b3c5c40..0197e75 100644 --- a/signature.hpp +++ b/signature.hpp @@ -2,6 +2,7 @@ #define SIGNATURE_HPP #include <memory> +#include <filesystem> #include <opencv2/core.hpp> struct signature_config @@ -36,6 +37,8 @@ public: double distance(const signature &o) const; bool operator ==(const signature &o) const; + static signature from_path(const std::filesystem::path &path, const signature_config &cfg); + static signature from_file(const char *fn, const signature_config &cfg); /* diff --git a/tests/testdrive.cpp b/tests/testdrive.cpp index c0d1038..2d8b0eb 100644 --- a/tests/testdrive.cpp +++ b/tests/testdrive.cpp @@ -1,5 +1,3 @@ -#include "signature.hpp" - #include <cstdio> #include <cstring> @@ -16,44 +14,55 @@ #include <getopt.h> +#ifdef _WIN32 //for the superior operating system +#include <cwchar> +#include <processenv.h> +#include <shellapi.h> +#endif + +#include "signature.hpp" +#include "imageutil.hpp" + #include "thread_pool.hpp" #define DEBUG 0 +namespace fs = std::filesystem; + int ctr; int recursive; -int njobs=1; -double threshold=0.3; -std::vector<std::string> paths; -std::vector<std::string> files; +int njobs = 1; +double threshold = 0.3; +std::vector<fs::path> paths; +std::vector<fs::path> files; int nsliceh = 3; int nslicev = 3; signature_config cfg_full = { - 9, //slices - 3, //blur_window - 2, //min_window - true, //crop - true, //comp - 0.5, //pr - 1./128,//noise_threshold - 0.05, //contrast_threshold - 0.25 //max_cropping + 9, //slices + 3, //blur_window + 2, //min_window + true, //crop + true, //comp + 0.5, //pr + 1./128, //noise_threshold + 0.05, //contrast_threshold + 0.25 //max_cropping }; signature_config cfg_subslice = { - 4, //slices - 16, //blur_window - 2, //min_window - false, //crop - true, //comp - 0.5, //pr - 1./64, //noise_threshold - 0.05, //contrast_threshold - 0.25 //max_cropping + 4, //slices + 16, //blur_window + 2, //min_window + false, //crop + true, //comp + 0.5, //pr + 1./64, //noise_threshold + 0.05, //contrast_threshold + 0.25 //max_cropping }; struct sig_eq @@ -73,48 +82,59 @@ std::vector<std::pair<size_t, size_t>> out; int parse_arguments(int argc,char **argv) { - recursive=0; - int help=0; + recursive = 0; + int help = 0; option longopt[]= { - {"recursive",no_argument ,&recursive,1}, -// {"destdir" ,required_argument,0 ,'D'}, - {"jobs" ,required_argument,0 ,'j'}, -// {"threshold",required_argument,0 ,'d'}, - {"help" ,no_argument ,&help ,1}, - {0 ,0 ,0 ,0} + {"recursive", no_argument , &recursive, 1}, +// {"destdir" , required_argument, 0 , 'D'}, + {"jobs" , required_argument, 0 , 'j'}, +// {"threshold", required_argument, 0 , 'd'}, + {"help" , no_argument , &help , 1}, + {0 , 0 , 0 , 0} }; while(1) { - int idx=0; - int c=getopt_long(argc,argv,"rhj:",longopt,&idx); - if(!~c)break; - switch(c) + int idx = 0; + int c = getopt_long(argc, argv, "rhj:", longopt, &idx); + if (!~c) break; + switch (c) { case 0: - if(longopt[idx].flag)break; - if(std::string("jobs")==longopt[idx].name) - sscanf(optarg,"%d",&njobs); - //if(std::string("threshold")==longopt[idx].name) - //sscanf(optarg,"%lf",&threshold); + if (longopt[idx].flag) break; + if (std::string("jobs") == longopt[idx].name) + sscanf(optarg, "%d", &njobs); + //if(std::string("threshold") == longopt[idx].name) + //sscanf(optarg, "%lf", &threshold); break; case 'r': - recursive=1; + recursive = 1; break; case 'h': - help=1; + help = 1; break; case 'j': - sscanf(optarg,"%d",&njobs); + sscanf(optarg, "%d", &njobs); break; case 'd': - sscanf(optarg,"%lf",&threshold); + //sscanf(optarg, "%lf", &threshold); break; } } - for(;optind<argc;++optind) +#ifdef _WIN32 //w*ndows, ugh + wchar_t *args = GetCommandLineW(); + int wargc; + wchar_t **wargv = CommandLineToArgvW(args, &wargc); + if (wargv && wargc == argc) + { + for (; optind < argc; ++optind) + path.push_back(wargv[optind]); + } +#else + for (; optind < argc; ++optind) paths.push_back(argv[optind]); - if(help||argc<2) +#endif + if (help || argc < 2) { printf( "Usage: %s [OPTION] PATH...\n" @@ -127,13 +147,13 @@ int parse_arguments(int argc,char **argv) ); return 1; } - if(threshold>1||threshold<0) + if (threshold > 1 || threshold < 0) { puts("Invalid threshold value."); return 2; } - if(threshold<1e-6)threshold=1e-6; - if(!paths.size()) + if (threshold < 1e-6) threshold = 1e-6; + if (!paths.size()) { puts("Missing image path."); return 2; @@ -141,12 +161,12 @@ int parse_arguments(int argc,char **argv) return 0; } -void build_file_list(std::filesystem::path path,bool recursive,std::vector<std::string>&out) +void build_file_list(fs::path path, bool recursive, std::vector<fs::path> &out) { - if(recursive) + if (recursive) { - auto dirit=std::filesystem::recursive_directory_iterator(path); - for(auto &p:dirit) + auto dirit = fs::recursive_directory_iterator(path); + for (auto &p : dirit) { FILE* fp = fopen(p.path().c_str(),"r"); char c[8]; @@ -164,8 +184,8 @@ void build_file_list(std::filesystem::path path,bool recursive,std::vector<std:: } else { - auto dirit=std::filesystem::directory_iterator(path); - for(auto &p:dirit) + auto dirit = fs::directory_iterator(path); + for(auto &p : dirit) { FILE* fp = fopen(p.path().c_str(),"r"); char c[8]; @@ -185,7 +205,7 @@ void build_file_list(std::filesystem::path path,bool recursive,std::vector<std:: void job_func(int thid, size_t id) { - cv::Mat img = cv::imread(files[id].c_str(), cv::IMREAD_UNCHANGED); + cv::Mat img = image_util::imread_path(files[id], cv::IMREAD_UNCHANGED); signature s = signature::from_cvmatrix(img, cfg_full); #if DEBUG > 1 s.dump(); @@ -250,36 +270,40 @@ void job_func(int thid, size_t id) void run() { thread_pool tp(njobs); - for(size_t i=0;i<files.size();++i) + for(size_t i = 0; i < files.size(); ++i) { - tp.create_task(job_func,i); + tp.create_task(job_func, i); } tp.wait(); } int main(int argc,char** argv) { - if(int pr=parse_arguments(argc,argv))return pr-1; + if (int pr = parse_arguments(argc, argv)) return pr - 1; puts("building list of files to compare..."); - for(auto&p:paths) - build_file_list(p,recursive,files); - printf("%lu files to compare.\n",files.size()); + for (auto &p : paths) + build_file_list(p, recursive, files); + printf("%lu files to compare.\n", files.size()); puts("computing signature vectors..."); signatures.resize(files.size()); run(); FILE *outf = fopen("result", "wb"); - for(auto &p : out) + for (auto &p : out) { +#ifdef _WIN32 + wprintf(L"%ls %ls %f\n", files[p.first].c_str(), files[p.second].c_str(), signatures[p.first].distance(signatures[p.second])); +#else printf("%s %s %f\n", files[p.first].c_str(), files[p.second].c_str(), signatures[p.first].distance(signatures[p.second])); +#endif int t; double ts; - t = (int)files[p.first].length(); + t = (int)files[p.first].native().length(); fwrite(&t, sizeof(int), 1, outf); - fwrite(files[p.first].c_str(), 1, files[p.first].length(), outf); - t = (int)files[p.second].length(); + fwrite(files[p.first].c_str(), sizeof(fs::path::value_type), t, outf); + t = (int)files[p.second].native().length(); fwrite(&t, sizeof(int), 1, outf); - fwrite(files[p.second].c_str(), 1, files[p.second].length(), outf); + fwrite(files[p.second].c_str(), sizeof(fs::path::value_type), t, outf); ts = signatures[p.first].distance(signatures[p.second]); fwrite(&ts, sizeof(double), 1, outf); } |