summaryrefslogtreecommitdiff
path: root/options.cpp
diff options
context:
space:
mode:
authorTom Smeding <tom@tomsmeding.com>2025-09-03 23:31:40 +0200
committerTom Smeding <tom@tomsmeding.com>2025-09-03 23:37:53 +0200
commit7e60437d3c064bca402d486be967c43bf4326067 (patch)
tree8b4307b90c942861176d9c705cd760b9fe7b6c6b /options.cpp
Initial (with old code)HEADmaster
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.cpp200
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: