├── Trainer ├── stdafx.h ├── stdafx.cpp ├── Trainer.cpp └── targetver.h ├── NeuralNetwork ├── test.ssn ├── Test.cpp ├── NeuralNetwork.vcxproj.filters ├── neural_scaler.h ├── NeuralNetwork.vcxproj └── neural_scaler.cpp ├── dependencies.bat ├── .gitignore ├── SProcUnitTests ├── stdafx.cpp ├── targetver.h ├── stdafx.h ├── MaybeFixed.cpp ├── SaveRuleTests.cpp ├── SProcUnitTests.vcxproj.filters ├── ScoreProcessesTest.cpp ├── ColorTests.cpp └── SProcUnitTests.vcxproj ├── ScoreProcessor ├── stdafx.cpp ├── targetver.h ├── stdafx.h ├── lib │ └── exstring │ │ ├── exmacro.h │ │ ├── exstdstring.h │ │ └── exfiles.h ├── allAlgorithms.h ├── imagefind.h ├── Logs.cpp ├── Logs.h ├── support.h ├── ImageUtils.cpp ├── Cluster.cpp ├── shorthand.h ├── parse.h ├── ScoreProcessor.vcxproj.filters ├── Cluster.h ├── ImageMath.cpp ├── moreAlgorithms.h ├── Splice.h ├── ImageUtils.h ├── Splice.cpp ├── Processes.h └── Interface.cpp ├── Readme.MD └── ScoreProcessor.sln /Trainer/stdafx.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardx999/ScoreProcessor/HEAD/Trainer/stdafx.h -------------------------------------------------------------------------------- /Trainer/stdafx.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardx999/ScoreProcessor/HEAD/Trainer/stdafx.cpp -------------------------------------------------------------------------------- /Trainer/Trainer.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardx999/ScoreProcessor/HEAD/Trainer/Trainer.cpp -------------------------------------------------------------------------------- /Trainer/targetver.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardx999/ScoreProcessor/HEAD/Trainer/targetver.h -------------------------------------------------------------------------------- /NeuralNetwork/test.ssn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edwardx999/ScoreProcessor/HEAD/NeuralNetwork/test.ssn -------------------------------------------------------------------------------- /dependencies.bat: -------------------------------------------------------------------------------- 1 | vcpkg install libjpeg-turbo:x64-windows-static 2 | vcpkg install libjpeg-turbo:x86-windows-static 3 | vcpkg install tiff:x64-windows-static 4 | vcpkg install tiff:x86-windows-static 5 | vcpkg install libpng:x64-windows-static 6 | vcpkg install libpng:x86-windows-static -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | Debug/ 5 | *.iobj 6 | *.obj 7 | *.txt 8 | *.jpg 9 | *.png 10 | *.ipdb 11 | *.pdb 12 | *.pdn 13 | ScoreProcessor/x64/ 14 | ScoreProcessor/Release/ 15 | Release/test/ 16 | *.user 17 | *.ssn 18 | *.tlog 19 | *.pch 20 | *.log 21 | *.lib 22 | *.exp 23 | *.exe 24 | -------------------------------------------------------------------------------- /NeuralNetwork/Test.cpp: -------------------------------------------------------------------------------- 1 | #include "neural_net.h" 2 | 3 | int main() 4 | { 5 | neural_net::net<> test(0x0A,0x0B,0x0C); 6 | test.randomize(); 7 | std::cout< 9 | -------------------------------------------------------------------------------- /SProcUnitTests/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | // Headers for CppUnitTest 11 | #include "CppUnitTest.h" 12 | 13 | // TODO: reference additional headers your program requires here 14 | -------------------------------------------------------------------------------- /ScoreProcessor/targetver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Including SDKDDKVer.h defines the highest available Windows platform. 4 | 5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and 6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. 7 | #include 8 | #define _WIN32_WINNT _WIN32_WINNT_WIN7 9 | #include 10 | -------------------------------------------------------------------------------- /ScoreProcessor/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #include "targetver.h" 9 | 10 | #include 11 | #include 12 | #include "CImg.h" 13 | #include 14 | 15 | 16 | // TODO: reference additional headers your program requires here 17 | -------------------------------------------------------------------------------- /SProcUnitTests/MaybeFixed.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CppUnitTest.h" 3 | #include "../ScoreProcessor/ImageUtils.h" 4 | 5 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 6 | using namespace ImageUtils; 7 | #define TM TEST_METHOD 8 | namespace SProcUnitTests { 9 | TEST_CLASS(MaybeFixedTests) 10 | { 11 | TM(fixed1) 12 | { 13 | maybe_fixed<> t(10); 14 | Assert::AreEqual(t(10),10); 15 | } 16 | }; 17 | } -------------------------------------------------------------------------------- /ScoreProcessor/lib/exstring/exmacro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef EXMACRO_H 3 | #define EXMACRO_H 4 | #if __cpp_impl_three_way_comparison 5 | #define EXLIB_FOR_ALL_COMP_OPS_H(MACRO_NAME) MACRO_NAME(<) MACRO_NAME(>) MACRO_NAME(<=) MACRO_NAME(>=) MACRO_NAME(==) MACRO_NAME(!=) MACRO_NAME(<=>) 6 | #else 7 | #define EXLIB_FOR_ALL_COMP_OPS_H(MACRO_NAME) MACRO_NAME(<) MACRO_NAME(>) MACRO_NAME(<=) MACRO_NAME(>=) MACRO_NAME(==) MACRO_NAME(!=) 8 | #endif 9 | #define EXLIB_FOR_ALL_COMP_OPS(MACRO_NAME) EXLIB_FOR_ALL_COMP_OPS_H(MACRO_NAME) 10 | 11 | #endif -------------------------------------------------------------------------------- /ScoreProcessor/allAlgorithms.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #include "CImg.h" 18 | #include "ImageMath.h" 19 | #include "ImageUtils.h" 20 | #include "moreAlgorithms.h" 21 | #include "ScoreProcesses.h" 22 | #include "Cluster.h" -------------------------------------------------------------------------------- /ScoreProcessor/imagefind.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef IMAGEFIND_H 18 | #define IMAGEFIND_H 19 | #include "CImg.h" 20 | #include 21 | namespace ScoreProcessor { 22 | template 23 | void replace(cimg_library::CImg& img,cimg_library::CImg const& target,cimg_library::CImg const& replacer) 24 | { 25 | assert(replacer._width==target._width); 26 | assert(replacer._height==target._height); 27 | assert(img._spectrum==target._spectrum); 28 | } 29 | 30 | template 31 | void remove_page_numbers(cimg_library::CImg& img); 32 | 33 | template 34 | void remove_system_separators(cimg_library::CImg& img); 35 | } 36 | #endif -------------------------------------------------------------------------------- /NeuralNetwork/NeuralNetwork.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | -------------------------------------------------------------------------------- /SProcUnitTests/SaveRuleTests.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CppUnitTest.h" 3 | #include "../ScoreProcessor/ImageProcess.h" 4 | 5 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 6 | using namespace ScoreProcessor; 7 | #define TM TEST_METHOD 8 | namespace SProcUnitTests { 9 | TEST_CLASS(SaveRuleTest) 10 | { 11 | public: 12 | TM(empty) 13 | { 14 | SaveRules sr(""); 15 | std::string exp=""; 16 | auto res=sr.make_filename("blahblah"); 17 | Assert::AreEqual(exp,res); 18 | } 19 | TM(filename_index_extension1) 20 | { 21 | SaveRules sr("%f%1.%x"); 22 | std::string exp="hello_1.png"; 23 | auto res=sr.make_filename("hello_.png"); 24 | Assert::AreEqual(exp,res); 25 | } 26 | TM(filename_index_extension2) 27 | { 28 | SaveRules sr("%f%3.%x"); 29 | std::string exp="hello_002.png"; 30 | auto res=sr.make_filename("hello_.png",2); 31 | Assert::AreEqual(exp,res); 32 | } 33 | TM(copy) 34 | { 35 | SaveRules sr("%c"); 36 | std::string exp="hello_1.png"; 37 | auto res=sr.make_filename(exp); 38 | Assert::AreEqual(exp,res); 39 | } 40 | TM(path_whole) 41 | { 42 | SaveRules sr("%p/%w"); 43 | std::string exp="my_folder/hello_1.png"; 44 | auto res=sr.make_filename(exp); 45 | Assert::AreEqual(exp,res); 46 | std::string exp2="./hello_1.png"; 47 | auto res2=sr.make_filename("hello_1.png"); 48 | Assert::AreEqual(exp2,res2); 49 | } 50 | TM(indexing_index_new_extension_percent) 51 | { 52 | SaveRules sr("p%%_%3.bmp"); 53 | std::string exp="p%_010.bmp"; 54 | auto res=sr.make_filename("random_name.png",10); 55 | Assert::AreEqual(exp,res); 56 | } 57 | }; 58 | } -------------------------------------------------------------------------------- /ScoreProcessor/Logs.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Logs.h" 3 | #include 4 | namespace Loggers { 5 | void CoutLog::log(char const* msg,size_t len,size_t) 6 | { 7 | std::cout.write(msg,len); 8 | } 9 | void CoutLog::log_error(char const* msg,size_t len,size_t) 10 | { 11 | std::cout.write(msg,len); 12 | } 13 | 14 | void AmountLog::log(char const* msg,size_t len,size_t) 15 | { 16 | if(msg[0]=='S') 17 | { 18 | if(!begun) 19 | { 20 | begun=true; 21 | std::cout.write(message_template.get(),buffer_length); 22 | } 23 | } 24 | else 25 | { 26 | char buffer[BUFFER_SIZE]; 27 | //if(msg[0]=='F') 28 | { 29 | auto c=++count; 30 | assert(c<=amount); 31 | //if(c==count) 32 | { 33 | insert_message(buffer,c); 34 | if(c==count) 35 | { 36 | std::lock_guard lock(mtx); 37 | if(c==count) 38 | { 39 | std::cout.write(buffer,buffer_length); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | void AmountLog::log_error(char const* msg,size_t len,size_t) 48 | { 49 | size_t const msg_buf_len=len+buffer_length; 50 | std::unique_ptr buffer(new char[msg_buf_len]); 51 | memcpy(buffer.get(),msg,len); 52 | size_t const c=count; 53 | insert_message(buffer.get()+len,c); 54 | if(c==count) 55 | { 56 | std::lock_guard lock(mtx); 57 | if(c==count) 58 | { 59 | std::cout.write(buffer.get(),msg_buf_len); 60 | } 61 | else 62 | { 63 | std::cout.write(buffer.get(),len); 64 | } 65 | } 66 | else 67 | { 68 | std::cout.write(buffer.get(),len); 69 | } 70 | } 71 | AmountLog::~AmountLog() 72 | { 73 | std::cout<<'\n'; 74 | } 75 | } -------------------------------------------------------------------------------- /SProcUnitTests/SProcUnitTests.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | Source Files 40 | 41 | 42 | -------------------------------------------------------------------------------- /SProcUnitTests/ScoreProcessesTest.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CppUnitTest.h" 3 | #include "../ScoreProcessor/ScoreProcesses.h" 4 | #include 5 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 6 | using namespace ScoreProcessor; 7 | using namespace cil; 8 | namespace SProcUnitTests { 9 | using Rint=ImageUtils::Rectangle; 10 | TEST_CLASS(UnitTest2) 11 | { 12 | private: 13 | template 14 | void AssertEquals(CImg const& a,CImg const& b) 15 | { 16 | if(a!=b) 17 | { 18 | std::thread c([&]() 19 | { 20 | a.display(); 21 | }); 22 | std::thread d([&]() 23 | { 24 | b.display(); 25 | }); 26 | c.join(); 27 | d.join(); 28 | Assert::IsFalse(true); 29 | } 30 | } 31 | public: 32 | TEST_METHOD(CropFill1) 33 | { 34 | CImg img(10,10); 35 | img.fill(0); 36 | auto res=get_crop_fill(img,Rint({-2,12,-4,14}),unsigned char(255)); 37 | Assert::AreEqual(15U,res._width); 38 | Assert::AreEqual(19U,res._height); 39 | CImg exp(15,19); 40 | exp.fill(0); 41 | for(unsigned int x=0;x<15;++x) 42 | { 43 | for(unsigned int y=0;y<4;++y) 44 | { 45 | exp(x,y)=255; 46 | } 47 | } 48 | for(unsigned int x=0;x<15;++x) 49 | { 50 | for(unsigned int y=14;y<19;++y) 51 | { 52 | exp(x,y)=255; 53 | } 54 | } 55 | for(unsigned int x=0;x<2;++x) 56 | { 57 | for(unsigned int y=0;y<19;++y) 58 | { 59 | exp(x,y)=255; 60 | } 61 | } 62 | for(unsigned int x=12;x<15;++x) 63 | { 64 | for(unsigned int y=0;y<19;++y) 65 | { 66 | exp(x,y)=255; 67 | } 68 | } 69 | AssertEquals(exp,res); 70 | } 71 | TEST_METHOD(HorizPadding1) 72 | { 73 | /*CImg res(10,20); 74 | res.fill(0); 75 | horiz_padding(res,5,10); 76 | CImg exp(25,20); 77 | exp.fill(0); 78 | for(unsigned int x=0;x<5;++x) 79 | { 80 | for(unsigned int y=0;y<20;++y) 81 | { 82 | exp(x,y)=255; 83 | } 84 | } 85 | for(unsigned int x=15;x<25;++x) 86 | { 87 | for(unsigned int y=0;y<20;++y) 88 | { 89 | exp(x,y)=255; 90 | } 91 | } 92 | AssertEquals(exp,res);*/ 93 | } 94 | }; 95 | } -------------------------------------------------------------------------------- /ScoreProcessor/Logs.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGS_H 2 | #define LOGS_H 3 | #include "ImageProcess.h" 4 | #include "lib/exstring/exmath.h" 5 | #include "lib/exstring/exstring.h" 6 | namespace Loggers { 7 | class CoutLog:public ScoreProcessor::Log { 8 | public: 9 | void log(char const*,size_t,size_t) override; 10 | void log_error(char const*,size_t,size_t) override; 11 | }; 12 | 13 | class AmountLog:public ScoreProcessor::Log { 14 | private: 15 | std::atomic count;//count of finished pages 16 | size_t amount; 17 | unsigned int num_digs; 18 | size_t buffer_length; 19 | std::unique_ptr message_template; 20 | std::mutex mtx; 21 | bool begun;//atomicity not needed, double writing the first thing is ok 22 | static constexpr char const* START_MSG="Finished "; 23 | static constexpr size_t const START_STRLEN=exlib::strlen(START_MSG); 24 | static constexpr size_t const EXTRA_CHARS=2;//slash and carriage return 25 | static constexpr size_t const BUFFER_SIZE=START_STRLEN+EXTRA_CHARS+2*exlib::num_digits(std::numeric_limits::max());//enough to fit message and digits of max value of size_t 26 | inline static void insert_numbers(char* place,size_t num) 27 | { 28 | while(true) 29 | { 30 | *place=num%10+'0'; 31 | num/=10; 32 | if(num==0) 33 | { 34 | break; 35 | } 36 | --place; 37 | } 38 | } 39 | inline void insert_message(char* place,size_t num) 40 | { 41 | memcpy(place,message_template.get(),buffer_length); 42 | char* it=place+START_STRLEN+num_digs-1; 43 | insert_numbers(it,num); 44 | } 45 | public: 46 | inline AmountLog(size_t amount): 47 | amount(amount), 48 | count(0), 49 | num_digs(exlib::num_digits(amount)), 50 | buffer_length(START_STRLEN+EXTRA_CHARS+2*num_digs), 51 | message_template(new char[buffer_length]), 52 | begun(false) 53 | { 54 | memcpy(message_template.get(),START_MSG,START_STRLEN); 55 | size_t const len=num_digs-1; 56 | memset(message_template.get()+START_STRLEN,' ',len); 57 | message_template[START_STRLEN+len]='0'; 58 | message_template[START_STRLEN+num_digs]='/'; 59 | insert_numbers(message_template.get()+buffer_length-2,amount); 60 | message_template[START_STRLEN+1+2*num_digs]='\r'; 61 | } 62 | void log(char const*,size_t,size_t) override; 63 | void log_error(char const*,size_t,size_t) override; 64 | ~AmountLog() override; 65 | }; 66 | } 67 | #endif -------------------------------------------------------------------------------- /ScoreProcessor/support.h: -------------------------------------------------------------------------------- 1 | #ifndef SUPPORT_H 2 | #define SUPPORT_H 3 | #include "lib\exstring\exstring.h" 4 | #include "lib\exstring\exfiles.h" 5 | #include "lib\exstring\exalg.h" 6 | enum class support_type { 7 | no,png,jpeg,bmp,tiff 8 | }; 9 | 10 | struct no_case_compare { 11 | template 12 | constexpr int operator()(T a,U b) const 13 | { 14 | return exlib::strncmp_nocase(a,b); 15 | } 16 | }; 17 | using support_map_kp=exlib::map_pair; 18 | inline constexpr auto CTMap=exlib::make_ct_map( 19 | support_map_kp{"png",support_type::png}, 20 | support_map_kp{"jpeg",support_type::jpeg}, 21 | support_map_kp{"jpg",support_type::jpeg}, 22 | support_map_kp{"jpe",support_type::jpeg}, 23 | support_map_kp{"jfif",support_type::jpeg}, 24 | support_map_kp{"jif",support_type::jpeg}, 25 | support_map_kp{"bmp",support_type::bmp}, 26 | support_map_kp{"tiff",support_type::tiff}, 27 | support_map_kp{"tif",support_type::tiff} 28 | ); 29 | 30 | template 31 | support_type supported(Extension ext) 32 | { 33 | auto const iter=CTMap.find(ext); 34 | if(iter==CTMap.end()) 35 | { 36 | return support_type::no; 37 | } 38 | return iter->value(); 39 | } 40 | 41 | template 42 | support_type validate_extension(Extension ext) 43 | { 44 | auto const iter=CTMap.find(ext); 45 | if(iter==CTMap.end()) 46 | { 47 | auto const strlen=exlib::strlen(ext); 48 | if(strlen) 49 | { 50 | throw std::invalid_argument{std::string{"Unsupported file type: "}.append(ext,ext+strlen)}; 51 | } 52 | throw std::invalid_argument{"Unsupported file type: (no extension)"}; 53 | } 54 | return iter->value(); 55 | } 56 | 57 | template 58 | support_type supported_path(Path begin,Path end) 59 | { 60 | return supported(exlib::find_extension(begin,end)); 61 | } 62 | 63 | template 64 | support_type supported_path(Path begin) 65 | { 66 | auto const strlen=exlib::strlen(begin); 67 | return supported(exlib::find_extension(begin,begin+strlen)); 68 | } 69 | 70 | template 71 | support_type validate_path(Path begin) 72 | { 73 | auto const strlen=exlib::strlen(begin); 74 | return validate_extension(exlib::find_extension(begin,begin+strlen)); 75 | } 76 | 77 | template 78 | support_type validate_path(Path begin,Path end) 79 | { 80 | return validate_extension(exlib::find_extension(begin,end)); 81 | } 82 | #endif -------------------------------------------------------------------------------- /ScoreProcessor/ImageUtils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #include "stdafx.h" 18 | #include "ImageUtils.h" 19 | #include 20 | #include "moreAlgorithms.h" 21 | namespace ImageUtils { 22 | #pragma region ColorRGB 23 | 24 | float ColorRGB::difference(ColorRGB other) { 25 | float 26 | rdif=misc_alg::abs_dif(r,other.r), 27 | gdif=misc_alg::abs_dif(g,other.g), 28 | bdif=misc_alg::abs_dif(b,other.b); 29 | return (rdif*rdif+gdif*gdif+bdif*bdif)/(255.0f*255.0f*3.0f); 30 | } 31 | 32 | float ColorRGB::color_diff(unsigned char const* c1,unsigned char const* c2) { 33 | float 34 | rdif=misc_alg::abs_dif(c1[0],c2[0]), 35 | gdif=misc_alg::abs_dif(c1[1],c2[1]), 36 | bdif=misc_alg::abs_dif(c1[2],c2[2]); 37 | return (rdif*rdif+gdif*gdif+bdif*bdif)/(255.0f*255.0f*3.0f); 38 | } 39 | ColorRGB const ColorRGB::WHITE={255,255,255}; 40 | ColorRGB const ColorRGB::BLACK={0,0,0}; 41 | #pragma endregion 42 | #pragma region Grayscale 43 | float const max_dif_gs=255.0f; 44 | float Grayscale::color_diff(unsigned char const* c1,unsigned char const* c2) { 45 | float dif=misc_alg::abs_dif(*c1,*c2); 46 | return dif/max_dif_gs; 47 | } 48 | float const twofivefivesquared=255.0f*255.0f; 49 | float gray_diff(Grayscale g1,Grayscale g2) { 50 | float dif=(g1>g2?g1-g2:g2-g1); 51 | return dif*dif/twofivefivesquared; 52 | } 53 | 54 | Grayscale const Grayscale::WHITE=255; 55 | Grayscale const Grayscale::BLACK=0; 56 | 57 | float Grayscale::difference(Grayscale other) { 58 | return static_cast(misc_alg::abs_dif(*this,other))/255.0f; 59 | } 60 | #pragma endregion 61 | ColorRGB ColorRGBA::toRGB() const { 62 | return ColorRGB{r,g,b}; 63 | } 64 | 65 | ColorHSV const ColorHSV::WHITE={0,0,255}; 66 | ColorHSV const ColorHSV::BLACK={0,0,0}; 67 | } -------------------------------------------------------------------------------- /SProcUnitTests/ColorTests.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "CppUnitTest.h" 3 | #include "../ScoreProcessor/ImageUtils.h" 4 | 5 | using namespace Microsoft::VisualStudio::CppUnitTestFramework; 6 | 7 | namespace SProcUnitTests { 8 | TEST_CLASS(UnitTest1) 9 | { 10 | public: 11 | 12 | TEST_METHOD(ColorRGBToColorHSV1) 13 | { 14 | using namespace ImageUtils; 15 | ColorRGB red_dark={255,0,0}; 16 | ColorHSV red_dark_hsv=red_dark; 17 | ColorHSV expected={0,255,255}; 18 | Assert::AreEqual(red_dark_hsv.h,expected.h); 19 | Assert::AreEqual(red_dark_hsv.s,expected.s); 20 | Assert::AreEqual(red_dark_hsv.v,expected.v); 21 | } 22 | TEST_METHOD(ColorRGBToColorHSV2) 23 | { 24 | using namespace ImageUtils; 25 | ColorRGB red_dark={128,0,0}; 26 | ColorHSV red_dark_hsv=red_dark; 27 | ColorHSV expected={0,255,128}; 28 | Assert::AreEqual(red_dark_hsv.h,expected.h); 29 | Assert::AreEqual(red_dark_hsv.s,expected.s); 30 | Assert::AreEqual(red_dark_hsv.v,expected.v); 31 | } 32 | TEST_METHOD(ColorRGBToColorHSV3) 33 | { 34 | using namespace ImageUtils; 35 | ColorRGB red_dark={0,0,0}; 36 | ColorHSV red_dark_hsv=red_dark; 37 | ColorHSV expected={0,0,0}; 38 | //Assert::AreEqual(red_dark_hsv.h,expected.h); 39 | Assert::AreEqual(red_dark_hsv.s,expected.s); 40 | Assert::AreEqual(red_dark_hsv.v,expected.v); 41 | } 42 | TEST_METHOD(ColorRGBToColorHSV4) 43 | { 44 | using namespace ImageUtils; 45 | ColorRGB rgb={128,128,128}; 46 | ColorHSV hsv=rgb; 47 | ColorHSV expected={0,0,128}; 48 | Assert::AreEqual(hsv.h,expected.h); 49 | Assert::AreEqual(hsv.s,expected.s); 50 | Assert::AreEqual(hsv.v,expected.v); 51 | } 52 | TEST_METHOD(ColorRGBToColorHSV5) 53 | { 54 | using namespace ImageUtils; 55 | ColorRGB rgb={69,47,127}; 56 | ColorHSV hsv=rgb; 57 | ColorHSV expected={182,161,127}; 58 | Assert::AreEqual(hsv.h,expected.h); 59 | Assert::AreEqual(hsv.s,expected.s); 60 | Assert::AreEqual(hsv.v,expected.v); 61 | } 62 | TEST_METHOD(AssertWithinRange) 63 | { 64 | using namespace ImageUtils; 65 | for(unsigned int r=0;r<256;++r) 66 | { 67 | for(unsigned int g=0;g<256;++g) 68 | { 69 | for(unsigned int b=0;b<256;++b) 70 | { 71 | float red=r;float green=g;float blue=b; 72 | auto min=std::min(red,std::min(green,blue)); 73 | auto max=std::max(red,std::max(green,blue)); 74 | auto delta=max-min; 75 | if(max!=0) 76 | Assert::IsTrue(std::round(delta/max*255)<256); 77 | } 78 | } 79 | } 80 | } 81 | TEST_METHOD(ColorRGBToColorHSV6) 82 | { 83 | using namespace ImageUtils; 84 | ColorRGB rgb={46,255,22}; 85 | ColorHSV hsv=rgb; 86 | ColorHSV expected={81,233,255}; 87 | Assert::AreEqual(hsv.h,expected.h); 88 | Assert::AreEqual(hsv.s,expected.s); 89 | Assert::AreEqual(hsv.v,expected.v); 90 | } 91 | }; 92 | } -------------------------------------------------------------------------------- /ScoreProcessor/lib/exstring/exstdstring.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef EXSTDSTRING_H 18 | #define EXSTDSTRING_H 19 | #include 20 | #include 21 | #include 22 | namespace exlib { 23 | template 24 | std::basic_string front_padded_string(std::basic_string const& in,size_t desired_length,stringtype padding); 25 | 26 | template 27 | std::basic_string back_padded_string(std::basic_string const& in,size_t desired_length,stringtype padding); 28 | 29 | template 30 | stype_dest string_cast(std::basic_string const&); 31 | 32 | std::string letter_numbering(size_t num); 33 | 34 | template 35 | std::basic_string front_padded_string(std::basic_string const& in,size_t numpadding,stringtype padding) { 36 | typedef std::basic_string bs; 37 | if(in.size()>=numpadding) return in; 38 | size_t padding_needed=numpadding-in.size(); 39 | return bs(padding_needed,padding)+in; 40 | } 41 | 42 | template 43 | std::basic_string back_padded_string(std::basic_string const& in,size_t numpadding,stringtype padding) { 44 | typedef std::basic_string bs; 45 | if(in.size()>=numpadding) return in; 46 | size_t padding_needed=numpadding-in.size(); 47 | return in+bs(padding_needed,padding); 48 | } 49 | 50 | template 51 | stype_dest string_cast(std::basic_string const& in) { 52 | stype_dest out; 53 | out.resize(in.size()); 54 | for(size_t i=0;i. 16 | */ 17 | #include "stdafx.h" 18 | #include "Cluster.h" 19 | #include 20 | #include 21 | using namespace std; 22 | namespace ScoreProcessor { 23 | unsigned int Cluster::size() const 24 | { 25 | unsigned int size=0; 26 | for(auto const& rect:ranges) 27 | { 28 | size+=rect.area(); 29 | } 30 | return size; 31 | } 32 | vector const& Cluster::get_ranges() const 33 | { 34 | return ranges; 35 | } 36 | ImageUtils::Rectangle Cluster::bounding_box() const 37 | { 38 | ImageUtils::Rectangle box={~0,0,~0,0}; 39 | for(auto const rect:ranges) 40 | { 41 | if(rect.topbox.bottom) 46 | { 47 | box.bottom=rect.bottom; 48 | } 49 | if(rect.leftbox.right) 54 | { 55 | box.right=rect.right; 56 | } 57 | } 58 | return box; 59 | } 60 | 61 | #define xfirst_point_finder(xdef,xterm,xcomp,ydef,yterm,ycomp) \ 62 | ImageUtils::Point point={xdef,ydef}; \ 63 | for(auto const& rect:ranges) \ 64 | { \ 65 | if(rect. xterm xcomp point.x&&rect. yterm ycomp point.y) \ 66 | { \ 67 | point={rect. xterm,rect. yterm}; \ 68 | } \ 69 | } \ 70 | return point 71 | 72 | ImageUtils::Point Cluster::right_top() const 73 | { 74 | xfirst_point_finder(0,right,>,~0,top,<); 75 | } 76 | ImageUtils::Point Cluster::right_bottom() const 77 | { 78 | xfirst_point_finder(0,right,>,0,bottom,>); 79 | } 80 | ImageUtils::Point Cluster::left_top() const 81 | { 82 | xfirst_point_finder(~0,left,<,~0,top,<); 83 | } 84 | ImageUtils::Point Cluster::left_bottom() const 85 | { 86 | xfirst_point_finder(~0,left,<,0,bottom,>); 87 | } 88 | #undef xfirst_point_finder 89 | ImageUtils::vertical_line Cluster::right_side() const 90 | { 91 | ImageUtils::vertical_line<> line={0,~0,0}; 92 | for(auto const& rect:ranges) 93 | { 94 | if(rect.right>line.x) 95 | { 96 | line.x=rect.right; 97 | line.top=rect.top; 98 | line.bottom=rect.bottom; 99 | } 100 | else if(rect.right==line.x) 101 | { 102 | if(rect.topline.bottom) 107 | { 108 | line.bottom=rect.bottom; 109 | } 110 | } 111 | } 112 | return line; 113 | } 114 | } -------------------------------------------------------------------------------- /ScoreProcessor/shorthand.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | //THIS FILE SHOULD BE INCLUDED LAST 18 | #ifdef _MEMORY_ 19 | #ifndef MEMORY_SHORTHAND 20 | #define MEMORY_SHORTHAND 21 | namespace std { 22 | template 23 | using uptr=unique_ptr; 24 | template 25 | using sptr=shared_ptr; 26 | template 27 | using wptr=weak_ptr; 28 | } 29 | #endif //MEMORY_SHORTHAND 30 | #endif //_MEMORY_ 31 | 32 | #ifndef NUMBER_SHORTHAND 33 | #define NUMBER_SHORTHAND 34 | typedef unsigned int uint; 35 | typedef unsigned long ulong; 36 | typedef unsigned short ushort; 37 | typedef unsigned char uchar; 38 | typedef unsigned long long ulonglong; 39 | typedef unsigned int const uintc; 40 | typedef unsigned long const ulongc; 41 | typedef unsigned short const ushortc; 42 | typedef unsigned char const ucharc; 43 | typedef unsigned long long const ulonglongc; 44 | typedef signed int sint; 45 | typedef signed long slong; 46 | typedef signed short sshort; 47 | typedef signed char schar; 48 | typedef signed long long slonglong; 49 | typedef signed int const sintc; 50 | typedef signed long const slongc; 51 | typedef signed short const sshortc; 52 | typedef signed char const scharc; 53 | typedef signed long long const slonglongc; 54 | 55 | typedef unsigned int* uintp; 56 | typedef unsigned long* ulongp; 57 | typedef unsigned short* ushortp; 58 | typedef unsigned char* ucharp; 59 | typedef unsigned long long* ulonglongp; 60 | typedef unsigned int const* uintcp; 61 | typedef unsigned long const* ulongcp; 62 | typedef unsigned short const* ushortcp; 63 | typedef unsigned char const* ucharcp; 64 | typedef unsigned long long const* ulonglongcp; 65 | typedef signed int* sintp; 66 | typedef signed long* slongp; 67 | typedef signed short* sshortp; 68 | typedef signed char* scharp; 69 | typedef signed long long* slonglongp; 70 | typedef signed int const* sintcp; 71 | typedef signed long const* slongcp; 72 | typedef signed short const* sshortcp; 73 | typedef signed char const* scharcp; 74 | typedef signed long long const* slonglongcp; 75 | 76 | typedef unsigned int* uintpc; 77 | typedef unsigned long* ulongpc; 78 | typedef unsigned short* ushortpc; 79 | typedef unsigned char* ucharpc; 80 | typedef unsigned long long* ulonglongpc; 81 | typedef unsigned int const* uintcpc; 82 | typedef unsigned long const* ulongcpc; 83 | typedef unsigned short const* ushortcpc; 84 | typedef unsigned char const* ucharcpc; 85 | typedef unsigned long long const* ulonglongcpc; 86 | typedef signed int* sintpc; 87 | typedef signed long* slongpc; 88 | typedef signed short* sshortpc; 89 | typedef signed char* scharpc; 90 | typedef signed long long* slonglongpc; 91 | typedef signed int const* sintcpc; 92 | typedef signed long const* slongcpc; 93 | typedef signed short const* sshortcpc; 94 | typedef signed char const* scharcpc; 95 | typedef signed long long const* slonglongcpc; 96 | #endif //signed_SHORTHAND 97 | 98 | #ifndef CAST_SHORTHAND 99 | #define CAST_SHORTHAND 100 | #define scast static_cast 101 | #define dcast dynamic_cast 102 | #define rcast reinterpret_cast 103 | #define ccast const_cast 104 | #endif //CAST_SHORTHAND -------------------------------------------------------------------------------- /Readme.MD: -------------------------------------------------------------------------------- 1 | Program useful for editing batches of images, specializing in score images 2 | Useful side feature is mass renaming/copying files. 3 | Give no arguments to get readme 4 |
 5 | Version: Nov 03 2019 22:31:12 Copyright 2017-2019 Edward Xie
 6 | Syntax: filename_or_folder... command params... ...
 7 | If you want to recursively search a folder, type -r before it
 8 | If a file starts with a dash, double the starting dash: "-my-file.jpg" -> "--my-file.jpg"
 9 | parameters that require multiple values are notated with a comma
