diff --git a/CMakeLists.txt b/CMakeLists.txt index d812e89..ac66099 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ set(Dithering_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/blue_noise.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/image.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/arg_parse.cpp ) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic") diff --git a/src/arg_parse.cpp b/src/arg_parse.cpp new file mode 100644 index 0000000..1ed1746 --- /dev/null +++ b/src/arg_parse.cpp @@ -0,0 +1,76 @@ +#include "arg_parse.hpp" + +#include +#include + +Args::Args() + : generate_blue_noise_(false), + use_opencl_(true), + overwrite_file_(false), + blue_noise_size_(32), + threads_(4), + output_filename_("output.png") {} + +void Args::DisplayHelp() { + std::cout + << "[-h | --help] [-b | --blue-noise ] [--usecl | " + "--nousecl]\n" + " -h | --help\t\t\t\tDisplay this help text\n" + " -b | --blue-noise \tGenerate blue noise square with " + "size\n" + " --usecl | --nousecl\t\t\tUse/Disable OpenCL (enabled by default)\n" + " -t | --threads \t\tUse CPU thread count when not using " + "OpenCL\n" + " -o | --output \tOutput filename to use\n" + " --overwrite\t\t\t\tEnable overwriting of file (default disabled)\n"; +} + +bool Args::ParseArgs(int argc, char **argv) { + --argc; + ++argv; + while (argc > 0) { + if (std::strcmp(argv[0], "-h") == 0 || + std::strcmp(argv[0], "--help") == 0) { + DisplayHelp(); + return true; + } else if (std::strcmp(argv[0], "--usecl") == 0) { + use_opencl_ = true; + } else if (std::strcmp(argv[0], "--nousecl") == 0) { + use_opencl_ = false; + } else if (std::strcmp(argv[0], "--overwrite") == 0) { + overwrite_file_ = true; + } else if (argc > 1 && (std::strcmp(argv[0], "-b") == 0 || + std::strcmp(argv[0], "--blue-noise") == 0)) { + generate_blue_noise_ = true; + blue_noise_size_ = std::strtoul(argv[1], nullptr, 10); + if (blue_noise_size_ == 0) { + std::cout << "ERROR: Failed to parse size for blue-noise" << std::endl; + generate_blue_noise_ = false; + } + --argc; + ++argv; + } else if (argc > 1 && (std::strcmp(argv[0], "-t") == 0 || + std::strcmp(argv[0], "--threads") == 0)) { + threads_ = std::strtoul(argv[1], nullptr, 10); + if (threads_ == 0) { + std::cout << "ERROR: Failed to parse thread count, using 4 by default" + << std::endl; + threads_ = 4; + } + --argc; + ++argv; + } else if (argc > 1 && (std::strcmp(argv[0], "-o") == 0 || + std::strcmp(argv[0], "--output") == 0)) { + output_filename_ = std::string(argv[1]); + --argc; + ++argv; + } else { + std::cout << "WARNING: Ignoring invalid input \"" << argv[0] << "\"" + << std::endl; + } + --argc; + ++argv; + } + + return false; +} diff --git a/src/arg_parse.hpp b/src/arg_parse.hpp new file mode 100644 index 0000000..ea6c63b --- /dev/null +++ b/src/arg_parse.hpp @@ -0,0 +1,22 @@ +#ifndef DITHERING_ARG_PARSE_HPP_ +#define DITHERING_ARG_PARSE_HPP_ + +#include + +struct Args { + Args(); + + static void DisplayHelp(); + + /// Returns true if help was printed + bool ParseArgs(int argc, char **argv); + + bool generate_blue_noise_; + bool use_opencl_; + bool overwrite_file_; + unsigned int blue_noise_size_; + unsigned int threads_; + std::string output_filename_; +}; + +#endif diff --git a/src/blue_noise.cpp b/src/blue_noise.cpp index 32bc0b2..a57f322 100644 --- a/src/blue_noise.cpp +++ b/src/blue_noise.cpp @@ -141,12 +141,13 @@ std::vector dither::internal::blue_noise_impl(int width, int heigh #ifndef NDEBUG internal::write_filter(filter_out, width, "filter_out_start.pgm"); #endif + std::cout << "Begin BinaryArray generation loop\n"; while(true) { -//#ifndef NDEBUG +#ifndef NDEBUG // if(++iterations % 10 == 0) { printf("Iteration %d\n", ++iterations); // } -//#endif +#endif // get filter values internal::compute_filter(pbp, width, height, count, filter_size, filter_out, precomputed.get(), threads); @@ -221,7 +222,9 @@ std::vector dither::internal::blue_noise_impl(int width, int heigh std::vector pbp_copy(pbp); std::cout << "Ranking minority pixels...\n"; for (unsigned int i = pixel_count; i-- > 0;) { +#ifndef NDEBUG std::cout << i << ' '; +#endif internal::compute_filter(pbp, width, height, count, filter_size, filter_out, precomputed.get(), threads); std::tie(std::ignore, max) = internal::filter_minmax(filter_out, pbp); @@ -232,7 +235,9 @@ std::vector dither::internal::blue_noise_impl(int width, int heigh } std::cout << "\nRanking remainder of first half of pixels...\n"; for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); ++i) { +#ifndef NDEBUG std::cout << i << ' '; +#endif internal::compute_filter(pbp, width, height, count, filter_size, filter_out, precomputed.get(), threads); std::tie(min, std::ignore) = internal::filter_minmax(filter_out, pbp); @@ -242,7 +247,9 @@ std::vector dither::internal::blue_noise_impl(int width, int heigh std::cout << "\nRanking last half of pixels...\n"; std::vector reversed_pbp(pbp); for (unsigned int i = (count + 1) / 2; i < (unsigned int)count; ++i) { +#ifndef NDEBUG std::cout << i << ' '; +#endif for(unsigned int i = 0; i < pbp.size(); ++i) { reversed_pbp[i] = !pbp[i]; } @@ -476,9 +483,9 @@ std::vector dither::internal::blue_noise_cl_impl( }; { +#ifndef NDEBUG printf("Inserting %d pixels into image of max count %d\n", pixel_count, count); // generate image from randomized pbp -#ifndef NDEBUG FILE *random_noise_image = fopen("random_noise.pbm", "w"); fprintf(random_noise_image, "P1\n%d %d\n", width, height); for(int y = 0; y < height; ++y) { @@ -507,8 +514,11 @@ std::vector dither::internal::blue_noise_cl_impl( int iterations = 0; + std::cout << "Begin BinaryArray generation loop\n"; while(true) { +#ifndef NDEBUG printf("Iteration %d\n", ++iterations); +#endif if(!get_filter()) { std::cerr << "OpenCL: Failed to execute do_filter\n"; @@ -537,10 +547,10 @@ std::vector dither::internal::blue_noise_cl_impl( } if(iterations % 100 == 0) { +#ifndef NDEBUG std::cout << "max was " << max << ", second_min is " << second_min << std::endl; // generate blue_noise image from pbp -#ifndef NDEBUG FILE *blue_noise_image = fopen("blue_noise.pbm", "w"); fprintf(blue_noise_image, "P1\n%d %d\n", width, height); for(int y = 0; y < height; ++y) { @@ -579,23 +589,29 @@ std::vector dither::internal::blue_noise_cl_impl( #endif std::cout << "Generating dither_array...\n"; +#ifndef NDEBUG std::unordered_set set; +#endif std::vector dither_array(count, 0); int min, max; { std::vector pbp_copy(pbp); std::cout << "Ranking minority pixels...\n"; for (unsigned int i = pixel_count; i-- > 0;) { +#ifndef NDEBUG std::cout << i << ' '; +#endif get_filter(); std::tie(std::ignore, max) = internal::filter_minmax(filter, pbp); pbp.at(max) = false; dither_array.at(max) = i; +#ifndef NDEBUG if (set.find(max) != set.end()) { std::cout << "\nWARNING: Reusing index " << max << '\n'; } else { set.insert(max); } +#endif } pbp = pbp_copy; #ifndef NDEBUG @@ -605,16 +621,20 @@ std::vector dither::internal::blue_noise_cl_impl( } std::cout << "\nRanking remainder of first half of pixels...\n"; for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); ++i) { +#ifndef NDEBUG std::cout << i << ' '; +#endif get_filter(); std::tie(min, std::ignore) = internal::filter_minmax(filter, pbp); pbp.at(min) = true; dither_array.at(min) = i; +#ifndef NDEBUG if (set.find(min) != set.end()) { std::cout << "\nWARNING: Reusing index " << min << '\n'; } else { set.insert(min); } +#endif } #ifndef NDEBUG { @@ -629,16 +649,20 @@ std::vector dither::internal::blue_noise_cl_impl( std::cout << "\nRanking last half of pixels...\n"; reversed_pbp = true; for (unsigned int i = (count + 1) / 2; i < (unsigned int)count; ++i) { +#ifndef NDEBUG std::cout << i << ' '; +#endif get_filter(); std::tie(std::ignore, max) = internal::filter_minmax(filter, pbp); pbp.at(max) = true; dither_array.at(max) = i; +#ifndef NDEBUG if (set.find(max) != set.end()) { std::cout << "\nWARNING: Reusing index " << max << '\n'; } else { set.insert(max); } +#endif } std::cout << std::endl; diff --git a/src/blue_noise.hpp b/src/blue_noise.hpp index 3a12e46..75d76f4 100644 --- a/src/blue_noise.hpp +++ b/src/blue_noise.hpp @@ -408,7 +408,9 @@ namespace internal { } } +#ifndef NDEBUG std::cout << "rangeToBl: Got min == " << min << " and max == " << max << std::endl; +#endif max -= min; diff --git a/src/main.cpp b/src/main.cpp index 1626d70..2c0c471 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,15 +1,51 @@ +#include +#include + +#include "arg_parse.hpp" #include "blue_noise.hpp" -#include - int main(int argc, char **argv) { - std::cout << "Trying blue_noise..." << std::endl; - image::Bl bl = dither::blue_noise(32, 32, 15, false); - if(!bl.writeToFile(image::file_type::PNG, true, "blueNoiseOut.png")) { - std::cout << "ERROR: Failed to write result to file\n"; - std::cout << "size is " << bl.getSize() << ", width is " - << bl.getWidth() << ", height is " << bl.getHeight() - << std::endl; + Args args; + if(args.ParseArgs(argc, argv)) { + return 0; + } + + // validation + if (args.generate_blue_noise_) { + if (args.output_filename_.empty()) { + std::cout << "ERROR: Cannot generate blue-noise, output filename is not specified" + << std::endl; + Args::DisplayHelp(); + return 1; + } else if (args.blue_noise_size_ < 16) { + std::cout << "ERROR: blue-noise size is too small" + << std::endl; + Args::DisplayHelp(); + return 1; + } else if (!args.overwrite_file_) { + FILE *file = std::fopen(args.output_filename_.c_str(), "r"); + if (file) { + std::fclose(file); + std::cout << "ERROR: overwrite not specified, but filename exists" + << std::endl; + Args::DisplayHelp(); + return 1; + } + } + } else { + std::cout << "ERROR: No operation specified\n"; + Args::DisplayHelp(); + } + + if (args.generate_blue_noise_) { + std::cout << "Generating blue_noise..." << std::endl; + image::Bl bl = dither::blue_noise(args.blue_noise_size_, + args.blue_noise_size_, + args.threads_, + args.use_opencl_); + if(!bl.writeToFile(image::file_type::PNG, args.overwrite_file_, args.output_filename_)) { + std::cout << "ERROR: Failed to write blue-noise to file\n"; + } } return 0;