diff options
author | Tom Smeding <tom@tomsmeding.com> | 2025-09-03 23:31:40 +0200 |
---|---|---|
committer | Tom Smeding <tom@tomsmeding.com> | 2025-09-03 23:37:53 +0200 |
commit | 7e60437d3c064bca402d486be967c43bf4326067 (patch) | |
tree | 8b4307b90c942861176d9c705cd760b9fe7b6c6b /options.cpp |
This includes old code too, perhaps from 2021-05-05, judging from the
birth timestamp on the directory. The old code included option parsing
and equation solving for fancy mutually-dependent options, but no actual
rendering (imagine that).
Diffstat (limited to 'options.cpp')
-rw-r--r-- | options.cpp | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/options.cpp b/options.cpp new file mode 100644 index 0000000..3d7786b --- /dev/null +++ b/options.cpp @@ -0,0 +1,200 @@ +#include "options.h" +#include <thread> +#include <cctype> +#include <cassert> + + +static int64_t ipow(int64_t b, int64_t e) { + if (e == 0) return 1; + assert(e >= 0); + int64_t x = 1; + while (e > 0) { + if (e & 1) x *= b; + b *= b; + e >>= 1; + } + return x; +} + +std::optional<const char*> parse_int_param(int64_t *dest, const char *str) { + if (!str || !*str) return "empty string"; + char *endp; + int64_t whole = strtoll(str, &endp, 10); + + int64_t fractional = 0, fract_offset = 0; + if (*endp == '.' && isdigit(endp[1])) { + char *endp2; + fractional = strtoll(endp + 1, &endp2, 10); + fract_offset = endp2 - (endp + 1); + endp = endp2; + } + + int64_t exponent = 0; + if ((*endp == 'e' || *endp == 'E') && isdigit(endp[1])) { + exponent = strtoll(endp + 1, &endp, 10); + // log2 (10^20) ~= 66 > 64 + if (exponent < 0 || exponent >= 20) return "exponent too large"; + } + + if (fract_offset > exponent) return "not integer (more fractional digits than exponent)"; + + if (*endp != '\0') return "unexpected character"; + *dest = (whole * ipow(10, fract_offset) + fractional) * ipow(10, exponent - fract_offset); + return std::nullopt; +} + +void Options::fill_dimensions() { + System sys; + + sys.eqs.push_back( + Equation{ + Sum{{xmax.toProduct()}}, + Sum{{xmin.toProduct(), cplxwidth.toProduct()}} + } + ); + + sys.eqs.push_back( + Equation{ + Sum{{ymax.toProduct()}}, + Sum{{ymin.toProduct(), cplxheight.toProduct()}} + } + ); + + sys.eqs.push_back( + Equation{ + Sum{{xmin.toProduct(), xmax.toProduct()}}, + Sum{{cx.toProduct().times(2.0)}} + } + ); + + sys.eqs.push_back( + Equation{ + Sum{{ymin.toProduct(), ymax.toProduct()}}, + Sum{{cy.toProduct().times(2.0)}} + } + ); + + sys.eqs.push_back( + Equation{ + Sum{{imgwidth.toProduct().times(cplxheight.toProduct())}}, + Sum{{cplxwidth.toProduct().times(imgheight.toProduct())}} + } + ); + + auto res = sys.solve_inplace(debug_paramsolver); + if (auto errmsg = std::get_if<std::string>(&res)) { + std::cerr << *errmsg; + exit(1); + } else { + auto assignment = std::get<1>(res); + xmin.from_computed(assignment); + xmax.from_computed(assignment); + ymin.from_computed(assignment); + ymax.from_computed(assignment); + cplxwidth.from_computed(assignment); + cplxheight.from_computed(assignment); + cx.from_computed(assignment); + cy.from_computed(assignment); + imgwidth.from_computed(assignment); + imgheight.from_computed(assignment); + } +} + +void Options::validate() { + if (maxiter.value < 0) { + std::cerr << "Negative maxiter" << std::endl; + exit(1); + } + + if (numorbits.value < 0) { + std::cerr << "Negative numorbits" << std::endl; + exit(1); + } + + if (num_threads.value <= 0) { + std::cerr << "Non-positive number of threads" << std::endl; + exit(1); + } + + if (orbit_burnin.value < 0) { + std::cerr << "Negative orbit_burnin" << std::endl; + exit(1); + } + + if (!outfname.valid()) { + std::cerr << "Output filename (-o) not given" << std::endl; + exit(1); + } + + if (imgwidth.value <= 0 || imgheight.value <= 0) { + std::cerr << "Zero or negative image size; check window options" << std::endl; + exit(1); + } +} + +std::ostream& operator<<(std::ostream &os, const Options &opts) { + os << "window: [" << opts.xmin.value << "," << opts.xmax.value << "] (" << opts.cplxwidth.value << ") x [" << opts.ymin.value << "," << opts.ymax.value << "] (" << opts.cplxheight.value << ") around (" << opts.cx.value << "," << opts.cy.value << ")\n"; + os << "image: " << opts.imgwidth.value << " x " << opts.imgheight.value << "; "; + os << "maxiter=" << opts.maxiter.value << "; "; + os << "numorbits=" << opts.numorbits.value << "; "; + os << "num_threads=" << opts.num_threads.value << "; "; + os << "algorithm=" << opts.algorithm.value; + return os; +} + +void usage(const char *argv0) { + std::cout << "Usage: " << argv0 << " [OPTIONS]\n"; + std::cout << " -L [float] Left boundary of window in C (xmin)\n"; + std::cout << " -R [float] Right boundary of window in C (xmax)\n"; + std::cout << " -T [float] Top boundary of window in C (ymax)\n"; + std::cout << " -B [float] Bottom boundary of window in C (ymin)\n"; + std::cout << " -W [float] Width of window in C (cplxwidth)\n"; + std::cout << " -H [float] Height of window in C (cplxheight)\n"; + std::cout << " -X [float] Horizontal middle of window in C (cx)\n"; + std::cout << " -Y [float] Vertical middle of window in C (cy)\n"; + std::cout << " -w [int] Image width (imgwidth)\n"; + std::cout << " -h [int] Image height (imgheight)\n"; + std::cout << " -m [int] Number of iterations before escape (maxiter)\n"; + std::cout << " -n [int] Number of orbits to trace (numorbits)\n"; + std::cout << " -t [int] Number of threads (num_threads)\n"; + std::cout << " -o [string] Output file name (PNG file) (outfname)\n"; + std::cout << " -al [string] Algorithm to use (see compute.cpp) (algorithm)\n"; + std::cout << " --help This text\n"; + std::cout << "\n"; + std::cout << " -ds Debug window equation solving\n"; + std::cout << std::flush; +} + +Options parse_options(int argc, char **argv) { + Options options; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-L") == 0) options.xmin.parse(argv[i+1], "-L"), i++; + else if (strcmp(argv[i], "-R") == 0) options.xmax.parse(argv[i+1], "-R"), i++; + else if (strcmp(argv[i], "-T") == 0) options.ymax.parse(argv[i+1], "-T"), i++; + else if (strcmp(argv[i], "-B") == 0) options.ymin.parse(argv[i+1], "-B"), i++; + else if (strcmp(argv[i], "-W") == 0) options.cplxwidth.parse(argv[i+1], "-W"), i++; + else if (strcmp(argv[i], "-H") == 0) options.cplxheight.parse(argv[i+1], "-H"), i++; + else if (strcmp(argv[i], "-X") == 0) options.cx.parse(argv[i+1], "-X"), i++; + else if (strcmp(argv[i], "-Y") == 0) options.cy.parse(argv[i+1], "-Y"), i++; + else if (strcmp(argv[i], "-w") == 0) options.imgwidth.parse(argv[i+1], "-w"), i++; + else if (strcmp(argv[i], "-h") == 0) options.imgheight.parse(argv[i+1], "-h"), i++; + else if (strcmp(argv[i], "-m") == 0) options.maxiter.parse(argv[i+1], "-m"), i++; + else if (strcmp(argv[i], "-n") == 0) options.numorbits.parse(argv[i+1], "-n"), i++; + else if (strcmp(argv[i], "-ds") == 0) options.debug_paramsolver = true; + else if (strcmp(argv[i], "-t") == 0) options.num_threads.parse(argv[i+1], "-t"), i++; + else if (strcmp(argv[i], "-o") == 0) options.outfname.parse(argv[i+1], "-o"), i++; + else if (strcmp(argv[i], "-al") == 0) options.algorithm.parse(argv[i+1], "-o"), i++; + else if (strcmp(argv[i], "--help") == 0) { usage(argv[0]); exit(0); } + else { + std::cerr << "Unexpected argument '" << argv[i] << "'" << std::endl; + exit(1); + } + } + + options.fill_dimensions(); + options.validate(); + + return options; +} + +// vim: set sw=4 ts=4 noet: |