10 | parameters can be tagged to reference a specific input with prefix:value
11 | prefixes sometimes allow switching between different types of input
12 | ex: img0.png --image1.jpg my_folder -r rec_folder -fg 180 -ccg bsr:0,30 -fr l:100 w:100 h:30 t:0 -o %f.%x t
13 | Type command alone to get readme
14 | Available commands:
15 |   Single Page Operations:
16 |     Convert to Grayscale:          -cg 
17 |     Filter Gray:                   -fg min max=255 replacer=255
18 |     Horizontal Padding:            -hp left right=l tolerance=0.005 background_threshold=128
19 |     Vertical Padding:              -vp top bottom=t tolerance=0.005 background_threshold=128
20 |     Straighten:                    -str min_angle=-5 max_angle=5 angle_prec=0.1 pixel_prec=1 boundary=128 gamma=2 use_horiz=t
21 |     Rotate:                        -rot angle mode=cubic gamma=2
22 |     Fill Rectangle:                -fr left top horiz vert color=255 origin=tl
23 |     Rescale Brightness:            -rcg min mid max=255
24 |     Cluster Clear Gray:            -ccg required_color_range=0,255 bad_size_range=0,0 sel_range=0,200 repl_color=255 eight_way=false
25 |     Blur:                          -bl st_dev gamma=2
26 |     Extract First Layer:           -exl 
27 |     Cover Transparency:            -ct red=255 green=r blue=r
28 |     Remove Border (DANGER):        -rb tolerance=0.9
29 |     Rescale:                       -rs factor interpolation_mode=auto gamma=2
30 |     Crop:                          -crp left top horizontal vertical
31 |     Rescale Absolute:              -rsa width height ratio=preserve mode=automatic gamma=2
32 |     Change Canvas Size:            -ccs width=preserve height=preserve origin=tl
33 |     Morphological AA:              -mlaa contrast_threshold=128 gamma=2
34 |     Cluster Match Erase:           -tme template_file_name threshold=0.95
35 |     Sliding Erase Downscale Match: -stme template_file_names downscale thresh=0.95 replace=fill:255 l=-99999 t=-999999 h=99999 v=99999 o=tl
36 |     Remove Empty Lines:            -rel min_space max_presence=5 background_threshold=128
37 |     Vertical Compress:             -vc min_vert_space min_horiz_pr max_vert_pr background=128 min_horiz_space=mvs
38 |   Multi Page Operations:
39 |     Splice:                        -spl horiz_pad=3% opt_pad=5% min_pad=1.2% opt_hgt=55% excs_wgt=10 pad_wgt=1 bg=128 divider=""
40 |     Cut:                           -cut min_width=66% min_height=8% horiz_weight=20 min_vert_space=0 bg=128
41 |   Options:
42 |     Output:                        -o pattern=%w move=false
43 |     Verbosity:                     -vb level
44 |     Number of Threads:             -nt num
45 |     Boundary Select:               -bsel first_file1 last_file1 ... first_filen last_filen
46 |     Starting index:                -si index
47 |     Filter:                        -flt pattern keep_match
48 |     List Files:                    -list 
49 |     Quality:                       -q quality
50 | Multiple Single Page Operations can be done at once. They are performed in the order they are given.
51 | A Multi Page Operation can not be done with other operations.
52 | 
53 | 54 | Building 55 | 56 | Just a Visual Studio Solution 57 | 58 | Get the following dependencies with vcpkg: 59 | 60 | libjpeg-turbo:x(64|86)-windows-static 61 | tiff:x(64|86)-windows-static 62 | libpng:x(64|86)-windows-static 63 | 64 | ~~See READMEs in ScoreProcessor/lib/cudnn and ScoreProcessor/lib/mkldnn~~ 65 | 66 | Because I started this as a C++ noob, I broke some stuff with the Debug build, and have not bothered to fix it. Only Release is known to work. 67 | 68 | Install 69 | 70 | get sproc.zip from Releases, unzip to a directory, and add that directory to PATH. Running addme.bat should do this automatically. 71 | You will need microsoft redistributable c++. If you do not have it, you can get it here: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads 72 | -------------------------------------------------------------------------------- /ScoreProcessor/parse.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef SCORE_PARSE_H 18 | #define SCORE_PARSE_H 19 | #include 20 | #include 21 | #include 22 | #include 23 | namespace ScoreProcessor { 24 | //I really need MSVC to implement from_chars. 25 | inline int parse_str(double& out,char const* str) 26 | { 27 | int& errno_ref=errno; 28 | errno_ref=0; 29 | char* end; 30 | out=std::strtod(str,&end); 31 | return errno_ref||str==end||(*end!='\0'&&!std::isspace(*end)); 32 | } 33 | inline int parse_str(float& out,char const* str) 34 | { 35 | int& errno_ref=errno; 36 | errno_ref=0; 37 | char* end; 38 | out=std::strtof(str,&end); 39 | return errno_ref||str==end||(*end!='\0'&&!std::isspace(*end)); 40 | } 41 | inline int parse_str(unsigned long& out,char const* str) 42 | { 43 | int& errno_ref=errno; 44 | errno_ref=0; 45 | char* end; 46 | out=std::strtoul(str,&end,10); 47 | return errno_ref||str==end||(*end!='\0'&&!std::isspace(*end)); 48 | } 49 | inline int parse_str(long& out,char const* str) 50 | { 51 | int& errno_ref=errno; 52 | errno_ref=0; 53 | char* end; 54 | out=std::strtol(str,&end,10); 55 | return errno_ref||str==end||(*end!='\0'&&!std::isspace(*end)); 56 | } 57 | #define make_parse_str_signed(type)\ 58 | inline int parse_str(##type##& out,char const* str){\ 59 | int& errno_ref=errno;\ 60 | errno_ref=0;\ 61 | char* end;\ 62 | auto temp=std::strtol(str,&end,10);\ 63 | if(errno_ref)\ 64 | {\ 65 | return errno_ref;\ 66 | }\ 67 | if(end==str||(*end!='\0'&&!std::isspace(*end))||temp>std::numeric_limits<##type##>::max()||temp::min())\ 68 | {\ 69 | return 1;\ 70 | }\ 71 | out=temp;\ 72 | return 0;\ 73 | } 74 | make_parse_str_signed(char) 75 | make_parse_str_signed(short) 76 | make_parse_str_signed(int) 77 | #undef make_parse_str_signed 78 | #define make_parse_str_unsigned(type)\ 79 | inline int parse_str(##type##& out,char const* str){\ 80 | int& errno_ref=errno;\ 81 | errno_ref=0;\ 82 | char* end;\ 83 | auto temp=std::strtoul(str,&end,10);\ 84 | if(errno_ref)\ 85 | {\ 86 | return errno_ref;\ 87 | }\ 88 | if(end==str||(*end!='\0'&&!std::isspace(*end))||*str=='-'||temp>std::numeric_limits<##type##>::max())\ 89 | {\ 90 | return 1;\ 91 | }\ 92 | out=temp;\ 93 | return 0;\ 94 | } 95 | make_parse_str_unsigned(unsigned char) 96 | make_parse_str_unsigned(unsigned short) 97 | make_parse_str_unsigned(unsigned int) 98 | //-1 is success 99 | //err val 0 is too few arguments 100 | //err val 1 is too many arguments 101 | //err val n in [2,num_args+1] mean missing (n-2)th argument 102 | //err val n in [num_args+2,2*num_args+1] means invalid (n-num_args-2)th argument 103 | //other err vals are defined by the given constraints function 104 | template 105 | int parse_range( 106 | std::array& out, 107 | std::string& str, 108 | std::array,num_args> const& default_values, 109 | Func constraints) noexcept 110 | { 111 | static_assert(num_args,"Must have positive number of args"); 112 | size_t comma_pos=0; 113 | for(size_t i=0;i 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | Resource Files 21 | 22 | 23 | Resource Files 24 | 25 | 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Resource Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | Header Files 77 | 78 | 79 | Header Files 80 | 81 | 82 | Header Files 83 | 84 | 85 | Header Files 86 | 87 | 88 | 89 | 90 | Source Files 91 | 92 | 93 | Source Files 94 | 95 | 96 | Source Files 97 | 98 | 99 | Source Files 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files 112 | 113 | 114 | Source Files 115 | 116 | 117 | Source Files 118 | 119 | 120 | Source Files 121 | 122 | 123 | 124 | 125 | Resource Files 126 | 127 | 128 | Resource Files 129 | 130 | 131 | Resource Files 132 | 133 | 134 | Resource Files 135 | 136 | 137 | Resource Files 138 | 139 | 140 | Resource Files 141 | 142 | 143 | Resource Files 144 | 145 | 146 | Resource Files 147 | 148 | 149 | Resource Files 150 | 151 | 152 | Resource Files 153 | 154 | 155 | Resource Files 156 | 157 | 158 | -------------------------------------------------------------------------------- /NeuralNetwork/neural_scaler.h: -------------------------------------------------------------------------------- 1 | #ifndef NEURAL_SCALER_H 2 | #define NEURAL_SCALER_H 3 | 4 | #include "neural_net.h" 5 | #include 6 | #include "../ScoreProcessor/CImg.h" 7 | #include 8 | namespace ScoreProcessor { 9 | template 10 | struct smart_scaler_base { 11 | auto& smart_scale(cil::CImg& img,float scale,unsigned int num_threads=std::thread::hardware_concurrency()) const 12 | { 13 | unsigned int new_x=std::round(img._width*scale); 14 | unsigned int new_y=std::round(img._height*scale); 15 | if(new_x==img._width&&new_y==img._height) 16 | { 17 | return img; 18 | } 19 | img=static_cast(this)->get_smart_scale(img,scale,num_threads); 20 | return img; 21 | } 22 | }; 23 | 24 | struct neural_scaler:smart_scaler_base { 25 | private: 26 | unsigned int _nscale; 27 | unsigned int _out_dim; 28 | unsigned int _in_dim; 29 | neural_net::net<> _net; 30 | 31 | static unsigned int int_sqrt(size_t a,char const* msg) 32 | { 33 | auto sqrt=std::sqrt(a); 34 | auto r=std::round(sqrt); 35 | if(r-sqrt!=0) 36 | { 37 | throw std::invalid_argument(msg); 38 | } 39 | return static_cast(r); 40 | }; 41 | 42 | template 43 | static auto assert_dim(unsigned int s,Net&& src) 44 | { 45 | auto o=int_sqrt(src.layers().back().neuron_count(),"Invalid output dim, must be square"); 46 | auto i=int_sqrt(src.layers().front().neuron_count(),"Invalid input dim, must be square"); 47 | if(o%s) 48 | { 49 | throw std::invalid_argument("Output dim must be a multiple of scale factor"); 50 | } 51 | if(i*s(o,i,std::forward(src)); 60 | } 61 | public: 62 | auto& net() 63 | { 64 | return _net; 65 | } 66 | auto const& net() const 67 | { 68 | return _net; 69 | } 70 | inline neural_scaler(unsigned int scale,neural_net::net<> const& src):_nscale(scale) 71 | { 72 | auto res=assert_dim(scale,src); 73 | _net=std::get<2>(res); 74 | _out_dim=std::get<0>(res); 75 | _in_dim=std::get<1>(res); 76 | } 77 | inline neural_scaler(unsigned int scale,neural_net::net<>&& src):_nscale(scale) 78 | { 79 | auto res=assert_dim(scale,src); 80 | _net=std::get<2>(res); 81 | _out_dim=std::get<0>(res); 82 | _in_dim=std::get<1>(res); 83 | } 84 | cil::CImg get_smart_scale(cil::CImg const& img,float scale,unsigned int num_threads=std::thread::hardware_concurrency()) const; 85 | 86 | private: 87 | struct is_writable_h { 88 | template 89 | static constexpr auto val(int)->decltype(std::declval().write(std::declval(),size_t()),false) 90 | { 91 | return true; 92 | } 93 | template 94 | static constexpr auto val(...) 95 | { 96 | return false; 97 | } 98 | }; 99 | 100 | template 101 | struct is_writable:std::integral_constant(0)> { 102 | 103 | }; 104 | 105 | struct is_readable_h { 106 | template 107 | static constexpr auto val(int)->decltype(std::declval().read(std::declval(),size_t()),false) 108 | { 109 | return true; 110 | } 111 | template 112 | static constexpr auto val(...) 113 | { 114 | return false; 115 | } 116 | }; 117 | 118 | template 119 | struct is_readable:std::integral_constant(0)> { 120 | 121 | }; 122 | public: 123 | 124 | template 125 | auto save(Stream& src) -> typename std::enable_if::value>::type 126 | { 127 | uint64_t scale=_nscale; 128 | src.write(reinterpret_cast(&scale),sizeof(uint64_t)); 129 | _net.save(src); 130 | } 131 | void save(char const* path) 132 | { 133 | std::ofstream e(path,std::ios::out|std::ios::binary); 134 | if(e) 135 | { 136 | return save(e); 137 | } 138 | throw std::runtime_error("Failed to save"); 139 | } 140 | 141 | template 142 | auto load(Stream& src) -> typename std::enable_if::value>::type 143 | { 144 | uint64_t scale; 145 | src.read(reinterpret_cast(&scale),sizeof(scale)); 146 | neural_net::net<> net(src); 147 | auto res=assert_dim(scale,net); 148 | _nscale=static_cast(scale); 149 | _in_dim=std::get<1>(res); 150 | _out_dim=std::get<0>(res); 151 | _net=std::move(net); 152 | } 153 | void load(char const* path) 154 | { 155 | std::ifstream e(path,std::ios::in|std::ios::binary); 156 | if(e) 157 | { 158 | return load(e); 159 | } 160 | throw std::runtime_error("Failed to load"); 161 | } 162 | inline unsigned int input_dim() const 163 | { 164 | return _in_dim; 165 | } 166 | inline unsigned int output_dim() const 167 | { 168 | return _out_dim; 169 | } 170 | inline unsigned int scale_factor() const 171 | { 172 | return _nscale; 173 | } 174 | inline unsigned int padding() const 175 | { 176 | return (input_dim()-output_dim()/scale_factor())/2; 177 | } 178 | inline void feed(float* out,float const* in) const 179 | { 180 | auto res=_net.feed_forward_store(in); 181 | std::memcpy(out,res[_net.layers().size()-1].get(),_net.layers().back().neuron_count()*sizeof(float)); 182 | } 183 | neural_scaler(char const* path) 184 | { 185 | load(path); 186 | } 187 | private: 188 | static void place_values(cil::CImg& img,float const* in,unsigned int x,unsigned int y,unsigned int dim); 189 | public: 190 | 191 | 192 | 193 | }; 194 | } 195 | 196 | #endif -------------------------------------------------------------------------------- /ScoreProcessor/lib/exstring/exfiles.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef EXFILES_H 18 | #define EXFILES_H 19 | #include 20 | #ifdef _WINDOWS 21 | #include 22 | #endif 23 | #include 24 | namespace exlib { 25 | /* 26 | Returns a String with any consecutive slashes replaced by a single slash. 27 | */ 28 | template 29 | String clean_multislashes(U const* input); 30 | 31 | /* 32 | Returns a String with any consecutive slashes replaced by a single slash. 33 | */ 34 | template 35 | String clean_multislashes(String const& input); 36 | 37 | /* 38 | Returns any consecutive slahes from the input, and returns the new size of the input string. 39 | */ 40 | template 41 | size_t remove_multislashes(T* input); 42 | 43 | #ifdef _WINDOWS 44 | 45 | /* 46 | Returns a vector containing the filenames of all files in the first level of the given directory. 47 | */ 48 | template 49 | std::vector files_in_dir(String path,String const& wildcard="*.*",DWORD banned_attributes=FILE_ATTRIBUTE_DIRECTORY,DWORD required_attributes=0) 50 | { 51 | String search=std::move(path)+wildcard; 52 | HANDLE hFind; 53 | WIN32_FIND_DATAA fdata; 54 | hFind=FindFirstFileA(search.c_str(),&fdata); 55 | std::vector files; 56 | if(hFind!=INVALID_HANDLE_VALUE) 57 | { 58 | do 59 | { 60 | auto const filename=fdata.cFileName; 61 | if(filename[0]=='.'&&(filename[1]=='\0'||(filename[1]=='.'&&filename[2]=='\0'))) 62 | { 63 | continue; 64 | } 65 | if(!(fdata.dwFileAttributes&banned_attributes)&&((fdata.dwFileAttributes&required_attributes)==required_attributes)) 66 | { 67 | files.emplace_back(fdata.cFileName); 68 | } 69 | } while(FindNextFileA(hFind,&fdata)); 70 | FindClose(hFind); 71 | } 72 | std::sort(files.begin(),files.end(),[](auto const& a,auto const& b) 73 | { 74 | return exlib::strncmp_wind(a.c_str(),b.c_str())<0; 75 | }); 76 | return files; 77 | } 78 | 79 | template 80 | std::vector files_in_dir_rec(String const& path) 81 | { 82 | String search=path+"*.*"; 83 | HANDLE hFind; 84 | WIN32_FIND_DATAA fdata; 85 | hFind=FindFirstFileA(search.c_str(),&fdata); 86 | std::vector end_files; 87 | std::vector rec_searches; 88 | if(hFind!=INVALID_HANDLE_VALUE) 89 | { 90 | do 91 | { 92 | if(!(fdata.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) 93 | { 94 | end_files.emplace_back(fdata.cFileName); 95 | } 96 | else 97 | { 98 | auto const filename=fdata.cFileName; 99 | if(filename[0]=='.'&&(filename[1]=='\0'||(filename[1]=='.'&&filename[2]=='\0'))) 100 | { 101 | continue; 102 | } 103 | auto rec=String(filename)+"\\"; 104 | rec_searches.emplace_back(std::move(rec)); 105 | } 106 | } while(FindNextFileA(hFind,&fdata)); 107 | FindClose(hFind); 108 | } 109 | auto sorter=[](auto const& a,auto const& b) 110 | { 111 | return strncmp_wind(a.c_str(),b.c_str())<0; 112 | }; 113 | std::sort(rec_searches.begin(),rec_searches.end(),sorter); 114 | std::sort(end_files.begin(),end_files.end(),sorter); 115 | std::vector files; 116 | for(auto const& rec:rec_searches) 117 | { 118 | auto res=files_in_dir_rec(path+rec); 119 | for(auto const& r:res) 120 | { 121 | files.emplace_back(rec+r); 122 | } 123 | } 124 | for(auto& f:end_files) 125 | { 126 | files.emplace_back(std::move(f)); 127 | } 128 | return files; 129 | } 130 | #endif 131 | 132 | template 133 | String clean_multislashes(U const* input) 134 | { 135 | String out; 136 | out.reserve(30); 137 | bool found=false; 138 | while(*input) 139 | { 140 | if(found) 141 | { 142 | if(*input!='\\'&&*input!='/') 143 | { 144 | found=false; 145 | out.push_back(*input); 146 | } 147 | } 148 | else 149 | { 150 | if(*input=='\\'||*input=='/') 151 | { 152 | found=true; 153 | } 154 | out.push_back(*input); 155 | } 156 | ++input; 157 | } 158 | return out; 159 | } 160 | 161 | template 162 | String clean_multislashes(String const& input) 163 | { 164 | return clean_multislashes(input.c_str()); 165 | } 166 | 167 | template 168 | size_t remove_multislashes(T* input) 169 | { 170 | T* it=input; 171 | struct keep { 172 | T* start; 173 | T* end; 174 | }; 175 | std::vector keeps; 176 | keeps.push_back({it,nullptr}); 177 | bool found=false; 178 | int i=0; 179 | while(*it) 180 | { 181 | if(keeps.back().end)//still in valid territory 182 | { 183 | if(*it!='/'&&*it!='\\') 184 | { 185 | keeps.push_back({it,nullptr}); 186 | } 187 | } 188 | else 189 | { 190 | if(*it=='/'||*it=='\\') 191 | { 192 | keeps.back().end=it+1; 193 | } 194 | } 195 | ++it; 196 | } 197 | keeps.back().end=it; 198 | it=input; 199 | for(auto& k:keeps) 200 | { 201 | while(k.start!=k.end) 202 | { 203 | *it=*k.start; 204 | ++it; 205 | ++k.start; 206 | } 207 | } 208 | *it=0; 209 | return it-input; 210 | } 211 | 212 | template 213 | Iter find_extension(Iter begin,Iter end) 214 | { 215 | --begin; 216 | auto it=end-1; 217 | while(1) 218 | { 219 | if(it==begin) 220 | { 221 | return end; 222 | } 223 | if(*it=='.') 224 | { 225 | return it+1; 226 | } 227 | if(*it=='\\'||*it=='/') 228 | { 229 | return end; 230 | } 231 | --it; 232 | } 233 | } 234 | 235 | template 236 | Iter find_filename(Iter begin,Iter end) 237 | { 238 | for(;end!=begin;) 239 | { 240 | --end; 241 | if(*end=='/'||*end=='\\') 242 | { 243 | return end+1; 244 | } 245 | } 246 | return end; 247 | } 248 | 249 | template 250 | Iter find_path_end(Iter begin,Iter end) 251 | { 252 | for(;end!=begin;) 253 | { 254 | --end; 255 | if(*end=='/'||*end=='\\') 256 | { 257 | return end; 258 | } 259 | } 260 | return end; 261 | } 262 | } 263 | #endif -------------------------------------------------------------------------------- /NeuralNetwork/NeuralNetwork.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6} 24 | NeuralNetwork 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v142 32 | MultiByte 33 | 34 | 35 | StaticLibrary 36 | false 37 | v142 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v142 45 | MultiByte 46 | 47 | 48 | StaticLibrary 49 | false 50 | v142 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | $(SolutionDir)ScoreProcessor\lib\neural_net 74 | neural_net 75 | 76 | 77 | $(SolutionDir)ScoreProcessor\lib\neural_net 78 | neural_net_x86 79 | 80 | 81 | 82 | Level3 83 | MaxSpeed 84 | true 85 | true 86 | true 87 | true 88 | stdcpplatest 89 | /Zc:__cplusplus %(AdditionalOptions) 90 | 91 | 92 | true 93 | true 94 | 95 | 96 | 97 | 98 | Level3 99 | Disabled 100 | true 101 | true 102 | 103 | 104 | 105 | 106 | Level3 107 | Disabled 108 | true 109 | true 110 | 111 | 112 | 113 | 114 | Level3 115 | MaxSpeed 116 | true 117 | true 118 | true 119 | true 120 | stdcpp17 121 | 122 | 123 | true 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /ScoreProcessor/Cluster.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef CLUSTER_H 18 | #define CLUSTER_H 19 | #include 20 | #include "ImageUtils.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | namespace ScoreProcessor { 26 | 27 | class Cluster { 28 | private: 29 | ::std::vector> ranges; 30 | public: 31 | /* 32 | Returns the area of the cluster 33 | (i.e. the sum of the areas of all sub-rectangles) 34 | */ 35 | unsigned int size() const; 36 | /* 37 | Returns the bounding box of the cluster 38 | If cluster contains no rectangles, returns {MAX_UINT,0,MAXUINT,0} 39 | */ 40 | ImageUtils::Rectangle bounding_box() const; 41 | /* 42 | Returns the center of mass of the cluster 43 | If cluster contains no rectangles, returns {0,0} 44 | */ 45 | template 46 | ImageUtils::Point center() const 47 | { 48 | if(ranges.size()==0) 49 | { 50 | return {T(0),T(0)}; 51 | } 52 | ImageUtils::Point center={0.0,0.0}; 53 | double denom=0.0; 54 | for(auto const& rect:ranges) 55 | { 56 | double area=rect.area(); 57 | auto rect_center=rect.center(); 58 | center.x+=area*rect_center.x; 59 | center.y+=area*rect_center.y; 60 | denom+=area; 61 | } 62 | center.x/=denom; 63 | center.y/=denom; 64 | return {T(center.x),T(center.y)}; 65 | } 66 | /* 67 | Returns the highest rightmost point of the cluster 68 | */ 69 | ImageUtils::Point right_top() const; 70 | /* 71 | Returns the lowest rightmost point of the cluster 72 | */ 73 | ImageUtils::Point right_bottom() const; 74 | ImageUtils::Point left_top() const; 75 | ImageUtils::Point left_bottom() const; 76 | /* 77 | Returns the vertical_line representing the right side of the cluster 78 | */ 79 | ImageUtils::vertical_line right_side() const; 80 | /* 81 | Gets the ranges of the cluster 82 | */ 83 | ::std::vector> const& get_ranges() const; 84 | 85 | template> 86 | void sort(Comp c=Comp()) noexcept 87 | { 88 | std::sort(ranges.begin(),ranges.end(),c); 89 | } 90 | private: 91 | 92 | template 93 | static ::std::vector cluster_ranges_base(::std::vector> const& ranges,OverlapFunc cf) 94 | { 95 | ::std::vector cluster_container; 96 | struct ClusterPart { 97 | bool clustered; 98 | ImageUtils::Rectangle const* rect; 99 | ClusterPart(ImageUtils::Rectangle const* rect):clustered(false),rect(rect) 100 | {} 101 | }; 102 | struct ClusterTestNode { 103 | ClusterPart* parent; 104 | bool _is_top; 105 | unsigned int y; 106 | bool operator<(ClusterTestNode const& other) const 107 | { 108 | if(y==other.y) 109 | { 110 | return _is_toprect->top:parent->rect->bottom) 126 | {} 127 | }; 128 | std::vector parts;parts.reserve(ranges.size()); 129 | std::vector tests;tests.reserve(ranges.size()*2); 130 | for(unsigned int i=0;i search_stack; 138 | size_t const max=tests.size(); 139 | for(unsigned int i=max-1;iclustered) 147 | { 148 | Cluster current_cluster; 149 | search_stack.push(i); 150 | while(!search_stack.empty()) 151 | { 152 | unsigned int search_index=search_stack.top(); 153 | search_stack.pop(); 154 | ClusterTestNode const& search_node=tests[search_index]; 155 | if(search_node.parent->clustered) 156 | { 157 | continue; 158 | } 159 | search_node.parent->clustered=true; 160 | current_cluster.ranges.push_back(*search_node.parent->rect); 161 | for(unsigned int s=search_index-1;srect->top) 166 | { 167 | break; 168 | } 169 | if(tests[s].y==search_node.parent->rect->top&& 170 | cf(*tests[s].parent->rect,*search_node.parent->rect)) 171 | { 172 | search_stack.push(s); 173 | } 174 | } 175 | } 176 | for(unsigned int s=search_index+1;ssearch_node.parent->rect->bottom) 181 | { 182 | break; 183 | } 184 | if(tests[s].y==search_node.parent->rect->bottom&& 185 | cf(*tests[s].parent->rect,*search_node.parent->rect)) 186 | { 187 | search_stack.push(s); 188 | } 189 | } 190 | } 191 | } 192 | cluster_container.push_back(std::move(current_cluster)); 193 | } 194 | } 195 | return cluster_container; 196 | } 197 | 198 | public: 199 | 200 | /* 201 | Given a vector of rectangles, 202 | Returns a vector of pointers to clusters made from those rectangles 203 | */ 204 | inline static ::std::vector cluster_ranges(::std::vector> const& ranges) 205 | { 206 | using R=ImageUtils::Rectangle; 207 | return cluster_ranges_base(ranges,[](R a,R b) 208 | { 209 | return a.overlaps_x(b); 210 | }); 211 | } 212 | 213 | inline static ::std::vector cluster_ranges_8way(::std::vector> const& ranges) 214 | { 215 | using R=ImageUtils::Rectangle; 216 | return cluster_ranges_base(ranges,[](R a,R b) 217 | { 218 | return (a.left<=b.right&&a.right>=b.left); 219 | }); 220 | } 221 | }; 222 | } 223 | #endif // !CLUSTER_H 224 | -------------------------------------------------------------------------------- /NeuralNetwork/neural_scaler.cpp: -------------------------------------------------------------------------------- 1 | #include "neural_scaler.h" 2 | #include 3 | #include "../ScoreProcessor/lib/threadpool/thread_pool.h" 4 | namespace ScoreProcessor { 5 | cil::CImg neural_scaler::get_smart_scale(cil::CImg const& img,float scale,unsigned int num_threads) const 6 | { 7 | if(scale<1) 8 | { 9 | return img.get_resize( 10 | static_cast(std::round(img._width*scale)), 11 | static_cast(std::round(img._height*scale)), 12 | img._depth, 13 | img._spectrum, 14 | 2); 15 | } 16 | if(scale==1) 17 | { 18 | return img; 19 | } 20 | struct info { 21 | unsigned int padding; 22 | unsigned int input_dim; 23 | unsigned int output_dim; 24 | unsigned int scale_factor; 25 | }; 26 | info inf; 27 | inf.input_dim=input_dim(); 28 | inf.output_dim=output_dim(); 29 | inf.scale_factor=scale_factor(); 30 | inf.padding=padding(); 31 | auto const i=input_dim(); 32 | auto const o=output_dim(); 33 | auto const s=scale_factor(); 34 | using Img=cil::CImg; 35 | Img orig(img,true);//shared view of image 36 | unsigned int desired_width=static_cast(std::round(img._width*scale)); 37 | unsigned int desired_height=static_cast(std::round(img._height*scale)); 38 | 39 | while(true) 40 | { 41 | Img upscaled(orig._width*s,orig._height*s); 42 | struct Scaler { 43 | protected: 44 | unsigned int output_x; 45 | public: 46 | Scaler(unsigned int output_x): 47 | output_x(output_x) 48 | {} 49 | private: 50 | bool all_white(float const* input,size_t const limit) const 51 | { 52 | for(size_t i=0;i(out._width-output_x,output_dim); 66 | size_t const r_limit=std::min(out._height-y,output_dim); 67 | auto const in_limit=in_row+r_limit*output_dim; 68 | for(;in_row=1.0f) 74 | { 75 | out_row[c]=255; 76 | } 77 | else if(val<=0.0f) 78 | { 79 | out_row[c]=0; 80 | } 81 | else 82 | { 83 | out_row[c]=static_cast(std::round(val*255)); 84 | } 85 | } 86 | } 87 | } 88 | void write_to_img(Img& out,size_t const y,size_t const output_dim) const 89 | { 90 | auto const out_width=out._width; 91 | auto out_row=out._data+out_width*y+output_x; 92 | size_t const c_limit=std::min(out._width-output_x,output_dim); 93 | size_t const r_limit=std::min(out._height-y,output_dim); 94 | auto const out_limit=out_row+r_limit*out_width; 95 | for(;out_row input(new float[input_area]); 116 | std::unique_ptr output(new float[output_area]); 117 | st const out_height=out._height; 118 | st const in_height=in._height; 119 | st const in_width=in._width; 120 | st const x_begin=static_cast(input_x)-padding; 121 | st const x_end=x_begin+input_dim; 122 | st const x_start=std::max(0,x_begin); 123 | st const x_finish=std::min(in_width,x_end); 124 | if(x_begin<0) 125 | { 126 | auto const amount=-x_begin; 127 | for(st y=0;yin_width) 133 | { 134 | auto const offset=input.get()+in_width-x_begin; 135 | auto const count=x_end-in_width; 136 | for(st y=0;yin_height) 159 | { 160 | y_finish=in_height; 161 | st offset=(in_height-y_begin)*input_dim; 162 | st amount=(y_end-in_height)*input_dim; 163 | assert(offset+amount==input_area); 164 | std::fill_n(input.get()+offset,amount,1.0f); 165 | } 166 | else 167 | { 168 | y_finish=y_end; 169 | } 170 | for(st y_in=y_start;y_in pool(num_threads,&upscaled,&orig,this,&inf); 199 | for(unsigned int x=0;x=desired_width) 207 | { 208 | return upscaled.resize(desired_width,desired_height); 209 | } 210 | orig=std::move(upscaled); 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /ScoreProcessor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.15 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ScoreProcessor", "ScoreProcessor\ScoreProcessor.vcxproj", "{F88C20A0-D08B-4582-95A7-8620815D4F6D}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6} = {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SProcUnitTests", "SProcUnitTests\SProcUnitTests.vcxproj", "{347630B6-FDC2-49D8-86E8-562E287BD49E}" 12 | EndProject 13 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Trainer", "Trainer\Trainer.vcxproj", "{CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}" 14 | ProjectSection(ProjectDependencies) = postProject 15 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6} = {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6} 16 | EndProjectSection 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NeuralNetwork", "NeuralNetwork\NeuralNetwork.vcxproj", "{804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|x64 = Debug|x64 23 | Debug|x86 = Debug|x86 24 | Debugger|x64 = Debugger|x64 25 | Debugger|x86 = Debugger|x86 26 | Readme|x64 = Readme|x64 27 | Readme|x86 = Readme|x86 28 | Release|x64 = Release|x64 29 | Release|x86 = Release|x86 30 | WeakDebug|x64 = WeakDebug|x64 31 | WeakDebug|x86 = WeakDebug|x86 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debug|x64.ActiveCfg = Debug|x64 35 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debug|x64.Build.0 = Debug|x64 36 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debug|x86.ActiveCfg = Debug|Win32 37 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debug|x86.Build.0 = Debug|Win32 38 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debugger|x64.ActiveCfg = Debugger|x64 39 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debugger|x64.Build.0 = Debugger|x64 40 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debugger|x86.ActiveCfg = Debugger|Win32 41 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Debugger|x86.Build.0 = Debugger|Win32 42 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Readme|x64.ActiveCfg = Readme|x64 43 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Readme|x64.Build.0 = Readme|x64 44 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Readme|x86.ActiveCfg = Readme|Win32 45 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Readme|x86.Build.0 = Readme|Win32 46 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Release|x64.ActiveCfg = Release|x64 47 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Release|x64.Build.0 = Release|x64 48 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Release|x86.ActiveCfg = Release|Win32 49 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.Release|x86.Build.0 = Release|Win32 50 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.WeakDebug|x64.ActiveCfg = WeakDebug|x64 51 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.WeakDebug|x64.Build.0 = WeakDebug|x64 52 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.WeakDebug|x86.ActiveCfg = WeakDebug|Win32 53 | {F88C20A0-D08B-4582-95A7-8620815D4F6D}.WeakDebug|x86.Build.0 = WeakDebug|Win32 54 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debug|x64.ActiveCfg = Debug|x64 55 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debug|x64.Build.0 = Debug|x64 56 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debug|x86.ActiveCfg = Debug|Win32 57 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debug|x86.Build.0 = Debug|Win32 58 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debugger|x64.ActiveCfg = Debugger|x64 59 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debugger|x64.Build.0 = Debugger|x64 60 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debugger|x86.ActiveCfg = Debugger|Win32 61 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Debugger|x86.Build.0 = Debugger|Win32 62 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Readme|x64.ActiveCfg = Readme|x64 63 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Readme|x64.Build.0 = Readme|x64 64 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Readme|x86.ActiveCfg = Readme|Win32 65 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Readme|x86.Build.0 = Readme|Win32 66 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Release|x64.ActiveCfg = Release|x64 67 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Release|x64.Build.0 = Release|x64 68 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Release|x86.ActiveCfg = Release|Win32 69 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.Release|x86.Build.0 = Release|Win32 70 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.WeakDebug|x64.ActiveCfg = Debug|x64 71 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.WeakDebug|x64.Build.0 = Debug|x64 72 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.WeakDebug|x86.ActiveCfg = Debug|Win32 73 | {347630B6-FDC2-49D8-86E8-562E287BD49E}.WeakDebug|x86.Build.0 = Debug|Win32 74 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debug|x64.ActiveCfg = Debug|x64 75 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debug|x64.Build.0 = Debug|x64 76 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debug|x86.ActiveCfg = Debug|Win32 77 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debug|x86.Build.0 = Debug|Win32 78 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debugger|x64.ActiveCfg = Debug|x64 79 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debugger|x64.Build.0 = Debug|x64 80 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debugger|x86.ActiveCfg = Debug|Win32 81 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Debugger|x86.Build.0 = Debug|Win32 82 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Readme|x64.ActiveCfg = Release|x64 83 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Readme|x64.Build.0 = Release|x64 84 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Readme|x86.ActiveCfg = Release|Win32 85 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Readme|x86.Build.0 = Release|Win32 86 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Release|x64.ActiveCfg = Release|x64 87 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Release|x64.Build.0 = Release|x64 88 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Release|x86.ActiveCfg = Release|Win32 89 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.Release|x86.Build.0 = Release|Win32 90 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.WeakDebug|x64.ActiveCfg = Debug|x64 91 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.WeakDebug|x64.Build.0 = Debug|x64 92 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.WeakDebug|x86.ActiveCfg = Debug|Win32 93 | {CF03F3BF-60DE-4D9C-9E46-D9B074A097BC}.WeakDebug|x86.Build.0 = Debug|Win32 94 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debug|x64.ActiveCfg = Debug|x64 95 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debug|x64.Build.0 = Debug|x64 96 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debug|x86.ActiveCfg = Debug|Win32 97 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debug|x86.Build.0 = Debug|Win32 98 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debugger|x64.ActiveCfg = Debug|x64 99 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debugger|x64.Build.0 = Debug|x64 100 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debugger|x86.ActiveCfg = Debug|Win32 101 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Debugger|x86.Build.0 = Debug|Win32 102 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Readme|x64.ActiveCfg = Release|x64 103 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Readme|x64.Build.0 = Release|x64 104 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Readme|x86.ActiveCfg = Release|Win32 105 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Readme|x86.Build.0 = Release|Win32 106 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Release|x64.ActiveCfg = Release|x64 107 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Release|x64.Build.0 = Release|x64 108 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Release|x86.ActiveCfg = Release|Win32 109 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.Release|x86.Build.0 = Release|Win32 110 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.WeakDebug|x64.ActiveCfg = Debug|x64 111 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.WeakDebug|x64.Build.0 = Debug|x64 112 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.WeakDebug|x86.ActiveCfg = Debug|Win32 113 | {804DF0F3-7BC7-42D4-9382-EEF4064A3DF6}.WeakDebug|x86.Build.0 = Debug|Win32 114 | EndGlobalSection 115 | GlobalSection(SolutionProperties) = preSolution 116 | HideSolutionNode = FALSE 117 | EndGlobalSection 118 | GlobalSection(ExtensibilityGlobals) = postSolution 119 | SolutionGuid = {FC0E4C2E-F00C-4E66-BC17-CF25596853B1} 120 | EndGlobalSection 121 | EndGlobal 122 | -------------------------------------------------------------------------------- /ScoreProcessor/ImageMath.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #include "stdafx.h" 18 | #include "ImageMath.h" 19 | #include "ImageUtils.h" 20 | #include "moreAlgorithms.h" 21 | #include 22 | #define _USE_MATH_DEFINES 23 | #include 24 | #include "shorthand.h" 25 | #include 26 | #include 27 | #include "lib\exstring\exmath.h" 28 | #include 29 | using namespace ImageUtils; 30 | using namespace std; 31 | using namespace misc_alg; 32 | namespace cimg_library { 33 | 34 | unsigned char otsu(CImg const& img) 35 | { 36 | size_t const total=size_t{img._width}*img._height; 37 | size_t histogram[256]{0}; 38 | auto const data=img._data; 39 | if(img._spectrum>=3) 40 | { 41 | for(size_t i=0;i(std::round((data[i]+data[i+total]+data[i+total*2])/3.0))]; 44 | } 45 | } 46 | else 47 | { 48 | for(size_t i=0;i(w_f); 72 | auto const temp=static_cast(sum_b)/w_b-m_f; 73 | auto const between=w_b*w_f*temp*temp; 74 | if(between>=maximum) 75 | { 76 | level=i; 77 | maximum=between; 78 | } 79 | } 80 | } 81 | 82 | void place_vertical_gradient(unsigned char const* const __restrict img,unsigned int const width,unsigned int const height,signed char * const place) 83 | { 84 | typedef unsigned char const* const uccpc; 85 | typedef signed char* const scpc; 86 | constexpr unsigned char const f=2; 87 | { 88 | uccpc row_next=img+width; 89 | for(uint x=0;x get_vertical_gradient(::cimg_library::CImg const& img) 136 | { 137 | assert(img._height>2); 138 | CImg grad_img(img._width,img._height); 139 | place_vertical_gradient(img._data,img._width,img._height,grad_img._data); 140 | return grad_img; 141 | } 142 | 143 | CImg get_horizontal_gradient(::cimg_library::CImg const& img) 144 | { 145 | assert(img._width>1); 146 | CImg grad_img(img._width,img._height); 147 | place_horizontal_gradient(img._data,img._width,img._height,grad_img._data); 148 | return grad_img; 149 | } 150 | 151 | CImg get_absolute_gradient(::cimg_library::CImg const& img) 152 | { 153 | CImg grad_img(img._width,img._height,1,2); 154 | place_vertical_gradient(img._data,img._width,img._height,grad_img._data); 155 | place_horizontal_gradient(img._data,img._width,img._height,grad_img._data+img._height*img._width); 156 | return grad_img; 157 | } 158 | 159 | CImg make_sobel_kernel() 160 | { 161 | CImg ret(3,3,1,2); 162 | auto& r=ret._data; 163 | constexpr float const sw=0.09375f; 164 | constexpr float const bw=0.3125f; 165 | static_assert(2*sw+bw==0.5f,"Incorrect weight scale"); 166 | r[0]=-sw;r[1]=-bw;r[2]=-sw; 167 | r[3]=0;r[4]=0;r[5]=0; 168 | r[6]=sw;r[7]=bw;r[8]=sw; 169 | r[9]=-sw;r[10]=0;r[11]=sw; 170 | r[12]=-bw;r[13]=0;r[14]=bw; 171 | r[15]=-sw;r[16]=0;r[17]=sw; 172 | return ret; 173 | } 174 | CImg const sobel_kernel=make_sobel_kernel(); 175 | 176 | CImg get_vertical_gradient_sobel(::cimg_library::CImg const& img) 177 | { 178 | static auto const k=sobel_kernel.get_shared_channel(0); 179 | return img.get_convolve(k,false,false); 180 | } 181 | 182 | CImg get_horizontal_gradient_sobel(::cimg_library::CImg const& img) 183 | { 184 | static auto const k=sobel_kernel.get_shared_channel(1); 185 | return img.get_convolve(k,false,false); 186 | } 187 | 188 | CImg get_absolute_gradient_sobel(::cimg_library::CImg const& img) 189 | { 190 | return img.get_convolve(sobel_kernel,false,false); 191 | } 192 | 193 | CImg get_brightness_spectrum(cimg_library::CImg const& refImage) 194 | { 195 | CImg image(refImage._width,refImage._height); 196 | for(unsigned int x=0;x get_gradient(::cimg_library::CImg const& refImage) 206 | { 207 | return get_absolute_gradient(refImage); 208 | } 209 | ColorRGB average_color(cimg_library::CImg const& image) 210 | { 211 | auto c=fold<3>(image,[](auto color,auto acc) 212 | { 213 | return decltype(acc)({acc[0]+color[0],acc[1]+color[1],acc[2]+color[2]}); 214 | },std::array({0,0,0})); 215 | auto const num_pixels=image._width*image._height; 216 | return {unsigned char(c[0]/num_pixels),unsigned char(c[1]/num_pixels),unsigned char(c[2]/num_pixels)}; 217 | } 218 | Grayscale average_gray(cimg_library::CImg const& image) 219 | { 220 | auto gray=fold<1>(image,[](auto color,auto acc) 221 | { 222 | return acc+color[0]; 223 | },0U); 224 | return Grayscale(gray/(image._width*image._height)); 225 | } 226 | ColorRGB darkest_color(cimg_library::CImg const& image) 227 | { 228 | ColorRGB darkness,currcolo; 229 | unsigned char darkest=255,currbrig; 230 | for(unsigned int x=0;x const& image) 245 | { 246 | Grayscale darkest=255; 247 | for(unsigned int x=0;x const& image) 260 | { 261 | ColorRGB light,currcolo; 262 | unsigned char lightest=0,currbrig; 263 | for(unsigned int x=0;x=lightest) 269 | { 270 | light=currcolo; 271 | lightest=currbrig; 272 | } 273 | } 274 | } 275 | return light; 276 | } 277 | Grayscale brightest_gray(cimg_library::CImg const& image) 278 | { 279 | Grayscale lightest=0; 280 | for(unsigned int x=0;xlightest) 285 | { 286 | lightest=image(x,y); 287 | } 288 | } 289 | } 290 | return lightest; 291 | } 292 | CImg get_grayscale(cimg_library::CImg const& image) 293 | { 294 | CImg ret(image._width,image._height,1,1); 295 | for(unsigned int x=0;x get_grayscale_simple(CImg const& image) 305 | { 306 | return get_map<3,1>(image,[](auto color) 307 | { 308 | return std::array{unsigned char(std::round((double(color[0])+color[1]+color[2])/3.0f))}; 309 | }); 310 | } 311 | 312 | bool remove_transparency(::cimg_library::CImg& img,unsigned char threshold,ImageUtils::ColorRGB replacer) 313 | { 314 | bool edited=false; 315 | map_if<4>(img,[color=std::array({replacer.r,replacer.g,replacer.b,255})](auto pixel){ 316 | return color; 317 | },[&,threshold](auto color) 318 | { 319 | if(color[3]& img,ImageUtils::ColorRGB replacer) 330 | { 331 | bool edited=false; 332 | map_if<4>(img,[color=std::array({replacer.r,replacer.g,replacer.b}),&edited](auto pixel){ 333 | float opac=pixel[3]/255.0f; 334 | float trans=1.0f-opac; 335 | decltype(pixel) new_color; 336 | for(size_t i=0;i<3;++i) 337 | { 338 | new_color[i]=pixel[i]*opac+color[i]*trans; 339 | } 340 | new_color[3]=255; 341 | return new_color; 342 | },[&](auto pixel) 343 | { 344 | if(pixel[3]==255) 345 | { 346 | return false; 347 | } 348 | edited=true; 349 | return true; 350 | }); 351 | return edited; 352 | } 353 | 354 | } -------------------------------------------------------------------------------- /ScoreProcessor/moreAlgorithms.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef MORE_ALGORITHMS_H 18 | #define MORE_ALGORITHMS_H 19 | #include 20 | #include 21 | #include 22 | #include "ImageUtils.h" 23 | #include "CImg.h" 24 | namespace misc_alg { 25 | 26 | /* 27 | Returns absolute difference of two numbers 28 | */ 29 | template constexpr auto abs_dif(T1 x,T2 y) -> 30 | decltype(x-y) 31 | { 32 | return (x>y?x-y:y-x); 33 | } 34 | /** 35 | Rounds number to nearest radix 36 | @param x, the number to be rounded 37 | @param radix 38 | @return the number round with same type as the radix 39 | */ 40 | template inline T2 round_to_nearest(T1 const x,T2 const radix) 41 | { 42 | return ::std::round(static_cast(x)/radix)*radix; 43 | } 44 | template bool within_perc_tolerance(T1 const num1,T2 const num2,T3 const tolerance); 45 | template int shortest_path_one_dir(::std::vector>& resultContainer,::std::vector<::std::vector>,::std::allocator<::std::vector>>>& nodes,val(*nodeDistance)(T&,T&)); 46 | /* 47 | Returns the indices of the minimum n in a given vector 48 | */ 49 | template ::std::vector min_n_ind(::std::vector const& values,size_t const num_values); 50 | template inline bool within_perc_tolerance(T1 const num1,T2 const num2,T3 const tolerance) 51 | { 52 | return (abs_dif(num1,num2)/num1) 55 | /* 56 | Greedy shortest path if you can only go from a node in one layer to a node in the next layer 57 | */ 58 | int shortest_path_one_dir(::std::vector>& resultContainer,::std::vector<::std::vector>,::std::allocator<::std::vector>>>& nodes,val(*nodeDistance)(T&,T&)) 59 | { 60 | #ifdef USESAFETYCHECKS 61 | if(nodes.size()<1||nodes[0].size()<1) 62 | { 63 | return 1; 64 | } 65 | #endif // USESAFETYCHECKS 66 | struct DynamicNode { 67 | val distance; 68 | unsigned int prevNodeIndex; 69 | }; 70 | ::std::vector<::std::vector> dynamicPather; 71 | dynamicPather.resize(nodes.size()); 72 | dynamicPather[0].resize(nodes[0].size()); 73 | for(unsigned int n=0;n ::std::vector min_n_ind(::std::vector const& values,size_t const num_values) 124 | { 125 | vector mins; 126 | if(values.size()==0) 127 | { 128 | return mins; 129 | } 130 | mins.reserve(num_values+1); 131 | mins.push_back(0); 132 | size_t max=values.size(); 133 | for(auto i=1U;i=values[*--place]) 139 | { 140 | mins.insert(place+1,i); 141 | goto skip_insert_begin; 142 | } 143 | } while(place!=mins.begin()); 144 | mins.insert(place,i); 145 | skip_insert_begin: 146 | if(mins.size()>num_values) 147 | { 148 | mins.erase(mins.end()-1); 149 | } 150 | } 151 | return mins; 152 | } 153 | } 154 | namespace cimg_library { 155 | /* 156 | Traces a seam from right to left starting at y start_index 157 | @param map, energy map 158 | @param start_index, y coordinate to start seam 159 | @return vector containing y coordinates of seams, starts at x=0 160 | */ 161 | template ::std::vector trace_back_seam(CImg const& map,unsigned int start_index); 162 | template ::std::vector create_seam(CImg const& map); 163 | template void mark_seam(CImg& img,T const* values,::std::vector const& seam); 164 | /* 165 | MAP IS MODIFIED 166 | */ 167 | template void min_energy_to_right(CImg& map); 168 | template 169 | void accumulate_to_right(CImg& dst,CImg const& src,ImageUtils::Rectangle const area,Selector s); 170 | template void sandpile(CImg& sandPiles,T maxSize); 171 | 172 | template 173 | ::std::vector create_seam(CImg const& energyMap) 174 | { 175 | unsigned int x=energyMap._width-1; 176 | T minValue=energyMap(x,0); 177 | unsigned int index=0; 178 | for(unsigned int y=1;y 189 | void min_energy_to_right(CImg& energyGraph) 190 | { 191 | ImageUtils::Rectangle area={0,energyGraph._width,0,energyGraph._height}; 192 | accumulate_to_right(energyGraph,energyGraph,area,[](T a,T b) 193 | { 194 | return std::min(a,b); 195 | }); 196 | } 197 | template 198 | void accumulate_to_right(CImg& dst,CImg const& src,ImageUtils::Rectangle const area,Selector s) 199 | { 200 | unsigned int const width=dst._width; 201 | unsigned int const foffset=area.top*width; 202 | T* const dst_first_row=dst._data+foffset; 203 | T const* const src_first_row=src._data+foffset; 204 | T const* const src_first_row1=src_first_row+width; 205 | unsigned int const offset=(area.bottom-1)*width; 206 | T* const dst_last_row=dst._data+offset; 207 | T const* const src_last_row=src._data+offset; 208 | T const* const src_last_row1=src_last_row-width; 209 | for(unsigned int x=area.left+1;x=dst_last_row) 220 | { 221 | break; 222 | } 223 | srow+=width; 224 | } 225 | dst_last_row[x]=src_last_row[x]+s(src_last_row[prev],src_last_row1[prev]); 226 | } 227 | } 228 | /* 229 | Creates a seam reaching to edges starting at the given point 230 | */ 231 | template ::std::vector trace_seam(CImg const& energy_map,unsigned int const start_y,unsigned int const start_x) 232 | { 233 | auto const width=energy_map._width; 234 | auto const y_lim=energy_map._height-1; 235 | assert(start_x result_path(width); 238 | if(y_lim==0) 239 | { 240 | std::fill(result_path.begin(),result_path.end(),0); 241 | return result_path; 242 | } 243 | auto find_best_y=[y_lim,&energy_map](unsigned int x,unsigned int y) 244 | { 245 | if(y==0) 246 | { 247 | if(energy_map(x,0)<=energy_map(x,1)) 248 | { 249 | return 0U; 250 | } 251 | return 1U; 252 | } 253 | else if(y==y_lim) 254 | { 255 | auto const cand=y_lim-1; 256 | if(energy_map(x,y_lim)<=energy_map(x,cand)) 257 | { 258 | return y_lim; 259 | } 260 | return cand; 261 | } 262 | else 263 | { 264 | auto lower=y-1; 265 | auto upper=y+1; 266 | auto min_value=energy_map(x,y);//needs to be middle biased 267 | auto cand=energy_map(x,lower); 268 | if(min_value>cand) 269 | { 270 | min_value=cand; 271 | y=lower; 272 | } 273 | cand=energy_map(x,upper); 274 | if(min_value>cand)//favors lower in tie 275 | { 276 | return upper; 277 | } 278 | return y; 279 | } 280 | }; 281 | result_path[start_x]=start_y; 282 | unsigned int y=start_y; 283 | for(unsigned int x=start_x+1;x ::std::vector trace_back_seam(CImg const& energy_map,unsigned int const start_y) 297 | { 298 | trace_back_seam(energy_map,start_y,energy_map._width-1); 299 | } 300 | template void mark_seam(CImg& img,T const* values,::std::vector const& seam) 301 | { 302 | for(unsigned int x=0;x 311 | void sandpile(CImg& sandPiles,T maxSize) 312 | { 313 | while(true) 314 | { 315 | bool allFallen=true; 316 | for(unsigned int x=0;xmaxSize) 321 | { 322 | sandPiles(x,y)-=5; 323 | allFallen=false; 324 | for(int dx=-1;dx<=1;++dx) 325 | { 326 | for(int dy=-1;dy<=1;++dy) 327 | { 328 | unsigned int changedX=x+dx; 329 | unsigned int changedY=y+dy; 330 | if(changedX 5 | #include 6 | #include 7 | #include "ImageUtils.h" 8 | #include "lib/threadpool/thread_pool.h" 9 | #include "lib/exstring/exmath.h" 10 | #include 11 | #include "ImageProcess.h" 12 | namespace ScoreProcessor { 13 | 14 | //Anything in namespace Splice, except standard_heurstics, you should not access directly 15 | namespace Splice { 16 | 17 | //class used to manage when files are open and closed by splice 18 | class manager { 19 | private: 20 | cil::CImg _img; 21 | char const* filename; 22 | unsigned int times_used=0; 23 | std::mutex guard; 24 | public: 25 | [[nodiscard]] 26 | inline cil::CImg const& img() const 27 | { 28 | return _img; 29 | } 30 | [[nodiscard]] 31 | inline char const* fname() const 32 | { 33 | return filename; 34 | } 35 | inline void fname(char const* fn) 36 | { 37 | filename=fn; 38 | } 39 | inline void load() 40 | { 41 | std::lock_guard locker(guard); 42 | if(_img._data==0) 43 | { 44 | _img.load(filename); 45 | if(_img._spectrum==2) 46 | { 47 | cil::CImg temp(_img._width,_img._height,1,4); 48 | size_t const size=size_t{temp._width}*temp._height; 49 | std::memcpy(temp.data(),_img.data(),size); 50 | std::memcpy(temp.data()+size,_img.data(),size); 51 | std::memcpy(temp.data()+2*size,_img.data(),size); 52 | std::memset(temp.data()+3*size,255,size); 53 | _img=std::move(temp); 54 | } 55 | } 56 | } 57 | inline void finish() 58 | { 59 | std::lock_guard locker(guard); 60 | ++times_used; 61 | if(times_used==2) 62 | { 63 | delete[] _img._data; 64 | _img._data=0; 65 | } 66 | } 67 | }; 68 | 69 | //descriptor of a page's padding and height 70 | struct page_layout { 71 | unsigned int padding; 72 | unsigned int height; 73 | }; 74 | 75 | //descriptor of an image's true top and bottom (where actual image data is) as opposed to its raw size 76 | struct page { 77 | cil::CImg img; 78 | unsigned int top; 79 | unsigned int bottom; 80 | unsigned int true_height() const 81 | { 82 | return bottom-top; 83 | } 84 | }; 85 | 86 | //the top or bottom of a page as found by kerning images together or plainly finding where the image data starts 87 | struct edge { 88 | unsigned int raw; 89 | unsigned int kerned; 90 | }; 91 | 92 | //top and bottom edges of a page 93 | struct page_desc { 94 | edge top; 95 | edge bottom; 96 | }; 97 | 98 | // 99 | struct page_break { 100 | size_t index; 101 | unsigned int padding; 102 | }; 103 | 104 | //template class used to find the approprate edges of tops and bottoms 105 | //and descriptors between middle pages 106 | template 107 | class PageEval:private Top,private Middle,private Bottom { 108 | public: 109 | PageEval(Top t,Middle m,Bottom b):Top(t),Middle(m),Bottom(b) 110 | {} 111 | edge eval_top(cil::CImg const& img) const 112 | { 113 | return Top::operator()(img); 114 | } 115 | page_desc eval_middle(cil::CImg const& top,cil::CImg const& bottom) const 116 | { 117 | return Middle::operator()(top,bottom); 118 | } 119 | edge eval_bottom(cil::CImg const& img) const 120 | { 121 | return Bottom::operator()(img); 122 | } 123 | }; 124 | template 125 | PageEval(Top,Middle,Bottom)->PageEval; 126 | } 127 | 128 | //splices together the pages pointed to by imgs, padded apart by padding 129 | cil::CImg splice_images(Splice::page const* imgs,size_t num,unsigned int padding); 130 | 131 | //returns breaks in backwards order 132 | //determines where page breaks should go using Knuth word-wrap algorithm, based on given cost function, and 133 | //way of layout out pages 134 | template 135 | std::vector nongreedy_break(PageDescIter begin,PageDescIter end,CreateLayout cl,Cost cost) 136 | { 137 | struct node { 138 | #ifdef _WIN64 139 | double 140 | #else 141 | float 142 | #endif 143 | cost; 144 | Splice::page_layout layout; 145 | size_t previous; 146 | }; 147 | size_t const c=end-begin; 148 | std::vector nodes(c+1); 149 | nodes[0].cost=0; 150 | for(size_t i=1;i<=c;++i) 151 | { 152 | nodes[i].cost=INFINITY; 153 | for(size_t j=i-1;;) 154 | { 155 | auto layout=cl(&begin[j],i-j); 156 | auto local_cost=cost(layout); 157 | auto total_cost=local_cost+nodes[j].cost; 158 | if(total_cost<=nodes[i].cost) 159 | { 160 | nodes[i].cost=total_cost; 161 | nodes[i].previous=j; 162 | nodes[i].layout=layout; 163 | } 164 | if(j==0) 165 | { 166 | break; 167 | } 168 | --j; 169 | } 170 | } 171 | std::vector breaks; 172 | breaks.reserve(c); 173 | size_t index=c; 174 | do 175 | { 176 | breaks.push_back(Splice::page_break{index,nodes[index].layout.padding}); 177 | index=nodes[index].previous; 178 | } while(index); 179 | return breaks; 180 | } 181 | 182 | //splices together the non-greedily and multi-threadedly, based on the given page descriptors and evaluators 183 | //returns the number of pages spliced together 184 | template 185 | unsigned int splice_pages_parallel( 186 | std::vector& files, 187 | SaveRules const& output_rule, 188 | unsigned int starting_index, 189 | unsigned int num_threads, 190 | EvalPage ep, 191 | CreateLayout cl, 192 | Cost cost, 193 | Splicer splicer, 194 | Saver saver) 195 | { 196 | auto const c=files.size(); 197 | if(c<2) 198 | { 199 | throw std::invalid_argument("Need multiple pages to splice"); 200 | } 201 | //auto const extension=exlib::find_extension(output,output+std::strlen(output)); 202 | //validate_extension(extension); 203 | std::string error_log; 204 | std::mutex error_mutex; 205 | std::vector page_descs(c); 206 | exlib::thread_pool pool(num_threads); 207 | using parent_ref=typename decltype(pool)::parent_ref; 208 | auto send_error=[&error_mutex,&error_log](auto const& err,auto parent,auto filename) 209 | { 210 | { 211 | std::lock_guard lock{error_mutex}; 212 | error_log.append(filename).append(": ").append(err.what()).append("\n"); 213 | } 214 | parent.stop(); 215 | }; 216 | pool.push_back([work=files.data(),output=page_descs.data(),ep,send_error](parent_ref parent) noexcept{ 217 | try 218 | { 219 | work->load(); 220 | Splice::edge res=ep.eval_top(work->img()); 221 | output->top=res; 222 | work->finish(); 223 | } 224 | catch(std::exception const& ex) 225 | { 226 | send_error(ex,parent,work->fname()); 227 | } 228 | }); 229 | for(size_t i=1;iload(); 237 | } 238 | catch(std::exception const& err) 239 | { 240 | send_error(err,parent,(work-1)->fname()); 241 | return; 242 | } 243 | try 244 | { 245 | work->load(); 246 | } 247 | catch(std::exception const& err) 248 | { 249 | send_error(err,parent,work->fname()); 250 | return; 251 | } 252 | try 253 | { 254 | Splice::page_desc res=ep.eval_middle((work-1)->img(),work->img()); 255 | (output-1)->bottom=res.bottom; 256 | (output)->top=res.top; 257 | (work-1)->finish(); 258 | work->finish(); 259 | } 260 | catch(std::exception const& err) 261 | { 262 | std::string names=(work-1)->fname(); 263 | names+=" and "; 264 | names+=work->fname(); 265 | send_error(err,parent,names.c_str()); 266 | } 267 | }); 268 | } 269 | pool.push_back([work=files.data()+c-1,output=page_descs.data()+c-1,ep,send_error](parent_ref parent)noexcept{ 270 | try 271 | { 272 | work->load(); 273 | Splice::edge res=ep.eval_bottom(work->img()); 274 | output->bottom=res; 275 | work->finish(); 276 | } 277 | catch(std::exception const& ex) 278 | { 279 | send_error(ex,parent,work->fname()); 280 | } 281 | }); 282 | pool.wait(); 283 | if(!error_log.empty()) 284 | { 285 | throw std::logic_error(error_log); 286 | } 287 | 288 | std::vector breaks=nongreedy_break(page_descs.begin(),page_descs.end(),cl,cost); 289 | unsigned int num_digs=exlib::num_digits(breaks.size()+starting_index); 290 | num_digs=num_digs<3?3:num_digs; 291 | unsigned int num_imgs=0; 292 | auto start=0; 293 | for(size_t i=breaks.size()-1;;) 294 | { 295 | ++num_imgs; 296 | auto const end=breaks[i].index; 297 | auto const s=end-start; 298 | pool.push_back( 299 | [&output_rule, 300 | filename_index=start+starting_index, 301 | fbegin=files.data()+start, 302 | ibegin=page_descs.data()+start, 303 | num_pages=s, 304 | padding=breaks[i].padding, 305 | send_error, 306 | splicer, 307 | saver](parent_ref parent) noexcept { 308 | try 309 | { 310 | std::vector imgs(num_pages); 311 | for(size_t i=0;i 346 | unsigned int splice_pages_parallel( 347 | std::vector const& filenames, 348 | char const* output, 349 | unsigned int starting_index, 350 | unsigned int num_threads, 351 | EvalPage ep, 352 | CreateLayout cl, 353 | Cost c, 354 | int quality) 355 | { 356 | std::vector managers(filenames.size()); 357 | for(size_t i=0;i 365 | unsigned int splice_pages( 366 | std::vector const& filenames, 367 | char const* output, 368 | unsigned int starting_index, 369 | unsigned int num_threads, 370 | EvalPage ep, 371 | CreateLayout cl, 372 | Cost c) 373 | { 374 | auto const c=files.size(); 375 | if(c<2) 376 | { 377 | throw std::invalid_argument("Need multiple pages to splice"); 378 | } 379 | std::vector page_descs(c); 380 | page_descs.front().top=ep.eval_middle() 381 | } 382 | 383 | namespace Splice { 384 | using pv=exlib::maybe_fixed; 385 | struct standard_heuristics { 386 | unsigned char background_color; 387 | pv horiz_padding; 388 | pv optimal_height; 389 | pv optimal_padding; 390 | pv min_padding; 391 | float excess_weight; 392 | float padding_weight; 393 | }; 394 | struct options { 395 | unsigned int starting_index; 396 | unsigned int num_threads; 397 | int quality; 398 | bool make_folders; 399 | }; 400 | } 401 | 402 | //splices together images using the standard heuristics and dif^3 cost algorithm 403 | //cost is 404 | unsigned int splice_pages_parallel( 405 | std::vector const& filenames, 406 | SaveRules const& output_rule, 407 | Splice::options const& options, 408 | Splice::standard_heuristics const&); 409 | 410 | //splices together images using the standard heuristics and dif^3 cost algorithm 411 | //cost is 412 | unsigned int splice_pages_parallel( 413 | std::vector const& filenames, 414 | SaveRules const& output_rule, 415 | Splice::options const& options, 416 | Splice::standard_heuristics const&, 417 | cil::CImg const& divider); 418 | 419 | } 420 | #endif -------------------------------------------------------------------------------- /ScoreProcessor/ImageUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright(C) 2017-2018 Edward Xie 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | #ifndef IMAGEUTILS_H 18 | #define IMAGEUTILS_H 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | namespace ImageUtils { 25 | 26 | template 27 | constexpr T round_up(T val,T r) noexcept 28 | { 29 | auto const mod=val%r; 30 | if(mod==0) return val; 31 | return val-mod+r; 32 | } 33 | 34 | struct perc_or_val { 35 | bool is_perc; 36 | union { 37 | float perc; 38 | unsigned int val; 39 | }; 40 | inline perc_or_val(float perc):is_perc(true),perc(perc) 41 | {} 42 | inline perc_or_val(unsigned int val):is_perc(false),val(val) 43 | {} 44 | inline perc_or_val() 45 | {} 46 | unsigned int operator()(unsigned int base_val) const 47 | { 48 | if(is_perc) 49 | { 50 | return std::round(perc/100.0*base_val); 51 | } 52 | return val; 53 | } 54 | unsigned int value(unsigned int base_val) const 55 | { 56 | return operator()(base_val); 57 | } 58 | void fix_value(unsigned int base_val) 59 | { 60 | if(is_perc) 61 | { 62 | is_perc=false; 63 | val=std::round(perc/100.0*base_val); 64 | } 65 | } 66 | }; 67 | /* 68 | A cartesian vertical line. 69 | */ 70 | template 71 | struct vertical_line { 72 | T x,top,bottom; 73 | }; 74 | /* 75 | A cartesian horizontal line. 76 | */ 77 | template 78 | struct horizontal_line { 79 | T left,right,y; 80 | }; 81 | /* 82 | A 2 dimensional vector. 83 | */ 84 | template 85 | struct vec2_t { 86 | T x,y; 87 | template 88 | vec2_t& operator+=(vec2_t const& other) noexcept 89 | { 90 | x+=other.x; 91 | y+=other.y; 92 | } 93 | template 94 | vec2_t& operator-=(vec2_t const& other) noexcept 95 | { 96 | x-=other.x; 97 | y-=other.y; 98 | } 99 | }; 100 | template 101 | auto operator*(U val,vec2_t const& other) -> vec2_t 102 | { 103 | return {val*other.x,val*other.y}; 104 | } 105 | template 106 | auto operator*(vec2_t const& other,U val) -> vec2_t 107 | { 108 | return {val*other.x,val*other.y}; 109 | } 110 | template 111 | auto operator+(vec2_t const& a,vec2_t const& b) -> vec2_t 112 | { 113 | return {a.x+b.x,a.y+b.y}; 114 | } 115 | template 116 | auto operator-(vec2_t const& a,vec2_t const& b) -> vec2_t 117 | { 118 | return {a.x-b.x,a.y-b.y}; 119 | } 120 | template 121 | vec2_t operator-(vec2_t const& a) 122 | { 123 | return {-a.x,-a.y}; 124 | } 125 | template 126 | using Point=vec2_t; 127 | using PointUINT=Point; 128 | /* 129 | A cartesian line in 2 dimensions. 130 | */ 131 | template 132 | struct line { 133 | Point a,b; 134 | template 135 | friend ::std::ostream& operator<<(::std::ostream& os,line const& aline); 136 | }; 137 | /* 138 | A polar line in 2 dimensions. 139 | */ 140 | template 141 | struct line_norm { 142 | T theta;//the normal has this angle with the x-axis 143 | T r;//the line is r away from the origin 144 | }; 145 | /* 146 | A rectangle. 147 | */ 148 | template 149 | struct Rectangle { 150 | T left,right,top,bottom; 151 | /* 152 | Compares value of left side. 153 | If equal, value of tops. 154 | */ 155 | bool operator<(Rectangle const& other) const; 156 | /* 157 | Compares value of left side. 158 | If equal, value of tops. 159 | */ 160 | bool operator>(Rectangle const& other) const; 161 | 162 | bool operator==(Rectangle const& other) const 163 | { 164 | return left==other.left&&right==other.right&&top==other.top&&bottom==other.bottom; 165 | } 166 | /* 167 | Calculates the area. 168 | */ 169 | template 170 | U area() const; 171 | /* 172 | Calculates the perimeter. 173 | */ 174 | template 175 | U perimeter() const; 176 | /* 177 | Returns the height. 178 | */ 179 | template 180 | U height() const; 181 | /* 182 | Returns the width. 183 | */ 184 | template 185 | U width() const; 186 | template 187 | /* 188 | Returns the center of the rectangle. 189 | */ 190 | Point center() const; 191 | /* 192 | Returns true if this rectangle is bordering the top or bottom of the other. 193 | */ 194 | bool is_bordering_vert(Rectangle const& other) const; 195 | /* 196 | Returns true if the x-coordinates overlap. 197 | */ 198 | bool overlaps_x(Rectangle const& other) const; 199 | /* 200 | Returns true if this rectangle is bordering the left or right of the other. 201 | */ 202 | bool is_bordering_horiz(Rectangle const& other) const; 203 | /* 204 | Returns true if the y-coordinates overlap. 205 | */ 206 | bool overlaps_y(Rectangle const& other) const; 207 | /* 208 | Returns true if the rectangles intersect. 209 | */ 210 | bool intersects(Rectangle const& other) const; 211 | /* 212 | Returns the diagonal of this rectangle. 213 | */ 214 | line diagonal(); 215 | 216 | Point top_left() const 217 | { 218 | return {left,top}; 219 | } 220 | 221 | Point top_right() const 222 | { 223 | return {right,top}; 224 | } 225 | 226 | Point bottom_left() const 227 | { 228 | return {left,bottom}; 229 | } 230 | 231 | Point bottom_right() const 232 | { 233 | return {right,bottom}; 234 | } 235 | 236 | /* 237 | Console output 238 | */ 239 | template 240 | friend ::std::ostream& operator<<(::std::ostream& os,Rectangle const& rect); 241 | 242 | template 243 | operator Rectangle() const 244 | { 245 | return {U(left),U(right),U(top),U(bottom)}; 246 | } 247 | }; 248 | 249 | /* 250 | template 251 | void split_horiz(Rectangle* orig,Rectangle* buffer,unsigned int numRects); 252 | */ 253 | 254 | using RectangleUINT=Rectangle; 255 | 256 | struct ColorRGBA; 257 | struct ColorRGB; 258 | struct ColorHSV; 259 | struct Grayscale; 260 | 261 | unsigned char brightness(ColorRGB color); 262 | struct ColorRGB { 263 | static ColorRGB const WHITE; 264 | static ColorRGB const BLACK; 265 | 266 | unsigned char r,g,b; 267 | operator ColorRGBA() const; 268 | operator ColorHSV() const; 269 | operator Grayscale() const; 270 | unsigned char brightness() const; 271 | float difference(ColorRGB other); 272 | 273 | static float color_diff(unsigned char const*,unsigned char const*); 274 | }; 275 | 276 | struct ColorHSV { 277 | unsigned char h,s,v; 278 | 279 | operator ColorRGBA() const; 280 | operator ColorRGB() const; 281 | operator Grayscale() const; 282 | static ColorHSV const WHITE; 283 | static ColorHSV const BLACK; 284 | }; 285 | 286 | struct ColorRGBA { 287 | unsigned char r,g,b,a; 288 | ColorRGB toRGB() const; 289 | }; 290 | 291 | struct Grayscale { 292 | unsigned char value; 293 | inline operator unsigned char() const 294 | { 295 | return value; 296 | } 297 | Grayscale(unsigned char const val):value(val) 298 | {} 299 | Grayscale() 300 | {} 301 | 302 | inline Grayscale& operator=(unsigned char const val) 303 | { 304 | value=val; return *this; 305 | } 306 | static Grayscale const WHITE; 307 | static Grayscale const BLACK; 308 | float difference(Grayscale other); 309 | 310 | static float color_diff(unsigned char const*,unsigned char const*); 311 | }; 312 | 313 | float gray_diff(Grayscale g1,Grayscale g2); 314 | 315 | inline unsigned char brightness(Grayscale g) 316 | { 317 | return g; 318 | } 319 | inline unsigned char brightness(ColorRGB color) 320 | { 321 | return static_cast(color.r*0.2126f+color.g*0.7152f+color.b*0.0722f); 322 | } 323 | inline unsigned char ColorRGB::brightness() const 324 | { 325 | return ImageUtils::brightness(*this); 326 | } 327 | } 328 | namespace ImageUtils { 329 | template 330 | bool Rectangle::operator<(Rectangle const& other) const 331 | { 332 | if(leftother.left) return false; 334 | return top 337 | bool Rectangle::operator>(Rectangle const& other) const 338 | { 339 | if(left>other.left) return true; 340 | if(leftother.top; 342 | } 343 | template template 344 | U Rectangle::area() const 345 | { 346 | return U(right-left)*U(bottom-top); 347 | } 348 | template template 349 | U Rectangle::perimeter() const 350 | { 351 | return 2*U(right-left+bottom-top); 352 | } 353 | template template 354 | U Rectangle::height() const 355 | { 356 | return bottom-top; 357 | } 358 | template template 359 | U Rectangle::width() const 360 | { 361 | return right-left; 362 | } 363 | template template 364 | Point Rectangle::center() const 365 | { 366 | return {U(left+right)/2,U(top+bottom)/2}; 367 | } 368 | template 369 | bool Rectangle::is_bordering_vert(Rectangle const& other) const 370 | { 371 | return (other.bottom==top||other.top==bottom)&&overlaps_x(other); 372 | } 373 | template 374 | bool Rectangle::overlaps_x(Rectangle const& other) const 375 | { 376 | return (leftother.left); 377 | } 378 | template 379 | bool Rectangle::is_bordering_horiz(Rectangle const& other) const 380 | { 381 | return (other.left==right||other.right==left)&&overlaps_y(other); 382 | } 383 | template 384 | bool Rectangle::overlaps_y(Rectangle const& other) const 385 | { 386 | return (topother.top); 387 | } 388 | template 389 | bool Rectangle::intersects(Rectangle const& other) const 390 | { 391 | return overlaps_x(other)&&overlaps_y(other); 392 | } 393 | template 394 | line Rectangle::diagonal() 395 | { 396 | return {{left,top},{right,bottom}}; 397 | } 398 | template 399 | std::ostream& operator<<(std::ostream& os,Rectangle const& rect) 400 | { 401 | return os<<"[("< 405 | std::ostream& operator<<(::std::ostream& os,line const& aline) 406 | { 407 | return os<<"[("< 416 | Iter compress_rectangles(Iter begin,Iter end) 417 | { 418 | if(begin==end) 419 | { 420 | return end; 421 | } 422 | std::sort(begin,end,[](auto const& rect1,auto const& rect2) 423 | { 424 | if(rect1.leftrect2.left) 429 | { 430 | return false; 431 | } 432 | return rect1.top 460 | void compress_rectangles(Container& container) 461 | { 462 | auto iter=compress_rectangles(container.begin(),container.end()); 463 | container.erase(iter,container.end()); 464 | } 465 | 466 | inline ColorRGB::operator ColorHSV() const 467 | { 468 | ColorHSV ret; 469 | float r=this->r,b=this->b,g=this->g; 470 | auto min=std::min(r,std::min(g,b)); 471 | auto max=std::max(r,std::max(g,b)); 472 | ret.v=unsigned char(max); 473 | auto delta=max-min; 474 | if(max!=0) 475 | { 476 | ret.s=unsigned char(std::round(delta/max*255)); 477 | } 478 | else 479 | { 480 | ret.s=0; 481 | //doesn't matter what hue is 482 | return ret; 483 | } 484 | float hue; 485 | if(r==max) 486 | { 487 | hue=(g-b)/delta; 488 | } 489 | else if(g==max) 490 | { 491 | hue=2+(b-r)/delta; 492 | } 493 | else 494 | { 495 | hue=4+(r-g)/delta; 496 | } 497 | hue*=(256.0f/360*60); 498 | if(hue<0) 499 | { 500 | hue+=256; 501 | } 502 | ret.h=unsigned char(std::round(hue)); 503 | return ret; 504 | } 505 | 506 | inline ColorRGB::operator ColorRGBA() const 507 | { 508 | return ColorRGBA{r,g,b,255}; 509 | } 510 | 511 | inline ColorRGB::operator Grayscale() const 512 | { 513 | return static_cast((r*0.2126f+g*0.7152f+b*0.0722f)); 514 | } 515 | 516 | /*template template 517 | vec2_t::vec2_t(vec2_t const& other):x(other.x),y(other.y) {} 518 | template template 519 | vec2_t& vec2_t::operator=(vec2_t const& other) { 520 | x=other.x; 521 | y=other.y; 522 | return *this; 523 | } 524 | */ 525 | /* 526 | template 527 | void split_horiz(Rectangle* orig,Rectangle* buffer,unsigned int numRects) 528 | { 529 | int height=(orig->bottom-orig->top)/numRects; 530 | --numRects; 531 | for(int i=0;ileft,orig->right,orig->top+height*i,orig->top+height*(++i)}; 534 | } 535 | buffer[numRects]={orig->left,orig->right,orig->top+height*numRects,orig->bottom}; 536 | } 537 | */ 538 | } 539 | #endif -------------------------------------------------------------------------------- /SProcUnitTests/SProcUnitTests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debugger 6 | Win32 7 | 8 | 9 | Debugger 10 | x64 11 | 12 | 13 | Debug 14 | Win32 15 | 16 | 17 | Readme 18 | Win32 19 | 20 | 21 | Readme 22 | x64 23 | 24 | 25 | Release 26 | Win32 27 | 28 | 29 | Debug 30 | x64 31 | 32 | 33 | Release 34 | x64 35 | 36 | 37 | 38 | 15.0 39 | {347630B6-FDC2-49D8-86E8-562E287BD49E} 40 | Win32Proj 41 | SProcUnitTests 42 | 10.0 43 | NativeUnitTestProject 44 | 45 | 46 | 47 | DynamicLibrary 48 | true 49 | v142 50 | Unicode 51 | false 52 | 53 | 54 | DynamicLibrary 55 | false 56 | v142 57 | true 58 | Unicode 59 | false 60 | 61 | 62 | DynamicLibrary 63 | false 64 | v142 65 | true 66 | Unicode 67 | false 68 | 69 | 70 | DynamicLibrary 71 | false 72 | v142 73 | true 74 | Unicode 75 | false 76 | 77 | 78 | DynamicLibrary 79 | true 80 | v142 81 | Unicode 82 | false 83 | 84 | 85 | DynamicLibrary 86 | false 87 | v142 88 | true 89 | Unicode 90 | false 91 | 92 | 93 | DynamicLibrary 94 | false 95 | v142 96 | true 97 | Unicode 98 | false 99 | 100 | 101 | DynamicLibrary 102 | false 103 | v142 104 | true 105 | Unicode 106 | false 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | true 140 | 141 | 142 | true 143 | 144 | 145 | true 146 | 147 | 148 | true 149 | 150 | 151 | true 152 | 153 | 154 | true 155 | 156 | 157 | true 158 | 159 | 160 | true 161 | 162 | 163 | 164 | Level3 165 | Use 166 | MaxSpeed 167 | true 168 | true 169 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 170 | WIN32;NDEBUG;%(PreprocessorDefinitions);NOMINMAX 171 | true 172 | 173 | 174 | Windows 175 | true 176 | true 177 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 178 | 179 | 180 | 181 | 182 | Level3 183 | Use 184 | MaxSpeed 185 | true 186 | true 187 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 188 | WIN32;NDEBUG;%(PreprocessorDefinitions);NOMINMAX 189 | true 190 | 191 | 192 | Windows 193 | true 194 | true 195 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 196 | 197 | 198 | 199 | 200 | Level3 201 | Use 202 | MaxSpeed 203 | true 204 | true 205 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 206 | WIN32;NDEBUG;%(PreprocessorDefinitions);NOMINMAX 207 | true 208 | 209 | 210 | Windows 211 | true 212 | true 213 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 214 | 215 | 216 | 217 | 218 | Use 219 | Level3 220 | Disabled 221 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 222 | WIN32;_DEBUG;%(PreprocessorDefinitions) 223 | true 224 | 225 | 226 | Windows 227 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 228 | 229 | 230 | 231 | 232 | Use 233 | Level3 234 | Disabled 235 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 236 | _DEBUG;%(PreprocessorDefinitions) 237 | true 238 | 239 | 240 | Windows 241 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 242 | 243 | 244 | 245 | 246 | Level3 247 | Use 248 | MaxSpeed 249 | true 250 | true 251 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 252 | NDEBUG;%(PreprocessorDefinitions);NOMINMAX 253 | true 254 | /wd5045 %(AdditionalOptions) 255 | 256 | 257 | Windows 258 | true 259 | true 260 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 261 | 262 | 263 | 264 | 265 | Level3 266 | Use 267 | MaxSpeed 268 | true 269 | true 270 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 271 | NDEBUG;%(PreprocessorDefinitions);NOMINMAX 272 | true 273 | /wd5045 %(AdditionalOptions) 274 | 275 | 276 | Windows 277 | true 278 | true 279 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 280 | 281 | 282 | 283 | 284 | Level3 285 | Use 286 | MaxSpeed 287 | true 288 | true 289 | $(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) 290 | NDEBUG;%(PreprocessorDefinitions);NOMINMAX 291 | true 292 | /wd5045 %(AdditionalOptions) 293 | 294 | 295 | Windows 296 | true 297 | true 298 | $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories) 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | Create 311 | Create 312 | Create 313 | Create 314 | Create 315 | Create 316 | Create 317 | Create 318 | 319 | 320 | 321 | 322 | 323 | {f88c20a0-d08b-4582-95a7-8620815d4f6d} 324 | 325 | 326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /ScoreProcessor/Splice.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Splice.h" 3 | #include "ScoreProcesses.h" 4 | #ifdef HSPROC 5 | #include "Processes.h" 6 | #endif 7 | namespace ScoreProcessor { 8 | 9 | template//function (tog_pixel_pointer,tog_size,current_img,x,y) 10 | cil::CImg& splice_images_h(Splice::page const* imgs,size_t num,unsigned int padding,cil::CImg& tog,TreatPixel tp) 11 | { 12 | unsigned int ypos=padding; 13 | auto const size=size_t{tog._width}*tog._height; 14 | for(size_t i=0;itog._height+current.top) 27 | { 28 | end=tog._height+current.top-ypos; 29 | } 30 | else 31 | { 32 | end=current.img._height; 33 | } 34 | for(unsigned int y=begin;y splice_images(Splice::page const* imgs,size_t num,unsigned int padding) 48 | { 49 | unsigned int height=0; 50 | unsigned int width=0; 51 | unsigned int spectrum=0; 52 | for(size_t i=0;iwidth) 56 | { 57 | width=imgs[i].img._width; 58 | } 59 | if(imgs[i].img._spectrum>spectrum) 60 | { 61 | spectrum=imgs[i].img._spectrum; 62 | } 63 | } 64 | height+=padding*(num+1); 65 | cil::CImg tog(width,height,1,spectrum); 66 | tog.fill(255); 67 | switch(spectrum) 68 | { 69 | case 1: 70 | case 2: 71 | return splice_images_h(imgs,num,padding,tog,[](unsigned char* pixel,size_t const size,cil::CImg const& current,unsigned int x,unsigned int y) 72 | { 73 | *pixel=std::min(*pixel,current(x,y)); 74 | }); 75 | /*return splice_images_h(imgs,num,padding,tog,[](unsigned char* pixel,size_t const size,cil::CImg const& current,unsigned int x,unsigned int y) 76 | { 77 | double dark=(255U-*pixel)*(pixel[size]/255.0); 78 | double cdark; 79 | auto const cpixel=¤t(x,y); 80 | size_t csize; 81 | switch(current._spectrum) 82 | { 83 | case 1: 84 | cdark=255U-*cpixel; 85 | break; 86 | case 2: 87 | cdark=(255U-*cpixel)*(cpixel[csize=current._width*current._height]/255.0); 88 | } 89 | if(cdark>dark) 90 | { 91 | switch(current._spectrum) 92 | { 93 | case 1: 94 | *pixel=*cpixel; 95 | pixel[size]=255; 96 | break; 97 | case 2: 98 | *pixel=*cpixel; 99 | pixel[size]=cpixel[csize]; 100 | } 101 | } 102 | });*/ 103 | case 3: 104 | using pinfo=uint_fast32_t; 105 | return splice_images_h(imgs,num,padding,tog,[](unsigned char* pixel,size_t const size,cil::CImg const& current,unsigned int x,unsigned int y) 106 | { 107 | pinfo const br=(pinfo{pixel[0]}+pixel[size]+pixel[2*size]); 108 | size_t csize; 109 | pinfo cbr; 110 | auto const cpixel=¤t(x,y); 111 | switch(current._spectrum) 112 | { 113 | case 1: 114 | cbr=*cpixel*3U; 115 | break; 116 | case 3: 117 | csize=size_t{current._height}*current._width; 118 | cbr=pinfo{cpixel[0]}+cpixel[csize]+cpixel[2*csize]; 119 | } 120 | if(cbr const& current,unsigned int x,unsigned int y) 136 | { 137 | constexpr pinfo max=3U*255U; 138 | pinfo drk=(max-(pinfo{pixel[0]}+pixel[size]+pixel[2*size]))*pixel[3*size]; 139 | size_t csize; 140 | pinfo cdrk; 141 | auto const cpixel=¤t(x,y); 142 | switch(current._spectrum) 143 | { 144 | case 1: 145 | cdrk=(max-(*cpixel*3U))*255U; 146 | break; 147 | case 3: 148 | csize=size_t{current._height}*current._width; 149 | cdrk=(max-(pinfo{cpixel[0]}+cpixel[csize]+cpixel[2*csize]))*255U; 150 | break; 151 | case 4: 152 | csize=size_t{current._height}*current._width; 153 | cdrk=(max-(pinfo{cpixel[0]}+cpixel[csize]+cpixel[2*csize]))*cpixel[3*csize]; 154 | break; 155 | } 156 | if(cdrk>drk) 157 | { 158 | switch(current._spectrum) 159 | { 160 | case 1: 161 | pixel[0]=pixel[size]=pixel[2*size]=cpixel[0]; 162 | pixel[3*size]=255; 163 | break; 164 | case 3: 165 | pixel[0]=cpixel[0]; 166 | pixel[size]=cpixel[csize]; 167 | pixel[2*size]=cpixel[2*csize]; 168 | pixel[3*size]=255; 169 | break; 170 | case 4: 171 | pixel[0]=cpixel[0]; 172 | pixel[size]=cpixel[csize]; 173 | pixel[2*size]=cpixel[2*csize]; 174 | pixel[3*size]=cpixel[3*csize]; 175 | } 176 | } 177 | }); 178 | } 179 | } 180 | 181 | struct spacing { 182 | unsigned int bottom_sg; 183 | unsigned int top_sg; 184 | }; 185 | 186 | spacing find_spacing(std::vector const& bottom_of_top,unsigned int size_top,std::vector const& top_of_bottom) 187 | { 188 | auto b=bottom_of_top.rbegin(); 189 | auto t=top_of_bottom.rbegin(); 190 | auto end=b+std::min(bottom_of_top.size(),top_of_bottom.size()); 191 | unsigned int min_spacing=std::numeric_limits::max(); 192 | spacing ret; 193 | for(;b!=end;++b,++t) 194 | { 195 | unsigned int cand=size_top-*b+*t; 196 | if(cand const& img,unsigned char bg) 207 | { 208 | unsigned int min=img._height/2; 209 | if(img._spectrum<3) 210 | { 211 | for(unsigned int x=0;x(img(x,y))+img(x,y,1)+img(x,y,2)<=limit) 231 | { 232 | min=y; 233 | break; 234 | } 235 | } 236 | } 237 | } 238 | return min; 239 | } 240 | 241 | unsigned int splice_find_bottom(cil::CImg const& img,unsigned char bg) 242 | { 243 | unsigned int max=img._height/2; 244 | if(img._spectrum<3) 245 | { 246 | for(unsigned int x=0;xmax;--y) 249 | { 250 | if(img(x,y)<=bg) 251 | { 252 | max=y; 253 | break; 254 | } 255 | } 256 | } 257 | } 258 | else 259 | { 260 | unsigned int limit=3U*bg; 261 | for(unsigned int x=0;xmax;--y) 264 | { 265 | if(static_cast(img(x,y))+img(x,y,1)+img(x,y,2)<=limit) 266 | { 267 | max=y; 268 | break; 269 | } 270 | } 271 | } 272 | } 273 | return max; 274 | } 275 | 276 | void get_optimal_values(Splice::standard_heuristics const& sh,cil::CImg const& ref, 277 | unsigned int& horiz_padding,unsigned int& min_pad,unsigned int& opt_pad,unsigned int& opt_height) 278 | { 279 | std::array bases{ref._width,ref._height}; 280 | horiz_padding=sh.horiz_padding(bases); 281 | min_pad=sh.min_padding(bases); 282 | opt_pad=sh.optimal_padding(bases); 283 | opt_height=sh.optimal_height(bases); 284 | } 285 | 286 | float layout_cost(Splice::page_layout p,Splice::standard_heuristics const& sh,unsigned int horiz_padding,unsigned int opt_pad,unsigned int opt_height) 287 | { 288 | float numer; 289 | if(p.height>opt_height) 290 | { 291 | numer=sh.excess_weight*(p.height-opt_height); 292 | } 293 | else 294 | { 295 | numer=opt_height-p.height; 296 | } 297 | float height_cost=numer/opt_height; 298 | height_cost=height_cost*height_cost*height_cost; 299 | float padding_cost=sh.padding_weight*exlib::abs_dif(float(p.padding),opt_pad)/opt_pad; 300 | padding_cost=padding_cost*padding_cost*padding_cost; 301 | return height_cost+padding_cost; 302 | }; 303 | 304 | unsigned int splice_pages_parallel( 305 | std::vector const& filenames, 306 | SaveRules const& output_rule, 307 | Splice::options const& options, 308 | Splice::standard_heuristics const& sh) 309 | { 310 | if(filenames.size()<2) 311 | { 312 | throw std::invalid_argument("Need multiple pages to splice"); 313 | } 314 | std::vector managers(filenames.size()); 315 | for(size_t i=0;i; 323 | auto find_top=[bg=sh.background_color](Img const& img){ 324 | if(img._spectrum<3) 325 | { 326 | return build_top_profile(img,bg); 327 | } 328 | else 329 | { 330 | return build_top_profile(img,ImageUtils::ColorRGB({bg,bg,bg})); 331 | } 332 | }; 333 | auto find_bottom=[bg=sh.background_color](Img const& img){ 334 | if(img._spectrum<3) 335 | { 336 | return build_bottom_profile(img,bg); 337 | } 338 | else 339 | { 340 | return build_bottom_profile(img,ImageUtils::ColorRGB({bg,bg,bg})); 341 | } 342 | }; 343 | Splice::PageEval pe([bg=sh.background_color](Img const& img) 344 | { 345 | auto min=splice_find_top(img,bg); 346 | return Splice::edge{min,min}; 347 | },[=](Img const& t,Img const& b) 348 | { 349 | auto top=exlib::fattened_profile(find_bottom(t),horiz_padding,[](auto a,auto b) 350 | { 351 | return a>b; 352 | }); 353 | auto bot=exlib::fattened_profile(find_top(b),horiz_padding,[](auto a,auto b) 354 | { 355 | return a=opt_height) 390 | { 391 | return Splice::page_layout{min_pad,minned}; 392 | } 393 | else 394 | { 395 | return Splice::page_layout{unsigned int((opt_height-total_height)/(n+1)),opt_height}; 396 | } 397 | }; 398 | auto cost=[=](Splice::page_layout const p) 399 | { 400 | return layout_cost(p,sh,horiz_padding,opt_pad,opt_height); 401 | }; 402 | auto saver = [quality = options.quality, width = sh.optimal_height, make_folders = options.make_folders](auto const& image, char const* name) 403 | { 404 | auto support = supported_path(name); 405 | if(support==decltype(support)::no) 406 | { 407 | support=decltype(support)::png; 408 | } 409 | #ifdef HSPROC 410 | auto save=image.get_rotate(-90); 411 | if (width.fixed()) 412 | { 413 | ChangeCanvasSize(width(), -1, FillRectangle::origin_reference::middle).process(save); 414 | } 415 | #else 416 | auto const& save=image; 417 | #endif 418 | if (make_folders) 419 | { 420 | try 421 | { 422 | std::filesystem::create_directories(std::filesystem::path(name).parent_path()); 423 | } 424 | catch (std::exception const& err) 425 | { 426 | // throw std::runtime_error(std::string("Failed to create paths for ").append(name).append(": ").append(err.what())); 427 | } 428 | } 429 | return cil::save_image(save, name, support, quality); 430 | }; 431 | return splice_pages_parallel(managers,output_rule,options.starting_index,options.num_threads,pe,create_layout,cost,&splice_images, saver); 432 | } 433 | 434 | unsigned int splice_pages_parallel( 435 | std::vector const& filenames, 436 | SaveRules const& output_rule, 437 | Splice::options const& options, 438 | Splice::standard_heuristics const& sh, 439 | cil::CImg const& divider) 440 | { 441 | auto const c=filenames.size(); 442 | if(c<2) 443 | { 444 | throw std::invalid_argument("Need multiple pages to splice"); 445 | } 446 | // auto const extension=exlib::find_extension(output,output+std::strlen(output)); 447 | // validate_extension(extension); 448 | Splice::page divider_desc{{divider,true}}; 449 | std::vector descriptions(filenames.size()); 450 | exlib::thread_pool pool{options.num_threads}; 451 | std::mutex error_lock; 452 | std::string error_log; 453 | unsigned int horiz_padding,min_pad,opt_pad,opt_height; 454 | auto get_dims=[bg=sh.background_color](Splice::page& page){ 455 | page.top=splice_find_top(page.img,bg); 456 | page.bottom=splice_find_bottom(page.img,bg); 457 | }; 458 | pool.push_back([get_dims,÷r_desc]() noexcept 459 | { 460 | get_dims(divider_desc); 461 | }); 462 | pool.push_back([&,&desc=descriptions[0],&name=filenames[0],bg=get_dims](decltype(pool)::parent_ref parent) noexcept 463 | { 464 | try 465 | { 466 | desc.img.load(name.c_str()); 467 | get_dims(desc); 468 | get_optimal_values(sh,desc.img,horiz_padding,min_pad,opt_pad,opt_height); 469 | desc.img=cil::CImg{}; 470 | } 471 | catch(std::exception const& err) 472 | { 473 | parent.stop(); 474 | std::lock_guard guard{error_lock}; 475 | error_log.append(name).append(": ").append(err.what()).append("\n"); 476 | } 477 | } 478 | ); 479 | for(std::size_t i=1;i{}; 488 | } 489 | catch(std::exception const& err) 490 | { 491 | parent.stop(); 492 | std::lock_guard guard{error_lock}; 493 | error_log.append(name).append(": ").append(err.what()).append("\n"); 494 | } 495 | }); 496 | } 497 | pool.wait(); 498 | if(!error_log.empty()) 499 | { 500 | throw std::runtime_error(error_log); 501 | } 502 | auto create_layout=[&](Splice::page const* const items,size_t const n) 503 | { 504 | assert(n!=0); 505 | unsigned int total_height=(n-1)*divider_desc.true_height(); 506 | for(size_t i=0;i=opt_height) 512 | { 513 | return Splice::page_layout{min_pad,minned}; 514 | } 515 | else 516 | { 517 | return Splice::page_layout{unsigned int((opt_height-total_height)/(2*n)),opt_height}; 518 | } 519 | }; 520 | auto breaks=nongreedy_break( 521 | descriptions.begin(),descriptions.end(), 522 | create_layout, 523 | [=](Splice::page_layout const p) 524 | { 525 | return layout_cost(p,sh,horiz_padding,opt_pad,opt_height); 526 | }); 527 | unsigned int num_digs=exlib::num_digits(breaks.size()+options.starting_index); 528 | num_digs=num_digs<3?3:num_digs; 529 | unsigned int num_imgs=0; 530 | auto start=0; 531 | for(size_t i=breaks.size()-1;;) 532 | { 533 | ++num_imgs; 534 | auto const end=breaks[i].index; 535 | auto const s=end-start; 536 | pool.push_back( 537 | [&, 538 | filename_index=start+options.starting_index, 539 | fbegin=filenames.data()+start, 540 | ibegin=descriptions.data()+start, 541 | num_pages=s, 542 | padding=breaks[i].padding, 543 | quality=options.quality, 544 | make_folders=options.make_folders](decltype(pool)::parent_ref parent) noexcept{ 545 | try 546 | { 547 | std::vector imgs(num_pages*2-1); 548 | imgs[0].img.load(fbegin->c_str()); 549 | imgs[0].top=ibegin->top; 550 | imgs[0].bottom=ibegin->bottom; 551 | for(size_t i=1;i { 11 | public: 12 | bool process(Img& img) const override; 13 | }; 14 | 15 | class FillTransparency:public ImageProcess<> { 16 | ImageUtils::ColorRGB background; 17 | public: 18 | inline FillTransparency(unsigned char r,unsigned char g,unsigned char b) 19 | { 20 | background={r,g,b}; 21 | } 22 | bool process(Img& img) const override; 23 | }; 24 | 25 | class RemoveBorderGray:public ImageProcess<> { 26 | float tolerance; 27 | public: 28 | inline RemoveBorderGray(float tolerance):tolerance(tolerance) 29 | {} 30 | bool process(Img& img) const override; 31 | }; 32 | 33 | class FilterGray:public ImageProcess<> { 34 | unsigned char min; 35 | unsigned char max; 36 | unsigned char replacer; 37 | public: 38 | inline FilterGray(unsigned char min,unsigned char max,unsigned char replacer):min(min),max(max),replacer(replacer) 39 | {} 40 | bool process(Img& img) const override; 41 | }; 42 | 43 | class FilterHSV:public ImageProcess<> { 44 | ImageUtils::ColorHSV start,end; 45 | ImageUtils::ColorRGB replacer; 46 | public: 47 | inline FilterHSV(ImageUtils::ColorHSV start,ImageUtils::ColorHSV end,ImageUtils::ColorRGB replacer):start(start),end(end),replacer(replacer) 48 | {} 49 | bool process(Img& img) const override; 50 | }; 51 | 52 | class FilterRGB:public ImageProcess<> { 53 | ImageUtils::ColorRGB start,end; 54 | ImageUtils::ColorRGB replacer; 55 | public: 56 | inline FilterRGB(ImageUtils::ColorRGB start,ImageUtils::ColorRGB end,ImageUtils::ColorRGB replacer):start(start),end(end),replacer(replacer) 57 | {} 58 | bool process(Img& img) const override; 59 | }; 60 | 61 | class PadBase:public ImageProcess<> { 62 | public: 63 | //fixed amount or proportion of width or height 64 | using pv=exlib::maybe_fixed; 65 | enum bases:unsigned int { 66 | width,height 67 | }; 68 | protected: 69 | exlib::maybe_fixed side1,side2,tolerance; 70 | unsigned char background; 71 | bool cumulative; 72 | inline PadBase(pv side1,pv side2,pv tol,unsigned char bg,bool cumulative):side1(side1),side2(side2),tolerance(tol),background(bg),cumulative(cumulative) 73 | {} 74 | }; 75 | 76 | class PadHoriz:public PadBase { 77 | public: 78 | using PadBase::pv; 79 | PadHoriz(pv left,pv right,pv tol,unsigned char bg,bool cumulative):PadBase(left,right,tol,bg,cumulative) 80 | {} 81 | bool process(Img& img) const override; 82 | }; 83 | 84 | class PadVert:public PadBase { 85 | public: 86 | using PadBase::pv; 87 | PadVert(pv top,pv bottom,pv tol,unsigned char bg,bool cumulative):PadBase(top,bottom,tol,bg,cumulative) 88 | {} 89 | bool process(Img& img) const override; 90 | }; 91 | 92 | class PadAuto:public ImageProcess<> { 93 | unsigned int vert,min_h,max_h; 94 | signed int hoff; 95 | float opt_rat; 96 | public: 97 | inline PadAuto(unsigned int vert,unsigned int min_h,unsigned int max_h,signed int hoff,float opt_rat) 98 | :vert(vert),min_h(min_h),max_h(max_h),hoff(hoff),opt_rat(opt_rat) 99 | {} 100 | bool process(Img& img) const override; 101 | }; 102 | 103 | class PadCluster:public ImageProcess<> { 104 | unsigned int left,right,top,bottom; 105 | unsigned char bt; 106 | public: 107 | inline PadCluster(unsigned int left,unsigned int right,unsigned int top,unsigned int bottom,unsigned char bt): 108 | left(left),right(right),bottom(bottom),top(top),bt(bt) 109 | {} 110 | bool process(Img& img) const override; 111 | }; 112 | 113 | class Crop:public ImageProcess<> { 114 | public: 115 | using mark=std::optional; 116 | private: 117 | int left,top; 118 | mark right,bottom; 119 | public: 120 | inline Crop(int l,int t,mark r,mark b):left(l),top(t),right(r),bottom(b) 121 | {} 122 | bool process(Img& img) const override; 123 | }; 124 | 125 | class Rescale:public ImageProcess<> { 126 | double val; 127 | int interpolation; 128 | public: 129 | enum rescale_mode { 130 | automatic=-2, 131 | raw_mem, 132 | boundary_fill, 133 | nearest_neighbor, 134 | moving_average, 135 | linear, 136 | grid, 137 | cubic, 138 | lanczos 139 | }; 140 | inline Rescale(double val,int interpolation): 141 | val(val), 142 | interpolation(interpolation==automatic?(val>1?cubic:moving_average):interpolation) 143 | {} 144 | bool process(Img& img) const override; 145 | }; 146 | 147 | class ExtractLayer0NoRealloc:public ImageProcess<> { 148 | public: 149 | bool process(Img& img) const override; 150 | }; 151 | 152 | /* 153 | class ClusterClearGray:public ImageProcess<> { 154 | unsigned int min,max; 155 | unsigned char background; 156 | float tolerance; 157 | bool eight_way; 158 | public: 159 | inline ClusterClearGray(unsigned int min,unsigned int max,unsigned char background,float tolerance,bool eight_way):min(min),max(max),background(background),tolerance(tolerance),eight_way(eight_way) 160 | {} 161 | bool process(Img& img) const override; 162 | }; 163 | */ 164 | 165 | class ClusterClearGrayAlt:public ImageProcess<> { 166 | unsigned char required_min,required_max; 167 | unsigned int min_size,max_size; 168 | unsigned char sel_min; 169 | unsigned char sel_max; 170 | unsigned char background; 171 | bool eight; 172 | public: 173 | inline ClusterClearGrayAlt(unsigned char rcmi,unsigned char rcma,unsigned int mis,unsigned mas,unsigned char smi,unsigned char sma,unsigned char back,bool eight): 174 | required_min(rcmi),required_max(rcma),min_size(mis),max_size(mas),sel_min(smi),sel_max(sma),background(back),eight(eight) 175 | {} 176 | bool process(Img& img) const override; 177 | }; 178 | 179 | class RescaleGray:public ImageProcess<> { 180 | unsigned char min,mid,max; 181 | public: 182 | inline RescaleGray(unsigned char min,unsigned char mid,unsigned char max=255):min(min),mid(mid),max(max) 183 | {} 184 | bool process(Img& img) const override; 185 | }; 186 | 187 | class FillRectangle:public ImageProcess<> { 188 | public: 189 | enum origin_reference { 190 | top_left,top_middle,top_right, 191 | middle_left,middle,middle_right, 192 | bottom_left,bottom_middle,bottom_right 193 | }; 194 | private: 195 | ImageUtils::Rectangle offsets; 196 | origin_reference origin; 197 | std::array color; 198 | unsigned int num_layers; 199 | public: 200 | FillRectangle(ImageUtils::Rectangle offsets,std::array color,unsigned int num_layers,origin_reference orgn):offsets(offsets),color(color),num_layers(num_layers),origin(orgn) 201 | { 202 | assert(origin>=top_left&&origin<=bottom_right); 203 | } 204 | bool process(Img& img) const override; 205 | }; 206 | 207 | class Blur:public ImageProcess<> { 208 | float radius; 209 | public: 210 | inline Blur(float radius):radius(radius) 211 | {} 212 | bool process(Img& img) const override; 213 | }; 214 | 215 | class Straighten:public ImageProcess<> { 216 | double pixel_prec; 217 | unsigned int num_steps; 218 | double min_angle,max_angle; 219 | unsigned char boundary; 220 | float gamma; 221 | bool use_horiz; 222 | public: 223 | inline Straighten(double pixel_prec,double min_angle,double max_angle,double angle_prec,unsigned char boundary,float gamma,bool use_horiz) 224 | :pixel_prec(pixel_prec), 225 | min_angle(M_PI_2+min_angle*DEG_RAD),max_angle(M_PI_2+max_angle*DEG_RAD), 226 | num_steps(std::ceil((max_angle-min_angle)/angle_prec)), 227 | boundary(boundary),gamma(gamma),use_horiz(use_horiz) 228 | {} 229 | bool process(Img& img) const override; 230 | }; 231 | 232 | class Rotate:public ImageProcess<> { 233 | public: 234 | enum interp_mode { 235 | nearest_neighbor, 236 | linear, 237 | cubic 238 | }; 239 | float angle; 240 | interp_mode mode; 241 | public: 242 | Rotate(float angle,interp_mode mode):angle(angle),mode(mode) 243 | {} 244 | bool process(Img& img) const override; 245 | }; 246 | 247 | class Gamma:public ImageProcess<> { 248 | float gamma; 249 | public: 250 | inline Gamma(float g):gamma(g) 251 | {} 252 | bool process(Img& img) const override; 253 | }; 254 | 255 | class ThreadOverride:public ImageProcess<> { 256 | unsigned int const* _num_threads; 257 | protected: 258 | inline ThreadOverride(unsigned int const* num_threads):_num_threads(num_threads) 259 | {} 260 | inline unsigned int num_threads() const 261 | { 262 | return *_num_threads; 263 | } 264 | }; 265 | 266 | class ShiftFixer:public ImageProcess<> { 267 | protected: 268 | bool side,direction;unsigned char background_threshold; 269 | inline ShiftFixer(bool side,bool direction,unsigned char bt):side(side),direction(direction),background_threshold(bt) 270 | {} 271 | }; 272 | 273 | class HorizontalShift:public ShiftFixer { 274 | public: 275 | HorizontalShift(bool eval_right,bool from_bottom,unsigned char bt):ShiftFixer(eval_right,from_bottom,bt) 276 | {} 277 | bool process(Img&) const override; 278 | }; 279 | 280 | class VerticalShift:public ShiftFixer { 281 | public: 282 | inline VerticalShift(bool eval_bottom,bool from_right,unsigned char bt):ShiftFixer(eval_bottom,from_right,bt) 283 | {} 284 | bool process(Img&) const override; 285 | }; 286 | 287 | class RescaleAbsolute:public ImageProcess<> { 288 | unsigned int width; 289 | unsigned int height; 290 | float ratio; 291 | Rescale::rescale_mode mode; 292 | public: 293 | RescaleAbsolute(unsigned int width,unsigned int height,float ratio,Rescale::rescale_mode mode):width{width},height{height},ratio{ratio},mode{mode}{} 294 | bool process(Img&) const override; 295 | }; 296 | 297 | class ChangeCanvasSize:public ImageProcess<> { 298 | int width; 299 | int height; 300 | FillRectangle::origin_reference origin; 301 | unsigned char fill; 302 | public: 303 | ChangeCanvasSize(int width, int height, FillRectangle::origin_reference origin, unsigned char fill = 255):width{width}, height{height}, origin{origin}, fill{fill}{} 304 | bool process(Img&) const override; 305 | }; 306 | 307 | class MLAA:public ImageProcess<> { 308 | double gamma; 309 | unsigned char contrast_threshold; 310 | public: 311 | MLAA(double gamma,unsigned char contrast_threshold):gamma{gamma},contrast_threshold{contrast_threshold}{} 312 | bool process(Img&) const override; 313 | }; 314 | 315 | class NeuralScale:public ThreadOverride { 316 | ScoreProcessor::neural_scaler scaler; 317 | float ratio; 318 | public: 319 | inline NeuralScale(float ratio,char const* network,unsigned int const* num_threads):ratio(ratio),scaler(network),ThreadOverride(num_threads) 320 | {} 321 | bool process(Img&) const override; 322 | }; 323 | 324 | class TemplateMatchErase:public ImageProcess<> { 325 | cil::CImg tmplt; 326 | float threshold; 327 | public: 328 | TemplateMatchErase(char const* filename,float threshold):tmplt(filename),threshold{threshold}{} 329 | bool process(Img&) const override; 330 | }; 331 | class SlidingTemplateMatchEraseExact:public ImageProcess<> { 332 | std::vector> tmplts; 333 | unsigned int downscaling; 334 | float threshold; 335 | decltype(tmplts) downsized_tmplts; 336 | ImageUtils::Rectangle offsets; 337 | FillRectangle::origin_reference origin; 338 | std::function replacer; 339 | static auto get_downsized(decltype(tmplts)& t,unsigned int scale) 340 | { 341 | decltype(tmplts) down; 342 | down.reserve(t.size()); 343 | if(scale==1) 344 | { 345 | for(auto& tmplt:t) 346 | { 347 | down.push_back({tmplt,true}); 348 | } 349 | } 350 | else 351 | { 352 | for(auto& tmplt:t) 353 | { 354 | down.push_back(integral_downscale(tmplt,scale)); 355 | } 356 | } 357 | return down; 358 | } 359 | public: 360 | SlidingTemplateMatchEraseExact(decltype(tmplts) the_tmplts,unsigned int downscaling,float threshold,decltype(replacer) replacer,decltype(offsets) off,decltype(origin) or): 361 | tmplts(std::move(the_tmplts)), 362 | downscaling{downscaling}, 363 | threshold{threshold}, 364 | downsized_tmplts(get_downsized(tmplts,downscaling)), 365 | replacer{std::move(replacer)}, 366 | offsets{off}, 367 | origin{or} 368 | { 369 | } 370 | bool process(Img&) const override; 371 | }; 372 | 373 | class PyramidTemplateErase:public ImageProcess<> { 374 | std::vector> tmplts; 375 | std::size_t num_images; 376 | std::vector scales; 377 | float threshold; 378 | std::function replacer; 379 | void verify_scales() 380 | { 381 | if(scales.size()==0) 382 | { 383 | throw std::invalid_argument("At least 1 scale required"); 384 | } 385 | scales.erase(std::unique(scales.begin(),scales.end()),scales.end()); 386 | std::sort(scales.begin(),scales.end(),std::greater<>{}); 387 | for(auto const scale:scales) 388 | { 389 | if(scale==0) 390 | { 391 | throw std::invalid_argument("Scale cannot be zero"); 392 | } 393 | } 394 | for(std::size_t i=1;i get_downscale(cil::CImg& img,unsigned int downscale) 403 | { 404 | if(downscale==1) 405 | { 406 | return {img,true}; 407 | } 408 | return integral_downscale(img,downscale); 409 | } 410 | public: 411 | PyramidTemplateErase(std::string_view const* tmplt_names,std::size_t n,decltype(scales) scale_factors,float threshold,decltype(replacer) replacer): 412 | scales(std::move(scale_factors)),num_images{n},threshold{threshold},replacer{std::move(replacer)} 413 | { 414 | verify_scales(); 415 | tmplts.reserve(n*scales.size()); 416 | std::unique_ptr string_buffer(new char[std::max_element(tmplt_names,tmplt_names+n,[](auto a,auto b) 417 | { 418 | return a.size()size()+1]); 420 | for(size_t i=0;i orig(string_buffer.get()); 426 | for(size_t j=0;j { 438 | unsigned int min_space; 439 | unsigned int max_presence; 440 | unsigned char background_threshold; 441 | public: 442 | RemoveEmptyLines(unsigned int ms,unsigned int mp,unsigned char bt):min_space{ms},max_presence{mp},background_threshold{bt}{} 443 | bool process(Img&) const override; 444 | }; 445 | 446 | class VertCompress:public ImageProcess<> { 447 | unsigned int min_vert_space; 448 | unsigned int min_horiz_space; 449 | unsigned int min_horizontal_protection; 450 | unsigned int max_vertical_protection; 451 | unsigned char background_threshold; 452 | bool only_straight_paths; 453 | unsigned int staff_line_length; 454 | unsigned int min_staff_separation; 455 | public: 456 | VertCompress(unsigned int min_vert_space,unsigned int min_horiz_space,unsigned int min_horiz_prot,unsigned int max_vert_prot,unsigned char bt,bool only_straight,unsigned int staff_line_length,unsigned int min_staff_separation): 457 | min_vert_space{min_vert_space}, 458 | min_horiz_space{min_horiz_space==-1?min_vert_space:min_horiz_space}, 459 | min_horizontal_protection{min_horiz_prot}, 460 | max_vertical_protection{max_vert_prot}, 461 | background_threshold{bt}, 462 | only_straight_paths{only_straight}, 463 | staff_line_length{staff_line_length}, 464 | min_staff_separation{min_staff_separation}{} 465 | bool process(Img&) const override; 466 | }; 467 | 468 | class ResizeToBound:public ImageProcess<> { 469 | protected: 470 | unsigned int width; 471 | unsigned int height; 472 | bool pad; 473 | unsigned char fill; 474 | Rescale::rescale_mode mode; 475 | float gamma; 476 | public: 477 | ResizeToBound(unsigned int width, unsigned int height, bool pad=false, unsigned char fill=255,Rescale::rescale_mode mode=Rescale::automatic, float gamma=2): 478 | width(width),height(height),pad(pad),fill(fill),mode(mode),gamma(gamma){} 479 | 480 | bool process(Img&) const override; 481 | }; 482 | 483 | class SquishToBound:public ResizeToBound { 484 | public: 485 | using ResizeToBound::ResizeToBound; 486 | bool process(Img&) const override; 487 | }; 488 | 489 | class ClusterWiden:public ImageProcess<> { 490 | unsigned char _lower_bound; 491 | unsigned char _upper_bound; 492 | unsigned int _widen_to; 493 | Rescale::rescale_mode _mode; 494 | float _gamma; 495 | public: 496 | ClusterWiden(unsigned char lower_bound, unsigned char upper_bound, unsigned int widen_to, Rescale::rescale_mode mode, float gamma) noexcept: 497 | _lower_bound(lower_bound),_upper_bound(upper_bound),_widen_to(widen_to),_mode(mode),_gamma(gamma) 498 | {} 499 | bool process(Img&) const override; 500 | }; 501 | 502 | class Invert:public ImageProcess<> { 503 | public: 504 | Invert() {} 505 | bool process(Img&) const override; 506 | }; 507 | 508 | class WhiteToTransparent:public ImageProcess<> { 509 | public: 510 | WhiteToTransparent() {} 511 | bool process(Img&) const override; 512 | }; 513 | 514 | class FloodFill:public ImageProcess<> { 515 | unsigned char _lower_bound; 516 | unsigned char _upper_bound; 517 | std::array _replacer_color; 518 | unsigned int _replacer_num_layers; 519 | ImageUtils::Rectangle _region; 520 | FillRectangle::origin_reference _origin; 521 | public: 522 | FloodFill(unsigned char lower_bound,unsigned char upper_bound,std::array replacer,unsigned int replacer_num_layers,ImageUtils::Rectangle start_region,FillRectangle::origin_reference origin): 523 | _lower_bound(lower_bound), 524 | _upper_bound(upper_bound), 525 | _replacer_color(replacer), 526 | _replacer_num_layers(replacer_num_layers), 527 | _region(start_region), 528 | _origin(origin) 529 | {} 530 | bool process(Img&) const override; 531 | }; 532 | 533 | class FlipHorizontal:public ImageProcess<> { 534 | public: 535 | FlipHorizontal() 536 | {} 537 | bool process(Img&) const override; 538 | }; 539 | 540 | class FlipVertical:public ImageProcess<> { 541 | public: 542 | FlipVertical() 543 | {} 544 | bool process(Img&) const override; 545 | }; 546 | 547 | class NormalizeBrightness:public ImageProcess<> { 548 | unsigned char _median; 549 | unsigned char _select_lower_bound; 550 | unsigned char _select_upper_bound; 551 | public: 552 | NormalizeBrightness(unsigned char median, unsigned char select_lb, unsigned char select_ub): 553 | _median(median), 554 | _select_lower_bound(select_lb), 555 | _select_upper_bound(select_ub) 556 | {} 557 | bool process(Img&) const override; 558 | }; 559 | 560 | class MedianAdaptiveThreshold:public ImageProcess<> { 561 | unsigned int _window_width; 562 | unsigned int _window_height; 563 | int _median_adjustment; 564 | float _gamma; 565 | unsigned char _replacer; 566 | public: 567 | MedianAdaptiveThreshold(unsigned int window_width, unsigned int window_height, int median_adjustment, unsigned char replacer, float gamma): 568 | _window_width(window_width), 569 | _window_height(window_height), 570 | _median_adjustment(median_adjustment), 571 | _gamma(gamma), 572 | _replacer(replacer) 573 | {} 574 | bool process(Img&) const override; 575 | }; 576 | 577 | class HathiCorrect:public ImageProcess<> { 578 | public: 579 | HathiCorrect(){} 580 | bool process(Img&) const override; 581 | }; 582 | } 583 | #endif -------------------------------------------------------------------------------- /ScoreProcessor/Interface.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | #include "Interface.h" 3 | 4 | namespace ScoreProcessor { 5 | namespace Output { 6 | decltype(maker) 7 | maker("Specifies the output format\n" 8 | "pattern: the output template, see below; tags: o, out, p, pat\n" 9 | "move: whether to copy or move files (ignored by multi); tags: m, mv, move\n" 10 | "overwrite: allow overwrite existing files; tags: overwrite, ow\n" 11 | "recurse: whether to recursively make folders for output; tags: r, rec\n" 12 | "template specifiers:\n" 13 | " %w filename and extension\n" 14 | " %c entire filename\n" 15 | " %p path without trailing slash\n" 16 | " %0 numbers 0-9 indicate index with number of padding\n" 17 | " %f filename\n" 18 | " %x extension\n" 19 | " %% literal percent\n" 20 | "Double the starting dash if you want the file to start with a dash.", 21 | "Output", 22 | "pattern=%w move=false overwrite=true recurse=true"); 23 | } 24 | 25 | namespace NumThreads { 26 | MakerTFull> maker("Controls number of threads, will not exceed number of files", "Number of Threads", "num"); 27 | } 28 | 29 | namespace Verbosity { 30 | MakerTFull maker("Changes verbosity of output: Silent=0=s, Errors-only=1=e, Count=2=c (default), Loud=3=l", "Verbosity", "level"); 31 | } 32 | 33 | namespace StrMaker { 34 | decltype(maker) 35 | maker("Straightens the image\n" 36 | "min angle: minimum angle to consider rotation; tags: mn, min, mna\n" 37 | "max angle: maximum angle to consider rotation; tags: mx, max, mxa\n" 38 | "angle prec: quantization of angles to consider; tags: a, ap\n" 39 | "pixel prec: pixels this close are considered the same; tags: p, pp\n" 40 | "boundary, vertical transition across this is considered an edge; tags: b\n" 41 | "gamma: gamma correction applied; tags: g, gam\n" 42 | "use horiz: whether to use horizontal or vertical lines to determine angle", 43 | "Straighten", 44 | "min_angle=-5 max_angle=5 angle_prec=0.1 pixel_prec=1 boundary=128 gamma=2 use_horiz=t"); 45 | } 46 | 47 | namespace CGMaker { 48 | SingMaker maker("Converts the image to grayscale", "Convert to Grayscale", ""); 49 | } 50 | 51 | namespace FRMaker { 52 | SingMaker, IntegerParser, Right, Bottom, Color, Origin> 53 | maker("Fills in a rectangle of specified color\n" 54 | "left: left coord of rectangle; tags: l, left\n" 55 | "top: top coord of rectangle; tags: t, top\n" 56 | "horiz: right coord or width, defaults to right coord; tags: r, right, w, width\n" 57 | "vert: bottom coord or height, defaults to bottom coord; tags: b, bottom, h, height\n" 58 | "color: color to fill with, can be grayscale, rgb, or rgba with comma-separated values;\n" 59 | " tags: clr, color\n" 60 | "origin: origin from which coords are taken, +y is always down, +x is always right,\n" 61 | " combination of t,m,b and l,m,r, e.g. tl for top left; tags: o, or", 62 | "Fill Rectangle", 63 | "left top horiz vert color=255 origin=tl"); 64 | } 65 | 66 | namespace GamMaker { 67 | Maker maker; 68 | } 69 | 70 | namespace RotMaker { 71 | SingMaker maker( 72 | "Rotates the image\n" 73 | "angle: angle to rotate the image, ccw is positive, defaults to degrees; tags: d, deg, r, rad\n" 74 | "interpolation_mode: see below; tags: i, im, m\n" 75 | "gamma: gamma correction for rotation; tags: g, gam\n" 76 | "Modes are:\n" 77 | " nearest neighbor\n" 78 | " linear\n" 79 | " cubic\n" 80 | "To specify mode, type the first letter of the mode; other characters are ignored", 81 | "Rotate", 82 | "angle mode=cubic gamma=2"); 83 | } 84 | 85 | namespace RsMaker { 86 | SingMaker, Mode, RotMaker::GammaParser> 87 | maker("Rescales image by given factor\n" 88 | "factor: factor to scale image by; tags: f, fact\n" 89 | "interpolation_mode: see below; tags: i, im\n" 90 | "gamma: gamma correction applied; tags: g, gam\n" 91 | "Rescale modes are:\n" 92 | " auto (moving average if downscaling, else cubic)\n" 93 | " nearest neighbor\n" 94 | " moving average\n" 95 | " linear\n" 96 | " grid\n" 97 | " cubic\n" 98 | " lanczos\n" 99 | "To specify mode, type as many letters as needed to unambiguously identify mode", 100 | "Rescale", 101 | "factor interpolation_mode=auto gamma=2"); 102 | } 103 | 104 | namespace FGMaker { 105 | SingMaker, 107 | IntegerParser, 108 | IntegerParser> maker( 109 | "Replacers pixels with brightness [min,max] with replacer\n" 110 | "min: minimum brightness to replace; tags: mn, min, mnv\n" 111 | "max: maximum brightness to replace; tags: mx, max, mxv\n" 112 | "replacer: color to replacer with; tags: r, rep", 113 | "Filter Gray", 114 | "min max=255 replacer=255"); 115 | } 116 | 117 | namespace BSel { 118 | BoundSelMaker maker; 119 | } 120 | 121 | namespace List { 122 | MakerTFull maker("Makes program list out files", "List Files", ""); 123 | } 124 | 125 | namespace SIMaker { 126 | MakerTFull> maker("Indicates the starting index to number files", "Starting index", "index"); 127 | } 128 | 129 | namespace RgxFilter { 130 | MakerTFull maker( 131 | "Filters the input files by a regex pattern\n" 132 | "regex: pattern\n" 133 | "keep_match: whether matches are kept or removed", 134 | "Filter", 135 | "pattern keep_match"); 136 | } 137 | 138 | namespace CCGMaker { 139 | MakerTFull, EightWay> maker( 140 | "Clears clusters of specific constraints\n" 141 | "required_color_range: clusters that do not contains a color in this range are replaced;\n" 142 | " tags: rcr\n" 143 | "bad_size_range: clusters within this size range are replaced; tags: bsr\n" 144 | "selection_range: pixels in this color range will be clustered; tags: sr\n" 145 | "replacement_color: chosen colors are replaced by this color; tags: rc, bc\n" 146 | "eight_way: whether pixels are clustered in eight way (instead of four ways)\n; tags: ew" 147 | , 148 | "Cluster Clear Gray", 149 | "required_color_range=0,255 bad_size_range=0,0 sel_range=0,200 repl_color=255 eight_way=false"); 150 | } 151 | 152 | namespace BlurMaker { 153 | SingMaker, RotMaker::GammaParser> maker( 154 | "Does a gaussian blur of given standard deviation\n" 155 | "st_dev: standard deviation of blur\n" 156 | "gamma: gamma correction applied", 157 | "Blur", 158 | "st_dev gamma=2"); 159 | } 160 | 161 | namespace EXLMaker { 162 | SingMaker maker("Extracts the first layer with no reallocation (cheap convert to grayscale)", "Extract First Layer", ""); 163 | } 164 | 165 | namespace CTMaker { 166 | SingMaker maker( 167 | "Mixes transparent pixels with the given rgb color" 168 | "red tags: r, red\n" 169 | "green tags: g, green\n" 170 | "blue tags: b, blue\n" 171 | "A value may be given or you can specify that one channel should be equal to another", 172 | "Cover Transparency", 173 | "red=255 green=r blue=r"); 174 | } 175 | 176 | namespace RBMaker { 177 | SingMaker> maker( 178 | "Flood fills pixels from edge with tolerance of black with white\n" 179 | "Neither reliable nor safe and you should probably not use it", 180 | "Remove Border (DANGER)", 181 | "tolerance=0.9"); 182 | } 183 | 184 | namespace HPMaker { 185 | decltype(maker) maker( 186 | "Pads the left and right sides of image.\n" 187 | "left: left padding, use k to keep padding, or r to assign equal to right,\n" 188 | " use lpw or lph to calculate it as a proportion of width or height respectively;\n" 189 | " tags: l, left, lph, lpw\n" 190 | "right: right padding, use k to keep padding, or l to assign equal to left,\n" 191 | " use rpw or rph to calculate it as a proportion of width or height respectively;\n" 192 | " tags: r, right, rph, rpw\n" 193 | "tolerance: this many pixels below background threshold is considered the side,\n" 194 | " use tpw or tph to calculate it as a proportion of width or height respectively;\n" 195 | " tags: tol, tph, tpw\n" 196 | "background_threshold: see tolerance; tags: bg\n" 197 | "cumulative: whether pixels in columns are counted cumulatively; tags: cm, cum", 198 | "Horizontal Padding", 199 | "left right=l tolerance=0.005 background_threshold=128 cumulative=true" 200 | ); 201 | } 202 | 203 | namespace VPMaker { 204 | decltype(maker) maker( 205 | "Pads the top and bottom sides of image.\n" 206 | "top: top padding, use k to keep padding, or b to assign equal to bottom,\n" 207 | " use lpw or lph to calculate it as a proportion of width or height respectively;\n" 208 | " tags: t, top, tph, tpw\n" 209 | "bottom: bottom padding, use k to keep padding, or t to assign equal to top,\n" 210 | " use rpw or rph to calculate it as a proportion of width or height respectively;\n" 211 | " tags: b, bottom, bot, bph, bpw\n" 212 | "tolerance: this many pixels below background threshold is considered the side,\n" 213 | " use tpw or tph to calculate it as a proportion of width or height respectively;\n" 214 | " tags: tol, tph, tpw\n" 215 | "background_threshold: see tolerance; tags: bg\n" 216 | "cumulative: whether pixels in rows are counted cumulatively; tags: cm, cum", 217 | "Vertical Padding", 218 | "top bottom=t tolerance=0.005 background_threshold=128 cumulative=true" 219 | ); 220 | } 221 | 222 | namespace RCGMaker { 223 | SingMaker, UCharParser, UCharParser> maker( 224 | "Colors are scaled such that values less than or equal to min become 0,\n" 225 | "and values greater than or equal to max becomes 255.\n" 226 | "They are scaled based on their distance from mid.\n" 227 | "min tags: mn, min\n" 228 | "mid tags: md, mid\n" 229 | "max tags: mx, max", 230 | "Rescale Brightness", 231 | "min mid max=255" 232 | ); 233 | } 234 | 235 | namespace HSMaker { 236 | SingMaker maker("specific to a problem with my scanner; don't use", "Horizontal Shift", "side direction background=128"); 237 | } 238 | 239 | namespace VSMaker { 240 | SingMaker maker("specific to a problem with my scanner; don't use", "Vertical Shift", "side direction background=128"); 241 | } 242 | 243 | namespace SpliceMaker { 244 | decltype(maker) maker( 245 | "Splices the pages together assuming right alignment.\n" 246 | "Knuth algorithm that tries to minimize deviation from optimal height and optimal padding.\n" 247 | "horiz_pad: horizontal space given between score elements; tags: hp, hppw, hpph\n" 248 | "opt_pad: optimal padding between pages, see below; tags: op, oppw, opph\n" 249 | "min_pad: minimum padding between pages; tags: mp, mppw, mpph\n" 250 | "opt_hgt: optimal height of pages, see below; tags: oh, ohpw, ohph\n" 251 | "excs_wgt: penalty weight applied to height deviation above optimal, see below; tags: exw, ew\n" 252 | "pad_wgt: weight applied to padding deviation, see below; tags: pw\n" 253 | "bg: background threshold to determine kerning; tags: bg\n" 254 | "divider: divider between pages; tags: div\n" 255 | "pw or ph at end of tags indicates value is taken as proportion of width or height, respectively\n" 256 | "if untagged, % indicates percentage of width taken, otherwise fixed amount\n" 257 | "Cost function is\n" 258 | "if(height>opt_height)\n" 259 | " (excess_weight*(height-opt_height)/opt_height)^3+\n" 260 | " (pad_weight*abs_dif(padding,opt_padding)/opt_padding)^3\n" 261 | "else\n" 262 | " ((opt_height-height)/opt_height)^3+\n" 263 | " (pad_weight*abs_dif(padding,opt_padding)/opt_padding)^3\n" 264 | "Dimensions are taken from the first page.", 265 | "Splice", 266 | "horiz_pad=3% opt_pad=5% min_pad=1.2% opt_hgt=55% excs_wgt=10 pad_wgt=1 bg=128 divider=\"\""); 267 | } 268 | 269 | namespace CutMaker { 270 | MakerTFull< 271 | UseTuple, 272 | MultiCommand, 273 | pv_parser, pv_parser, pv_parser, FloatParser, UCharParser> maker 274 | ( 275 | "Cuts the image into separate systems\n" 276 | "min_width: pixel groups under this width are not considered a system;\n" 277 | " tags: mw, mwpw, mwph\n" 278 | "min_height: pixel groups under this height are not considered a system;\n" 279 | " tags: mh, mhpw, mhph\n" 280 | "min_vert_space: a vertical space under this height is not considered a system division\n" 281 | " tags: mv, mvpw, mvph\n" 282 | "horiz_weight: energy rating compared to vertical space to consider spacing;\n" 283 | " tags: hw\n" 284 | "bg: colors less than or equal this brightness can be considered part of a system; tags: bg\n" 285 | "pw or ph at end of tags indicates value is taken as proportion of width or height,respectively\n" 286 | "if untagged, % indicates percentage of width taken, otherwise fixed amount", 287 | "Cut", 288 | "min_width=66% min_height=8% horiz_weight=20 min_vert_space=0 bg=128"); 289 | } 290 | 291 | namespace SmartScale { 292 | SingMaker, Input> maker( 293 | "Scales using an neural network\n" 294 | "factor tag: f\n" 295 | "network_path tag: net\n", 296 | "Smart Scale", 297 | "factor network_path=(search program directory for first network)"); 298 | } 299 | 300 | namespace Cropper { 301 | decltype(maker) maker( 302 | "Crops the image\n" 303 | "tags: l; t; r, w; b, h", 304 | "Crop", 305 | "left top horizontal vertical"); 306 | } 307 | 308 | namespace Quality { 309 | decltype(maker) maker("Set the quality of the save file [0,100], only affects jpegs", "Quality", "quality"); 310 | } 311 | 312 | namespace RescaleAbsoluteMaker { 313 | decltype(maker) maker{ 314 | "Rescale to an absolute width and height\n" 315 | "Give two out of width height and ratio\n" 316 | "width: the width to resize image; tags: w, width\n" 317 | "height: the height to resize image; tags: h, height\n" 318 | "ratio: the ratio of width to height to resize; tags: r, rat, ratio\n" 319 | "mode: resize interpolation mode; see -rs rescale for tags and info\n" 320 | "gamma: resize gamma; see -rs rescale for tags and info" 321 | ,"Rescale Absolute", 322 | "width height ratio=preserve mode=automatic gamma=2"}; 323 | } 324 | 325 | namespace CCSMaker { 326 | decltype(maker) maker{ 327 | "Change the canvas size (see paint.net)\n" 328 | "width; tags: w, width\n" 329 | "height; tags: h, height\n" 330 | "origin: see -fr, Fill Rectangle; tags: o, or", 331 | "Change Canvas Size", 332 | "width=preserve height=preserve origin=tl" 333 | }; 334 | } 335 | 336 | namespace MlaaMaker { 337 | decltype(maker) maker{ 338 | "Morphological Anti-Aliasing\n" 339 | "contrast_threshold: threshold considered an edge; tags: c, ct\n" 340 | "gamma: gamma correction when blending; see -rs rescale for tags", 341 | "Morphological AA", 342 | "contrast_threshold=128 gamma=2" 343 | }; 344 | } 345 | 346 | namespace TemplateClearMaker { 347 | decltype(maker) maker{ 348 | "Cluster Template Match Erase\n" 349 | "finds clusters in an image, compares them to template to erase matches\n" 350 | "template: what to match\n" 351 | "threshold: correspondence threshold to erase", 352 | "Cluster Match Erase", 353 | "template_file_name threshold=0.95" 354 | }; 355 | } 356 | namespace SlidingTemplateClearMaker { 357 | decltype(maker) maker{ 358 | "slides a template across a downscaled version of the image to erase matches\n" 359 | "template: what to match, separate multiple files with *; tags: name, nm, tnm\n" 360 | "downscale: the downscale ratio of the image used to compare the templates; tags: dsf, fact, f\n" 361 | "thresh: correspondence threshold to erase; tags: th, thr\n" 362 | "replace: what to do with a match\n" 363 | " mff:upper_threshold, mutual flood fill, flood fills corresponding parts\n" 364 | " rpl: , replace match with image\n" 365 | " fill:color, fill match with color\n" 366 | "left,top,horiz,vert,origin: region to limit matching to; see -fr, fill rectangle", 367 | "Sliding Erase Downscale Match", 368 | "template_file_names downscale thresh=0.95 replace=fill:255 left=-99999 top=-99999 horiz=r:99999 vert=b:99999 origin=tl" 369 | }; 370 | } 371 | namespace RemoveEmptyLinesMaker { 372 | decltype(maker) maker{ 373 | "Lines that contain fewer non-background pixels than max presence\n" 374 | "are removed if their removal does not result in a space smaller\n" 375 | "than min space\n" 376 | "min space; tag: ms\n" 377 | "max presence; tag: mp\n" 378 | "background threshold; tag: bg", 379 | "Remove Empty Lines", 380 | "min_space max_presence=5 background_threshold=128" 381 | }; 382 | } 383 | 384 | namespace VerticalCompressMaker { 385 | decltype(maker) maker{ 386 | "Removes lines where there is a lot of vertical space.\n" 387 | "All but the largest cluster (as determined by background)\n" 388 | "are protected. Pixels of the largest cluster through which\n" 389 | "a long horizontal path or only a short vertical path\n" 390 | "are protected.\n" 391 | "If there are more than staff_line_length pixels in a row,\n" 392 | "it is treated as a staff line, and min_staff_separation\n" 393 | "will be maintained between such lines.\n" 394 | "min vertical space; tag: ms, mvs\n" 395 | "min horizontal protection; tag: mhp\n" 396 | "max vertical protection; tag: mvp\n" 397 | "background threshold; tag: bg\n" 398 | "min horizontal space; tag: mhs\n" 399 | "staff line length; tag: sll\n" 400 | "min staff separation; tag: mss\n", 401 | "Vertical Compress", 402 | "min_vert_space min_horiz_pr max_vert_pr background=128 min_horiz_space=mvs only_straight_lines=f staff_line_length=-1 min_staff_separation=-1" 403 | }; 404 | } 405 | 406 | namespace ResizeToBoundMaker { 407 | decltype(maker) maker{ 408 | "Forces image to fit in given size bounds,\n" 409 | "optionally padding image\n" 410 | "width; tags: w, width\n" 411 | "height; tags: h, height\n" 412 | "fill; tags: fill:color, pad:t or f; pad:t equivalent to fill:255\n" 413 | "mode; see -rs\n" 414 | "gamma; see -rs", 415 | "Resize to Bound", 416 | "width height fill=pad:f interpolation_mode=auto gamma=2" 417 | }; 418 | } 419 | 420 | namespace ClusterWidenMaker { 421 | decltype(maker) maker{ 422 | "Widens widest cluster to certain width" 423 | "width; tags: w, wt\n" 424 | "lower_bound: cluster color lower bound; tags: l, lb\n" 425 | "upper_bound: cluster color upper bound; tags: l, lb\n" 426 | "mode; see -rs\n" 427 | "gamma; see -rs", 428 | "Cluster Widen", 429 | "width lower_bound upper_bound interpolation_mode=auto gamma=2" 430 | }; 431 | } 432 | 433 | namespace ClusterPaddingMaker { 434 | decltype(maker) maker{ 435 | "Cluster Padding", 436 | "Cluster Padding", 437 | "l r t b bg" 438 | }; 439 | } 440 | 441 | namespace InvertMaker { 442 | decltype(maker) maker{ 443 | "Invert", 444 | "Invert", 445 | "no arguments" 446 | }; 447 | } 448 | 449 | namespace WhiteToTransparentMaker { 450 | decltype(maker) maker{ 451 | "Converts White Pixels to Transparent Pixels", 452 | "White to Transparent", 453 | "no arguments" 454 | }; 455 | } 456 | 457 | namespace FloodFillMaker { 458 | decltype(maker) maker{ 459 | "Flood fill seed at selected rectangle, and replacing pixels in required color range with replacer" 460 | "left, right, horiz, vert, origin, replacer; see -fr fill rectangle\n" 461 | "required_color_range: pixels of this brightness replaced; see -ccg cluster clear gray", 462 | "Flood Fill", 463 | "left right horiz=width:1 vert=height:1 origin=tl required_color_range=0,254 replacer=255" 464 | }; 465 | } 466 | 467 | namespace FlipVerticalMaker { 468 | decltype(maker) maker{ 469 | "Flips Image Vertically (across x-axis)", 470 | "Flip Vertical", 471 | "no arguments" 472 | }; 473 | } 474 | 475 | namespace FlipHorizontalMaker { 476 | decltype(maker) maker{ 477 | "Flips Images Horizontal (across y-axis)", 478 | "Flip Horizontal", 479 | "no arguments" 480 | }; 481 | } 482 | namespace NormalizeBrightnessMaker { 483 | decltype(maker) maker{ 484 | "Lowers median brightness of select pixel to desired", 485 | "Normalize Brightness", 486 | "median select_upper_brightness select_lower_brightness" 487 | }; 488 | } 489 | 490 | namespace MedianAdaptiveFilter { 491 | decltype(maker) maker{ 492 | "Median Adaptive Filter\n" 493 | "window_width: the width of window to take the median from, if <= 0: window_height; tags: w, wwidth, ww\n" 494 | "window_height: the height of window to take the median from, if <= 0: window_width; tags: h, wheight, wh\n" 495 | "median_adjustment: pixels >= median + this_value replaced; tags: adj, madj\n" 496 | "gamma: gamma adjustment for brightness; tags: g, gam\n" 497 | "replacer: color to replace with; tags: r, rep", 498 | "Median Adaptive Filter", 499 | "window_width=0 window_height=0 median_adjustment=0 gamma=0.5 replacer=255" 500 | }; 501 | } 502 | 503 | namespace HathiCorrectMaker { 504 | decltype(maker) maker{ 505 | "HathiCorrect", 506 | "", 507 | "" 508 | }; 509 | } 510 | } --------------------------------------------------------------------------------