diff options
118 files changed, 6624 insertions, 4550 deletions
@@ -1,7 +1,9 @@ +*.ico *.png *.webp -/old +/benoit-gui +/manual /render /target -Benoit.toml +benoit.toml Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 61fa717..6eec048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +# 3.0.0 + +* Bump major version +* Rewrite project +* Update copyright years +* Improve commenting +* Update gitignore +* Avoid unsafety +* Divide project into library and executables (CLI and GUI, latter currently unsupported) +* Create new logo +* Add installation script +* Add documentation +* Configure lints +* Add to-do list + +## `benoit` + +* Support palettes with alpha +* Remove WebP-support (support only PNG frames) +* Add new palette(s): `ink`, `mask`, `thunder`, `glacier` +* Support interior palettes +* Export images with 16 bits per channel +* Use 64 (in reality 63) bits for iteration counts +* Use our own complex data type +* Add new fractal(s): `antibrot` +* Define our own error type + +## `benoit-cli` + +* Register terminate +* Rework CLI +* Rework configuration +* Support resuming animations +* Define our own error type + # 2.7.1 * Improve info logs @@ -1,28 +1,164 @@ -[package] -name = "benoit" -version = "2.7.1" -authors = ["Gabriel Bjørnager Jensen"] -edition = "2021" -description = "Multithreaded Mandelbrot renderer with support for PNG and WebP encoding." -readme = "README.md" -repository = "https://mandelbrot.dk/benoit" +[workspace] +members = [ + "benoit", + "benoit-cli", +# "benoit-gui", +] +resolver = "2" -[[bin]] -name = "benoit" -path = "source/benoit/main.rs" +[workspace.package] +version = "3.0.0" +authors = ["Gabriel Bjørnager Jensen"] -[profile.release] -codegen-units = 1 -lto = "fat" - -[dependencies] -enum-iterator = "1.4.1" -png = "0.17.10" -rayon = "1.8.0" -rug = "1.22.0" -sdl2 = "0.35.2" -toml = "0.8.2" -webp = "0.2.6" +[workspace.lints.clippy] +as_ptr_cast_mut = "deny" +as_underscore = "warn" +assertions_on_result_states = "warn" +bool_to_int_with_if = "warn" +borrow_as_ptr = "deny" +branches_sharing_code = "warn" +cast_lossless = "warn" +cast_possible_wrap = "warn" +cast_ptr_alignment = "deny" +checked_conversions = "warn" +clear_with_drain = "warn" +cloned_instead_of_copied = "warn" +collection_is_never_read = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +deref_by_slicing = "warn" +derive_partial_eq_without_eq = "deny" +empty_enum = "warn" +empty_enum_variants_with_brackets = "warn" +empty_line_after_doc_comments = "warn" +empty_line_after_outer_attr = "warn" +empty_structs_with_brackets = "warn" +enum_glob_use = "warn" +enum_variant_names = "allow" +equatable_if_let = "warn" +excessive_precision = "allow" +exit = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +fallible_impl_from = "deny" +flat_map_option = "warn" +float_cmp = "deny" # Fortran, is that you? +float_cmp_const = "deny" +format_push_string = "warn" +from_iter_instead_of_collect = "warn" +future_not_send = "deny" +if_not_else = "warn" +if_then_some_else_none = "warn" +ignored_unit_patterns = "warn" +impl_trait_in_params = "warn" +implicit_clone = "warn" +imprecise_flops = "deny" +inconsistent_struct_constructor = "deny" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +infinite_loop = "deny" +into_iter_without_iter = "deny" +invalid_upcast_comparisons = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "deny" +iter_on_empty_collections = "warn" +iter_on_single_items = "warn" +iter_with_drain = "warn" +iter_without_into_iter = "deny" +macro_use_imports = "warn" +manual_assert = "warn" +manual_c_str_literals = "warn" +manual_instant_elapsed = "warn" +manual_is_variant_and = "warn" +manual_let_else = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +map_unwrap_or = "warn" +match_bool = "warn" +match_on_vec_items = "warn" +match_same_arms = "warn" +mismatching_type_param_order = "warn" +missing_const_for_fn = "warn" +mixed_read_write_in_expression = "deny" +multiple_unsafe_ops_per_block = "deny" +must_use_candidate = "deny" +mut_mut = "deny" +mutex_atomic = "deny" +mutex_integer = "deny" +needless_bitwise_bool = "deny" +needless_collect = "warn" +needless_continue = "warn" +needless_pass_by_ref_mut = "warn" +needless_pass_by_value = "deny" +needless_raw_string_hashes = "warn" +needless_raw_strings = "warn" +no_effect_underscore_binding = "deny" +no_mangle_with_rust_abi = "deny" +non_ascii_literal = "deny" +nonstandard_macro_braces = "warn" +option_as_ref_cloned = "warn" +option_if_let_else = "warn" +option_option = "deny" +or_fun_call = "deny" +panic_in_result_fn = "deny" +path_buf_push_overwrite = "deny" +pattern_type_mismatch = "deny" +ptr_as_ptr = "deny" +ptr_cast_constness = "deny" +pub_underscore_fields = "deny" +pub_with_shorthand = "deny" +read_zero_byte_vec = "deny" +redundant_clone = "deny" +redundant_closure_for_method_calls = "warn" +redundant_else = "warn" +redundant_pub_crate = "warn" +redundant_type_annotations = "warn" +ref_as_ptr = "deny" +ref_binding_to_reference = "warn" +ref_option_ref = "deny" +rest_pat_in_fully_bound_structs = "warn" +return_self_not_must_use = "deny" +same_functions_in_if_condition = "deny" +same_name_method = "deny" +self_named_module_files = "deny" +semicolon_if_nothing_returned = "warn" +semicolon_outside_block = "warn" +separated_literal_suffix = "warn" +single_char_pattern = "warn" +str_split_at_newline = "warn" +string_lit_as_bytes = "deny" +string_lit_chars_any = "deny" +string_to_string = "deny" +suboptimal_flops = "deny" +trait_duplication_in_bounds = "deny" +transmute_ptr_to_ptr = "deny" +type_repetition_in_bounds = "deny" +uninhabited_references = "deny" +uninlined_format_args = "deny" +unnecessary_box_returns = "deny" +unnecessary_join = "deny" +unnecessary_self_imports = "deny" +unnecessary_wraps = "warn" +unneeded_field_pattern = "warn" +unnested_or_patterns = "warn" +unused_async = "warn" +unused_peekable = "warn" +unused_rounding = "warn" +unused_self = "warn" +use_self = "deny" +used_underscore_binding = "deny" +useless_let_if_seq = "warn" +verbose_bit_mask = "warn" +verbose_file_reads = "warn" +wildcard_dependencies = "deny" +wildcard_enum_match_arm = "deny" +zero_sized_map_values = "deny" -[target.'cfg(windows)'.dependencies] -windows = "0.51.1" +[profile.release] +codegen-units = 1 +lto = "fat" +opt-level = 3 +overflow-checks = true @@ -1,26 +1,21 @@ -# BENOÎT +# About -[*Benoit*](https://mandelbrot.dk/benoit) is a free and open‐source Mandelbrot renderer written in Rust. Its goal is to render arbitrary positions as performant and accurate as possiple. Usage: +[*Benoit*](https://mandelbrot.dk/benoit) is a free and open‐source Mandelbrot renderer written in Rust. -``` -benoit [--help] [path] -``` +The project is structured around the main `benoit` library, of which front-ends can make use of. -… where *path* denotes the configuration file to read (optional). If no path is provided, the program is run in *interactive* mode, wherein the fractal is rendered in real‐time. Otherwise, *script* mode is run using the provided configuration. +The official front-ends currently include `benoit-cli` and `benoit-gui`, although the latter is currently not present in this repository. -# Dependencies +# Usage -Benoit makes use of the following external libraries: +``` +benoit-cli <path> +benoit-gui +``` -* [`enum-iterator`](https://crates.io/crates/enum-iterator) for pre‐calculating palettes -* [`png`](https://crates.io/crates/png) for encoding PNG images -* [`rayon`](https://crates.io/crates/rayon) for threadpooling -* [`rug`](https://crates.io/crates/rug) for multi‐precision -* [`sdl2`](https://crates.io/crates/sdl2) for interactive viewports -* [`toml`](https://crates.io/crates/toml) for parsing TOML files -* [`webp`](https://crates.io/crates/webp) for encoding WebP images +The thread count may be specified using the environment variable `RAYON_NUM_THREADS`. -Furthermore – on Windows – the `windows` package is used for setting the terminal title. +See [Docs.rs](https://docs.rs/benoit/latest/benoit/) for documentation. # Mirrors @@ -32,10 +27,28 @@ Benoit is officially hosted on the following mirrors: # Copyright & License -Copyright 2021, 2023 Gabriel Bjørnager Jensen. +Also see individual files for their licenses. + +Note that the section *`benoit`* does **NOT** represent the entirety of the Benoit project, instead only the `benoit` library found in the `benoit` directory. + +The contents of this readme are released under a Creative Commons Attribution-ShareAlike 4.0 International license, see <https://creativecommons.org/licenses/by-sa/4.0/> for more information. + +## `benoit` + +Copyright 2021, 2023-2024 Gabriel Bjørnager Jensen. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. + +## `benoit-cli` and `benoit-gui` + +Copyright 2021, 2023-2024 Gabriel Bjørnager Jensen. + +This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. @@ -0,0 +1,66 @@ +This document specifies the current priorities of the project. + +Please discuss with maintainers before adding new entries. + +# High priority + +## User-defined palettes + +Allow for the end-user to specify custom palettes. + +The primary problem is currently to decide how the interface should be? +Lua scripts, Python, scripts, webasm...? + +## Magnet fractal + +The fractal of the form + +*z*<sub>*n*+1</sub> = ((*z*<sub>*n*</sub><sup>2</sup> + *c* - 1) / ((2*z*<sub>*n*</sub> + *c* - 2))<sup>2 + +should be defined with the `magnet` identifier. + +## Multibrot Sets + +Include more powers of the Multibrot Set fractals. +Main obstacle is writing them for use with Rug. +Also finish refactoring of `multibrot4`. + +## Modular factorisers + +The factorisers (see `benoit/src/render/render/plot.rs`) could be implemented similarly to palettese, etc. + +Especially the interior factoriser I am *not* very proud about: + +```rust +let factor = data.distance; // data: RawElement +``` + +# Normal priority + +## More fractals + +Find more fractals to be added. + +Requirements include: + +* New fractals should be easily recognisable and different from others +* Although self-similar, new fractals should not perfectly repeat themselves + +## Rug replacement + +Find a replacement for Rug, optimally a Rust-written project. + +This goal depends on the assumption that floating-point operations are **not** necessary for us. + +In the case that using floats instead of rationals etc., Rug *appears* to be the best solution, at the moment at least. + +# Low priority + +## Web front-end + +Not currently necessary to include in the main repository. +Should have the identifier `benoit-web`. + +## Interior colour parameters + +Should the parameters used internally by `Colour` be RGBA (as they currently are) or another encoding, e.g. XYZA? diff --git a/benoit-cli/COPYING b/benoit-cli/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/benoit-cli/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +<https://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +<https://www.gnu.org/licenses/why-not-lgpl.html>. diff --git a/benoit-cli/Cargo.toml b/benoit-cli/Cargo.toml new file mode 100644 index 0000000..8c92f6c --- /dev/null +++ b/benoit-cli/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "benoit-cli" +edition = "2021" +license = "GPL-3.0-or-later" + +version.workspace = true +authors.workspace = true + +[features] +notify = ["notify-rust"] + +[dependencies] +benoit = { path = "../benoit" } + +ctrlc = "3.4.4" +num-traits = "0.2.18" +rug = "1.24.0" +toml = "0.8.10" + +notify-rust = { version = "4.10.0", optional = true } + +[target.'cfg(windows)'.dependencies] +windows = "0.54.0" + +[lints] +workspace = true diff --git a/benoit-cli/src/config/config/load.rs b/benoit-cli/src/config/config/load.rs new file mode 100644 index 0000000..8ac0165 --- /dev/null +++ b/benoit-cli/src/config/config/load.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::config::Config; +use crate::error::Error; + +use std::env::args; + +impl Config { + /// Loads the configuration file as specified on the command line. + /// + /// # Errors + /// + /// Returns an error depending on the command line arguments and validity of the configuration file. + pub fn load() -> Result<Self, Error> { + let mut args = args().skip(0x1); + + let Some(config_path) = args.next() else { + return Err(Error::MissingConfigPath); + }; + + if args.next().is_some() { return Err(Error::TooManyCliArguments) }; + + let config = Self::load_from(config_path)?; + config.validate()?; + + Ok(config) + } +} diff --git a/benoit-cli/src/config/config/load_from.rs b/benoit-cli/src/config/config/load_from.rs new file mode 100644 index 0000000..61ae5cd --- /dev/null +++ b/benoit-cli/src/config/config/load_from.rs @@ -0,0 +1,89 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in (the) hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::config::{Config, Section, Take}; +use crate::error::Error; +use crate::keyframe::Keyframe; + +use std::path::Path; + +macro_rules! take_field { + ($name:ident in ($section:expr)) => {{ + $section.get_child(stringify!($name)).take()? + }}; + + ($name:ident in ($section:expr) str) => {{ + $section.get_child(stringify!($name)).take_from_str()?? + }}; +} + +impl Config { + /// Loads the configuration file at `path`. + /// + /// # Errors + /// + /// If the configuration file could not be read or parsed, an error message is returned. + pub fn load_from<P: AsRef<Path>>(path: P) -> Result<Self, Error> { + let root_section = Section::create_root(path)?; + + let render_section: Section = root_section.get_child("render").take()?; + let final_section: Section = root_section.get_child("final").take()?; + let output_section: Section = root_section.get_child("output").take()?; + + let render_start_section: Section = render_section.get_child("start").take()?; + let render_stop_section: Section = render_section.get_child("stop").take()?; + + let render_width = take_field!(width in (render_section)); + let render_height = take_field!(height in (render_section)); + + Ok(Self { + frame_count: take_field!(count in (render_section)), + render_size: (render_width, render_height), + fractal: take_field!(fractal in (render_section) str), + inverse: take_field!(inverse in (render_section)), + julia: take_field!(julia in (render_section)), + + start: Keyframe { + frame: take_field!(frame in (render_start_section)), + max_iter_count: take_field!(max_iter_count in (render_start_section)), + centre: take_field!(centre in (render_start_section)), + seed: take_field!(seed in (render_start_section)), + zoom: take_field!(zoom in (render_start_section)), + colour_range: take_field!(colour_range in (render_start_section)), + }, + stop: Keyframe { + frame: take_field!(frame in (render_stop_section)), + max_iter_count: take_field!(max_iter_count in (render_stop_section)), + centre: take_field!(centre in (render_stop_section)), + seed: take_field!(seed in (render_stop_section)), + zoom: take_field!(zoom in (render_stop_section)), + colour_range: take_field!(colour_range in (render_stop_section)), + }, + + palette: take_field!(palette in (final_section) str), + + output_directory: take_field!(directory in (output_section)), + }) + } +} diff --git a/benoit-cli/src/config/config/mod.rs b/benoit-cli/src/config/config/mod.rs new file mode 100644 index 0000000..e10c25e --- /dev/null +++ b/benoit-cli/src/config/config/mod.rs @@ -0,0 +1,124 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +mod load; +mod load_from; +mod print_help; + +use crate::error::Error; +use crate::keyframe::Keyframe; + +use benoit::fractal::Fractal; +use benoit::palette::Palette; +use std::path::PathBuf; + +/// For configuring animations. +/// +/// A configuration can be loaded from a file using [`load_from`](Config::load_from). +/// +/// An example of a configuration is the following: +/// +/// ```toml +/// # This renders a single frame. +/// +/// [render] +/// count = 1 +/// width = 1024 +/// height = 1024 +/// +/// fractal = "mandelbrot" # multibrot2 +/// inverse = false +/// julia = false +/// +/// [render.start] +/// frame = 0 +/// +/// max_iter_count = 0x100 +/// +/// centre = "0.0+0.0i" +/// seed = "0.0+0.0i" +/// zoom = "1.0" +/// +/// colour_range = 64.0 +/// +/// [render.stop] +/// frame = 0 +/// +/// max_iter_count = 0x100 +/// +/// centre = "0.0+0.0i" +/// seed = "0.0+0.0i" +/// zoom = "1.0" +/// +/// colour_range = 64.0 +/// +/// [final] +/// palette = "fire" +/// +/// [output] +/// directory = "render/" +/// ``` +#[derive(Clone, Debug)] +pub struct Config { + pub frame_count: u32, + pub render_size: (u32, u32), + pub fractal: Fractal, + pub inverse: bool, + pub julia: bool, + + pub start: Keyframe, + pub stop: Keyframe, + + pub palette: Palette, + + pub output_directory: PathBuf, +} + +impl Config { + /// Tries to validate `self`. + /// + /// # Errors + /// + /// If any configuration field couldn't be validated, an `Err` object is returned. + pub fn validate(&self) -> Result<(), Error> { + self.start.validate()?; + self.stop.validate()?; + + if self.start.frame > self.stop.frame { + Err( + Error::InvalidConfig { + message: format!("start frame ({}) cannot be after stop frame ({})", self.start.frame, self.stop.frame), + }, + ) + } else if self.stop.frame >= self.frame_count { + Err( + Error::InvalidConfig { + message: format!("stop frame ({}) cannot be after last frame ({})", self.stop.frame, self.frame_count - 0x1), + }, + ) + } else { + Ok(()) + } + } +} diff --git a/benoit-cli/src/config/config/print_help.rs b/benoit-cli/src/config/config/print_help.rs new file mode 100644 index 0000000..b16d6f8 --- /dev/null +++ b/benoit-cli/src/config/config/print_help.rs @@ -0,0 +1,45 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::config::Config; + +use benoit::VERSION; +use std::process::exit; + +impl Config { + /// Prints help to `stdout`. + /// + /// This function marks the end of execution and does therefore not return. + pub fn print_help() -> ! { + println!("Benoit {}.{}.{}", VERSION.0, VERSION.1, VERSION.2); + println!(); + println!("Usage:"); + println!(" benoit <path>"); + println!(); + println!("Wherein <path> denotes the path to the configuration file."); + println!(); + + exit(0x0); + } +} diff --git a/benoit-cli/src/config/field/mod.rs b/benoit-cli/src/config/field/mod.rs new file mode 100644 index 0000000..489dc7a --- /dev/null +++ b/benoit-cli/src/config/field/mod.rs @@ -0,0 +1,68 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::error::Error; + +use std::str::FromStr; +use toml::Value; + +/// Denotes a configuration field. +/// +/// By default, these fields are typeless. +/// They can, however, be "transformed" to a given type using the [`Take`](crate::config::Take) trait. +pub struct Field<'a> { + pub(in super) name: String, + pub(in super) value: Option<&'a Value>, +} + +impl<'a> Field<'a> { + /// Borows the contained value. + /// + /// The returned reference is still considered "typeless," as is defined by `toml`'s own [`Value`] type. + /// + /// # Errors + /// + /// Returns an error if the field could not be found. + pub(in super) fn borrow_value(&mut self) -> Result<&'a Value, Error> { + self.value + .take() + .ok_or_else(|| Error::MissingConfigField { name: self.name.clone() }) + } +} + +impl Field<'_> { + /// Transforms the field into the given type `T`. + /// The result of [`FromStr`] implementation is passed on. + /// + /// # Errors + /// + /// Returns an error if the field doesn't exist, or is a different type, or if the [`FromStr`] implementation failed. + pub fn take_from_str<T: FromStr>(mut self) -> Result<Result<T, <T as FromStr>::Err>, Error> { + let Value::String(ref s) = *self.borrow_value()? else { + return Err(Error::WrongFieldType { name: self.name, ok_type: "string" }) + }; + + Ok(FromStr::from_str(s)) + } +} diff --git a/benoit-cli/src/config/mod.rs b/benoit-cli/src/config/mod.rs new file mode 100644 index 0000000..aeba3b4 --- /dev/null +++ b/benoit-cli/src/config/mod.rs @@ -0,0 +1,31 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +//! Animation configuration. + +use crate::use_mod; +use_mod!(pub, config); +use_mod!(pub, field); +use_mod!(pub, section); +use_mod!(pub, take); diff --git a/benoit-cli/src/config/section/mod.rs b/benoit-cli/src/config/section/mod.rs new file mode 100644 index 0000000..59d716a --- /dev/null +++ b/benoit-cli/src/config/section/mod.rs @@ -0,0 +1,86 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::config::Field; +use crate::error::Error; + +use std::borrow::Cow; +use std::fs::read_to_string; +use std::path::Path; +use std::str::FromStr; +use toml::Table; + +/// Denotes a configuration section. +pub struct Section<'a> { + pub(in super) name: Option<String>, + pub(in super) table: Cow<'a, Table>, +} + +impl<'a> Section<'a> { + /// Creates a new root section. + /// + /// This is done by parsing the configuration file at `path`. + /// + /// # Errors + /// + /// Returns an error if unable to read and parse the configuration file. + pub fn create_root<P: AsRef<Path>>(path: P) -> Result<Self, Error> { + let path = path.as_ref(); + + let config = read_to_string(path) + .map_err(|e| Error::ConfigReadFailure { path: path.to_owned(), source: e })?; + + let table = Table::from_str(&config) + .map_err(|e| Error::ConfigParseFailure { path: path.to_owned(), source: e })?; + + Ok(Self { + name: None, + table: Cow::Owned(table) + }) + } + + /// Searches the section's table for children. + /// + /// The returned child will reference its parent section. + /// + /// The child isn't guaranteed to exist, and in the event that it does, is typeless. + /// See the [`Field`] type for more information. + #[must_use] + pub fn get_child(&'a self, name: &str) -> Field<'a> { + let value = self.table.get(name); + + let name = self.name + .as_ref() + .map_or_else( + || name.to_owned(), + + |parent| format!("{parent}.{name}"), + ); + + Field { + name, + value, + } + } +} diff --git a/benoit-cli/src/config/take/mod.rs b/benoit-cli/src/config/take/mod.rs new file mode 100644 index 0000000..4580e5b --- /dev/null +++ b/benoit-cli/src/config/take/mod.rs @@ -0,0 +1,282 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::config::{Field, Section}; +use crate::error::Error; + +use benoit::PRECISION; +use benoit::complex::Complex; +use rug::Float; +use std::borrow::Cow; +use std::fs::canonicalize; +use std::path::PathBuf; +use std::str::FromStr; +use toml::Value; + +/// Used for loading configuration fields. +/// +/// More specifically, this is to be used with the untyped [`Field`] type, where using [`take`](Take::take) collapses the field into a definitively typed object. +pub trait Take<T> { + /// Collapses the field into the given type `T`. + /// + /// # Errors + /// + /// Returns an error if the field doesn't exist or is a different type. + + fn take(self) -> Result<T, Error>; +} + +fn test_field_domain<T, U>(name: &str, value: &T, min: U, max: U) -> Result<(), Error> +where + T: PartialOrd + ToString, + U: Copy + Into<T> + ToString, { + if *value < min.into() { + Err(Error::FieldLowerBounds { + name: name.to_owned(), + value: value.to_string(), + limit: min.to_string(), + }) + } else if *value > max.into() { + Err(Error::FieldUpperBounds { + name: name.to_owned(), + value: value.to_string(), + limit: max.to_string(), + }) + } else { + Ok(()) + } +} + +impl Take<bool> for Field<'_> { + fn take(mut self) -> Result<bool, Error> { + let Value::Boolean(value) = *self.borrow_value()? else { + return Err(Error::WrongFieldType { name: self.name, ok_type: "bool" }); + }; + + Ok(value) + } +} + +impl Take<Complex> for Field<'_> { + fn take(mut self) -> Result<Complex, Error> { + let s = if let Value::String(ref s) = *self.borrow_value()? { + Ok(s) + } else { + Err(Error::WrongFieldType { name: self.name, ok_type: "complex" }) + }?; + + FromStr::from_str(s).map_err(Into::into) + } +} + +impl Take<f32> for Field<'_> { + fn take(mut self) -> Result<f32, Error> { + if let Value::Float(value) = *self.borrow_value()? { + // Is this even possible? + if value.is_nan() { + return Err(Error::NonNumberField { name: self.name }); + } + + if value.is_infinite() { + return Err(Error::InfiniteField { name: self.name }); + } + + test_field_domain(&self.name, &value, -f32::MAX, f32::MAX)?; + + Ok(value as f32) + } else { + Err(Error::WrongFieldType { name: self.name, ok_type: "float" }) + } + } +} + +impl Take<f64> for Field<'_> { + fn take(mut self) -> Result<f64, Error> { + if let Value::Float(value) = *self.borrow_value()? { + // Is this even possible? + if value.is_nan() { + return Err(Error::NonNumberField { name: self.name }); + } + + if value.is_infinite() { + return Err(Error::InfiniteField { name: self.name }); + } + + Ok(value) + } else { + Err(Error::WrongFieldType { name: self.name, ok_type: "float" }) + } + } +} + +impl Take<Float> for Field<'_> { + fn take(mut self) -> Result<Float, Error> { + if let Value::String(ref value) = *self.borrow_value()? { + if let Ok(value) = Float::parse(value) { + return Ok(Float::with_val(PRECISION, value)); + } + } + + Err(Error::WrongFieldType { name: self.name, ok_type: "bigfloat" }) + } +} + +impl Take<i16> for Field<'_> { + fn take(mut self) -> Result<i16, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + test_field_domain(&self.name, &value, i16::MIN, i16::MAX)?; + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<i32> for Field<'_> { + fn take(mut self) -> Result<i32, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + test_field_domain(&self.name, &value, i32::MIN, i32::MAX)?; + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<i64> for Field<'_> { + fn take(mut self) -> Result<i64, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + return Ok(value); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<i8> for Field<'_> { + fn take(mut self) -> Result<i8, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + test_field_domain(&self.name, &value, i8::MIN, i8::MAX)?; + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<PathBuf> for Field<'_> { + fn take(self) -> Result<PathBuf, Error> { + let s = <Self as Take<String>>::take(self)?; + + canonicalize(&s).map_err(|e| Error::InvalidPath { path: s, source: e }) + } +} + +impl<'a> Take<Section<'a>> for Field<'a> { + fn take(mut self) -> Result<Section<'a>, Error> { + if let Value::Table(ref table) = *self.borrow_value()? { + return Ok(Section { + name: Some(self.name), + table: Cow::Borrowed(table), + }); + } + + Err(Error::WrongFieldType { name: self.name, ok_type: "section" }) + } +} + +impl Take<String> for Field<'_> { + fn take(mut self) -> Result<String, Error> { + if let Value::String(ref s) = *self.borrow_value()? { + Ok(s.to_owned()) + } else { + Err(Error::WrongFieldType { name: self.name, ok_type: "string" }) + } + } +} + +impl Take<u16> for Field<'_> { + fn take(mut self) -> Result<u16, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + if value.is_negative() { + return Err(Error::NegativeField { name: self.name }); + } + + test_field_domain(&self.name, &value, u16::MIN, u16::MAX)?; + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<u32> for Field<'_> { + fn take(mut self) -> Result<u32, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + if value.is_negative() { + return Err(Error::NegativeField { name: self.name }); + } + + test_field_domain(&self.name, &value, u32::MIN, u32::MAX)?; + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<u64> for Field<'_> { + fn take(mut self) -> Result<u64, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + if value.is_negative() { + return Err(Error::NegativeField { name: self.name }) + } + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} + +impl Take<u8> for Field<'_> { + fn take(mut self) -> Result<u8, Error> { + if let Value::Integer(value) = *self.borrow_value()? { + if value.is_negative() { + return Err(Error::NegativeField { name: self.name }); + } + + test_field_domain(&self.name, &value, u8::MIN, u8::MAX)?; + + return Ok(TryFrom::try_from(value).unwrap()); + }; + + Err(Error::WrongFieldType { name: self.name, ok_type: "integer" }) + } +} diff --git a/benoit-cli/src/error/mod.rs b/benoit-cli/src/error/mod.rs new file mode 100644 index 0000000..32a9f40 --- /dev/null +++ b/benoit-cli/src/error/mod.rs @@ -0,0 +1,159 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use benoit::error::Error as LibError; +use std::error::Error as StdError; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::io::Error as IoError; +use std::path::PathBuf; +use toml::de::Error as TomlError; + +/// Denotes an error. +#[derive(Debug)] +pub enum Error { + BenoitError { source: LibError }, + + ConfigParseFailure { path: PathBuf, source: TomlError }, + + ConfigReadFailure { path: PathBuf, source: IoError }, + + InvalidConfig { message: String }, + + InvalidKeyframe { message: String }, + + InvalidPath { path: String, source: IoError }, + + FieldLowerBounds { name: String, value: String, limit: String }, + + FieldUpperBounds { name: String, value: String, limit: String }, + + InfiniteField { name: String }, + + MissingConfigField { name: String }, + + MissingConfigPath, + + NegativeField { name: String }, + + NonNumberField { name: String }, + + TooManyCliArguments, + + WrongFieldType { name: String, ok_type: &'static str }, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + #[allow(clippy::enum_glob_use)] + use Error::*; + + match *self { + BenoitError { ref source } => { + write!(f, "{source}") + }, + + ConfigParseFailure { ref path, ref source } => { + write!(f, "unable to parse configuration at \"{}\": \"{source}\"", path.display()) + } + + ConfigReadFailure { ref path, ref source } => { + write!(f, "unable to read configuration at \"{}\": \"{source}\"", path.display()) + }, + + FieldLowerBounds { ref name, ref value, ref limit } => { + write!(f, "field `{name}` cannot be less than ({limit}) but was ({value})") + }, + + FieldUpperBounds { ref name, ref value, ref limit } => { + write!(f, "field `{name}` cannot be greater than ({limit}) but was ({value})") + }, + + InfiniteField { ref name } => { + write!(f, "field `{name}` must be finite (was infinite)") + }, + + InvalidConfig { ref message } => { + write!(f, "invalid configuration: {message}") + }, + + InvalidKeyframe { ref message } => { + write!(f, "invalid keyframe: {message}") + }, + + InvalidPath { ref path, ref source } => { + write!(f, "invalid path \"{path}\": \"{source}\"") + }, + + MissingConfigField { ref name } => { + write!(f, "missing configuration field `{name}`") + }, + + MissingConfigPath => { + write!(f, "missing configuration path") + }, + + NegativeField { ref name } => { + write!(f, "field `{name}` cannot be negative") + }, + + NonNumberField { ref name } => { + write!(f, "field `{name}` must be a number (was NaN)") + }, + + TooManyCliArguments => { + write!(f, "too many arguments provided on the command line") + }, + + WrongFieldType { ref name, ok_type } => { + write!(f, "type of field `{name} should'be been `{ok_type}`") + }, + } + } +} + +impl From<LibError> for Error { + fn from(value: LibError) -> Self { + Self::BenoitError { source: value } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + #[allow(clippy::enum_glob_use)] + use Error::*; + + #[allow(clippy::match_same_arms, clippy::wildcard_enum_match_arm)] + match *self { + BenoitError { ref source } => Some(source), + + ConfigParseFailure { ref source, .. } => Some(source), + + ConfigReadFailure { ref source, .. } => Some(source), + + InvalidPath { ref source, .. } => Some(source), + + _ => None, + } + } +} diff --git a/benoit-cli/src/instance/mod.rs b/benoit-cli/src/instance/mod.rs new file mode 100644 index 0000000..26f67a0 --- /dev/null +++ b/benoit-cli/src/instance/mod.rs @@ -0,0 +1,48 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +//! Runtime. + +mod run; + +use rug::float::{free_cache, FreeCache}; + +/// Denotes a programme instance. +/// +/// In theory, due to Rust's safety, any ammount of instances can be created at any time... disregarding hardware constraints, of course. +/// +/// The choice of creating this structure in the first place is a personal one. +/// I can be argued, however, that some things should be leveraged using [`Drop`]. +pub struct Instance; + +impl Default for Instance { + #[inline(always)] + fn default() -> Self { Self } +} + +impl Drop for Instance { + fn drop(&mut self) { + free_cache(FreeCache::All); + } +} diff --git a/benoit-cli/src/instance/run.rs b/benoit-cli/src/instance/run.rs new file mode 100644 index 0000000..8ba86ab --- /dev/null +++ b/benoit-cli/src/instance/run.rs @@ -0,0 +1,138 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::set_title; +use crate::error::Error; +use crate::instance::Instance; +use crate::config::Config; +use crate::keyframe::Keyframe; + +use benoit::log; +use benoit::render::{Render, RenderConfig}; +use benoit::stopwatch::Stopwatch; +use std::path::Path; + +#[cfg(feature = "notify")] +use notify_rust::{Notification, Timeout}; + +impl Instance { + /// Consumes the programme instance and starts execution. + /// + /// # Errors + /// + /// Returns any error that occurs during executiom, if any at all. + /// + /// This does not include panics, which are *not* handled by us (at the moment). + + #[allow(clippy::unused_self)] + pub fn run(self, config: &Config) -> Result<(), Error> { + let mut render = Render::new(config.render_size.0, config.render_size.1)?; + + let mut dump_frame = |keyframe: &Keyframe, path: &Path| -> Result<(), Error> { + let render_config = RenderConfig { + fractal: config.fractal.clone(), + inverse: config.inverse, + julia: config.julia, + + max_iter_count: keyframe.max_iter_count, + centre: keyframe.centre.clone(), + seed: keyframe.seed.clone(), + zoom: keyframe.zoom.clone(), + }; + + run_job("render", || render.generate(&render_config)); + run_job("paint", || render.plot(config.palette, keyframe.colour_range))?; + run_job("dump", || render.dump_image(path))?; + + Ok(()) + }; + + let mut global_timer = Stopwatch::from_now(); + + for keyframe in config.start.interpolate_between(&config.stop, config.frame_count) { + log!(value, keyframe); + + let path = { + let mut path = config.output_directory.clone(); + path.push(format!("frame{:010}.png", keyframe.frame)); + + path + }; + + eprint!("\u{001B}[1m"); + eprintln!("frame no. {:.>10} \u{2510}", keyframe.frame); + eprintln!(" of {:.>10} total \u{2534}> \"{}\"", config.stop.frame, path.display()); + eprint!("\u{001B}[22m"); + + let frame_timer = Stopwatch::test_result(|| dump_frame(&keyframe, &path))?; + global_timer.note(); + + eprintln!(" = \u{001B}[4m{frame_timer}\u{001B}[24m\u{001B}[2m ({global_timer} total)\u{001B}[22m"); + eprintln!(); + } + + global_timer.note(); + + eprintln!("\u{001B}[1manimation completed\u{001B}[22m - took {global_timer} in total"); + set_title!("done"); + + #[cfg(feature = "notify")] + { + // We don't really care if the notification fails. + let _ = Notification::new() + .appname("Benoit") + .summary("\u{2728} Animation completed! \u{2728}") + .body(&format!("Took {global_timer}.")) + .icon("benoit") + .timeout(Timeout::Never) + .show(); + } + + Ok(()) + } +} + +pub fn run_job<F: FnOnce() -> Output, Output>(name: &str, func: F) -> Output { + eprint!("\u{2610} running job `{name}` ..."); + set_title!("{name}"); + + let (timer, result) = Stopwatch::test(func); + + // Indent to the 40th column; two have already been + // used for the bullet: + eprintln!("\r\u{2612} \u{001B}[2m{: <38}\u{001B}[22m\u{001B}[1m+ {timer}\u{001B}[22m", format!("completed job `{name}`")); + + result +} + +// +// ## ## +// # # # +// # # +// # # +// # # +// # # +// # # +// # +// diff --git a/benoit-cli/src/keyframe/keyframe/interpolate_between.rs b/benoit-cli/src/keyframe/keyframe/interpolate_between.rs new file mode 100644 index 0000000..42e8c3e --- /dev/null +++ b/benoit-cli/src/keyframe/keyframe/interpolate_between.rs @@ -0,0 +1,165 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::keyframe::{Keyframe, KeyframeInterpolater}; + +use benoit::{log, PRECISION}; +use benoit::complex::Complex; +use num_traits::Num; +use rug::Float; +use rug::ops::PowAssign; + +impl Keyframe { + /// Interpolates between `self` and the given other keyframe. + /// + /// The returned interpolater can be used as an iterator. + /// + /// The way in which the values are interpolated differ: + /// The centre, seed, and colour range values are interpreted as linear functions of the form (*a* · *b* + *c*). + /// The zoom value, however, is interpreted as a growth function of the form (*a*<sup>*x*</sup> · *b*). + /// + /// # Panics + /// + /// Panics if frame indices don't match. + + #[must_use] + pub fn interpolate_between(&self, other: &Self, frame_count: u32) -> KeyframeInterpolater { + assert!(self.frame <= other.frame, "starting frame ({}) is after stopping frame ({})", self.frame, other.frame); + assert!(other.frame < frame_count, "stopping frame ({}) is after last frame ({})", other.frame, frame_count - 0x1); + + let first_possible_frame = 0x0; + let last_possible_frame = frame_count - 0x1; + + let max_iter_count_step = linear_step( + (first_possible_frame, i64::try_from(self.max_iter_count).unwrap()), + (last_possible_frame, i64::try_from(other.max_iter_count).unwrap()), + ); + + let centre_step = linear_step_complex( (first_possible_frame, &self.centre), (last_possible_frame, &other.centre)); + let seed_step = linear_step_complex( (first_possible_frame, &self.seed), (last_possible_frame, &other.seed)); + let zoom_factor = growth_factor_bigfloat((first_possible_frame, &self.zoom), (last_possible_frame, &other.zoom)); + let colour_range_step = linear_step( (first_possible_frame, self.colour_range), (last_possible_frame, other.colour_range)); + + let mut interpolator = KeyframeInterpolater { + frame: Some(self.frame), + last_frame: other.frame, + + max_iter_count: self.max_iter_count, + centre: self.centre.clone(), + seed: self.seed.clone(), + zoom: self.zoom.clone(), + colour_range: self.colour_range, + + max_iter_count_step, + centre_step, + seed_step, + zoom_factor, + colour_range_step, + }; + + // Skip to the starting frame: + for _ in 0x0..self.frame { + interpolator.advance_values(); + } + + log!(value, interpolator); + interpolator + } +} + +#[must_use] +fn linear_step<T: Default + From<u32> + Num>((t0, f0): (u32, T), (t1, f1): (u32, T)) -> T { + if t1 == t0 || f1 == f0 { return Default::default() } + + // A linear function `f` of time `t` can be defined + // as: + // + // f(t) = at+b + // + // The starting value `b` is already known. We use + // the following expression to get the slope coef- + // ficient `a`: + // + // a = (t1-t0)/(t1-t0) + // + // wherein f1 and f0 are the last and first results + // of `f` - respectively - and t1 and t0 are like- + // wise the last and first timepoints. + + (f1 - f0) / (T::from(t1) - T::from(t0)) +} + +#[must_use] +fn linear_step_bigfloat((t0, f0): (u32, &Float), (t1, f1): (u32, &Float)) -> Float { + if t1 == t0 || f1 == f0 { return Float::new(PRECISION) } + + let denominator = f64::from(t1) - f64::from(t0); + + let numerator = Float::with_val(PRECISION, f1 - f0); + numerator / denominator +} + +#[must_use] +fn linear_step_complex((t0, f0): (u32, &Complex), (t1, f1): (u32, &Complex)) -> Complex { + let real_step = linear_step_bigfloat((t0, &f0.real), (t1, &f1.real)); + let imag_step = linear_step_bigfloat((t0, &f0.imag), (t1, &f1.imag)); + + Complex { real: real_step, imag: imag_step } +} + +#[must_use] +fn growth_factor_bigfloat((t0, f0): (u32, &Float), (t1, f1): (u32, &Float)) -> Float { + if t1 == t0 || f1 == f0 { return Float::with_val(PRECISION, 0x1) } + + // The growth function `f` of time `t` is: + // + // f(t) = b*a^t + // + // wherein `b` is the starting value and `a` is the + // growth factor. + // + // To isolate `a` we use: + // + // a = nroot(t1-t0,f1/f0) + // + // wherein `t0`, `t1`, `f1`, and `f0` are the first + // and last values on the two dimensions (`t` and + // `f`), altough in theory arbitrary. + // + // Because the following is true: + // + // nroot(n,m) = m^(1/n) + // + // the expression for `a` is simplified for use + // with Rug: + // + // a = (t1/t0)^(1/(f1-f0)) + + let exponent = 1.0 / (f64::from(t1) - f64::from(t0)); + + let mut factor = Float::with_val(PRECISION, f1 / f0); + factor.pow_assign(exponent); + + factor +} diff --git a/benoit-cli/src/keyframe/keyframe/mod.rs b/benoit-cli/src/keyframe/keyframe/mod.rs new file mode 100644 index 0000000..9a4e84a --- /dev/null +++ b/benoit-cli/src/keyframe/keyframe/mod.rs @@ -0,0 +1,99 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +mod interpolate_between; + +use crate::error::Error; + +use benoit::PRECISION; +use benoit::complex::Complex; +use rug::Float; + +/// Defines a single keyframe for animating. +/// +/// Keyframes can be interpolated using the [`interpolate_between`](Keyframe::interpolate_between) method. +#[derive(Clone, Debug)] +pub struct Keyframe { + pub frame: u32, + pub max_iter_count: u64, + pub centre: Complex, + pub seed: Complex, + pub zoom: Float, + pub colour_range: f64, +} + +impl Keyframe { + /// Validates `self`, wrapping it in a [`Result`]. + /// + /// The following requirements must be upheld: + /// + /// * The maximum iteration count must be non-zero and less than or equal to `9223372036854775807`; + /// * The zoom value must be positive and non-zero; + /// * The colour range must be positive and non-zero; + /// + /// If these are upheld, an [`Ok`] object is returned. + /// + /// Do note that an "invalid" keyframe does not inherintly result in undefined behaviour if used. + /// + /// # Errors + /// + /// Yields an error if the keyframe could not be validated. + pub fn validate(&self) -> Result<(), Error> { + macro_rules! test { + ($assertion:expr, $($message:tt)*) => {{ + if !($assertion as bool) { + return Err(Error::InvalidKeyframe { message: format!($($message)?) }) + } + }}; + } + + test!(self.max_iter_count != 0x0, "max. iter. count ({}) cannot be zero", self.max_iter_count); + + // This is also tested by Config, but we don't know + // if this keyframe even came from a configuration. + test!( + self.max_iter_count <= u64::try_from(i64::MAX).unwrap(), + "max. iter. count ({}) cannot be greater than ({})", + self.max_iter_count, + i64::MAX, + ); + + test!(self.zoom > 0x0, "zoom ({}) must be greater than zero", self.zoom); + + test!(self.colour_range > 0.0, "colour range ({}) must be positive and non-zero", self.colour_range); + + Ok(()) + } +} + +impl Default for Keyframe { + fn default() -> Self { Self { + frame: 0x0, + max_iter_count: 0x100, + centre: Complex::new(PRECISION), + seed: Complex::new(PRECISION), + zoom: Float::with_val(PRECISION, 0x1), + colour_range: 256.0, + } } +} diff --git a/benoit-cli/src/keyframe/keyframe_interpolater/mod.rs b/benoit-cli/src/keyframe/keyframe_interpolater/mod.rs new file mode 100644 index 0000000..3faa546 --- /dev/null +++ b/benoit-cli/src/keyframe/keyframe_interpolater/mod.rs @@ -0,0 +1,95 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::keyframe::Keyframe; + +use benoit::complex::Complex; +use rug::Float; + +/// Interpolater between keyframes. +/// +/// This is done as an iterator over the interpolated values. +#[derive(Clone, Debug)] +pub struct KeyframeInterpolater { + pub(in super) frame: Option<u32>, + pub(in super) last_frame: u32, + + pub(in super) max_iter_count: u64, + pub(in super) centre: Complex, + pub(in super) seed: Complex, + pub(in super) zoom: Float, + pub(in super) colour_range: f64, + + pub(in super) max_iter_count_step: i64, + pub(in super) centre_step: Complex, + pub(in super) seed_step: Complex, + pub(in super) zoom_factor: Float, + pub(in super) colour_range_step: f64, +} + +impl KeyframeInterpolater { + /// Advances the contained values to the next step. + /// + /// # Panics + /// + /// Panics if overflow occurs when calculating the new maximum iteration count. + /// This is guaranteed to not happen as long as the iterator hasn't been completed. + pub(in super) fn advance_values(&mut self) { + self.max_iter_count = self.max_iter_count + .checked_add_signed(self.max_iter_count_step) + .unwrap(); + + self.centre.real += &self.centre_step.real; + self.centre.imag += &self.centre_step.imag; + self.seed.real += &self.seed_step.real; + self.seed.imag += &self.seed_step.imag; + self.zoom *= &self.zoom_factor; + self.colour_range += self.colour_range_step; + } +} + +impl Iterator for KeyframeInterpolater { + type Item = Keyframe; + + fn next(&mut self) -> Option<Self::Item> { + let frame = self.frame?; + assert!(frame <= self.last_frame); + + let keyframe = Keyframe { + frame, + max_iter_count: self.max_iter_count, + centre: self.centre.clone(), + seed: self.seed.clone(), + zoom: self.zoom.clone(), + colour_range: self.colour_range, + }; + + self.frame = (frame != self.last_frame).then(|| { + self.advance_values(); + frame + 0x1 + }); + + Some(keyframe) + } +} diff --git a/benoit-cli/src/keyframe/mod.rs b/benoit-cli/src/keyframe/mod.rs new file mode 100644 index 0000000..a4c722a --- /dev/null +++ b/benoit-cli/src/keyframe/mod.rs @@ -0,0 +1,29 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +//! Animation keyframes. + +use crate::use_mod; +use_mod!(pub, keyframe); +use_mod!(pub, keyframe_interpolater); diff --git a/benoit-cli/src/main.rs b/benoit-cli/src/main.rs new file mode 100644 index 0000000..03494bd --- /dev/null +++ b/benoit-cli/src/main.rs @@ -0,0 +1,90 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +macro_rules! use_mod { + ($visibility:vis, $name:ident) => { + mod $name; + $visibility use $name::*; + }; +} +pub(in crate) use use_mod; + +pub mod config; +pub mod error; +pub mod instance; +pub mod keyframe; + +use benoit::log; + +#[cfg(windows)] +use windows::Win32::System::Console::SetConsoleTitleA; + +/// Execute according to the build mode. +/// +/// Use this as a shorthand for testing `debug_assertions`. +#[macro_export] +macro_rules! debug { + ($($body:stmt)*) => { if cfg!(debug_assertions) { $($body)? } }; +} + +/// Sets the terminal's title. +#[macro_export] +macro_rules! set_title { + ($($format:tt)*) => {{ + #[cfg(unix)] + let set_title = |title: &str| { + eprint!("\u{001B}]0;{title}\u{0007}"); + }; + + #[cfg(windows)] + let set_title = |title: &str| { + unsafe { SetConsoleTitleA(title) }; + }; + + let message = format!($($format)?); + set_title(&message); + }}; +} + +fn main() -> Result<(), i32> { + let config = match config::Config::load() { + Ok(config) => config, + + Err(message) => { + log!(error, "{message}"); + return Err(0x2); + }, + }; + + log!(value, config); + + let instance: instance::Instance = Default::default(); + + if let Err(message) = instance.run(&config) { + log!(error, "{message}"); + return Err(0x1); + } + + Ok(()) +} diff --git a/benoit.desktop b/benoit.desktop index f5e948e..43b08b3 100755 --- a/benoit.desktop +++ b/benoit.desktop @@ -1,5 +1,5 @@ [Desktop Entry] Type=Application Name=Benoit -Exec=/usr/bin/benoit +Exec=/usr/bin/benoit-gui Icon=benoit @@ -1,18 +1,31 @@ <svg height="96" width="96" xmlns="http://www.w3.org/2000/svg"> - <!-- gradients --> - <linearGradient id="backgroundColour" x1="0" x2="1" y1="0" y2="1"> - <stop offset="0%" stop-color="#252525" /> - <stop offset="100%" stop-color="#1E1E1E" /> - </linearGradient> - <!-- masks --> - <mask id="background"> - <polygon fill="white" points="21,21 75,21 75,57.243 57.243,75 21,75" /> + <!-- gradients: --> + + <!-- clips: --> + + <!-- masks: --> + + <mask id="eye"> + <ellipse cx="48" cy="48" fill="white" rx="36" ry="24" /> + <ellipse cx="48" cy="48" fill="black" rx="33" ry="21" /> + + <ellipse cx="48" cy="48" fill="white" rx="30" ry="24" /> + <ellipse cx="48" cy="48" fill="black" rx="27" ry="21" /> + + <ellipse cx="48" cy="48" fill="white" rx="24" wry="24" /> + <ellipse cx="48" cy="48" fill="black" rx="21" ry="21" /> </mask> - <mask id="foreground"> - <polygon fill="white" points="24,24 40,24 48,37.856 56,24 72,24 72,56 56,72 56,51.713 48,65.569 40,51.713 40,72 24,72" /> - </mask> - <!-- fills --> - <rect fill="url(#backgroundColour)" height="96" width="96" x="0" y="0" /> - <rect fill="#EAEAEA" height="96" mask="url(#background)" width="96" x="0" y="0" /> - <rect fill="#B2223B" height="48" mask="url(#foreground)" width="48" x="24" y="24" /> + + <!-- fills: --> + + <polygon fill="#BA0035" points="0,0 96,0 96,96 0,96" /> <!-- oklch(50% 0.199 17.860) "red" --> + <polygon fill="#CD0325" points="0,0 96,0 96,96 19.096,96" /> + <polygon fill="#DC1D02" points="0,0 96,0 96,96 39.765,96" /> + <polygon fill="#E04400" points="0,0 96,0 96,96 64.145,96" /> + <polygon fill="#E55C04" points="0,0 96,0 96,96 96,96" /> + <polygon fill="#EB6F02" points="0,0 96,0 143.674,96" /> + <polygon fill="#F18103" points="0,0 96,0 231.766,96" /> + <polygon fill="#F79204" points="0,0 96,0 482.625,96" /> <!-- oklch(75% 0.170 63.220) "yellow" --> + + <rect fill="#FFF9F4" mask="url(#eye)" height="96" width="96" x="0" y="0" /> </svg> diff --git a/benoit/Cargo.toml b/benoit/Cargo.toml new file mode 100644 index 0000000..ef5503d --- /dev/null +++ b/benoit/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "benoit" +edition = "2021" +license = "AGPL-3.0-or-later" +description = "Multithreaded Mandelbrot renderer." + +version.workspace = true +authors.workspace = true + +[features] +wgpu-colour = ["wgpu"] + +[dependencies] +enum-iterator = "2.0.0" +lazy_static = "1.4.0" +num-traits = "0.2.18" +png = "0.17.13" +rayon = "1.9.0" +rug = "1.24.0" + +wgpu = { version = "0.19.3", optional = true } + +[lints] +workspace = true diff --git a/benoit/src/colour/mod.rs b/benoit/src/colour/mod.rs new file mode 100644 index 0000000..886eab1 --- /dev/null +++ b/benoit/src/colour/mod.rs @@ -0,0 +1,344 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +//! Colour manipulations facilities. +//! +//! This includes the [`Colour`] structure for handling colours. +//! +//! These are primarily intended for colouring (plotting) renders, and is as such used by the `Palette` and `Render` types. + +mod web; + +use crate::error::Error; + +#[cfg(feature = "wgpu-colour")] +use wgpu::Color as WGpuColour; + +/// Represents a colour value. +/// +/// Internally, this is encoded with RGBA parameters in linear gamma. +/// The type is, however, to be thought of as a purely abstract descriptor. +#[derive(Copy, Clone, Debug)] +pub struct Colour { + red: f32, + green: f32, + blue: f32, + alpha: f32, +} + +/// Validates that the given parameter is valid. +/// +/// That is, it must not be NaN. +/// +/// In theory, it would be okay for parameters to be outside of the range 0..1, but for simplicity's sake this is avoided (at least in most cases). +/// +/// If the parameter is infinite, no guarantees are made on our end for the results of conversion functions. +/// +/// # Errors +/// +/// If the value is NaN, an error message is returned in an [`Err`] object. +macro_rules! validate_parameter { + ($param:ident: $name:expr) => { + #[allow(unused_mut)] + let mut $param: f64 = $param.into(); + + if $param.is_nan() { + return Err(Error::NanColourParameter { name: $name }); + } + }; +} + +impl Colour { + /// Zips the provided parameters in a [`Colour`] structure. + /// + /// # Errors + /// + /// If any parameter is NaN, an error is returned. + #[inline(always)] + pub fn from_rgba<T: Into<f64>>(red: T, green: T, blue: T, alpha: T) -> Result<Self, Error> { + validate_parameter!(red: "red"); + validate_parameter!(green: "green"); + validate_parameter!(blue: "blue"); + validate_parameter!(alpha: "alpha"); + + Ok(Self { + red: red as f32, + green: green as f32, + blue: blue as f32, + alpha: alpha as f32, + }) + } + + /// Zips the provided parameters in a [`Colour`] structure. + /// + /// The values are interpreted as being sRGBA and. + /// + /// # Errors + /// + /// If any parameter is NaN, an error is returned. + pub fn from_srgba<T: Into<f64>>(red: T, green: T, blue: T, alpha: T) -> Result<Self, Error> { + validate_parameter!(red: "red"); + validate_parameter!(green: "green"); + validate_parameter!(blue: "blue"); + validate_parameter!(alpha: "alpha"); + + for value in [&mut red, &mut green, &mut blue, &mut alpha] { + *value = if *value > 0.091_568_643 { + ((*value + 0.055) / 1.055).powf(2.4) + } else { + *value / 11.920 + }; + } + + Ok(Self { + red: red as f32, + green: green as f32, + blue: blue as f32, + alpha: alpha as f32, + }) + } + + /// Zips the provided HSVA parameters in a [`Colour`] structure. + /// + /// # Errors + /// + /// If any parameter is NaN, an error is returned. + pub fn from_hsva<T: Into<f64>>(hue: T, saturation: T, value: T, alpha: T) -> Result<Self, Error> { + validate_parameter!(hue: "hue"); + validate_parameter!(saturation: "saturation"); + validate_parameter!(value: "value"); + validate_parameter!(alpha: "alpha"); + + if saturation <= 0.0 { + let value = value.clamp(0.0, 1.0); + + Self::from_srgba(value, value, value, alpha) + } else { + let h = hue % 1.0 * 6.0; + let s = saturation.clamp(0.0, 1.0); + let v = value.clamp(0.0, 1.0); + let a = alpha; + + let f = h % 1.0; + let p = v * (1.0 - s); + let q = v * (-s).mul_add(f, 1.0); // v * (1.0 - s * f) + let t = v * (-s).mul_add(1.0 - f, 1.0); // v * (1.0 - s * (1.0 - f)) + + match h.trunc() as u8 { + 0x0 => Self::from_srgba(v, t, p, a), + 0x1 => Self::from_srgba(q, v, p, a), + 0x2 => Self::from_srgba(p, v, t, a), + 0x3 => Self::from_srgba(p, q, v, a), + 0x4 => Self::from_srgba(t, p, v, a), + 0x5 => Self::from_srgba(v, p, q, a), + _ => unreachable!(), // Because h is guaranteed to be in the interval 0..=5. + } + } + } + + /// Zips the provided HSVA parameters in a [`Colour`] structure. + /// + /// # Errors + /// + /// If any parameter is NaN, an error is returned. + pub fn from_xyza<T: Into<f64>>(x: T, y: T, z: T, alpha: T) -> Result<Self, Error> { + validate_parameter!(x: "x"); + validate_parameter!(y: "y"); + validate_parameter!(z: "z"); + validate_parameter!(alpha: "alpha"); + + // Convert directly to RGBA. + + let m: [[f64; 0x3]; 0x3] = [ + [ 3.240_969_942, -1.537_383_178, -0.498_610_760], + [-0.969_243_636, 1.875_967_502, 0.041_555_057], + [ 0.055_630_080, -0.203_976_959, 1.056_971_514], + ]; + + let r = m[0x0][0x0].mul_add(x, m[0x0][0x1].mul_add(y, m[0x0][0x2] * z)); + let g = m[0x1][0x0].mul_add(x, m[0x1][0x1].mul_add(y, m[0x1][0x2] * z)); + let b = m[0x2][0x0].mul_add(x, m[0x2][0x1].mul_add(y, m[0x2][0x2] * z)); + + Self::from_rgba(r, g, b, alpha) + } + + /// Zips the provided LABA parameters in a [`Colour`] structure. + /// + /// # Errors + /// + /// If any parameter is NaN, an error is returned. + pub fn from_laba<T: Into<f64>>(lightness: T, astar: T, bstar: T, alpha: T) -> Result<Self, Error> { + validate_parameter!(lightness: "lightness"); + validate_parameter!(astar: "a*"); + validate_parameter!(bstar: "b*"); + validate_parameter!(alpha: "alpha"); + + // Convert to XYZA. + + // astar: a* + // bstar: b* + + let kappa: f64 = 903.296_296_296; + let epsilon: f64 = 0.008_856_452; + + let f1 = (lightness + 16.0) / 116.0; + let f0 = astar.mul_add(1.0 / 500.0, f1); + let f2 = (-bstar).mul_add(1.0 / 200.0, f1); + + let temporary = (lightness + 16.0) / 116.0; + + let mut x = f0 * f0 * f0; + let mut y = temporary * temporary * temporary; + let mut z = f2 * f2 * f2; + + if x <= epsilon { x = 0.964_016_736 * (f0.mul_add(116.0, -16.0) / kappa) }; + if lightness <= kappa * epsilon { y = lightness / kappa }; + if z <= epsilon { z = 0.825_104_603 * (f2.mul_add(116.0, -16.0) / kappa) }; + + Self::from_xyza(x, y, z, alpha) + } + + /// Zips the provided LCHA parameters in a [`Colour`] structure. + /// + /// # Errors + /// + /// If any parameter is NaN, an error is returned. + pub fn from_lcha<T: Into<f64>>(lightness: T, chroma: T, hue:T, alpha: T) -> Result<Self, Error> { + validate_parameter!(lightness: "lightness"); + validate_parameter!(chroma: "chroma"); + validate_parameter!(hue: "hua"); + validate_parameter!(alpha: "alpha"); + + // Convert to LABA. + + // Convert turns to radians: + // 1 turn = 2pi radians + + hue *= std::f64::consts::PI * 2.0; + + let astar = chroma * hue.cos(); // a* + let bstar = chroma * hue.sin(); // b* + + Self::from_laba(lightness, astar, bstar, alpha) + } + + /// Transforms the contained parameters to sRGBA colour space. + #[must_use] + pub fn to_srgba(self) -> (f64, f64, f64, f64) { + assert!(!self.red.is_nan()); + assert!(!self.green.is_nan()); + assert!(!self.blue.is_nan()); + assert!(!self.alpha.is_nan()); + + let mut red = f64::from(self.red); + let mut green = f64::from(self.green); + let mut blue = f64::from(self.blue); + let mut alpha = f64::from(self.alpha); + + for value in [&mut red, &mut green, &mut blue, &mut alpha] { + *value = if *value > 0.000_313_080 { + value.powf(0.416_666_667).mul_add(1.055, -0.055) + } else { + 11.920 * *value + }; + } + + (red, green, blue, alpha) + } + + /// Transforms the contained parameters to integer sRGBA with 8 bits per channel. + #[must_use] + pub fn to_srgba8(self) -> (u8, u8, u8, u8) { + assert!(!self.red.is_nan()); + assert!(!self.green.is_nan()); + assert!(!self.blue.is_nan()); + assert!(!self.alpha.is_nan()); + + let (red, green, blue, alpha) = self.to_srgba(); + + let red = (red * f64::from(u8::MAX)).trunc() as u8; + let green = (green * f64::from(u8::MAX)).trunc() as u8; + let blue = (blue * f64::from(u8::MAX)).trunc() as u8; + let alpha = (alpha * f64::from(u8::MAX)).trunc() as u8; + + (red, green, blue, alpha) + } + + /// Transforms the contained parameters to integer sRGBA with 16 bits per channel. + #[must_use] + pub fn to_srgba16(self) -> (u16, u16, u16, u16) { + assert!(!self.red.is_nan()); + assert!(!self.green.is_nan()); + assert!(!self.blue.is_nan()); + assert!(!self.alpha.is_nan()); + + let (red, green, blue, alpha) = self.to_srgba(); + + let red = (red * f64::from(u16::MAX)).trunc() as u16; + let green = (green * f64::from(u16::MAX)).trunc() as u16; + let blue = (blue * f64::from(u16::MAX)).trunc() as u16; + let alpha = (alpha * f64::from(u16::MAX)).trunc() as u16; + + (red, green, blue, alpha) + } + + /// Transforms the contained parameters to integer sRGBA with 32 bits per channel. + #[must_use] + pub fn to_srgba32(self) -> (u32, u32, u32, u32) { + assert!(!self.red.is_nan()); + assert!(!self.green.is_nan()); + assert!(!self.blue.is_nan()); + assert!(!self.alpha.is_nan()); + + let (red, green, blue, alpha) = self.to_srgba(); + + let red = (red * f64::from(u32::MAX)).trunc() as u32; + let green = (green * f64::from(u32::MAX)).trunc() as u32; + let blue = (blue * f64::from(u32::MAX)).trunc() as u32; + let alpha = (alpha * f64::from(u32::MAX)).trunc() as u32; + + (red, green, blue, alpha) + } +} + +impl Default for Colour { + #[inline(always)] + fn default() -> Self { Self::from_rgba(1.0, 0.0, 1.0, 1.0).unwrap() } +} + +// Will be used by benoit-gui: +#[cfg(feature = "wgpu-colour")] +impl From<Colour> for WGpuColour { + /// Transforms the contained parameters to a [`WGpuColour`] object. + /// + /// To be used by the `[benoit-gui]` crate. + /// + /// Currently, this also transforms the colour value to sRGBA in the process. + fn from(value: Colour) -> Self { + // I think wgpu expects perceptual gamma? + + let (r, g, b, a) = value.to_srgba(); + Self { r, g, b, a} + } +} diff --git a/benoit/src/colour/web.rs b/benoit/src/colour/web.rs new file mode 100644 index 0000000..7b59aff --- /dev/null +++ b/benoit/src/colour/web.rs @@ -0,0 +1,1155 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::colour::Colour; + +impl Colour { + /// The web colour <span style="color: aliceblue;">`aliceblue`</span>. + pub const ALICE_BLUE: Self = Self { + red: 0.941176471, + green: 0.972549020, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: antiquewhite;">`antiquewhite`</span>. + pub const ANTIQUE_WHITE: Self = Self { + red: 0.980392157, + green: 0.921568627, + blue: 0.843137255, + alpha: 1.0, + }; + + /// The web colour <span style="color: aqua;">`aqua`</span>. + pub const AQUA: Self = Self { + red: 0.0, + green: 1.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: aquamarine;">`aquamarine`</span>. + pub const AQUAMARINE: Self = Self { + red: 0.498039216, + green: 1.0, + blue: 0.831372549, + alpha: 1.0, + }; + + /// The web colour <span style="color: azure;">`azure`</span>. + pub const AZURE: Self = Self { + red: 0.941176471, + green: 1.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: beige;">`beige`</span>. + pub const BEIGE: Self = Self { + red: 0.960784314, + green: 0.960784314, + blue: 0.862745098, + alpha: 1.0, + }; + + /// The web colour <span style="color: bisque;">`bisque`</span>. + pub const BISQUE: Self = Self { + red: 1.0, + green: 0.894117647, + blue: 0.768627451, + alpha: 1.0, + }; + + /// The web colour <span style="color: black;">`black`</span>. + pub const BLACK: Self = Self { + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: blanchedalmond;">`blanchedalmond`</span>. + pub const BLANCHED_ALMOND: Self = Self { + red: 1.0, + green: 0.921568627, + blue: 0.803921569, + alpha: 1.0, + }; + + /// The web colour <span style="color: blue;">`blue`</span>. + pub const BLUE: Self = Self { + red: 0.0, + green: 0.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: blueviolet;">`blueviolet`</span>. + pub const BLUE_VIOLET: Self = Self { + red: 0.541176471, + green: 0.168627451, + blue: 0.886274510, + alpha: 1.0, + }; + + /// The web colour <span style="color: brown;">`brown`</span>. + pub const BROWN: Self = Self { + red: 0.647058824, + green: 0.164705882, + blue: 0.164705882, + alpha: 1.0, + }; + + /// The web colour <span style="color: burlywood;">`burlywood`</span>. + pub const BURLYWOOD: Self = Self { + red: 0.870588235, + green: 0.721568627, + blue: 0.529411765, + alpha: 1.0, + }; + + /// The web colour <span style="color: cadetblue;">`cadetblue`</span>. + pub const CADET_BLUE: Self = Self { + red: 0.372549020, + green: 0.619607843, + blue: 0.627450980, + alpha: 1.0, + }; + + /// The web colour <span style="color: chartreuse;">`chartreuse`</span>. + pub const CHARTREUSE: Self = Self { + red: 0.498039216, + green: 1.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: chocolate;">`chocolate`</span>. + pub const CHOCOLATE: Self = Self { + red: 0.823529412, + green: 0.411764706, + blue: 0.117647059, + alpha: 1.0, + }; + + /// The web colour <span style="color: coral;">`coral`</span>. + pub const CORAL: Self = Self { + red: 1.0, + green: 0.498039216, + blue: 0.313725490, + alpha: 1.0, + }; + + /// The web colour <span style="color: cornflowerblue;">`cornflowerblue`</span>. + pub const CORNFLOWER_BLUE: Self = Self { + red: 0.392156863, + green: 0.584313725, + blue: 0.929411765, + alpha: 1.0, + }; + + /// The web colour <span style="color: cornsilk;">`cornsilk`</span>. + pub const CORNSILK: Self = Self { + red: 1.0, + green: 0.972549020, + blue: 0.862745098, + alpha: 1.0, + }; + + /// The web colour <span style="color: crimson;">`crimson`</span>. + pub const CRIMSON: Self = Self { + red: 0.862745098, + green: 0.078431373, + blue: 0.235294118, + alpha: 1.0, + }; + + /// The web colour <span style="color: cyan;">`cyan`</span>. + pub const CYAN: Self = Self { + red: 0.0, + green: 1.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkblue;">`darkblue`</span>. + pub const DARK_BLUE: Self = Self { + red: 0.0, + green: 0.0, + blue: 0.545098039, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkcyan;">`darkcyan`</span>. + pub const DARK_CYAN: Self = Self { + red: 0.0, + green: 0.545098039, + blue: 0.545098039, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkgoldenrod;">`darkgoldenrod`</span>. + pub const DARK_GOLDENROD: Self = Self { + red: 0.721568627, + green: 0.525490196, + blue: 0.043137255, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkgreen;">`darkgreen`</span>. + pub const DARK_GREEN: Self = Self { + red: 0.0, + green: 0.392156863, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkgrey;">`darkgrey`</span>. + pub const DARK_GREY: Self = Self { + red: 0.662745098, + green: 0.662745098, + blue: 0.662745098, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkkhaki;">`darkkhaki`</span>. + pub const DARK_KHAKI: Self = Self { + red: 0.741176471, + green: 0.717647059, + blue: 0.419607843, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkmagenta;">`darkmagenta`</span>. + pub const DARK_MAGENTA: Self = Self { + red: 0.545098039, + green: 0.0, + blue: 0.545098039, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkolivegreen;">`darkolivegreen`</span>. + pub const DARK_OLIVE_GREEN: Self = Self { + red: 0.333333333, + green: 0.419607843, + blue: 0.184313725, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkorange;">`darkorange`</span>. + pub const DARK_ORANGE: Self = Self { + red: 1.0, + green: 0.549019608, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkorchid;">`darkorchid`</span>. + pub const DARK_ORCHID: Self = Self { + red: 0.6, + green: 0.196078431, + blue: 0.8, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkred;">`darkred`</span>. + pub const DARK_RED: Self = Self { + red: 0.545098039, + green: 0.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: darksalmon;">`darksalmon`</span>. + pub const DARK_SALMON: Self = Self { + red: 0.913725490, + green: 0.588235294, + blue: 0.478431373, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkseagreen;">`darkseagreen`</span>. + pub const DARK_SEA_GREEN: Self = Self { + red: 0.560784314, + green: 0.737254902, + blue: 0.560784314, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkslateblue;">`darkslateblue`</span>. + pub const DARK_SLATE_BLUE: Self = Self { + red: 0.282352941, + green: 0.239215686, + blue: 0.545098039, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkslategrey;">`darkslategrey`</span>. + pub const DARK_SLATE_GREY: Self = Self { + red: 0.184313725, + green: 0.309803922, + blue: 0.309803922, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkturquoise;">`darkturquoise`</span>. + pub const DARK_TURQUOISE: Self = Self { + red: 0.0, + green: 0.807843137, + blue: 0.819607843, + alpha: 1.0, + }; + + /// The web colour <span style="color: darkviolet;">`darkviolet`</span>. + pub const DARK_VIOLET: Self = Self { + red: 0.580392157, + green: 0.0, + blue: 0.827450980, + alpha: 1.0, + }; + + /// The web colour <span style="color: deeppink;">`deeppink`</span>. + pub const DEEP_PINK: Self = Self { + red: 1.0, + green: 0.078431373, + blue: 0.576470588, + alpha: 1.0, + }; + + /// The web colour <span style="color: deepskyblue;">`deepskyblue`</span>. + pub const DEEP_SKY_BLUE: Self = Self { + red: 0.0, + green: 0.749019608, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: dimgrey;">`dimgrey`</span>. + pub const DIM_GREY: Self = Self { + red: 0.411764706, + green: 0.411764706, + blue: 0.411764706, + alpha: 1.0, + }; + + /// The web colour <span style="color: dodgerblue;">`dodgerblue`</span>. + pub const DODGER_BLUE: Self = Self { + red: 0.117647059, + green: 0.564705882, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: firebrick;">`firebrick`</span>. + pub const FIRE_BRICK: Self = Self { + red: 0.698039216, + green: 0.133333333, + blue: 0.133333333, + alpha: 1.0, + }; + + /// The web colour <span style="color: floralwhite;">`floralwhite`</span>. + pub const FLORAL_WHITE: Self = Self { + red: 1.0, + green: 0.980392157, + blue: 0.941176471, + alpha: 1.0, + }; + + /// The web colour <span style="color: forestgreen;">`forestgreen`</span>. + pub const FOREST_GREEN: Self = Self { + red: 0.133333333, + green: 0.545098039, + blue: 0.133333333, + alpha: 1.0, + }; + + /// The web colour <span style="color: fuchsia;">`fuchsia`</span>. + pub const FUCHSIA: Self = Self { + red: 1.0, + green: 0.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: gainsboro;">`gainsboro`</span>. + pub const GAINSBORO: Self = Self { + red: 0.862745098, + green: 0.862745098, + blue: 0.862745098, + alpha: 1.0, + }; + + /// The web colour <span style="color: ghostwhite;">`ghostwhite`</span>. + pub const GHOST_WHITE: Self = Self { + red: 0.972549020, + green: 0.972549020, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: gold;">`gold`</span>. + pub const GOLD: Self = Self { + red: 1.0, + green: 0.843137255, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: goldenrod;">`goldenrod`</span>. + pub const GOLDENROD: Self = Self { + red: 0.854901961, + green: 0.647058824, + blue: 0.125490196, + alpha: 1.0, + }; + + /// The web colour <span style="color: grey;">`grey`</span>. + pub const GREY: Self = Self { + red: 0.501960784, + green: 0.501960784, + blue: 0.501960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: green;">`green`</span>. + pub const GREEN: Self = Self { + red: 0.0, + green: 0.501960784, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: greenyellow;">`greenyellow`</span>. + pub const GREEN_YELLOW: Self = Self { + red: 0.678431373, + green: 1.0, + blue: 0.184313725, + alpha: 1.0, + }; + + /// The web colour <span style="color: honeydew;">`honeydew`</span>. + pub const HONEYDEW: Self = Self { + red: 0.941176471, + green: 1.0, + blue: 0.941176471, + alpha: 1.0, + }; + + /// The web colour <span style="color: hotpink;">`hotpink`</span>. + pub const HOT_PINK: Self = Self { + red: 1.0, + green: 0.411764706, + blue: 0.705882353, + alpha: 1.0, + }; + + /// The web colour <span style="color: indianred;">`indianred`</span>. + pub const INDIAN_RED: Self = Self { + red: 0.803921569, + green: 0.360784314, + blue: 0.360784314, + alpha: 1.0, + }; + + /// The web colour <span style="color: indigo;">`indigo`</span>. + pub const INDIGO: Self = Self { + red: 0.294117647, + green: 0.0, + blue: 0.509803922, + alpha: 1.0, + }; + + /// The web colour <span style="color: ivory;">`ivory`</span>. + pub const IVORY: Self = Self { + red: 1.0, + green: 1.0, + blue: 0.941176471, + alpha: 1.0, + }; + + /// The web colour <span style="color: khaki;">`khaki`</span>. + pub const KHAKI: Self = Self { + red: 0.941176471, + green: 0.901960784, + blue: 0.549019608, + alpha: 1.0, + }; + + /// The web colour <span style="color: lavender;">`lavender`</span>. + pub const LAVENDER: Self = Self { + red: 0.901960784, + green: 0.901960784, + blue: 0.980392157, + alpha: 1.0, + }; + + /// The web colour <span style="color: lavenderblush;">`lavenderblush`</span>. + pub const LAVENDER_BLUSH: Self = Self { + red: 1.0, + green: 0.941176471, + blue: 0.960784314, + alpha: 1.0, + }; + + /// The web colour <span style="color: lawngreen;">`lawngreen`</span>. + pub const LAWN_GREEN: Self = Self { + red: 0.486274510, + green: 0.988235294, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: lemonchiffon;">`lemonchiffon`</span>. + pub const LEMON_CHIFFON: Self = Self { + red: 1.0, + green: 0.980392157, + blue: 0.803921569, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightblue;">`lightblue`</span>. + pub const LIGHT_BLUE: Self = Self { + red: 0.678431373, + green: 0.847058824, + blue: 0.901960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightcoral;">`lightcoral`</span>. + pub const LIGHT_CORAL: Self = Self { + red: 0.941176471, + green: 0.501960784, + blue: 0.501960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightcyan;">`lightcyan`</span>. + pub const LIGHT_CYAN: Self = Self { + red: 0.878431373, + green: 1.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightgoldenrodyellow;">`lightgoldenrodyellow`</span>. + pub const LIGHT_GOLDENROD_YELLOW: Self = Self { + red: 0.980392157, + green: 0.980392157, + blue: 0.823529412, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightgreen;">`lightgreen`</span>. + pub const LIGHT_GREEN: Self = Self { + red: 0.564705882, + green: 0.933333333, + blue: 0.564705882, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightgrey;">`lightgrey`</span>. + pub const LIGHT_GREY: Self = Self { + red: 0.827450980, + green: 0.827450980, + blue: 0.827450980, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightpink;">`lightpink`</span>. + pub const LIGHT_PINK: Self = Self { + red: 1.0, + green: 0.713725490, + blue: 0.756862745, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightsalmon;">`lightsalmon`</span>. + pub const LIGHT_SALMON: Self = Self { + red: 1.0, + green: 0.627450980, + blue: 0.478431373, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightseagreen;">`lightseagreen`</span>. + pub const LIGHT_SEA_GREEN: Self = Self { + red: 0.125490196, + green: 0.698039216, + blue: 0.666666667, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightskyblue;">`lightskyblue`</span>. + pub const LIGHT_SKY_BLUE: Self = Self { + red: 0.529411765, + green: 0.807843137, + blue: 0.980392157, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightslategrey;">`lightslategrey`</span>. + pub const LIGHT_SLATE_GREY: Self = Self { + red: 0.466666667, + green: 0.533333333, + blue: 0.6, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightsteelblue;">`lightsteelblue`</span>. + pub const LIGHT_STEEL_BLUE: Self = Self { + red: 0.690196078, + green: 0.768627451, + blue: 0.870588235, + alpha: 1.0, + }; + + /// The web colour <span style="color: lightyellow;">`lightyellow`</span>. + pub const LIGHT_YELLOW: Self = Self { + red: 1.0, + green: 1.0, + blue: 0.878431373, + alpha: 1.0, + }; + + /// The web colour <span style="color: lime;">`lime`</span>. + pub const LIME: Self = Self { + red: 0.0, + green: 1.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: limegreen;">`limegreen`</span>. + pub const LIME_GREEN: Self = Self { + red: 0.196078431, + green: 0.803921569, + blue: 0.196078431, + alpha: 1.0, + }; + + /// The web colour <span style="color: linen;">`linen`</span>. + pub const LINEN: Self = Self { + red: 0.980392157, + green: 0.941176471, + blue: 0.901960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: magenta;">`magenta`</span>. + pub const MAGENTA: Self = Self { + red: 1.0, + green: 0.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: maroon;">`maroon`</span>. + pub const MAROON: Self = Self { + red: 0.501960784, + green: 0.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumaquamarine;">`mediumaquamarine`</span>. + pub const MEDIUM_AQUAMARINE: Self = Self { + red: 0.4, + green: 0.803921569, + blue: 0.666666667, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumblue;">`mediumblue`</span>. + pub const MEDIUM_BLUE: Self = Self { + red: 0.0, + green: 0.0, + blue: 0.803921569, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumorchid;">`mediumorchid`</span>. + pub const MEDIUM_ORCHID: Self = Self { + red: 0.729411765, + green: 0.333333333, + blue: 0.827450980, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumpurple;">`mediumpurple`</span>. + pub const MEDIUM_PURPLE: Self = Self { + red: 0.576470588, + green: 0.439215686, + blue: 0.858823529, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumseagreen;">`mediumseagreen`</span>. + pub const MEDIUM_SEA_GREEN: Self = Self { + red: 0.235294118, + green: 0.701960784, + blue: 0.443137255, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumslateblue;">`mediumslateblue`</span>. + pub const MEDIUM_SLATE_BLUE: Self = Self { + red: 0.482352941, + green: 0.407843137, + blue: 0.933333333, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumspringgreen;">`mediumspringgreen`</span>. + pub const MEDIUM_SPRING_GREEN: Self = Self { + red: 0.0, + green: 0.980392157, + blue: 0.603921569, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumturquoise;">`mediumturquoise`</span>. + pub const MEDIUM_TURQUOISE: Self = Self { + red: 0.282352941, + green: 0.819607843, + blue: 0.8, + alpha: 1.0, + }; + + /// The web colour <span style="color: mediumvioletred;">`mediumvioletred`</span>. + pub const MEDIUM_VIOLET_RED: Self = Self { + red: 0.780392157, + green: 0.082352941, + blue: 0.521568627, + alpha: 1.0, + }; + + /// The web colour <span style="color: midnightblue;">`midnightblue`</span>. + pub const MIDNIGHT_BLUE: Self = Self { + red: 0.098039216, + green: 0.098039216, + blue: 0.439215686, + alpha: 1.0, + }; + + /// The web colour <span style="color: mintcream;">`mintcream`</span>. + pub const MINT_CREAM: Self = Self { + red: 0.960784314, + green: 1.0, + blue: 0.980392157, + alpha: 1.0, + }; + + /// The web colour <span style="color: mistyrose;">`mistyrose`</span>. + pub const MISTY_ROSE: Self = Self { + red: 1.0, + green: 0.894117647, + blue: 0.882352941, + alpha: 1.0, + }; + + /// The web colour <span style="color: moccasin;">`moccasin`</span>. + pub const MOCCASIN: Self = Self { + red: 1.0, + green: 0.894117647, + blue: 0.709803922, + alpha: 1.0, + }; + + /// The web colour <span style="color: navajowhite;">`navajowhite`</span>. + pub const NAVAJO_WHITE: Self = Self { + red: 1.0, + green: 0.870588235, + blue: 0.678431373, + alpha: 1.0, + }; + + /// The web colour <span style="color: navy;">`navy`</span>. + pub const NAVY: Self = Self { + red: 0.0, + green: 0.0, + blue: 0.501960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: oldlace;">`oldlace`</span>. + pub const OLD_LACE: Self = Self { + red: 0.992156863, + green: 0.960784314, + blue: 0.901960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: olive;">`olive`</span>. + pub const OLIVE: Self = Self { + red: 0.501960784, + green: 0.501960784, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: olivedrab;">`olivedrab`</span>. + pub const OLIVE_DRAB: Self = Self { + red: 0.419607843, + green: 0.556862745, + blue: 0.137254902, + alpha: 1.0, + }; + + /// The web colour <span style="color: orange;">`orange`</span>. + pub const ORANGE: Self = Self { + red: 1.0, + green: 0.647058824, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: orangered;">`orangered`</span>. + pub const ORANGE_RED: Self = Self { + red: 1.0, + green: 0.270588235, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: orchid;">`orchid`</span>. + pub const ORCHID: Self = Self { + red: 0.854901961, + green: 0.439215686, + blue: 0.839215686, + alpha: 1.0, + }; + + /// The web colour <span style="color: palegoldenrod;">`palegoldenrod`</span>. + pub const PALE_GOLDENROD: Self = Self { + red: 0.933333333, + green: 0.909803922, + blue: 0.666666667, + alpha: 1.0, + }; + + /// The web colour <span style="color: palegreen;">`palegreen`</span>. + pub const PALE_GREEN: Self = Self { + red: 0.596078431, + green: 0.984313725, + blue: 0.596078431, + alpha: 1.0, + }; + + /// The web colour <span style="color: paleturquoise;">`paleturquoise`</span>. + pub const PALE_TURQUOISE: Self = Self { + red: 0.686274510, + green: 0.933333333, + blue: 0.933333333, + alpha: 1.0, + }; + + /// The web colour <span style="color: palevioletred;">`palevioletred`</span>. + pub const PALE_VIOLET_RED: Self = Self { + red: 0.858823529, + green: 0.439215686, + blue: 0.576470588, + alpha: 1.0, + }; + + /// The web colour <span style="color: papayawhip;">`papayawhip`</span>. + pub const PAPAYA_WHIP: Self = Self { + red: 1.0, + green: 0.937254902, + blue: 0.835294118, + alpha: 1.0, + }; + + /// The web colour <span style="color: peachpuff;">`peachpuff`</span>. + pub const PEACH_PUFF: Self = Self { + red: 1.0, + green: 0.854901961, + blue: 0.725490196, + alpha: 1.0, + }; + + /// The web colour <span style="color: peru;">`peru`</span>. + pub const PERU: Self = Self { + red: 0.803921569, + green: 0.521568627, + blue: 0.247058824, + alpha: 1.0, + }; + + /// The web colour <span style="color: pink;">`pink`</span>. + pub const PINK: Self = Self { + red: 1.0, + green: 0.752941176, + blue: 0.796078431, + alpha: 1.0, + }; + + /// The web colour <span style="color: plum;">`plum`</span>. + pub const PLUM: Self = Self { + red: 0.866666667, + green: 0.627450980, + blue: 0.866666667, + alpha: 1.0, + }; + + /// The web colour <span style="color: powderblue;">`powderblue`</span>. + pub const POWDER_BLUE: Self = Self { + red: 0.690196078, + green: 0.878431373, + blue: 0.901960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: purple;">`purple`</span>. + pub const PURPLE: Self = Self { + red: 0.501960784, + green: 0.0, + blue: 0.501960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: rebeccapurple;">`rebeccapurple`</span>. + pub const REBECCA_PURPLE: Self = Self { + red: 0.4, + green: 0.2, + blue: 0.6, + alpha: 1.0, + }; + + /// The web colour <span style="color: red;">`red`</span>. + pub const RED: Self = Self { + red: 1.0, + green: 0.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: rosybrown;">`rosybrown`</span>. + pub const ROSY_BROWN: Self = Self { + red: 0.737254902, + green: 0.560784314, + blue: 0.560784314, + alpha: 1.0, + }; + + /// The web colour <span style="color: royalblue;">`royalblue`</span>. + pub const ROYAL_BLUE: Self = Self { + red: 0.254901961, + green: 0.411764706, + blue: 0.882352941, + alpha: 1.0, + }; + + /// The web colour <span style="color: saddlebrown;">`saddlebrown`</span>. + pub const SADDLE_BROWN: Self = Self { + red: 0.545098039, + green: 0.270588235, + blue: 0.074509804, + alpha: 1.0, + }; + + /// The web colour <span style="color: salmon;">`salmon`</span>. + pub const SALMON: Self = Self { + red: 0.980392157, + green: 0.501960784, + blue: 0.447058824, + alpha: 1.0, + }; + + /// The web colour <span style="color: sandybrown;">`sandybrown`</span>. + pub const SANDY_BROWN: Self = Self { + red: 0.956862745, + green: 0.643137255, + blue: 0.376470588, + alpha: 1.0, + }; + + /// The web colour <span style="color: seagreen;">`seagreen`</span>. + pub const SEA_GREEN: Self = Self { + red: 0.180392157, + green: 0.545098039, + blue: 0.341176471, + alpha: 1.0, + }; + + /// The web colour <span style="color: seashell;">`seashell`</span>. + pub const SEASHELL: Self = Self { + red: 1.0, + green: 0.960784314, + blue: 0.933333333, + alpha: 1.0, + }; + + /// The web colour <span style="color: sienna;">`sienna`</span>. + pub const SIENNA: Self = Self { + red: 0.627450980, + green: 0.321568627, + blue: 0.176470588, + alpha: 1.0, + }; + + /// The web colour <span style="color: silver;">`silver`</span>. + pub const SILVER: Self = Self { + red: 0.752941176, + green: 0.752941176, + blue: 0.752941176, + alpha: 1.0, + }; + + /// The web colour <span style="color: skyblue;">`skyblue`</span>. + pub const SKY_BLUE: Self = Self { + red: 0.529411765, + green: 0.807843137, + blue: 0.921568627, + alpha: 1.0, + }; + + /// The web colour <span style="color: slateblue;">`slateblue`</span>. + pub const SLATE_BLUE: Self = Self { + red: 0.415686275, + green: 0.352941176, + blue: 0.803921569, + alpha: 1.0, + }; + + /// The web colour <span style="color: slategrey;">`slategrey`</span>. + pub const SLATE_GREY: Self = Self { + red: 0.439215686, + green: 0.501960784, + blue: 0.564705882, + alpha: 1.0, + }; + + /// The web colour <span style="color: snow;">`snow`</span>. + pub const SNOW: Self = Self { + red: 1.0, + green: 0.980392157, + blue: 0.980392157, + alpha: 1.0, + }; + + /// The web colour <span style="color: springgreen;">`springgreen`</span>. + pub const SPRING_GREEN: Self = Self { + red: 0.0, + green: 1.0, + blue: 0.498039216, + alpha: 1.0, + }; + + /// The web colour <span style="color: steelblue;">`steelblue`</span>. + pub const STEEL_BLUE: Self = Self { + red: 0.274509804, + green: 0.509803922, + blue: 0.705882353, + alpha: 1.0, + }; + + /// The web colour <span style="color: tan;">`tan`</span>. + pub const TAN: Self = Self { + red: 0.823529412, + green: 0.705882353, + blue: 0.549019608, + alpha: 1.0, + }; + + /// The web colour <span style="color: teal;">`teal`</span>. + pub const TEAL: Self = Self { + red: 0.0, + green: 0.501960784, + blue: 0.501960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: thistle;">`thistle`</span>. + pub const THISTLE: Self = Self { + red: 0.847058824, + green: 0.749019608, + blue: 0.847058824, + alpha: 1.0, + }; + + /// The web colour <span style="color: tomato;">`tomato`</span>. + pub const TOMATO: Self = Self { + red: 1.0, + green: 0.388235294, + blue: 0.278431373, + alpha: 1.0, + }; + + /// The web colour <span style="color: turquoise;">`turquoise`</span>. + pub const TURQUOISE: Self = Self { + red: 0.250980392, + green: 0.878431373, + blue: 0.815686275, + alpha: 1.0, + }; + + /// The web colour <span style="color: violet;">`violet`</span>. + pub const VIOLET: Self = Self { + red: 0.933333333, + green: 0.509803922, + blue: 0.933333333, + alpha: 1.0, + }; + + /// The web colour <span style="color: wheat;">`wheat`</span>. + pub const WHEAT: Self = Self { + red: 0.960784314, + green: 0.870588235, + blue: 0.701960784, + alpha: 1.0, + }; + + /// The web colour <span style="color: white;">`white`</span>. + pub const WHITE: Self = Self { + red: 1.0, + green: 1.0, + blue: 1.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: whitesmoke;">`whitesmoke`</span>. + pub const WHITE_SMOKE: Self = Self { + red: 0.960784314, + green: 0.960784314, + blue: 0.960784314, + alpha: 1.0, + }; + + /// The web colour <span style="color: yellow;">`yellow`</span>. + pub const YELLOW: Self = Self { + red: 1.0, + green: 1.0, + blue: 0.0, + alpha: 1.0, + }; + + /// The web colour <span style="color: yellowgreen;">`yellowgreen`</span>. + pub const YELLOW_GREEN: Self = Self { + red: 0.603921569, + green: 0.803921569, + blue: 0.196078431, + alpha: 1.0, + }; +} diff --git a/benoit/src/complex/from_str.rs b/benoit/src/complex/from_str.rs new file mode 100644 index 0000000..5ea7f36 --- /dev/null +++ b/benoit/src/complex/from_str.rs @@ -0,0 +1,65 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::PRECISION; +use crate::complex::Complex; +use crate::error::Error; + +use rug::Float; +use std::str::FromStr; + +impl FromStr for Complex { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let seperator = { + let skip: usize = match s.find('-') { + Some(0x0) => 0x1, + _ => 0x0, + }; + + let s = &s[skip..]; + + if let Some(index) = s.find('+') { + index + skip + } else if let Some(index) = s.find('-') { + index + skip + } else { + return Err(Error::BadComplexSeperator { expr: s.to_owned() }); + } + }; + + let unit = s.len() - 0x1; + + if s.find('i') != Some(unit) { + return Err(Error::MissingImaginaryUnit { expr: s.to_owned() }); + }; + + + let real = Float::parse(&s[..seperator])?; + let imag = Float::parse(&s[seperator..unit])?; + + Ok(Self::with_val(PRECISION, real, imag)) + } +} diff --git a/benoit/src/complex/mod.rs b/benoit/src/complex/mod.rs new file mode 100644 index 0000000..812e849 --- /dev/null +++ b/benoit/src/complex/mod.rs @@ -0,0 +1,75 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +//! Complex numbers. + +mod from_str; + +use rug::{Assign, Float}; +use rug::complex::Prec; + +/// Complex floating-point type. +/// +/// This structure tries to emulate Rug's own `Complex`, but without boilerplate. +/// +/// This is mainly done by not defining any arithmetic traits. +/// As we do have performance critical needs, we should instead use the parts (real and imaginary) directly, as doing this can also lead to optimisations on our end. +/// +/// We still do, however, define the [`new`](Complex::new) and [`with_val`](Complex::with_val) constructors, altough the latter has a sligthly different syntax. +/// +/// The alternative to defining this structure would be to use a tuple `(rug::Float, rug::Float)`. +#[derive(Clone, Debug, PartialEq)] +pub struct Complex { + /// The real part of the complex number. + pub real: Float, + + /// The imaginary part of the complex number. + pub imag: Float, +} + +impl Complex { + /// Constructs a new complex number with the value zero. + #[must_use] + pub fn new<P: Prec>(prec: P) -> Self { + let prec = prec.prec(); + + let real = Float::new(prec.0); + let imag = Float::new(prec.1); + + Self { real, imag } + } + + /// Constructs a new complex number with the specified value. + #[must_use] + pub fn with_val<P: Prec, T, U>(prec: P, real: T, imag: U) -> Self + where + Float: Assign<T> + Assign<U>, { + let prec = prec.prec(); + + let real = Float::with_val(prec.0, real); + let imag = Float::with_val(prec.1, imag); + + Self { real, imag } + } +} diff --git a/benoit/src/error/mod.rs b/benoit/src/error/mod.rs new file mode 100644 index 0000000..da99e65 --- /dev/null +++ b/benoit/src/error/mod.rs @@ -0,0 +1,136 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use png::EncodingError; +use rug::float::ParseFloatError; +use std::error::Error as StdError; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::path::PathBuf; + +/// Denotes an error. +#[derive(Debug)] +pub enum Error { + BadComplexSeperator { expr: String }, + + BigFloatParseFailure { source: ParseFloatError }, + + FileCreationFailure { path: PathBuf }, + + ImageEncodingFailure { source: EncodingError }, + + MissingImaginaryUnit { expr: String }, + + MissingRenderGeneration, + + MissingRenderPlot, + + NanColourParameter { name: &'static str }, + + UnknownFractal { string: String }, + + UnknownPalette { string: String }, + + ZeroLengthPaletteData, + + ZeroLengthRender, +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + #[allow(clippy::enum_glob_use)] + use Error::*; + + match *self { + BadComplexSeperator { ref expr } => { + write!(f, "bad complex seperator ('+' or '-') in expression \"{expr}\"") + }, + + BigFloatParseFailure { source } => { + write!(f, "unable to parse bigfloat: \"{source}\"") + }, + + FileCreationFailure { ref path } => { + write!(f, "unable to create file at \"{}\"", path.display()) + }, + + ImageEncodingFailure { ref source } => { + write!(f, "unable to encode image: \"{source}\"") + }, + + MissingImaginaryUnit { ref expr } => { + write!(f, "missing imaginary unit 'i' in expression \"{expr}\"") + }, + + MissingRenderGeneration => { + write!(f, "no render has been generated yet") + }, + + MissingRenderPlot => { + write!(f, "no image has been plotted") + }, + + NanColourParameter { name } => { + write!(f, "parameter `{name}` cannot be nan") + }, + + UnknownFractal { ref string } => { + write!(f, "unknown fractal \"{string}\"") + }, + + UnknownPalette { ref string } => { + write!(f, "unknown palette \"{string}\"") + }, + + ZeroLengthPaletteData => { + write!(f, "palette data cannot have a length of zero") + }, + + ZeroLengthRender => { + write!(f, "total render size must be non-zero") + }, + } + } +} + +impl From<ParseFloatError> for Error { + fn from(value: ParseFloatError) -> Self { + Self::BigFloatParseFailure { source: value } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + #[allow(clippy::enum_glob_use)] + use Error::*; + + #[allow(clippy::wildcard_enum_match_arm)] + match *self { + BigFloatParseFailure { ref source } => Some(source), + + ImageEncodingFailure { ref source } => Some(source), + + _ => None, + } + } +} diff --git a/benoit/src/fractal/iters.rs b/benoit/src/fractal/iters.rs new file mode 100644 index 0000000..0a705a9 --- /dev/null +++ b/benoit/src/fractal/iters.rs @@ -0,0 +1,233 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::PRECISION; +use crate::complex::Complex; + +use rug::{Assign, Float}; + +pub(in super) fn iter_antibrot(z: &mut Complex, c: &Complex) { + // z(n + 1) = int z(n)^2 + c dz + // + // let: + // za = Re z, zb = zb + // ca = Re c, cb = Im c + // + // (za + zb * i)^(2 + 1) / (2 + 1) + (za + zb * i) * (ca + cb * i) + // = (za + zb * i)^3 / 3 + (za + zb * i) * (ca + cb * i) + // = (za + zb * i) * (za + zb * i) * (za + zb * i) / 3 + (za + zb * i) * (ca + cb * i) + // = (za^3 + za^2 * zb * i + za^2 * zb * i - za * zb^2 + za^2 * zb * i - za * zb^2 - za * zb^2 - zb^3 * i) / 3 + za * ca + za * cb * i + zb * ca * i - zb * cb + // = (za^3 + za^2 * zb * i * 3 - za * zb^2 * 3 - zb^3 * i) / 3 + za * ca + za * cb * i + zb * ca * i - zb * cb + // + // a + // = (za^3 - za * zb^2 * 3) / 3 + za * ca - zb * cb + // = za * (za^2 - zb^2 * 3) / 3 + za * ca - zb * cb + // + // b + // = (za^2 * zb * 3 - zb^3) / 3 + za * cb + zb * ca + // = zb * (za^2 * 3 - zb^2) / 3 + za * cb + zb * ca + + let temp_z = z.clone(); // z + + let za_square = z.real.clone().square(); // za^2 + let zb_square = z.imag.clone().square(); // zb^2 + + let mut temp = Float::with_val(PRECISION, &zb_square * -0x3); // zb^2 * -3 = -(zb^2 * 3) + temp += &za_square; // za^2 - zb^2 * 3 + + z.real *= &temp; // za^3 - za * zb^2 * 3 + z.real /= 0x3; // (za^3 - za * zb^2 * 3) / 3 + z.real += &temp_z.real * &c.real; // (za^3 - za * zb^2 * 3) / 3 + za * ca + z.real -= &temp_z.imag * &c.imag; // (za^3 - za * zb^2 * 3) / 3 + za * ca - zb * cb + + temp.assign(&za_square * 0x3); // za^2 * 3 + temp -= &zb_square; // za^2 * 3 - zb^2 + + z.imag *= &temp; // za^2 * zb * 3 - zb^3 + z.imag /= 0x3; // (za^2 * zb * 3 - zb^3) / 3 + z.imag += &temp_z.real * &c.imag; // (za^2 * zb * 3 - zb^3) / 3 + za * cb + z.imag += &temp_z.imag * &c.real; // (za^2 * zb * 3 - zb^3) / 3 + za * cb + zb * ca +} + +pub(in super) fn iter_burning_ship(z: &mut Complex, c: &Complex) { + // z(n + 1) = (|Re z(n)| + |Im z(n)| * i)^2 + c + // + // let: + // za = Re z, zb = Im z + // ca = Re c, cb = Im c + // + // (|za| + |zb| * i)^2 + ca + cb * i + // = (|za| + |zb| * i) * (|za| + |zb| * i) + ca + cb * i + // = |za|^2 + |za| * |zb| * i + |za| * |zb| * i - |zb|^2 + ca + cb * i + // = za^2 + |zb * za| * i * 2 - zb^2 + ca + cb * i + // + // a + // = za^2 - zb^2 + ca + // + // b + // = |za| * |zb| * 2 + cb + // = |za * zb| * 2 + cb + + let temp = z.real.clone(); // za + + z.real.square_mut(); // za^2 + z.real -= &z.imag * &z.imag; // za^2 - zb^2 + z.real += &c.real; // za^2 - zb^2 + ca + + z.imag *= &temp; // za * zb + z.imag.abs_mut(); // |za * zb| + z.imag *= 0x2; // |za * zb| * 2 + z.imag += &c.imag; // |za * zb| * 2 + cb +} + +pub(in super) fn iter_mandelbrot(z: &mut Complex, c: &Complex) { + // z(n + 1) = z(n)^2 + c + // + // let: + // za = Re z, zb = Im z + // ca = Re c, cb = Im c + // + // (za + zb * i)^2 + ca + cb * i + // = (za + zb * i) * (za + zb * i) + ca + cb * i + // = za^2 + za * zb * i + za * zb * i - zb^2 + ca + cb * i + // = za^2 + zb * za * i * 2 - zb^2 + ca + cb * i + // + // a + // = za^2 - zb^2 + ca + // + // b + // = za * zb * 2 + cb + + let temp = z.real.clone(); // za + + z.real.square_mut(); // za^2 + z.real -= &z.imag * &z.imag; // za^2 - zb^2 + z.real += &c.real; // za^2 - zb^2 + ca + + z.imag *= &temp; // zb * zb + z.imag *= 0x2; // za * zb * 2 + z.imag += &c.imag; // za * zb * 2 + cb +} + +pub(in super) fn iter_multibrot3(z: &mut Complex, c: &Complex) { + // z(n + 1) = z(n)^3 + c + // + // let: + // za = Re z, zb = zb + // ca = Re c, cb = Im c + // + // (za + zb * i)^3 + ca + cb * i + // = (za + zb * i) * (za + zb * i) * (za + zb * i) + ca + cb * i + // = za^3 + za^2 * zb * i + za^2 * zb * i - za * zb^2 + za^2 * zb * i - za * zb^2 - za * zb^2 - zb^3 * i + ca + cb * i + // = za^3 + za^2 * zb * i * 3 - za * zb^2 * 3 - zb^3 * i + ca + cb + i + // + // a + // = za^3 - za * zb^2 * 3 + ca + // = za * (za^2 - zb^2 * 3) + ca + // + // b + // = za^2 * zb * 3 - zb^3 + cb + // = zb * (za^2 * 3 - zb^2) + cb + + let za_square = z.real.clone().square(); // za^2 + let zb_square = z.imag.clone().square(); // zb^2 + + let mut temp = Float::with_val(PRECISION, &zb_square * -0x3); // zb^2 * (-3) = -(zb^2 * 3) + temp += &za_square; // za^2 - zb^2 * 3 + + z.real *= &temp; // za^3 - za * zb^2 * 3 + z.real += &c.real; // za^3 - za * zb^2 * 3 + ca + + temp.assign(&za_square * 0x3); // za^2 * 3 + temp -= &zb_square; // za^2 * 3 - zb^2 + + z.imag *= &temp; // za^2 * zb * 3 - zb^3 + z.imag += &c.imag; // za^2 * zb * 3 - zb^3 + cb +} + +pub(in super) fn iter_multibrot4(z: &mut Complex, c: &Complex) { + // TODO: This is the last iterator that needs to be + // refactored. + + // (a+bi)^4 + // = (a+bi)^2*(a+bi)^2 + // = (a^2-b^2+2abi)^2 + // = (a^2-b^2+2abi)(a^2-b^2+2abi) + // = a^4-(a^2)b^2+2(a^3)bi-(a^2)b^2+b^4-2a(b^3)i-4(a^2)b^2+2(a^3)bi-2a(b^3)i-4(a^2)b^2 + // = a^4-6(a^2)b^2+4(a^3)bi+b^4-4a(b^3)i + // + // <=> a = a^4-6(a^2)b^2+b^4 + // b = 4(a^3)bi-4a(b^3)i + + let temp0 = Float::with_val(PRECISION, &z.real * &z.real); // a^2 + let temp1 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2 + + let mut temp2 = Float::with_val(PRECISION, &z.real * &z.imag); // ab + temp2 *= 4.0; // 4ab + + z.real.assign(&temp0); // a^2 + z.real /= 6.0; // a^2/6 + z.real -= &temp1; // a^2/6-b^2 + z.real *= &temp0; // a^4/6-(a^2)b^2 + z.real *= 6.0; // a^4-6(a^2)b^2 + + z.imag.assign(&temp1); // b^2 + z.imag *= -1.0; // -b^2 + z.imag += &temp0; // a^2-b^2 + z.imag *= temp2; // 4(a^3)b-4ab^3 + + z.real += temp1.square(); // a^4-6(a^2)b^2+b^4 + z.real += &c.real; // a^4-6(a^2)b^2+b^4+Re(c) + + z.imag += &c.imag; // 4(a^3)b-4ab^3+Im(c) +} + +pub(in super) fn iter_tricorn(z: &mut Complex, c: &Complex) { + // z(n + 1) = (Re z(n) - Im z(n) * i)^2 + c + // + // let: + // za = Re z, zb = Im z + // ca = Re c, cb = Im c + // + // (za - zb * i)^2 + ca + cb * i + // = (za - zb * i) * (za - zb * i) + ca + cb * i + // = za^2 - za * zb * i - za * zb * i - zb^2 + ca + cb * i + // = za^2 - zb * za * i * 2 - zb^2 + ca + cb * i + // + // a + // = za^2 - zb^2 + ca + // + // b + // = cb - za * zb * 2 + + let temp = z.real.clone(); // za + + z.real.square_mut(); // za^2 + z.real -= &z.imag * &z.imag; // za^2 - zb^2 + z.real += &c.real; // za^2 + + z.imag *= &temp; // za * zb + z.imag *= -2.0; // za * zb * -2 = -(za * zb * 2) + z.imag += &c.imag; // cb - za * zb * 2 +} diff --git a/benoit/src/fractal/mod.rs b/benoit/src/fractal/mod.rs new file mode 100644 index 0000000..1dfd9a2 --- /dev/null +++ b/benoit/src/fractal/mod.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +//! Fractals. + +mod iters; + +use crate::complex::Complex; +use crate::error::Error; + +use std::str::FromStr; + +/// Defines a fractal. +#[derive(Clone, Debug)] +pub struct Fractal { + pub iter: fn(&mut Complex, &Complex), + + pub exponent: f64, +} + +impl Fractal { + const ANTIBROT: Self = Self { + iter: iters::iter_antibrot, + + exponent: 3.0, // But doesn't look right? + }; + + const BURNING_SHIP: Self = Self { + iter: iters::iter_burning_ship, + + exponent: 2.0, + }; + + const MANDELBROT: Self = Self { + iter: iters::iter_mandelbrot, + + exponent: 2.0, + }; + + const MULTIBROT3: Self = Self { + iter: iters::iter_multibrot3, + + exponent: 3.0, + }; + + const MULTIBROT4: Self = Self { + iter: iters::iter_multibrot4, + + exponent: 4.0, + }; + + const TRICORN: Self = Self { + iter: iters::iter_tricorn, + + exponent: 2.0, + }; +} + +impl Default for Fractal { + fn default() -> Self { Self::MANDELBROT } +} + +impl FromStr for Fractal { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + | "mandelbrot" + | "multibrot2" + => Ok(Self::MANDELBROT), + + | "antibrot" + | "antibrot2" + => Ok(Self::ANTIBROT), + + "burning_ship" => Ok(Self::BURNING_SHIP), + "multibrot3" => Ok(Self::MULTIBROT3), + "multibrot4" => Ok(Self::MULTIBROT4), + "tricorn" => Ok(Self::TRICORN), + + _ => Err(Error::UnknownFractal { string: s.to_owned() }) + } + } +} + +impl PartialEq<Self> for Fractal { + fn eq(&self, other: &Self) -> bool { + self.iter == other.iter + } +} diff --git a/benoit/src/lib.rs b/benoit/src/lib.rs new file mode 100644 index 0000000..c8aa098 --- /dev/null +++ b/benoit/src/lib.rs @@ -0,0 +1,84 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +//! Utilites for rendering the Mandelbrot Set etc. +//! +//! This library is mostly intended for use by either `benoit-cli` or `benoit-gui`, but could be used by any programme. +//! Do note that API changed can come without notice. + +macro_rules! use_mod { + ($visibility:vis, $name:ident) => { + mod $name; + $visibility use $name::*; + }; +} +pub(in crate) use use_mod; + +pub mod colour; +pub mod complex; +pub mod error; +pub mod fractal; +pub mod palette; +pub mod render; +pub mod stopwatch; + +/// The version number of benoit, currently being `3.0.0`. +/// +/// The version numbers of `benoit-cli` and `benoit-gui` match this value. +pub const VERSION: (u32, u32, u32) = (0x3, 0x0, 0x0); // After 76 days of work. + +/// The precision used for arbitrary precision computations, in bits. +/// +/// This is to be used at all times when creating [`rug::Float`] objects for use with benoit. +pub const PRECISION: u32 = 0x100; + +/// Logs to `stderr` using predefined formats. +/// +/// ## `debug` +/// +/// Only logs if debug assertions are activated, i.e. if compiling in debug mode. +/// +/// This check is simple done by testing `debug_assertions`. +/// +/// ## `error` +/// +/// Prints the message as specified by `$format` as if passed directly to [`format`]. +/// +/// ## `value` +/// +/// Prints the value of `$value` using the [`Debug`](std::fmt::Debug) trait. +#[macro_export] +macro_rules! log { + (debug, $($format:tt)*) => {{ + if cfg!(debug_assertions) { eprintln!("\u{001B}[93m{}\u{001B}[0m", format!($($format)?)) }; + }}; + + (error, $($format:tt)*) => {{ + eprintln!("\u{001B}[91merror\u{001B}[0m: {}", format!($($format)?)); + }}; + + (value, $value:expr) => {{ + $crate::log!(debug, "{:?}", $value); + }}; +} diff --git a/benoit/src/palette/mod.rs b/benoit/src/palette/mod.rs new file mode 100644 index 0000000..bc135d0 --- /dev/null +++ b/benoit/src/palette/mod.rs @@ -0,0 +1,29 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +//! Palette support. + +use crate::use_mod; +use_mod!(pub, palette); +use_mod!(pub, palette_data); diff --git a/benoit/src/palette/palette/data.rs b/benoit/src/palette/palette/data.rs new file mode 100644 index 0000000..f339301 --- /dev/null +++ b/benoit/src/palette/palette/data.rs @@ -0,0 +1,548 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::colour::Colour; +use crate::palette::{Palette, PaletteData}; + +use enum_iterator::{all, cardinality}; +use lazy_static::lazy_static; +use rayon::prelude::*; +use rug::Float; + +impl Palette { + #[must_use] + pub fn data(self) -> &'static PaletteData { + &DATA[self as usize] + } +} + +lazy_static! { + static ref DATA: Box<[PaletteData]> = { + const BUFFER_LENGTH: usize = 0x10000; + + let mut data = vec![PaletteData::new(BUFFER_LENGTH).unwrap(); cardinality::<Palette>()].into_boxed_slice(); + + for palette in all::<Palette>() { + #[allow(clippy::enum_glob_use)] + use Palette::*; + + let colour_mapper = match palette { + Emerald => map_emerald, + Fire => map_fire, + Glacier => map_glacier, + Greyscale => map_greyscale, + Hsv => map_hsv, + Ink => map_ink, + Lch => map_lch, + Mask => map_mask, + Ruby => map_ruby, + Sapphire => map_sapphire, + Simple => map_simple, + Thunder => map_thunder, + Twilight => map_twilight, + }; + + data + .get_mut(palette as usize) + .unwrap() + .par_iter_mut() + .enumerate() + .for_each(move |(i, &mut (ref mut body_buffer, ref mut complement_buffer))| { + const PRECISION: u32 = usize::BITS * 0x8; + + // We need to guarantee that + let factor = Float::with_val(PRECISION, i) / BUFFER_LENGTH; + + let (body, complement) = colour_mapper(factor.to_f64()); + + (*body_buffer, *complement_buffer) = (body.into(), complement.into()); + }); + } + + data + }; +} + +/// Normalises to 0..1. +/// +/// The given factor is clamped to the range 0..1 and +fn normalise_factor(mut factor: f64) -> f64 { + factor = factor.clamp(0.0, 1.0); + + factor = if factor >= 1.0 / 2.0 { + 1.0 - factor + } else { + factor + }; + + factor * 2.0 +} + +#[must_use] +fn map_emerald(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let factor = normalise_factor(factor); + + Colour::from_srgba( + factor * factor, + factor, + factor * factor * factor, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_fire(mut factor: f64) -> (Colour, Colour) { + // Classic colour palette, fixed (and smoothed) + // from version ↋. + + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let mut factor = factor; + + let (red, green, blue) = if factor <= 0.25 { + factor *= 4.0; + + (factor, 0.0, 0.0) + } else if factor <= 0.5 { + factor -= 0.25; + factor *= 4.0; + + (1.0, factor, 0.0) + } else if factor <= 0.75 { + factor -= 0.5; + factor *= 4.0; + + (1.0, 1.0, factor) + } else { + factor -= 0.75; + factor *= 4.0; + factor = 1.0 - factor; + + (factor, factor, factor) + }; + + Colour::from_srgba( + red, + green, + blue, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_glacier(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let mut factor = 1.0 - normalise_factor(factor); + + factor *= factor; + + Colour::from_rgba( + (-factor).mul_add(1.0, 1.0), + (-factor).mul_add(0.25, 1.0), + 1.0, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_greyscale(mut factor: f64) -> (Colour, Colour) { + // Original palette function after the Rust- + // rewrite (version 10). + + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let factor = normalise_factor(factor); + + Colour::from_srgba( + factor, + factor, + factor, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_hsv(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + //let mut factor = factor; + + Colour::from_hsva( + factor, + 0.875, + 0.875, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_ink(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.5, + 0.5, + 0.625, + 1.0, + ).unwrap() + }; + + let complement = { + let mut factor = normalise_factor(factor); + + factor *= factor; + + Colour::from_srgba( + (factor).mul_add(0.5, 1.0), + (factor).mul_add(0.5, 1.0), + (factor).mul_add(0.375, 1.0), + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_lch(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + //let mut factor = factor; + + //// sRGB: + //Colour::from_lcha( + // 66.667, + // 40.080, + // factor, + // 1.0, + //).unwrap() + + // DCI P3: + Colour::from_lcha( + 66.667, + 52.650, + factor, + 1.0, + ).unwrap() + + //// Rec2020: + //Colour::from_lcha( + // 66.667, + // 55.630, + // factor, + // 1.0, + //).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_mask(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 1.0, + 0.0, + factor, + 1.0, + ).unwrap() + }; + + let complement = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + factor, + 0.0, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_ruby(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let factor = normalise_factor(factor); + + Colour::from_srgba( + factor, + factor * factor * factor, + factor * factor, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_sapphire(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let factor = normalise_factor(factor); + + Colour::from_srgba( + factor * factor * factor, + factor * factor, + factor, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_simple(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + //let mut factor = factor; + + let red = factor * 3.0 % 1.0; + let green = factor * 5.0 % 1.0; + let blue = factor * 7.0 % 1.0; + + Colour::from_srgba( + red, + green, + blue, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_thunder(mut factor: f64) -> (Colour, Colour) { + // This palette is horrible and I hope to reimple- + // ment it differently in the future. + + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + let mut factor = factor; + + let (red, green, blue) = if factor <= 0.5 { + factor *= 2.0; + + (factor, factor, 1.0 - factor) + } else { + factor -= 0.5; + factor *= 2.0; + + (1.0 - factor, 1.0 - factor, factor) + }; + + Colour::from_rgba( + red, + green, + blue, + 1.0, + ).unwrap() + }; + + (body, complement) +} + +#[must_use] +fn map_twilight(mut factor: f64) -> (Colour, Colour) { + factor = factor.clamp(0.0, 1.0); + + let body = { + //let mut factor = factor; + + Colour::from_srgba( + 0.0, + 0.0, + 0.0, + 1.0, + ).unwrap() + }; + + let complement = { + //let mut factor = factor; + + let red = 9.0 * (1.0 - factor) * factor * factor * factor; + let green = 15.0 * (1.0 - factor) * (1.0 - factor) * factor * factor; + let blue = 8.5 * (1.0 - factor) * (1.0 - factor) * (1.0 - factor) * factor; + + Colour::from_srgba( + red, + green, + blue, + 1.0, + ).unwrap() + }; + + (body, complement) +} diff --git a/benoit/src/palette/palette/mod.rs b/benoit/src/palette/palette/mod.rs new file mode 100644 index 0000000..8c01350 --- /dev/null +++ b/benoit/src/palette/palette/mod.rs @@ -0,0 +1,79 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +mod data; + +use crate::error::Error; + +use enum_iterator::Sequence; +use std::str::FromStr; + +/// The different types of palettes. +#[derive(Clone, Copy, Debug, Sequence)] +pub enum Palette { + Simple, + Twilight, + Fire, + Greyscale, + Ruby, + Emerald, + Sapphire, + Hsv, + Lch, + Ink, + Mask, + Thunder, + Glacier, +} + +impl Default for Palette { + fn default() -> Self { Self::Fire } +} + +impl FromStr for Palette { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + #[allow(clippy::enum_glob_use)] + use Palette::*; + + match s { + "emerald" => Ok(Emerald), + "fire" => Ok(Fire), + "greyscale" => Ok(Greyscale), + "glacier" => Ok(Glacier), + "hsv" => Ok(Hsv), + "ink" => Ok(Ink), + "lch" => Ok(Lch), + "mask" => Ok(Mask), + "ruby" => Ok(Ruby), + "sapphire" => Ok(Sapphire), + "simple" => Ok(Simple), + "thunder" => Ok(Thunder), + "twilight" => Ok(Twilight), + + _ => Err(Error::UnknownPalette { string: s.to_owned() }) + } + } +} diff --git a/benoit/src/palette/palette_data/mod.rs b/benoit/src/palette/palette_data/mod.rs new file mode 100644 index 0000000..689e630 --- /dev/null +++ b/benoit/src/palette/palette_data/mod.rs @@ -0,0 +1,84 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::error::Error; +use crate::render::ImageElement; + +use std::ops::{Deref, DerefMut, Index, IndexMut}; + +/// The information of the palette. +/// +/// Whilst anyone could create their own [`PaletteData`](crate::palette::PaletteData) object, static data may be retrieved using [`Palette::data`](crate::palette::Palette::data). +#[derive(Clone)] +pub struct PaletteData { + buffer: Box<[(ImageElement, ImageElement)]>, +} + +impl PaletteData { + /// Allocates new palette data. + /// + /// # Errors + /// + /// If the length is zero, a [`ZeroLengthPaletteData`](Error::ZeroLengthPaletteData) instance is returned. + pub fn new(len: usize) -> Result<Self, Error> { + if len == 0x0 { return Err(Error::ZeroLengthPaletteData) }; + + Ok(Self { buffer: vec![Default::default(); len].into_boxed_slice() }) + } + + #[must_use] + fn factor_to_index(&self, factor: f64) -> usize { + let index = factor * self.len() as f64; + + (index.trunc() as usize).clamp(0x0, self.len() - 0x1) + } +} + +impl Deref for PaletteData { + type Target = [(ImageElement, ImageElement)]; + + #[inline(always)] + fn deref(&self) -> &Self::Target { &self.buffer } +} + +impl DerefMut for PaletteData { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.buffer } +} + +impl Index<f64> for PaletteData { + type Output = (ImageElement, ImageElement); + + fn index(&self, index: f64) -> &Self::Output { + let index = self.factor_to_index(index); + self.get(index).unwrap() + } +} + +impl IndexMut<f64> for PaletteData { + fn index_mut(&mut self, index: f64) -> &mut Self::Output { + let index = self.factor_to_index(index); + self.get_mut(index).unwrap() + } +} diff --git a/benoit/src/render/image_element/mod.rs b/benoit/src/render/image_element/mod.rs new file mode 100644 index 0000000..20e945d --- /dev/null +++ b/benoit/src/render/image_element/mod.rs @@ -0,0 +1,102 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::colour::Colour; + +/// Type for points on the image canvas. +/// +/// This type is used in the image buffer of the [`Render`](crate::render::Render) type, as well as in the [`PaletteData`](crate::palette::PaletteData) type. +/// +/// As PNG specifies network byte order, the fields of this structure must also be big-endian. +/// It is up to the user of the type to ensure that this is done, perhaps using the [`from_ne`](crate::render::ImageElement::from_ne) method. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(C, packed)] +pub struct ImageElement { + /// The red channel. + pub red: u16, + + /// The green channel. + pub green: u16, + + /// The blue channel. + pub blue: u16, + + /// The alpha channel. + pub alpha: u16, +} + +impl ImageElement { + /// Zips the provided parameters. + /// + /// All four values are converted to big-endian before zipping. + #[must_use] + pub const fn from_ne(red: u16, green: u16, blue: u16, alpha: u16) -> Self { + // Remember network byte order. + Self { + red: red.to_be(), + green: green.to_be(), + blue: blue.to_be(), + alpha: alpha.to_be(), + } + } + + /// Unzips the structure into its parameters. + /// + /// All values are converted to the native endian, if needed. + #[must_use] + pub const fn to_ne(self) -> (u16, u16, u16, u16) { + ( + u16::from_be(self.red), + u16::from_be(self.green), + u16::from_be(self.blue), + u16::from_be(self.alpha), + ) + } +} + +impl Default for ImageElement { + #[inline(always)] + fn default() -> Self { Self::from_ne(0x0, 0x0, 0x0, 0x0) } +} + +impl From<Colour> for ImageElement { + fn from(value: Colour) -> Self { + let (red, green, blue, alpha) = value.to_srgba16(); + Self::from_ne(red, green, blue, alpha) + } +} + +#[allow(clippy::fallible_impl_from)] +impl From<ImageElement> for Colour { + fn from(value: ImageElement) -> Self { + let (red, green, blue, alpha) = value.to_ne(); + + Self::from_rgba( + f32::from(red) / f32::from(u16::MAX), + f32::from(green) / f32::from(u16::MAX), + f32::from(blue) / f32::from(u16::MAX), + f32::from(alpha) / f32::from(u16::MAX), + ).unwrap() // This is infallible. + } +} diff --git a/benoit/src/render/mod.rs b/benoit/src/render/mod.rs new file mode 100644 index 0000000..65c0588 --- /dev/null +++ b/benoit/src/render/mod.rs @@ -0,0 +1,31 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +//! Render utilities. + +use crate::use_mod; +use_mod!(pub, image_element); +use_mod!(pub, raw_element); +use_mod!(pub, render); +use_mod!(pub, render_config); diff --git a/benoit/src/render/raw_element/mod.rs b/benoit/src/render/raw_element/mod.rs new file mode 100644 index 0000000..b510018 --- /dev/null +++ b/benoit/src/render/raw_element/mod.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +/// The result of a render generation. +/// +/// The *raw* buffer of the [`Render`](crate::render::Render) type uses this structure. +#[derive(Clone, Copy, Debug)] +#[repr(align(0x80))] +pub struct RawElement { + /// The ammount of iterations made before escaping. + pub iter_count: u64, + + /// The distance to the origin. + pub distance: f64, +} + +impl Default for RawElement { + #[inline(always)] + fn default() -> Self { Self { iter_count: 0x0, distance: 0.0 } } +} diff --git a/benoit/src/render/render/dump_image.rs b/benoit/src/render/render/dump_image.rs new file mode 100644 index 0000000..8043b69 --- /dev/null +++ b/benoit/src/render/render/dump_image.rs @@ -0,0 +1,80 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::error::Error; +use crate::render::Render; + +use png::{ + BitDepth, + ColorType, + Compression, + Encoder, + SrgbRenderingIntent, +}; +use std::fs::File; +use std::io::BufWriter; +use std::path::Path; + + +impl Render { + /// Dumps the stored image at the provided location. + /// # Errors + /// + /// Yields an error if dump fails, which could include: + /// + /// If no image has been plotted (as by [`Render::plot`]), an [`Err`] object is also returned instead. + pub fn dump_image(&self, path: &Path) -> Result<(), Error> { + let Some(data) = self.image_as_bytes() else { + return Err(Error::MissingRenderPlot); + }; + + let Ok(file) = File::create(path) else { + return Err(Error::FileCreationFailure { path: path.to_owned() }) + }; + + let w = BufWriter::new(file); + + let mut encoder = Encoder::new(w, self.size.0, self.size.1); + encoder.set_color(ColorType::Rgba); + encoder.set_depth(BitDepth::Sixteen); + encoder.set_srgb(SrgbRenderingIntent::Perceptual); + + let compression = if cfg!(debug_assertions) { + Compression::Fast + } else { + Compression::Best + }; + encoder.set_compression(compression); + + let mut w = encoder.write_header().map_err( + |e| Error::ImageEncodingFailure { source: e } + )?; + + w.write_image_data(data).map_err( + |e| Error::ImageEncodingFailure { source: e } + )?; + + Ok(()) + } +} diff --git a/benoit/src/render/render/generate.rs b/benoit/src/render/render/generate.rs new file mode 100644 index 0000000..b11ddbd --- /dev/null +++ b/benoit/src/render/render/generate.rs @@ -0,0 +1,179 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::PRECISION; +use crate::complex::Complex; +use crate::fractal::Fractal; +use crate::render::{RawElement, Render, RenderConfig}; + +use rayon::prelude::*; +use rug::{Assign, Float}; +use rug::float::{free_cache, FreeCache, Special as FloatSpecial}; + +// The GenerationCache structure saves some values +// which are needed at every point. As these va- +// lues are constant during renders, we share a si- +// ngle cache for efficiency's sake. + +struct GenerationCache<'a> { + pub fractal: &'a Fractal, + pub inverse: bool, + pub julia: bool, + + pub max_iter_count: u64, + + pub centre: &'a Complex, + pub seed: &'a Complex, + pub zoom: &'a Float, +} + +impl Render { + /// Generates the render as defined by the provided configuration. + /// + /// This populates (and allocates) the internal "raw" buffer, allowing for [`plot`](Self::plot) to be called. + pub fn generate(&mut self, config: &RenderConfig) { + let mut data = self.raw_data.take().map_or_else(|| self.allocate_buffer(), |d| d); + + // Account for non-square proportions. Each ratio + // is the factor between that side's width and that + // of the other side. + let (width_ratio, height_ratio): (f64, f64) = { + let width = f64::from(self.size.0); + let height = f64::from(self.size.1); + + if width > height { + (1.0, height / width) + } else { + (width / height, 1.0) + } + }; + + let cache = GenerationCache { + fractal: &config.fractal, + inverse: config.inverse, + julia: config.julia, + + max_iter_count: config.max_iter_count, + centre: &config.centre, + seed: &config.seed, + zoom: &config.zoom, + }; + + // This factor maps cavas coordinates from pixels + // to natural units, keeping the canvas ratio in + // mind. + // + // At default zoom (i.e. a factor of one), the + // width of the entire set is (4) in natural units. + // In pixels, this corresponds to render width (e. + // g. 256 pixel). + let x_factor = 4.0 / f64::from(self.size.0) * width_ratio; + let y_factor = 4.0 / f64::from(self.size.1) * height_ratio; + + data + .par_iter_mut() + .enumerate() + .for_each(|(index, point)| { + // These conversions are infallible: + let x = u32::try_from(index % self.size.0 as usize).unwrap(); + let y = u32::try_from(index / self.size.0 as usize).unwrap(); + + // Convert the pixel coordinates to natural units. + let x = f64::from(x).mul_add(x_factor, -2.0); + let y = f64::from(y).mul_add(y_factor, -2.0); + + *point = generate_at_pixel(x, y, &cache); + + // This is recommended. + free_cache(FreeCache::Local); + }); + + self.raw_data = Some(data); + self.last_config = Some(config.clone()); + } +} + +fn generate_at_pixel(x: f64, y: f64, cache: &GenerationCache) -> RawElement { + // Resize the viewfinder according to the zoom + // level. + + let mut c = Complex::with_val(PRECISION, x / cache.zoom, y / cache.zoom); + + // Remember that pixel coordinates grow "down- + // wards," whilst euclidian coordinates grop up- + // wards. + + c.real += &cache.centre.real; + c.imag -= &cache.centre.imag; + + if cache.inverse { + let factor = Float::with_val(PRECISION, &c.real * &c.real + &c.imag * &c.imag).recip(); + + c.real *= &factor; + c.imag *= &factor; + } + + // The seed is treated as the Julia set's equiva- + // lent point on the complex plane. If not in "Ju- + // lia mode," the seed is instead used for the + // starting value of `z`. + + let (mut z, c) = if cache.julia { + (c, cache.seed) + } else { + (cache.seed.clone(), &c) + }; + + // Used for periodicity checking. + let mut prev_z = Complex::with_val(PRECISION, FloatSpecial::Nan, FloatSpecial::Nan); + + let mut iter_count = 0x0u64; + let mut square_dist = Float::with_val(PRECISION, FloatSpecial::Nan); + + while { + square_dist.assign(&z.real * &z.real + &z.imag * &z.imag); + + // Check if the value is periodic, i.e. its + // sequence repeats. + + if z == prev_z { + iter_count = cache.max_iter_count; + } + + // Having a larger escape radius gives better + // results with regard to smoothing. Source: tests. + + square_dist <= 0x100 && iter_count < cache.max_iter_count + } { + prev_z.real.assign(&z.real); + prev_z.imag.assign(&z.imag); + + (cache.fractal.iter)(&mut z, c); + + iter_count += 0x1; + } + + let distance = square_dist.sqrt().to_f64(); + RawElement { iter_count, distance } +} diff --git a/benoit/src/render/render/mod.rs b/benoit/src/render/render/mod.rs new file mode 100644 index 0000000..ef25c50 --- /dev/null +++ b/benoit/src/render/render/mod.rs @@ -0,0 +1,144 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +mod dump_image; +mod generate; +mod plot; + +use crate::error::Error; +use crate::render::{ImageElement, RawElement, RenderConfig}; + +use std::mem::size_of; +use std::ptr::from_ref as ptr_from_ref; +use std::slice::from_raw_parts as slice_from_raw_parts; + +/// Container for raw render data and image buffer. +#[derive(Clone)] +pub struct Render { + size: (u32, u32), + + raw_data: Option<Box<[RawElement]>>, + image_data: Option<Box<[ImageElement]>>, // RGBA8 + + last_config: Option<RenderConfig>, +} + +impl Render { + /// Creates a new render with the given dimensions. + /// The internal buffers must be allocated using [`Render::generate`] and [`Render::plot`]. + /// + /// # Panics + /// + /// Panics if the given size yields a product of zero. + pub const fn new(width: u32, height: u32) -> Result<Self, Error> { + let data_length = height as usize * width as usize; + if data_length == 0x0 { + return Err(Error::ZeroLengthRender); + } + + Ok(Self { + size: (width, height), + + last_config: None, + + raw_data: None, + image_data: None, + }) + } + + /// Resizes the render to the dimensions specified by `size`. + /// Both buffers are destroyed in the process and must be reallocated using [`Render::generate`] and [`Render::plot`]. + /// + /// If, however, the size remains unchanged, the structure also remains unchanged (including buffers). + /// + /// # Panics + /// + /// Panics if the given size yields a product of zero. + pub fn resize(&mut self, size: (u32, u32)) -> Result<(), Error> { + if size == self.size { return Ok(()) }; + + let len = size.1 as usize * size.0 as usize; + if len == 0x0 { + return Err(Error::ZeroLengthRender); + } + + // Remember that the buffers are lazily allocated. + self.raw_data = None; + self.image_data = None; + + Ok(()) + } + + /// Returns the dimensions of the render. + #[inline(always)] + #[must_use] + pub const fn size(&self) -> (u32, u32) { self.size } + + /// Borrows the raw render data as generated by [`Render::generate`]. + /// + /// If no render has been generated, [`None`] is returned instead. + #[inline(always)] + #[must_use] + pub fn raw_data(&self) -> Option<&[RawElement]> { self.raw_data.as_deref() } + + /// Borrows the image data as plotted by [`Render::plot`]. + /// + /// If no image has been plotted, [`None`] is returned instead. + #[inline(always)] + #[must_use] + pub fn image_data(&self) -> Option<&[ImageElement]> { self.image_data.as_deref() } + + /// Converts the image data to a slice of raw [`u8`] elements. + /// + /// This is particularly useful when needing to encode the image using format-agnostic encoders. + /// + /// If no image has been plotted (as by [`Render::plot`]), [`None`] is returned instead. + #[must_use] + pub fn image_as_bytes(&self) -> Option<&[u8]> { + let data = ptr_from_ref::<[ImageElement]>(self.image_data.as_ref()?); + + let length = self.size.1 as usize * self.size.0 as usize * size_of::<ImageElement>(); + let data = unsafe { slice_from_raw_parts(data.cast::<u8>(), length) }; + + Some(data) + } + + /// Borrows the last render configuration used. + /// + /// If no image has been plotted (as by [`Render::plot`]), [`None`] is returned instead. + #[inline(always)] + #[must_use] + pub const fn last_config(&self) -> Option<&RenderConfig> { self.last_config.as_ref() } + + /// Allocates an internal buffer. + /// + /// This function is generic and can in practice be used for any buffer. + /// + /// The buffer's length is set according to the dimensions of `self`. + #[must_use] + fn allocate_buffer<T: Clone + Default>(&self) -> Box<[T]> { + let len = self.size.1 as usize * self.size.0 as usize; + vec![Default::default(); len].into_boxed_slice() + } +} diff --git a/benoit/src/render/render/plot.rs b/benoit/src/render/render/plot.rs new file mode 100644 index 0000000..c882a55 --- /dev/null +++ b/benoit/src/render/render/plot.rs @@ -0,0 +1,94 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::error::Error; +use crate::palette::{Palette, PaletteData}; +use crate::render::{ImageElement, RawElement, Render}; + +use rayon::prelude::*; + +struct PlottingCache<'a> { + pub max_iter_count: u64, + pub colour_range: f64, + + pub exponent: f64, + + pub palette_data: &'a PaletteData, +} + +impl Render { + /// Plots the image according to the raw render data. + /// + /// This populates (and allocates) the internal image buffer, allowing for [`dump_image`](Self::dump_image) to be called. + /// + /// # Errors + /// + /// Yields an error if no raw render data has been generated (as by using [`generate`](Self::generate)). + pub fn plot(&mut self, palette: Palette, colour_range: f64) -> Result<(), Error> { + let mut data = self.image_data.take().map_or_else(|| self.allocate_buffer(), |d| d); + + let config = self.last_config() + .ok_or_else(|| Error::MissingRenderGeneration)?; + + let cache = PlottingCache { + max_iter_count: config.max_iter_count, + colour_range, + + exponent: config.fractal.exponent, + + palette_data: palette.data(), + }; + + // We have checked if there is a generation. + let raw_data = self.raw_data.as_mut().unwrap(); + + let get_colour = |data: RawElement| -> ImageElement { + if data.iter_count == cache.max_iter_count { + let factor = data.distance; + + cache.palette_data[factor].0 + } else { + let mut factor = data.iter_count as f64; + factor += 1.0 - data.distance.ln().log(cache.exponent); + factor /= cache.colour_range; + factor %= 1.0; + + cache.palette_data[factor].1 + } + }; + + data + .par_iter_mut() + .enumerate() + .for_each(move |(index, point)| { + let data = raw_data[index]; + + *point = get_colour(data); + }); + + self.image_data = Some(data); + + Ok(()) + } +} diff --git a/benoit/src/render/render_config/mod.rs b/benoit/src/render/render_config/mod.rs new file mode 100644 index 0000000..857f055 --- /dev/null +++ b/benoit/src/render/render_config/mod.rs @@ -0,0 +1,60 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit. + + benoit is free software: you can redistribute it + and/or modify it under the terms of the GNU Af- + fero General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit is distributed in the hope that it will + be useful, but WITHOUT ANY WARRANTY; without + even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Af- + fero General Public License along with benoit. + If not, see <https://www.gnu.org/licenses/>. +*/ + +use crate::complex::Complex; +use crate::fractal::Fractal; + +use rug::Float; + +/// Used to configure renders. +/// +/// This is expected by [`Render::generate`](crate::render::Render::generate), and also used by [`Render::plot`](crate::render::Render::plot). +#[derive(Clone, Debug)] +pub struct RenderConfig { + /// The fractal to be rendered. + pub fractal: Fractal, + + /// Whether to render the fractal's inverse or not. + pub inverse: bool, + + /// Whether to render a Julia set or not. + /// + /// If this is enabled, the `seed` field is used as the Julia set's point on the complex plane. + /// In all other cases (i.e. it is disabled), `seed` is used as the starting value of *z*. + pub julia: bool, + + /// The maximum ammount of iterations permitted. + pub max_iter_count: u64, + + /// The centre value of the viewfinder, on the complex plane. + pub centre: Complex, + + /// See `julia`. + pub seed: Complex, + + /// The zoom level of the render. + /// + /// Larger values converge the viewfinder on the value `centre`. + pub zoom: Float, +} diff --git a/benoit/src/stopwatch/mod.rs b/benoit/src/stopwatch/mod.rs new file mode 100644 index 0000000..4fe7f61 --- /dev/null +++ b/benoit/src/stopwatch/mod.rs @@ -0,0 +1,131 @@ +/* + Copyright 2021, 2023-2024 Gabriel Bjørnager Jen- + sen. + + This file is part of benoit-cli. + + benoit-cli is free software: you can redistrib- + ute it and/or modify it under the terms of the + GNU General Public License as published by the + Free Software Foundation, either version 3 of + the License, or (at your option) any later ver- + sion. + + benoit-cli is distributed in the hope that it + will be useful, but WITHOUT ANY WARRANTY; with- + out even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Gene- + ral Public License along with benoit-cli. If + not, see <https://www.gnu.org/licenses/>. +*/ + +//! Timing utilities. +//! +//! These are used e.g. by `benoit-cli` for timing renders. + +use std::fmt::{Display, Error, Formatter}; +use std::time::{Duration, Instant}; + +/// Type for timing. +/// +/// This primarily includes the timing of functions. +pub struct Stopwatch { + start: Instant, + stop: Option<Instant>, +} + +impl Stopwatch { + /// Creates a new stopwatch, starting at the time of calling. + #[must_use] + pub fn from_now() -> Self { + Self { + start: Instant::now(), + stop: None, + } + } + + /// Times the execution time of `func`. + /// + /// This is done by starting a stopwatch right before calling the function, followed by noting after it has returned. + /// + /// The return value is passed on alongside the stopwatch. + #[must_use] + pub fn test<F: FnOnce() -> Output, Output>(func: F) -> (Self, Output) { + let mut timer = Self::from_now(); + let result = func(); + timer.note(); + + (timer, result) + } + + /// Times the execution time of `func`. + /// + /// This is done by starting a stopwatch right before calling the function, followed by noting after it has returned. + /// + /// Differently from [`test`](Self::test), this method is suitable for functions which return a [`Result`] object with `T = ()`. + /// + /// # Errors + /// + /// Returns the error yielded by `func` (if any). + pub fn test_result<F: FnOnce() -> Result<(), E>, E>(func: F) -> Result<Self, E> { + let (timer, result) = Self::test(func); + result.map(|()| timer) + } + + /// Notes the current time for use with comparing the + pub fn note(&mut self) { + self.stop = Some(Instant::now()); + } + + /// Calculates the duration from start to the last note. + /// + /// If no note has been made (as by using [`note`](Self::note)), a [`None`] instance is returned. + #[must_use] + pub fn duration(&self) -> Option<Duration> { + let stop = self.stop?; + + Some(stop.duration_since(self.start)) + } + + /// Calculates the total duration since the stopwatch' creation. + #[must_use] + pub fn total_duration(&self) -> Duration { + self.start.elapsed() + } +} + +impl Display for Stopwatch { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let duration = if let Some(duration) = self.duration() { + duration.as_nanos() as f64 + } else { + return Err(Error); + }; + + macro_rules! test_unit { + ($length:expr, $symbol:literal) => {{ + if duration >= $length { + write!(f, "{:.3}{}", duration / $length, $symbol)?; + return Ok(()); + } + }}; + } + + test_unit!(31_557_600_000_000_000_000_000_000.0, "Ma"); // billion (julian) years + test_unit!(31_557_600_000_000_000_000_000.0, "Ma"); // million (julian) years + test_unit!(31_557_600_000_000_000_000.0, "ka"); // thousand (julian) years + test_unit!(31_557_600_000_000_000.0, "a"); // (julian) year + test_unit!(86_400_000_000_000.0, "d"); // day + test_unit!(3_600_000_000_000.0, "h"); // hour + test_unit!(60_000_000_000.0, "min"); // minute + test_unit!(1_000_000_000.0, "s"); // second + test_unit!(1_000_000.0, "ms"); // millisecond + test_unit!(1_000.0, "\u{03BC}s"); // microsecond + test_unit!(1.0, "ns"); // nanosecond? + + unreachable!(); + } +} diff --git a/build_icon.sh b/build_icon.sh new file mode 100755 index 0000000..d39e764 --- /dev/null +++ b/build_icon.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env sh + +# build_icon.sh +# Copyright 2024 Gabriel Bjørnager Jensen. +# +# Permission is hereby granted, free of charge, to +# any person obtaining a copy of this software and +# associated documentation files (the “Software”), +# to deal in the Software without restriction, in- +# cluding without limitation the rights to use, +# copy, modify, merge, publish, distribute, subli- +# cense, and/or sell copies of the Software, and +# to permit persons to whom the Software is fur- +# nished to do so, subject to the following con- +# ditions: +# +# The above copyright notice and this permission +# notice shall be included in all copies or sub- +# stantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WAR- +# RANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +# BUT NOT LIMITED TO THE WARRANTIES OF MER- +# CHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE +# AND NONINFRINGEMENT. IN NO EVENT SHALL THE AU- +# THORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Use in case an ICO image is wanted. +# +# On Arch, install `extra/icoutils` and `extra/ +# imagemagick`. + +failure() { + echo failure: ${1} + exit 2 +} + +if [ -z "${1}" ] +then + failure "input name is not set" +fi + +if [ -z "${2}" ] +then + failure "output name is not set" +fi + +renderDirectory="$(mktemp -d)" + +render_icon() { + name="${1}" + width="${2}" + + output="${renderDirectory}/${name}_${width}x${width}.png" + + echo rendering \"${output}\" + convert -background transparent "${name}.svg" -scale "${width}" "${output}" +} + +input="${1}" + +render_icon "${input}" 16 +render_icon "${input}" 24 +render_icon "${input}" 32 +render_icon "${input}" 48 +render_icon "${input}" 64 +render_icon "${input}" 96 +render_icon "${input}" 128 +render_icon "${input}" 192 +render_icon "${input}" 256 + +output="${2}.ico" + +echo combining into \"${output}\" +icotool -co "${output}" "${renderDirectory}/"*".png" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..cda8d17 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +avoid-breaking-exported-api = false diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..f7d5453 --- /dev/null +++ b/icon.svg @@ -0,0 +1,25 @@ +<svg height="96" width="96" xmlns="http://www.w3.org/2000/svg"> + <!-- gradients: --> + + <!-- clips: --> + + <clipPath id="icon"> + <rect fill="white" height="96" rx="12" width="96" x="0" y="0" /> + </clipPath> + + <!-- masks: --> + + <mask id="eye"> + <ellipse cx="48" cy="48" fill="white" rx="36" ry="24" /> + <ellipse cx="48" cy="48" fill="black" rx="30" ry="18" /> + + <ellipse cx="48" cy="48" fill="white" rx="24" ry="24" /> + <ellipse cx="48" cy="48" fill="black" rx="18" ry="18" /> + </mask> + + <!-- fills: --> + + <rect clip-path="url(#icon)" fill="#BA0035" height="96" width="96" x="0" y="0" /> + + <rect mask="url(#eye)" fill="#FFF9F4" height="96" width="96" x="0" y="0" /> +</svg> diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..d99ed0c --- /dev/null +++ b/install.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env sh + +# install.sh +# Copyright 2024 Gabriel Bjørnager Jensen. +# +# Permission is hereby granted, free of charge, to +# any person obtaining a copy of this software and +# associated documentation files (the “Software”), +# to deal in the Software without restriction, in- +# cluding without limitation the rights to use, +# copy, modify, merge, publish, distribute, subli- +# cense, and/or sell copies of the Software, and +# to permit persons to whom the Software is fur- +# nished to do so, subject to the following con- +# ditions: +# +# The above copyright notice and this permission +# notice shall be included in all copies or sub- +# stantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WAR- +# RANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +# BUT NOT LIMITED TO THE WARRANTIES OF MER- +# CHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE +# AND NONINFRINGEMENT. IN NO EVENT SHALL THE AU- +# THORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +failure() { + echo failure: ${1} + exit 2 +} + +if [ -z "${1}" ] +then + failure "build directory is not set" +fi + +if [ -z "${2}" ] +then + failure "binary directory is not set" +fi + +sourceDirectory="${PWD}" +buildDirectory="${1}" +binaryDirectory="${2}" + +#CARGO_TARGET_DIR="${buildDirectory}" cargo build --release + +mkdir -pvm755 "${binaryDirectory}" + +install -vm755 "${buildDirectory}/release/benoit-cli" "${binaryDirectory}/benoit-cli" +#install -vm755 "${buildDirectory}/release/benoit-gui" "${binaryDirectory}/benoit-gui" + +if ! [ -z "${3}" ] +then + shareDirectory="${3}" + + mkdir -pvm755 "${shareDirectory}/applications" + mkdir -pvm755 "${shareDirectory}/icons" + + install -vm644 "icon.svg" "${shareDirectory}/icons/benoit.svg" + #desktop-file-install --dir="${shareDirectory}/applications" "benoit.desktop" +fi diff --git a/source/benoit/benoit.rs b/source/benoit/benoit.rs deleted file mode 100644 index 55a1e44..0000000 --- a/source/benoit/benoit.rs +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub mod app; -pub mod colour_data; -pub mod complex; -pub mod configuration; -pub mod image; -pub mod fractal; -pub mod launcher; -pub mod palette; -pub mod render; -pub mod render_data; -pub mod script; -pub mod video; - -pub const VERSION: (u32, u32, u32) = ( - 0x2, // Major - 0x7, // Minor - 0x1, // Patch -); - -pub const PRECISION: u32 = 0x80; - -pub const BAILOUT_DISTANCE: f32 = 256.0; - -pub fn width_height_ratio(width: u32, height: u32) -> (f32, f32) { - return if width > height { - (1.0, height as f32 / width as f32) - } else { - (width as f32 / height as f32, 1.0) - }; -} diff --git a/source/benoit/benoit/app.rs b/source/benoit/benoit/app.rs deleted file mode 100644 index 3f487f7..0000000 --- a/source/benoit/benoit/app.rs +++ /dev/null @@ -1,61 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; -use crate::benoit::fractal::Fractal; -use crate::benoit::palette::Palette; - -extern crate rug; - -use rug::Float; - -pub mod draw_feedback; -pub mod drop; -pub mod handle_keys; -pub mod new; -pub mod poll_events; -pub mod print_controls; -pub mod render; -pub mod run; - -pub struct App { - // Configuration: - fractal: Fractal, - - canvas_width: u32, - canvas_height: u32, - scale: u32, - - centre: Complex, - extra: Complex, - zoom: Float, - - max_iter_count: u32, - - palette: Palette, - colour_range: f32, - - // Flags: - do_render: bool, - do_textual_feedback: bool, -} diff --git a/source/benoit/benoit/app/draw_feedback.rs b/source/benoit/benoit/app/draw_feedback.rs deleted file mode 100644 index e21b343..0000000 --- a/source/benoit/benoit/app/draw_feedback.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; -use crate::benoit::complex::Complex; -use crate::benoit::video::Video; - -extern crate rug; - -use rug::Float; - -impl App { - pub(super) fn draw_feedback(&self, video: &mut Video, prev_centre: &Complex, prev_zoom: &Float) { - if { - // Don't draw translation feedback if rendering a - // Julia set or if we haven't done any viewport - // translations. - - &self.centre.real != &prev_centre.real - || &self.centre.imag != &prev_centre.imag - || &self.zoom != prev_zoom - }{ - video.draw_translation_feedback(self.canvas_width, self.canvas_height, self.scale, prev_centre, prev_zoom, &self.centre, &self.zoom); - } - - if self.do_textual_feedback { video.draw_textual_feedback(&self.centre, &self.zoom, self.max_iter_count) }; - } -}
\ No newline at end of file diff --git a/source/benoit/benoit/app/drop.rs b/source/benoit/benoit/app/drop.rs deleted file mode 100644 index dfa6de1..0000000 --- a/source/benoit/benoit/app/drop.rs +++ /dev/null @@ -1,31 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; - -impl Drop for App { - fn drop(&mut self) { - eprintln!(); - eprintln!("Goodbye!"); - } -} diff --git a/source/benoit/benoit/app/handle_keys.rs b/source/benoit/benoit/app/handle_keys.rs deleted file mode 100644 index df35ea5..0000000 --- a/source/benoit/benoit/app/handle_keys.rs +++ /dev/null @@ -1,170 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::PRECISION; -use crate::benoit::app::App; -use crate::benoit::configuration::Configuration; - -extern crate rug; -extern crate sdl2; - -use rug::{Assign, Float}; -use sdl2::keyboard::{KeyboardState, Scancode}; - -impl App { - #[must_use] - pub(super) fn handle_keys(&mut self, scan_code: Scancode, state: KeyboardState) -> bool { - if scan_code == Scancode::C { self.do_render = true }; - - if state.is_scancode_pressed(Scancode::LShift) { return self.handle_shift_keys(scan_code) }; - - match scan_code { - Scancode::Escape => return true, - Scancode::F1 => self.do_textual_feedback = !self.do_textual_feedback, - Scancode::LAlt => self.cycle_fractal(-0x1), - Scancode::LCtrl => self.toggle_inverse(), - Scancode::Left => self.cycle_palette(-0x1), - Scancode::RAlt => self.cycle_fractal(0x1), - Scancode::Right => self.cycle_palette(0x1), - Scancode::Tab => self.toggle_julia(), - Scancode::X => self.reset_viewport(), - Scancode::Z => self.dump_info(), - _ => {}, - }; - - self.translate(scan_code); - - self.max_iter_count = match scan_code { - Scancode::F => self.max_iter_count * 0x2, - Scancode::R => (self.max_iter_count / 0x2).max(0x1), - _ => self.max_iter_count, - }; - - const COLOUR_RANGE_FACTOR: f32 = 1.0 + 1.0 / 16.0; - - self.colour_range = match scan_code { - Scancode::Up => self.colour_range * COLOUR_RANGE_FACTOR, - Scancode::Down => (self.colour_range / COLOUR_RANGE_FACTOR).max(Configuration::MIN_COLOUR_RANGE), - _ => self.colour_range, - }; - - return false; - } - - #[must_use] - fn handle_shift_keys(&mut self, scan_code: Scancode) -> bool { - let translate_ammount = Float::with_val(PRECISION, 4.0 / 64.0 / &self.zoom); - - match scan_code { - Scancode::A => self.extra.real -= &translate_ammount, - Scancode::D => self.extra.real += &translate_ammount, - _ => {}, - }; - - match scan_code { - Scancode::S => self.extra.imag -= &translate_ammount, - Scancode::W => self.extra.imag += &translate_ammount, - _ => {}, - }; - - return false; - } - - fn translate(&mut self, scan_code: Scancode) { - const ZOOM_FACTOR: f32 = 1.0 + 1.0 / 4.0; - - match scan_code { - Scancode::E => self.zoom *= ZOOM_FACTOR, - Scancode::Q => self.zoom /= ZOOM_FACTOR, - _ => {}, - }; - - let translate_ammount = Float::with_val(PRECISION, 4.0 / 16.0 / &self.zoom); - - match scan_code { - Scancode::A => self.centre.real -= &translate_ammount, - Scancode::D => self.centre.real += &translate_ammount, - _ => {}, - }; - - match scan_code { - Scancode::S => self.centre.imag -= &translate_ammount, - Scancode::W => self.centre.imag += &translate_ammount, - _ => {}, - }; - } - - fn cycle_fractal(&mut self, distance: i8) { - self.fractal.kind.cycle(distance); - - eprintln!("renderer the {}", self.fractal.kind.name()); - } - - fn toggle_inverse(&mut self) { - self.fractal.inverse = !self.fractal.inverse; - - match self.fractal.inverse { - false => eprintln!("reverting fractal"), - true => eprintln!("inverting fractals"), - }; - } - - fn toggle_julia(&mut self) { - self.fractal.julia = !self.fractal.julia; - - match self.fractal.julia { - false => eprintln!("disabled the julia set"), - true => eprintln!("enabled the julia set"), - }; - } - - fn cycle_palette(&mut self, direction: i8) { - self.palette.cycle(direction); - - eprintln!("using palette \"{}\"", self.palette.name()); - } - - fn reset_viewport(&mut self) { - self.centre.real.assign(Configuration::DEFAULT_CENTRE.0); - self.centre.imag.assign(Configuration::DEFAULT_CENTRE.1); - self.zoom.assign( Configuration::DEFAULT_ZOOM); - - self.extra.real.assign(Configuration::DEFAULT_EXTRA.0); - self.extra.imag.assign(Configuration::DEFAULT_EXTRA.1); - - self.max_iter_count = Configuration::DEFAULT_MAX_ITER_COUNT; - - self.colour_range = Configuration::DEFAULT_COLOUR_RANGE; - } - - fn dump_info(&self) { - eprintln!("info dump: the {}", self.fractal.name()); - eprintln!(" re(c): {}", self.centre.real); - eprintln!(" im(c): {}", self.centre.imag); - eprintln!(" re(w): {}", self.extra.real); - eprintln!(" im(w): {}", self.extra.imag); - eprintln!(" zoom: {}", self.zoom); - eprintln!(" max. iter count: {}", self.max_iter_count); - eprintln!(" col. range: {}", self.colour_range); - } -} diff --git a/source/benoit/benoit/app/new.rs b/source/benoit/benoit/app/new.rs deleted file mode 100644 index f19380a..0000000 --- a/source/benoit/benoit/app/new.rs +++ /dev/null @@ -1,56 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::PRECISION; -use crate::benoit::app::App; -use crate::benoit::complex::Complex; -use crate::benoit::configuration::Configuration; - -extern crate rug; - -use rug::Float; - -impl App { - #[must_use] - pub fn new(canvas_width: u32, canvas_height: u32) -> App { - return App { - fractal: Configuration::DEFAULT_FRACTAL, - - canvas_width: canvas_width, - canvas_height: canvas_height, - scale: 0x2, - - centre: Complex::new(Float::with_val(PRECISION, Configuration::DEFAULT_CENTRE.0), Float::with_val(PRECISION, Configuration::DEFAULT_CENTRE.1)), - extra: Complex::new(Float::with_val(PRECISION, Configuration::DEFAULT_EXTRA.0), Float::with_val(PRECISION, Configuration::DEFAULT_EXTRA.1)), - zoom: Float::with_val(PRECISION, Configuration::DEFAULT_ZOOM), - - max_iter_count: Configuration::DEFAULT_MAX_ITER_COUNT, - - palette: Configuration::DEFAULT_PALETTE, - colour_range: Configuration::DEFAULT_COLOUR_RANGE, - - do_render: true, - do_textual_feedback: false, - }; - } -} diff --git a/source/benoit/benoit/app/poll_events.rs b/source/benoit/benoit/app/poll_events.rs deleted file mode 100644 index df24b0e..0000000 --- a/source/benoit/benoit/app/poll_events.rs +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; - -extern crate sdl2; - -use sdl2::EventPump; -use sdl2::event::Event; - -impl App { - #[must_use] - pub(super) fn poll_events(&mut self, pump: &mut EventPump) -> bool { - loop { - let event = match pump.poll_event() { - Some(event) => event, - None => break, - }; - - let quit = match event { - Event::KeyDown { - timestamp: _, - window_id: _, - keycode: _, - scancode: scan_code, - keymod: _, - repeat: _, - } => { - let state = pump.keyboard_state(); - - self.handle_keys(scan_code.unwrap(), state) - }, - Event::Quit { .. } => true, - _ => false, - }; - - if quit { return true }; - } - - return false; - } -} diff --git a/source/benoit/benoit/app/print_controls.rs b/source/benoit/benoit/app/print_controls.rs deleted file mode 100644 index 99cab61..0000000 --- a/source/benoit/benoit/app/print_controls.rs +++ /dev/null @@ -1,65 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; - -impl App { - pub(super) fn print_controls() { - println!(""); - println!("controls:"); - println!("- \u{1B}[1mW\u{1B}[0m translate +Im"); - println!("- \u{1B}[1mA\u{1B}[0m translate -Re"); - println!("- \u{1B}[1mS\u{1B}[0m translate -Im"); - println!("- \u{1B}[1mD\u{1B}[0m translate +Re"); - println!(); - println!("- \u{1B}[1mQ\u{1B}[0m zoom out"); - println!("- \u{1B}[1mE\u{1B}[0m zoom in"); - println!(); - println!("- \u{1B}[1mR\u{1B}[0m decrease max. iteration count"); - println!("- \u{1B}[1mF\u{1B}[0m increase max. iteration count"); - println!(); - println!("- \u{1B}[1mLEFT ALT\u{1B}[0m cycle to previous fractal"); - println!("- \u{1B}[1mRIGHT ALT\u{1B}[0m cycle to next fractal"); - println!("- \u{1B}[1mTAB\u{1B}[0m toggle Julia"); - println!("- \u{1B}[1mLEFT CTRL\u{1B}[0m toggle inverse"); - println!(); - println!("- \u{1B}[1mLEFT\u{1B}[0m cycle to previous palette"); - println!("- \u{1B}[1mRIGHT\u{1B}[0m cycle to next palette"); - println!("- \u{1B}[1mUP\u{1B}[0m increase colour range"); - println!("- \u{1B}[1mDOWN\u{1B}[0m decrease colour range"); - println!(); - println!("- \u{1B}[1mF1\u{1B}[0m toggle textual feedback"); - println!("- \u{1B}[1mZ\u{1B}[0m print centre value (c)"); - println!(); - println!("- \u{1B}[1mC\u{1B}[0m render frame"); - println!(); - println!("Controls (holding \u{1B}[1mSHIFT\u{1B}[0m):"); - println!("- \u{1B}[1mW\u{1B}[0m perturbate/translate +Im"); - println!("- \u{1B}[1mA\u{1B}[0m perturbate/translate -Re"); - println!("- \u{1B}[1mS\u{1B}[0m perturbate/translate -Im"); - println!("- \u{1B}[1mD\u{1B}[0m perturbate/translate +Re"); - println!(); - println!("- \u{1B}[1mC\u{1B}[0m render frame"); - println!(); - } -}
\ No newline at end of file diff --git a/source/benoit/benoit/app/render.rs b/source/benoit/benoit/app/render.rs deleted file mode 100644 index 1ba6c01..0000000 --- a/source/benoit/benoit/app/render.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; -use crate::benoit::render::Render; -use std::time::Instant; - -impl App { - pub(super) fn render(&self, render: &mut Render) -> Result<(), String> { - eprint!("rendering..."); - - let time_start = Instant::now(); - - render.render( - self.fractal, - &self.centre, - &self.zoom, - &self.extra, - self.max_iter_count, - )?; - - let render_time = time_start.elapsed(); - - eprintln!("\rrendered: {:.3}ms", render_time.as_micros() as f32 / 1000.0); - - return Ok(()); - } -}
\ No newline at end of file diff --git a/source/benoit/benoit/app/run.rs b/source/benoit/benoit/app/run.rs deleted file mode 100644 index 9652b8e..0000000 --- a/source/benoit/benoit/app/run.rs +++ /dev/null @@ -1,78 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; -use crate::benoit::image::Image; -use crate::benoit::render::Render; -use crate::benoit::video::Video; - -extern crate rug; - -use rug::Assign; -use std::time::Instant; - -impl App { - #[must_use] - pub fn run(mut self) -> Result<(), String> { - let mut video = Video::initialise(self.canvas_width, self.canvas_height, self.scale)?; - - App::print_controls(); - - let mut event_pump = match video.sdl.event_pump() { - Ok( pump) => pump, - Err(_) => return Err("unable to get event pump".to_string()), - }; - - let mut render = Render::allocate(self.canvas_width, self.canvas_height)?; - let mut image = Image::allocate( self.canvas_width, self.canvas_height)?; - - // Used for translation feedback: - let mut prev_centre = self.centre.clone(); - let mut prev_zoom = self.zoom.clone(); - - loop { - let start_frame = Instant::now(); - - if self.poll_events(&mut event_pump) { break }; - - if self.do_render { - self.render(&mut render)?; - - prev_centre.assign(&self.centre); - prev_zoom.assign( &self.zoom); - - self.do_render = false; - }; - - image.colour(&render, self.palette, self.max_iter_count, self.colour_range)?; - - video.draw_image(&image, self.scale); - self.draw_feedback(&mut video, &prev_centre, &prev_zoom); - video.update(); - - video.sync(&start_frame)?; - } - - return Ok(()); - } -}
\ No newline at end of file diff --git a/source/benoit/benoit/colour_data.rs b/source/benoit/benoit/colour_data.rs deleted file mode 100644 index 2c36672..0000000 --- a/source/benoit/benoit/colour_data.rs +++ /dev/null @@ -1,55 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::palette::PaletteData; - -pub struct ColourData { - exponent: f32, - max_iter_count: u32, - colour_range: f32, - - palette_data: &'static PaletteData, -} - -impl ColourData { - #[must_use] - pub fn new( - exponent: f32, - max_iter_count: u32, - colour_range: f32, - palette_data: &'static PaletteData, - ) -> ColourData { - return ColourData { - exponent: exponent, - max_iter_count: max_iter_count, - colour_range: colour_range, - - palette_data: palette_data, - }; - } - - #[must_use] - pub fn consts(&self) -> (f32, u32, f32, &'static PaletteData) { - return (self.exponent, self.max_iter_count, self.colour_range, self.palette_data); - } -} diff --git a/source/benoit/benoit/complex.rs b/source/benoit/benoit/complex.rs deleted file mode 100644 index 204b586..0000000 --- a/source/benoit/benoit/complex.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -extern crate rug; - -use rug::{Assign, Float}; - -#[derive(Clone)] -pub struct Complex { - pub real: Float, - pub imag: Float, -} - -impl Complex { - #[must_use] - pub fn new(real: Float, imag: Float) -> Complex { - return Complex { - real: real, - imag: imag, - }; - } - - pub fn assign(&mut self, other: &Self) { - self.real.assign(&other.real); - self.imag.assign(&other.imag); - } -} diff --git a/source/benoit/benoit/configuration.rs b/source/benoit/benoit/configuration.rs deleted file mode 100644 index 511a9aa..0000000 --- a/source/benoit/benoit/configuration.rs +++ /dev/null @@ -1,81 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::fractal::{Fractal, FractalKind}; -use crate::benoit::image::ImageFormat; -use crate::benoit::palette::Palette; - -extern crate rug; - -use rug::Float; - -pub mod default; -pub mod load; - -pub struct Configuration { - pub thread_count: u32, - - pub fractal: Fractal, - - pub canvas_width: u32, - pub canvas_height: u32, - pub palette: Palette, - - pub dump_path: String, - pub image_format: ImageFormat, - - pub start_frame: u32, - pub start_centre_real: Float, - pub start_centre_imag: Float, - pub start_extra_real: Float, - pub start_extra_imag: Float, - pub start_zoom: Float, - pub start_max_iter_count: u32, - pub start_colour_range: f32, - - pub stop_frame: u32, - pub stop_centre_real: Float, - pub stop_centre_imag: Float, - pub stop_extra_real: Float, - pub stop_extra_imag: Float, - pub stop_zoom: Float, - pub stop_max_iter_count: u32, - pub stop_colour_range: f32, -} - -impl Configuration { - pub const DEFAULT_FRACTAL: Fractal = Fractal { kind: FractalKind::Mandelbrot, inverse: false, julia: false }; - - pub const DEFAULT_CENTRE: (f64, f64) = (0.0, 0.0); - pub const DEFAULT_EXTRA: (f64, f64) = (0.0, 0.0); - pub const DEFAULT_ZOOM: f64 = 1.0; - - pub const DEFAULT_MAX_ITER_COUNT: u32 = 0x100; - - pub const DEFAULT_PALETTE: Palette = Palette::Fire; - pub const DEFAULT_COLOUR_RANGE: f32 = 64.0; - - pub const MIN_CANVAS_WIDTH: u32 = 0x2; - pub const MIN_MAX_ITER_COUNT: u32 = 0x1; - pub const MIN_COLOUR_RANGE: f32 = 2.0; -} diff --git a/source/benoit/benoit/configuration/default.rs b/source/benoit/benoit/configuration/default.rs deleted file mode 100644 index ecb6f58..0000000 --- a/source/benoit/benoit/configuration/default.rs +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::PRECISION; -use crate::benoit::configuration::Configuration; -use crate::benoit::image::ImageFormat; - -extern crate rug; - -use rug::Float; - -impl Configuration { - #[must_use] - pub fn default() -> Configuration { - return Configuration { - thread_count: 0x0, // Automatic if null. - - fractal: Self::DEFAULT_FRACTAL, - - canvas_width: 0x100, - canvas_height: 0x100, - palette: Self::DEFAULT_PALETTE, - - dump_path: "./render".to_string(), - image_format: ImageFormat::Png, - - start_frame: 0x0, - start_centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.0), - start_centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.1), - start_extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.0), - start_extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.1), - start_zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM), - start_max_iter_count: Self::DEFAULT_MAX_ITER_COUNT, - start_colour_range: Self::DEFAULT_COLOUR_RANGE, - - stop_frame: 0xF, - stop_centre_real: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.0), - stop_centre_imag: Float::with_val(PRECISION, Self::DEFAULT_CENTRE.1), - stop_extra_real: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.0), - stop_extra_imag: Float::with_val(PRECISION, Self::DEFAULT_EXTRA.1), - stop_zoom: Float::with_val(PRECISION, Self::DEFAULT_ZOOM), - stop_max_iter_count: Self::DEFAULT_MAX_ITER_COUNT, - stop_colour_range: Self::DEFAULT_COLOUR_RANGE, - }; - } -} diff --git a/source/benoit/benoit/configuration/load.rs b/source/benoit/benoit/configuration/load.rs deleted file mode 100644 index 4995163..0000000 --- a/source/benoit/benoit/configuration/load.rs +++ /dev/null @@ -1,170 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::PRECISION; -use crate::benoit::configuration::Configuration; -use crate::benoit::fractal::FractalKind; -use crate::benoit::image::ImageFormat; -use crate::benoit::palette::Palette; - -extern crate rug; -extern crate toml; - -use rug::Float; -use std::fs::read; -use std::str::FromStr; -use toml::{Table, Value}; - -impl Configuration { - #[must_use] - pub fn load(path: &str) -> Result<Configuration, String> { - eprintln!("loading configuration at \"{path}\""); - - let mut configuration = Configuration::default(); - - let configuration_text = match read(path) { - Ok( content) => String::from_utf8_lossy(&content).to_string(), - Err(_) => return Err("unable to read configuration file".to_string()), - }; - - let base_table = match Table::from_str(configuration_text.as_str()) { - Ok( table) => table, - Err(_) => return Err("unable to parse configuration".to_string()), - }; - - let start_table = get_table(&base_table, "start")?; - let stop_table = get_table(&base_table, "stop")?; - - get_integer(&mut configuration.thread_count, &base_table, "thread_count")?; - - get_boolean(&mut configuration.fractal.inverse, &base_table, "inverse")?; - get_boolean(&mut configuration.fractal.julia, &base_table, "julia")?; - - get_integer(&mut configuration.canvas_width, &base_table, "canvas_width")?; - get_integer(&mut configuration.canvas_height, &base_table, "canvas_height")?; - - if let Some(start_table) = start_table { - get_integer( &mut configuration.start_frame, &start_table, "frame")?; - get_bigfloat(&mut configuration.start_centre_real, &start_table, "real")?; - get_bigfloat(&mut configuration.start_centre_imag, &start_table, "imaginary")?; - get_bigfloat(&mut configuration.start_extra_real, &start_table, "extra_real")?; - get_bigfloat(&mut configuration.start_extra_imag, &start_table, "extra_imaginary")?; - get_bigfloat(&mut configuration.start_zoom, &start_table, "zoom")?; - get_integer( &mut configuration.start_max_iter_count, &start_table, "maximum_iteration_count")?; - get_float( &mut configuration.start_colour_range, &start_table, "colour_range")?; - } - - if let Some(stop_table) = stop_table { - get_integer( &mut configuration.stop_frame, &stop_table, "frame")?; - get_bigfloat(&mut configuration.stop_centre_real, &stop_table, "real")?; - get_bigfloat(&mut configuration.stop_centre_imag, &stop_table, "imaginary")?; - get_bigfloat(&mut configuration.stop_extra_real, &stop_table, "extra_real")?; - get_bigfloat(&mut configuration.stop_extra_imag, &stop_table, "extra_imaginary")?; - get_bigfloat(&mut configuration.stop_zoom, &stop_table, "zoom")?; - get_integer( &mut configuration.stop_max_iter_count, &stop_table, "maximum_iteration_count")?; - get_float( &mut configuration.stop_colour_range, &stop_table, "colour_range")?; - } - - // Strings: - if let Some(name) = get_string(&base_table, "fractal")? { - configuration.fractal.kind = FractalKind::from_str(name.as_str())?; - } - - if let Some(name) = get_string(&base_table, "palette")? { - configuration.palette = Palette::from_str(name.as_str())?; - } - - if let Some(path) = get_string(&base_table, "dump_path")? { - configuration.dump_path = path.clone(); - } - - if let Some(name) = get_string(&base_table, "image_format")? { - configuration.image_format = ImageFormat::from_str(name.as_str())?; - } - - return Ok(configuration); - } -} - -fn get_value<'a>(table: &'a Table, name: &str) -> Option<&'a Value> { - if !table.contains_key(name) { return None }; - - return Some(&table[name]); -} - -fn get_table<'a>(table: &'a Table, name: &str) -> Result<Option<&'a Table>, String> { - return match get_value(table, name) { - Some(Value::Table(table)) => Ok(Some(table)), - Some(_) => Err(format!("\"{name}\" should be a section")), - _ => Ok(None), - }; -} - -fn get_boolean(buffer: &mut bool, table: &Table, name: &str) -> Result<(), String> { - match get_value(table, name) { - Some(Value::Boolean(value)) => *buffer = *value, - Some(_) => return Err(format!("\"{name}\" should be a boolean")), - _ => {}, - }; - return Ok(()) -} - -fn get_integer(buffer: &mut u32, table: &Table, name: &str) -> Result<(), String> { - match get_value(table, name) { - Some(Value::Integer(value)) => *buffer = (*value) as u32, - Some(_) => return Err(format!("\"{name}\" should be an integer")), - _ => {}, - }; - return Ok(()) -} - -fn get_float(buffer: &mut f32, table: &Table, name: &str) -> Result<(), String> { - match get_value(table, name) { - Some(Value::Float(value)) => *buffer = (*value) as f32, - Some(_) => return Err(format!("\"{name}\" should be a float")), - _ => {}, - }; - return Ok(()) -} - -fn get_bigfloat(buffer: &mut Float, table: &Table, name: &str) -> Result<(), String> { - match get_value(table, name) { - Some(Value::String(string)) => { - *buffer = match Float::parse(string) { - Ok(value) => Float::with_val(PRECISION, value), - _ => return Err(format!("invalid format of \"{name}\"")), - } - }, - Some(_) => return Err(format!("\"{name}“ should be a quoted float")), - _ => {}, - }; - return Ok(()) -} - -fn get_string(table: &Table, name: &str) -> Result<Option<String>, String> { - return match get_value(table, name) { - Some(Value::String(value)) => Ok(Some(value.clone())), - Some(_) => Err(format!("\"{name}\" should be a string")), - _ => Ok(None), - }; -} diff --git a/source/benoit/benoit/fractal.rs b/source/benoit/benoit/fractal.rs deleted file mode 100644 index d47573d..0000000 --- a/source/benoit/benoit/fractal.rs +++ /dev/null @@ -1,115 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; - -use std::mem::transmute; - -pub mod from_str; - -mod iterate; - -pub type IteratorFunction = fn(&mut Complex, &Complex); - -#[derive(Clone, Copy)] -#[repr(u8)] -pub enum FractalKind { - Mandelbrot, - Multibrot3, - Multibrot4, - BurningShip, - Tricorn, -} - -impl FractalKind { - pub fn name(self) -> &'static str { - return match self { - FractalKind::BurningShip => "burning ship", - FractalKind::Mandelbrot => "mandelbrot set", - FractalKind::Multibrot3 => "multibrot3 set", - FractalKind::Multibrot4 => "multibrot4 set", - FractalKind::Tricorn => "tricorn", - }; - } - - pub fn cycle(&mut self, direction: i8) { - let raw = *self as i16 + direction as i16; - - const NUM: isize = FractalKind::Tricorn as isize + 0x1; - let new: u8 = match raw as isize { - -0x1 => (NUM - 0x1) as u8, - NUM => 0x0, - _ => raw as u8, - }; - - *self = unsafe { transmute(new) }; - } -} - -#[derive(Clone, Copy)] -pub struct Fractal { - pub kind: FractalKind, - pub inverse: bool, - pub julia: bool, -} - -impl Fractal { - pub fn name(&self) -> String { - let kind = self.kind.name(); - - let extra = if self.inverse && !self.julia { - "inverse" - } else if !self.inverse && self.julia { - "julia" - } else if self.inverse && self.julia { - "inverse julia" - } else { - "normal" - }; - - let name = format!("{kind} ({extra})"); - return name; - } - - #[must_use] - pub fn exponent(&self) -> f32 { - return match self.kind { - FractalKind::BurningShip => 2.0, - FractalKind::Mandelbrot => 2.0, - FractalKind::Multibrot3 => 3.0, - FractalKind::Multibrot4 => 4.0, - FractalKind::Tricorn => 2.0, - }; - } - - #[must_use] - pub fn iterator(&self) -> IteratorFunction { - return match self.kind { - FractalKind::BurningShip => iterate::burning_ship, - FractalKind::Mandelbrot => iterate::mandelbrot, - FractalKind::Multibrot3 => iterate::multibrot3, - FractalKind::Multibrot4 => iterate::multibrot4, - FractalKind::Tricorn => iterate::tricorn, - }; - } -} diff --git a/source/benoit/benoit/fractal/from_str.rs b/source/benoit/benoit/fractal/from_str.rs deleted file mode 100644 index 05cf22e..0000000 --- a/source/benoit/benoit/fractal/from_str.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::fractal::FractalKind; - -use std::str::FromStr; - -impl FromStr for FractalKind { - type Err = String; - - fn from_str(string: &str) -> Result<Self, Self::Err> { - use FractalKind::*; - - let kind = match string { - "burning_ship" => Some(BurningShip), - "mandelbrot" => Some(Mandelbrot), - "multibrot3" => Some(Multibrot3), - "multibrot4" => Some(Multibrot4), - "tricorn" => Some(Tricorn), - _ => None, - }; - - return match kind { - Some(kind) => Ok(kind), - _ => Err(format!("invalid fractal kind \"{string}\"")), - }; - } -} diff --git a/source/benoit/benoit/fractal/iterate.rs b/source/benoit/benoit/fractal/iterate.rs deleted file mode 100644 index 59c5a77..0000000 --- a/source/benoit/benoit/fractal/iterate.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub mod burning_ship; -pub mod mandelbrot; -pub mod multibrot3; -pub mod multibrot4; -pub mod tricorn; - -pub use burning_ship::*; -pub use mandelbrot::*; -pub use multibrot3::*; -pub use multibrot4::*; -pub use tricorn::*; diff --git a/source/benoit/benoit/fractal/iterate/burning_ship.rs b/source/benoit/benoit/fractal/iterate/burning_ship.rs deleted file mode 100644 index dae3a12..0000000 --- a/source/benoit/benoit/fractal/iterate/burning_ship.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; - -pub fn burning_ship(z: &mut Complex, c: &Complex) { - // The Burning Ship is different in that - during - // iteration - the real and imaginary parts of (z) - // are made absolute: - // - // z(n+1) = (abs(Re(z(n)))+i*abs(Im(z(n))))^2+c. - - z.real.abs_mut(); // abs(a) - let za_temporary = z.real.clone(); // abs(a) - - z.real.square_mut(); // abs(a)^2 - z.real -= &z.imag * &z.imag; // abs(a)^2-abs(b)^2 - z.real += &c.real; // abs(a)^2-abs(b)^2+Re(c) - - z.imag.abs_mut(); // abs(b) - z.imag *= &za_temporary; // abs(a) - z.imag *= 2.0; // 2*abs(a) - z.imag += &c.imag; // 2*abs(a)+Im(c) -} diff --git a/source/benoit/benoit/fractal/iterate/mandelbrot.rs b/source/benoit/benoit/fractal/iterate/mandelbrot.rs deleted file mode 100644 index 078cc18..0000000 --- a/source/benoit/benoit/fractal/iterate/mandelbrot.rs +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; - -pub fn mandelbrot(z: &mut Complex, c: &Complex) { - // The Mandelbrot Set (M) is defined as the set of - // values in the complex plane where the iterating - // function - // - // z(n+1) = z(n)^2+c - // - // stays bounded: I.e. the absolute value of (z) stays bounded: - // - // abs(z) = sqrt(Re(z)^2+Im(z)^2) <= 2^2 = 4. - - let za_temporary = z.real.clone(); // a - - // We can calculate the square of a complex number - // as: - // - // (a+bi)^2 - // = (a+bi)(a+bi) - // = a^2+abi+abi-b^2 - // = a^2-b^2+2abi. - - z.real.square_mut(); // a^2 - z.real -= &z.imag * &z.imag; // a^2-b^2 - z.real += &c.real; // a^2-b^2+Re(c) - - z.imag *= &za_temporary; // ab - z.imag *= 2.0; // 2ab - z.imag += &c.imag; // 2ab+Im(c) -} diff --git a/source/benoit/benoit/fractal/iterate/multibrot3.rs b/source/benoit/benoit/fractal/iterate/multibrot3.rs deleted file mode 100644 index da58a5e..0000000 --- a/source/benoit/benoit/fractal/iterate/multibrot3.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; - -use crate::benoit::PRECISION; - -extern crate rug; - -use rug::Float; - -pub fn multibrot3(z: &mut Complex, c: &Complex) { - let za_temporary = z.real.clone(); // a - - // (a+bi)^3 - // = (a+bi)(a+bi)(a+bi) - // = (a^2-b^2+2abi)(a+bi) - // = a^3+(a^2)bi-ab^2-(b^3)i+2(a^2)bi-2ab^2 - // = a^3+3(a^2)bi-3ab^2-(b^3)i - // - // <=> a = a^3-3ab^2 - // b = 3(a^2)b-b^3 - - let mut temporary0 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2 - - let temporary1 = Float::with_val(PRECISION, &temporary0 * &z.imag); // b^3 - - temporary0 *= &z.real; // ab^2 - temporary0 *= 0x3; // 3ab^2 - - z.real.square_mut(); // a^2 - - z.imag *= &z.real; // (a^2)b - - z.real *= &za_temporary; // a^3 - z.real -= &temporary0; // a^3-3ab^2 - z.real += &c.real; // a^3-3ab^2+Re(c) - - z.imag *= 3.0; // 3(a^2)b - z.imag -= &temporary1; // 3(a^2)b-b^3 - z.imag += &c.imag; // 3(a^2)b-b^3+Im(c) - -} diff --git a/source/benoit/benoit/fractal/iterate/multibrot4.rs b/source/benoit/benoit/fractal/iterate/multibrot4.rs deleted file mode 100644 index d040cf3..0000000 --- a/source/benoit/benoit/fractal/iterate/multibrot4.rs +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; - -use crate::benoit::PRECISION; - -extern crate rug; - -use rug::{Assign, Float}; - -pub fn multibrot4(z: &mut Complex, c: &Complex) { - // (a+bi)^4 - // = (a+bi)^2*(a+bi)^2 - // = (a^2-b^2+2abi)^2 - // = (a^2-b^2+2abi)(a^2-b^2+2abi) - // = a^4-(a^2)b^2+2(a^3)bi-(a^2)b^2+b^4-2a(b^3)i-4(a^2)b^2+2(a^3)bi-2a(b^3)i-4(a^2)b^2 - // = a^4-6(a^2)b^2+4(a^3)bi+b^4-4a(b^3)i - // - // <=> a = a^4-6(a^2)b^2+b^4 - // b = 4(a^3)bi-4a(b^3)i - - let temporary0 = Float::with_val(PRECISION, &z.real * &z.real); // a^2 - let temporary1 = Float::with_val(PRECISION, &z.imag * &z.imag); // b^2 - - let mut temporary2 = Float::with_val(PRECISION, &z.real * &z.imag); // ab - temporary2 *= 4.0; // 4ab - - z.real.assign(&temporary0); // a^2 - z.real /= 6.0; // a^2/6 - z.real -= &temporary1; // a^2/6-b^2 - z.real *= &temporary0; // a^4/6-(a^2)b^2 - z.real *= 6.0; // a^4-6(a^2)b^2 - - z.imag.assign(&temporary1); // b^2 - z.imag *= -1.0; // -b^2 - z.imag += &temporary0; // a^2-b^2 - z.imag *= temporary2; // 4(a^3)b-4ab^3 - - z.real += temporary1.square(); // a^4-6(a^2)b^2+b^4 - z.real += &c.real; // a^4-6(a^2)b^2+b^4+Re(c) - - z.imag += &c.imag; // 4(a^3)b-4ab^3+Im(c) -} diff --git a/source/benoit/benoit/fractal/iterate/tricorn.rs b/source/benoit/benoit/fractal/iterate/tricorn.rs deleted file mode 100644 index a64b3aa..0000000 --- a/source/benoit/benoit/fractal/iterate/tricorn.rs +++ /dev/null @@ -1,48 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; - -pub fn tricorn(z: &mut Complex, c: &Complex) { - // The Tricorn is only different from the - // Mandelbrot Set in that the conjugate of (z) is - // used instead of just (z): - // - // z(n+1) = (Re(z(n))-Im(z(n))i)^2+c. - - let za_temporary = z.real.clone(); // a - - z.real.square_mut(); // a^2 - z.real -= &z.imag * &z.imag; // a^2-b^2 - z.real += &c.real; // a^2 - - z.imag *= &za_temporary; // ab - // We can negate the value by multiplying with - // (-1). A multiplication can be saved, as - // - // a*2*(-1) = a*(-2). - // - // Thus, we may combine these two multiplications. - z.imag *= -2.0; // -2ab - z.imag += &c.imag; // -2ab+Im(c) -} diff --git a/source/benoit/benoit/image.rs b/source/benoit/benoit/image.rs deleted file mode 100644 index 6d43d7a..0000000 --- a/source/benoit/benoit/image.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use std::ops::{Index, IndexMut}; -use std::slice::from_raw_parts; - -pub mod allocate; -pub mod colour; -pub mod dump; -pub mod from_str; - -pub struct Image { - width: u32, - height: u32, - - data: Vec::<(u8, u8, u8)>, -} - -#[derive(Clone, Copy)] -pub enum ImageFormat { - Png, - Webp, -} - -impl Image { - #[must_use] - pub fn size(&self) -> (u32, u32) { - return (self.width, self.height); - } - - #[must_use] - pub fn raw<'a>(&'a self) -> &'a [u8] { - let data_pointer = self.data.as_ptr() as *const u8; - - let length = self.height as usize * self.width as usize * 0x3; - let slice = unsafe { from_raw_parts(data_pointer, length) }; - - return slice; - } -} - -impl Index<usize> for Image { - type Output = (u8, u8, u8); - - fn index<'a>(&'a self, index: usize) -> &'a Self::Output { - return &self.data[index]; - } -} - -impl IndexMut<usize> for Image { - fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Self::Output { - return &mut self.data[index]; - } -} diff --git a/source/benoit/benoit/image/allocate.rs b/source/benoit/benoit/image/allocate.rs deleted file mode 100644 index bfd5a46..0000000 --- a/source/benoit/benoit/image/allocate.rs +++ /dev/null @@ -1,44 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::configuration::Configuration; -use crate::benoit::image::Image; - -impl Image { - #[must_use] - pub fn allocate(width: u32, height: u32) -> Result<Image, String> { - if width < Configuration::MIN_CANVAS_WIDTH || height < Configuration::MIN_CANVAS_WIDTH { return Err(format!("width and height may not be more than {}", Configuration::MIN_CANVAS_WIDTH)) }; - - let (canvas_size, overflow) = (height as usize).overflowing_mul(width as usize); - if overflow { return Err("overflow when calculating canvas size".to_string()) }; - - let data: Vec::<(u8, u8, u8)> = vec![(0x0, 0x0, 0x0); canvas_size]; - - return Ok(Image { - width: width, - height: height, - - data: data, - }); - } -} diff --git a/source/benoit/benoit/image/colour.rs b/source/benoit/benoit/image/colour.rs deleted file mode 100644 index 3c3562e..0000000 --- a/source/benoit/benoit/image/colour.rs +++ /dev/null @@ -1,80 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::colour_data::ColourData; -use crate::benoit::configuration::Configuration; -use crate::benoit::image::Image; -use crate::benoit::palette::{Palette, PALETTE_DATA_LENGTH}; -use crate::benoit::render::Render; - -extern crate rayon; - -use rayon::prelude::*; - -impl Image { - pub fn colour(&mut self, render: &Render, palette: Palette, new_max_iter_count: u32, colour_range: f32) -> Result<(), String> { - if render.size() != self.size() { return Err(format!("size mismatch - {}*{} (render) vs. {}*{} (image)", render.size().0, render.size().1, self.size().0, self.size().1)) }; - - if new_max_iter_count < Configuration::MIN_MAX_ITER_COUNT { return Err(format!("maximum_iteration_count must be at least {}", Configuration::MIN_MAX_ITER_COUNT)) }; - if colour_range < Configuration::MIN_COLOUR_RANGE { return Err(format!("colour range may not be less than {}", Configuration::MIN_COLOUR_RANGE)) }; - - let (fractal, max_iter_count) = match render.info() { - Some(info) => info, - None => return Err("cannot colour before render".to_string()), - }; - - let data = ColourData::new( - fractal.exponent(), - max_iter_count.min(new_max_iter_count), - colour_range, - palette.data(), - ); - - self.data.par_iter_mut().enumerate().for_each(|(index, point)| { - let (iter_count, dist) = render[index]; - - *point = colour_point(&data, iter_count, dist); - }); - - return Ok(()); - } -} - -fn colour_point(data: &ColourData, iter_count: u32, dist: f32) -> (u8, u8, u8) { - let (exponent, max_iter_count, colour_range, palette_data) = data.consts(); - - let (red, green, blue) = if iter_count < max_iter_count { - let factor = (iter_count as f32 + 1.0 - dist.ln().log(exponent)) / colour_range; - - let index = (factor * PALETTE_DATA_LENGTH as f32).round() as usize % PALETTE_DATA_LENGTH; - palette_data[index] - } else { - (0.0, 0.0, 0.0) - }; - - let red = (red * 255.0).round() as u8; - let green = (green * 255.0).round() as u8; - let blue = (blue * 255.0).round() as u8; - - return (red, green, blue); -} diff --git a/source/benoit/benoit/image/dump.rs b/source/benoit/benoit/image/dump.rs deleted file mode 100644 index cd1f1ea..0000000 --- a/source/benoit/benoit/image/dump.rs +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::image::{Image, ImageFormat}; - -extern crate png; -extern crate webp; - -use std::fs::{File, write}; -use std::io::BufWriter; - -impl Image { - pub fn dump(&self, path: &str, format: ImageFormat) -> Result<(), String> { - use ImageFormat::*; - - return match format { - Png => self.dump_png( path), - Webp => self.dump_webp(path), - }; - } - - fn dump_png(&self, path: &str) -> Result<(), String> { - let path = path.to_owned() + ".png"; - - let file = match File::create(path) { - Ok( file) => file, - Err(_) => return Err("unable to create file".to_string()), - }; - let file_buffer = BufWriter::new(file); - - let mut encoder = png::Encoder::new(file_buffer, self.width, self.height); - encoder.set_color(png::ColorType::Rgb); - encoder.set_depth(png::BitDepth::Eight); - encoder.set_compression(png::Compression::Fast); - encoder.set_srgb(png::SrgbRenderingIntent::Perceptual); - - let mut writer = match encoder.write_header() { - Ok( writer) => writer, - Err(_) => return Err("unable to write image header".to_string()), - }; - if writer.write_image_data(self.raw()).is_err() { return Err("unable to write image".to_string()) }; - - return Ok(()); - } - - fn dump_webp(&self, path: &str) -> Result<(), String> { - let path = path.to_owned() + ".webp"; - - let encoder = webp::Encoder::from_rgb(self.raw(), self.width, self.height); - let data = encoder.encode_lossless(); - - if write(path, &*data).is_err() { return Err("unable to write image".to_string()) }; - - return Ok(()); - } -} diff --git a/source/benoit/benoit/image/from_str.rs b/source/benoit/benoit/image/from_str.rs deleted file mode 100644 index c4a86b9..0000000 --- a/source/benoit/benoit/image/from_str.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::image::ImageFormat; - -use std::str::FromStr; - -impl FromStr for ImageFormat { - type Err = String; - - fn from_str(string: &str) -> Result<Self, Self::Err> { - use ImageFormat::*; - - let kind = match string { - "png" => Some(Png), - "webp" => Some(Webp), - _ => None, - }; - - return match kind { - Some(kind) => Ok(kind), - _ => Err(format!("invalid image format \"{string}\"")), - }; - } -} diff --git a/source/benoit/benoit/launcher.rs b/source/benoit/benoit/launcher.rs deleted file mode 100644 index f38b6c3..0000000 --- a/source/benoit/benoit/launcher.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::configuration::Configuration; - -pub mod parse_arguments; -pub mod print_help; -pub mod print_message; -pub mod run; -pub mod set_title; -pub mod setup; - -pub enum Mode { - App( u32, u32), - Script(Configuration), -} - -pub struct Launcher {} - -impl Launcher { - #[must_use] - pub fn new() -> Launcher { - return Launcher {}; - } -} diff --git a/source/benoit/benoit/launcher/parse_arguments.rs b/source/benoit/benoit/launcher/parse_arguments.rs deleted file mode 100644 index 75adee1..0000000 --- a/source/benoit/benoit/launcher/parse_arguments.rs +++ /dev/null @@ -1,83 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::configuration::Configuration; -use crate::benoit::launcher::{Launcher, Mode}; - -use std::env::args; -use std::str::FromStr; - -impl Launcher { - #[must_use] - pub(super) fn parse_arguments(&self) -> Result<Mode, String> { - let arguments = args(); - - let mut width: Option<u32> = None; - let mut height: Option<u32> = None; - let mut configuration: Option<Configuration> = None; - - let mut command: Option<String> = None; - - for argument in arguments.skip(0x1) { - match command { - Some(_) => { - match command.take().unwrap().as_str() { - "height" => height = Some(u32::from_str(argument.as_str()).unwrap()), - "width" => width = Some(u32::from_str(argument.as_str()).unwrap()), - "path" => configuration = Some(Configuration::load(argument.as_str())?), - _ => {}, - } - continue; - }, - _ => {}, - } - - // Check if command doesn't take a value. - match argument.as_str() { - "help" => Launcher::print_help(), - _ => command = Some(argument.clone()), - }; - } - - if command.is_some() { return Err(format!("missing value to command \"{}\"", command.unwrap())) }; - - return if configuration.is_some() { - if width.is_some() || height.is_some() { eprintln!("width and height will be overwritten in script mode") }; - - Ok(Mode::Script(configuration.unwrap())) - } else { - // If only one length is specified, the other is - // assumed equal. - - let width_val = if let Some(val) = width { val } - else if let Some(val) = height { val } - else { 0x100 }; - - let height_val = if let Some(val) = height { val } - else if let Some(val) = width { val } - else { 0xC0 }; - - Ok(Mode::App(width_val, height_val)) - }; - } -} diff --git a/source/benoit/benoit/launcher/print_help.rs b/source/benoit/benoit/launcher/print_help.rs deleted file mode 100644 index 13d6805..0000000 --- a/source/benoit/benoit/launcher/print_help.rs +++ /dev/null @@ -1,66 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::launcher::Launcher; - -use std::process::exit; - -impl Launcher { - pub(super) fn print_help() -> ! { - println!("Usage:"); - println!(" benoit [--help] [path]"); - println!(); - println!("Configuration:"); - println!(" thread_count 0..=4294967295"); - println!(); - println!(" fractal \"mandelbrot\"|\"multibrot3\"|\"multibrot4\"|\"burningship\"|\"tricorn\""); - println!(" inverse false|true"); - println!(" julia false|true"); - println!(); - println!(" canvas_width 2..=4294967295"); - println!(" canvas_height 2..=4294967295"); - println!(" start_frame 0..=4294967295"); - println!(" stop_frame start_frame..=4294967295"); - println!(" palette \"emerald\"|\"fire\"|\"greyscale\"|\"hsv\"|\"lch\"|\"ruby\"|\"sapphire\"|\"simple\"|\"twilight\""); - println!(); - println!(" dump_path"); - println!(" image_format \"png\"|\"webp\""); - println!(); - println!("Additionally, the following parameters may be set in the \"start\" and \"stop\" sections:"); - println!(); - println!(" real \"0.0\"\u{B1}"); - println!(" imaginary \"0.0\"\u{B1}"); - println!(" extra_real \"0.0\"\u{B1}"); - println!(" extra_imaginary \"0.0\"\u{B1}"); - println!(" zoom \"0.0\"<"); - println!(" maximum_iteration_count 1..=4294967295"); - println!(" colour_range 2.0+"); - println!(); - println!("If not animating (stop_frame equals zero), only the latter section is used."); - println!("Note that real, imaginary, extra_real, extra_imaginary, and zoom - if present - must be quoted floats."); - println!("When choosing image format, keep in mind that PNG is faster whilst WebP yields better compression. Both are, however, lossless."); - println!(); - - exit(0x0); - } -} diff --git a/source/benoit/benoit/launcher/print_message.rs b/source/benoit/benoit/launcher/print_message.rs deleted file mode 100644 index 5f9314e..0000000 --- a/source/benoit/benoit/launcher/print_message.rs +++ /dev/null @@ -1,36 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::VERSION; -use crate::benoit::launcher::Launcher; - -impl Launcher { - pub(super) fn print_message() { - println!(); - println!(" \u{1B}[1mBENO\u{CE}T\u{1B}[0m {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2); - println!(" Copyright 2021, 2023 \u{1B}[1mGabriel Bj\u{F8}rnager Jensen\u{1B}[0m."); - println!(); - println!(" \u{1B}[3mLe p\u{E8}re cogita et c'est pourquoi il fut.\u{1B}[0m"); - println!(); - } -} diff --git a/source/benoit/benoit/launcher/run.rs b/source/benoit/benoit/launcher/run.rs deleted file mode 100644 index ca71143..0000000 --- a/source/benoit/benoit/launcher/run.rs +++ /dev/null @@ -1,57 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::app::App; -use crate::benoit::launcher::{Launcher, Mode}; -use crate::benoit::script::Script; - -impl Launcher { - #[must_use] - pub fn run(self) -> Result<(), String> { - Launcher::print_message(); - - let mode = self.parse_arguments()?; - - let thread_count = match &mode { - Mode::Script(configuration) => configuration.thread_count, - _ => 0x0, - }; - - self.setup(thread_count); - - return match mode { - Mode::App(width, height) => { - eprintln!("running in iteractive mode"); - - let app = App::new(width, height); - app.run() - }, - Mode::Script(configuration) => { - eprintln!("running in script mode"); - - let script = Script::configure(configuration); - script.run() - }, - }; - } -} diff --git a/source/benoit/benoit/launcher/set_title.rs b/source/benoit/benoit/launcher/set_title.rs deleted file mode 100644 index 8066369..0000000 --- a/source/benoit/benoit/launcher/set_title.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::launcher::Launcher; - -#[cfg(windows)] -extern crate windows; - -#[cfg(windows)] -use windows::Win32::System::Console::SetConsoleTitleA; - -impl Launcher { - pub fn set_title(title: &str) { - #[cfg(unix)] - { eprint!("\u{1B}]0;{title}\u{07}") }; - - #[cfg(windows)] - unsafe { SetConsoleTitleA(title) }; - } -} diff --git a/source/benoit/benoit/launcher/setup.rs b/source/benoit/benoit/launcher/setup.rs deleted file mode 100644 index da360dd..0000000 --- a/source/benoit/benoit/launcher/setup.rs +++ /dev/null @@ -1,51 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::VERSION; -use crate::benoit::launcher::Launcher; -use crate::benoit::palette::fill_palettes; - -extern crate rayon; - -use rayon::ThreadPoolBuilder; -use std::thread::available_parallelism; - -impl Launcher { - pub(super) fn setup(&self, thread_count: u32) { - Launcher::set_title(format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2).as_str()); - - fill_palettes(); - - let thread_count = if thread_count == 0x0 { - match available_parallelism() { - Ok(ammount) => ammount.get() as u32, - _ => 0x2, // We assume at least two threads. - } - } else { - thread_count - }; - - eprintln!("using {} threads", thread_count); - ThreadPoolBuilder::new().num_threads(thread_count as usize).build_global().unwrap(); - } -} diff --git a/source/benoit/benoit/palette.rs b/source/benoit/benoit/palette.rs deleted file mode 100644 index 8235bbc..0000000 --- a/source/benoit/benoit/palette.rs +++ /dev/null @@ -1,104 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -extern crate enum_iterator; - -use enum_iterator::Sequence; -use std::mem::transmute; - -pub mod from_str; - -mod data; -mod paint; - -pub use data::fill_palettes; - -pub const PALETTE_DATA_LENGTH: usize = 0x1000; -pub type PaletteData = [(f32, f32, f32); PALETTE_DATA_LENGTH]; - -#[derive(Clone, Copy, Sequence)] -#[repr(u8)] -pub enum Palette { - Simple, - Twilight, - Fire, - Greyscale, - Ruby, - Emerald, - Sapphire, - Hsv, - Lch, -} - -impl Palette { - pub const NUM: usize = Self::Lch as usize + 0x1; - - #[must_use] - pub fn name(self) -> &'static str { - return match self { - Palette::Emerald => "emerald", - Palette::Fire => "fire", - Palette::Greyscale => "greyscale", - Palette::Hsv => "hsv", - Palette::Lch => "lch", - Palette::Ruby => "ruby", - Palette::Sapphire => "sapphire", - Palette::Simple => "simple", - Palette::Twilight => "twilight", - }; - } - - #[must_use] - pub fn data(self) -> &'static PaletteData { - // This is safe as we return it as immutable. - return unsafe { &*self.mut_data() }; - } - - pub fn cycle(&mut self, direction: i8) { - let raw = *self as i16 + direction as i16; - - const NUM: isize = Palette::NUM as isize; - let new: u8 = match raw as isize { - -0x1 => (Self::NUM - 0x1) as u8, - NUM => 0x0, - _ => raw as u8, - }; - - *self = unsafe { transmute(new) }; - } - - #[must_use] - fn function(self) -> fn(f32) -> (f32, f32, f32) { - return match self { - Palette::Emerald => paint::emerald, - Palette::Fire => paint::fire, - Palette::Greyscale => paint::greyscale, - Palette::Hsv => paint::hsv, - Palette::Lch => paint::lch, - Palette::Ruby => paint::ruby, - Palette::Sapphire => paint::sapphire, - Palette::Simple => paint::simple, - Palette::Twilight => paint::twilight, - }; - } -} diff --git a/source/benoit/benoit/palette/data.rs b/source/benoit/benoit/palette/data.rs deleted file mode 100644 index fdb8fa9..0000000 --- a/source/benoit/benoit/palette/data.rs +++ /dev/null @@ -1,70 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::palette::{Palette, PALETTE_DATA_LENGTH, PaletteData}; - -extern crate enum_iterator; - -use enum_iterator::all; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::time::Instant; - -impl Palette { - #[must_use] - pub(super) unsafe fn mut_data(self) -> &'static mut PaletteData { - return &mut PALETTE_DATA[self as usize]; - } -} - -static mut PALETTE_DATA: [PaletteData; Palette::NUM] = [[(0.0, 0.0, 0.0); PALETTE_DATA_LENGTH]; Palette::NUM]; - -pub fn fill_palettes() { - static PALETTES_FILLED: AtomicBool = AtomicBool::new(false); - - match PALETTES_FILLED.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) { - Err(_) => panic!("palettes already filled"), - _ => {}, - }; - - // We would like to precalculate the palettes at - // compile-time, but Rust does not allow - // const floating-point arithmetic. - - eprint!("filling palettes..."); - let time = Instant::now(); - - for palette in all::<Palette>() { - let data = unsafe { palette.mut_data() }; - let function = palette.function(); - - for index in 0x0..PALETTE_DATA_LENGTH { - let factor = index as f32 / PALETTE_DATA_LENGTH as f32; - - let (red, green, blue) = function(factor); - - data[index as usize] = (red, green, blue); - } - } - - eprintln!(" {:.3}ms", time.elapsed().as_micros() as f32 / 1000.0); -} diff --git a/source/benoit/benoit/palette/from_str.rs b/source/benoit/benoit/palette/from_str.rs deleted file mode 100644 index 3576d74..0000000 --- a/source/benoit/benoit/palette/from_str.rs +++ /dev/null @@ -1,52 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::palette::Palette; - -use std::str::FromStr; - -impl FromStr for Palette { - type Err = String; - - fn from_str(string: &str) -> Result<Self, Self::Err> { - use Palette::*; - - let kind = match string { - "emerald" => Some(Emerald), - "fire" => Some(Fire), - "greyscale" => Some(Greyscale), - "hsv" => Some(Hsv), - "lch" => Some(Lch), - "ruby" => Some(Ruby), - "sapphire" => Some(Sapphire), - "simple" => Some(Simple), - "twilight" => Some(Twilight), - _ => None, - }; - - return match kind { - Some(kind) => Ok(kind), - _ => Err(format!("invalid palette \"{string}\"")), - }; - } -} diff --git a/source/benoit/benoit/palette/paint.rs b/source/benoit/benoit/palette/paint.rs deleted file mode 100644 index 84ff005..0000000 --- a/source/benoit/benoit/palette/paint.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub mod emerald; -pub mod fire; -pub mod greyscale; -pub mod hsv; -pub mod lch; -pub mod ruby; -pub mod sapphire; -pub mod simple; -pub mod twilight; - -pub use emerald::*; -pub use fire::*; -pub use greyscale::*; -pub use hsv::*; -pub use lch::*; -pub use ruby::*; -pub use sapphire::*; -pub use simple::*; -pub use twilight::*; diff --git a/source/benoit/benoit/palette/paint/emerald.rs b/source/benoit/benoit/palette/paint/emerald.rs deleted file mode 100644 index 7244e6a..0000000 --- a/source/benoit/benoit/palette/paint/emerald.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub fn emerald(factor: f32) -> (f32, f32, f32) { - let factor = factor % 1.0; - - let factor = (if factor >= 1.0 / 2.0 { - 1.0 - factor - } else { - factor - }) * 2.0; - - return (factor * factor, factor, factor * factor * factor); -} diff --git a/source/benoit/benoit/palette/paint/fire.rs b/source/benoit/benoit/palette/paint/fire.rs deleted file mode 100644 index 9895210..0000000 --- a/source/benoit/benoit/palette/paint/fire.rs +++ /dev/null @@ -1,47 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -// Classic colour palette, fixed (and smoothed) -// from version ↋. - -pub fn fire(factor: f32) -> (f32, f32, f32) { - let factor = factor % 1.0; - - let (red, green, blue) = if factor <= 1.0 / 4.0 { - (factor * 4.0, 0.0, 0.0) - } else if factor <= 1.0 / 2.0 { - let factor = factor - (1.0 / 4.0); - - (1.0, factor * 4.0, 0.0) - } else if factor <= 3.0 / 4.0 { - let factor = factor - (1.0 / 2.0); - - (1.0, 1.0, factor * 4.0) - } else { - let factor = 1.0 - factor; - - (factor * 4.0, factor * 4.0, factor * 4.0) - }; - - return (red, green, blue); -} diff --git a/source/benoit/benoit/palette/paint/greyscale.rs b/source/benoit/benoit/palette/paint/greyscale.rs deleted file mode 100644 index 046f6b8..0000000 --- a/source/benoit/benoit/palette/paint/greyscale.rs +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -// Original palette function after the Rust- -// rewrite (version 10). - -pub fn greyscale(factor: f32) -> (f32, f32, f32) { - let factor = factor % 1.0; - - let factor = (if factor >= 1.0 / 2.0 { - 1.0 - factor - } else { - factor - }) * 2.0; - - return (factor, factor, factor); -} diff --git a/source/benoit/benoit/palette/paint/hsv.rs b/source/benoit/benoit/palette/paint/hsv.rs deleted file mode 100644 index a9a6d9e..0000000 --- a/source/benoit/benoit/palette/paint/hsv.rs +++ /dev/null @@ -1,53 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub fn hsv(factor: f32) -> (f32, f32, f32) { - return hsv_to_rgb(factor, 7.0 / 8.0, 7.0 / 8.0); -} - -fn hsv_to_rgb(hue: f32, saturation: f32, value: f32) -> (f32, f32, f32) { - return if saturation <= 0.0 { - let value = value.min(1.0); - - (value, value, value) - } else { - let h = hue % 1.0 * 6.0; - let s = saturation.min(1.0); - let v = value.min(1.0); - - let f = h % 1.0; - let p = v * (1.0 - s); - let q = v * (1.0 - s * f); - let t = v * (1.0 - s * (1.0 - f)); - - match h.trunc() as u8 { - 0x0 => (v, t, p), - 0x1 => (q, v, p), - 0x2 => (p, v, t), - 0x3 => (p, q, v), - 0x4 => (t, p, v), - 0x5 => (v, p, q), - _ => unreachable!(), - } - }; -} diff --git a/source/benoit/benoit/palette/paint/lch.rs b/source/benoit/benoit/palette/paint/lch.rs deleted file mode 100644 index 51d2ac0..0000000 --- a/source/benoit/benoit/palette/paint/lch.rs +++ /dev/null @@ -1,102 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use std::f32::consts::PI; - -pub fn lch(factor: f32) -> (f32, f32, f32) { - // Convert turns to radians: - // 1 turn = 2pi radians - - let angle = factor * PI * 2.0; - - let (l, a, b) = lch_to_lab( 200.0 / 3.0, 1053.0 / 20.0, angle); - let (x, y, z) = lab_to_xyz( l, a, b); - let (r, g, b) = xyz_to_rgb( x, y, z); - let (r, g, b) = rgb_to_srgb(r, g, b); - - return (r, g, b); -} - -fn rgb_to_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) { - fn srgb(value: f32) -> f32 { - return if value > 7827.0 / 25000000.0 { - 211.0 / 200.0 * value.powf(5.0 / 12.0) - 11.0 / 200.0 - } else { - 298.0 / 25.0 * value - }; - } - - let r = srgb(r); - let g = srgb(g); - let b = srgb(b); - - (r, g, b) -} - -fn xyz_to_rgb(x: f32, y: f32, z: f32) -> (f32, f32, f32) { - let m: [[f32; 0x3]; 0x3] = [ - [ - 12831.0 / 3959.0, -329.0 / 214.0, -1974.0 / 3959.0, - ], - [ - -851781.0 / 878810.0, 1648619.0 / 878810.0, 36519.0 / 878810.0, - ], - [ - 705.0 / 12673.0, -2585.0 / 12673.0, 705.0 / 667.0, - ], - ]; - - let r = m[0x0][0x0] * x + m[0x0][0x1] * y + m[0x0][0x2] * z; - let g = m[0x1][0x0] * x + m[0x1][0x1] * y + m[0x1][0x2] * z; - let b = m[0x2][0x0] * x + m[0x2][0x1] * y + m[0x2][0x2] * z; - - return (r, g, b); -} - -fn lab_to_xyz(l: f32, a: f32, b: f32) -> (f32, f32, f32) { - let kappa: f32 = 24389.0 / 27.0; - let epsilon: f32 = 216.0 / 24389.0; - - let f1 = (l + 16.0) / 116.0; - let f0 = a / 500.0 + f1; - let f2 = f1 - b / 200.0; - - let temporary = (l + 16.0) / 116.0; - - let mut x = f0 * f0 * f0; - let mut y = temporary * temporary * temporary; - let mut z = f2 * f2 * f2; - - if x <= epsilon { x = 1152.0 / 1195.0 * ((116.0 * f0 - 16.0) / kappa) }; - if l <= kappa * epsilon { y = l / kappa }; - if z <= epsilon { z = 986.0 / 1195.0 * ((116.0 * f2 - 16.0) / kappa) }; - - return (x, y, z); -} - -fn lch_to_lab(l: f32, c: f32, h: f32) -> (f32, f32, f32) { - let a = c * h.cos(); - let b = c * h.sin(); - - return (l, a, b); -} diff --git a/source/benoit/benoit/palette/paint/ruby.rs b/source/benoit/benoit/palette/paint/ruby.rs deleted file mode 100644 index 8f23a3e..0000000 --- a/source/benoit/benoit/palette/paint/ruby.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub fn ruby(factor: f32) -> (f32, f32, f32) { - let factor = factor % 1.0; - - let factor = (if factor >= 1.0 / 2.0 { - 1.0 - factor - } else { - factor - }) * 2.0; - - return (factor, factor * factor * factor, factor * factor); -} diff --git a/source/benoit/benoit/palette/paint/sapphire.rs b/source/benoit/benoit/palette/paint/sapphire.rs deleted file mode 100644 index 9ae4b0c..0000000 --- a/source/benoit/benoit/palette/paint/sapphire.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -pub fn sapphire(factor: f32) -> (f32, f32, f32) { - let factor = factor % 1.0; - - let factor = (if factor >= 1.0 / 2.0 { - 1.0 - factor - } else { - factor - }) * 2.0; - - return (factor * factor * factor, factor * factor, factor); -} diff --git a/source/benoit/benoit/palette/paint/simple.rs b/source/benoit/benoit/palette/paint/simple.rs deleted file mode 100644 index c2e013f..0000000 --- a/source/benoit/benoit/palette/paint/simple.rs +++ /dev/null @@ -1,32 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -// Original colour palette from MandelbrotSDL. - -pub fn simple(factor: f32) -> (f32, f32, f32) { - let red = factor * 3.0 % 1.0; - let green = factor * 5.0 % 1.0; - let blue = factor * 7.0 % 1.0; - - return (red, green, blue); -} diff --git a/source/benoit/benoit/palette/paint/twilight.rs b/source/benoit/benoit/palette/paint/twilight.rs deleted file mode 100644 index 79931ae..0000000 --- a/source/benoit/benoit/palette/paint/twilight.rs +++ /dev/null @@ -1,35 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -// Palette function from mandelbrotsdl, my first -// Mandelbrot renderer. - -pub fn twilight(factor: f32) -> (f32, f32, f32) { - let factor = factor % 1.0; - - let red = 9.0 * (1.0 - factor) * factor * factor * factor; - let green = 15.0 * (1.0 - factor) * (1.0 - factor) * factor * factor; - let blue = 8.5 * (1.0 - factor) * (1.0 - factor) * (1.0 - factor) * factor; - - return (red, green, blue); -} diff --git a/source/benoit/benoit/render.rs b/source/benoit/benoit/render.rs deleted file mode 100644 index 9bd3756..0000000 --- a/source/benoit/benoit/render.rs +++ /dev/null @@ -1,64 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::fractal::Fractal; - -pub mod allocate; -pub mod render; - -use std::ops::{Index, IndexMut}; - -pub struct Render { - width: u32, - height: u32, - - info: Option<(Fractal, u32)>, - - data: Vec::<(u32, f32)>, -} - -impl Render { - #[must_use] - pub fn size(&self) -> (u32, u32) { - return (self.width, self.height); - } - - #[must_use] - pub fn info(&self) -> Option<(Fractal, u32)> { - return self.info.clone(); - } -} - -impl Index<usize> for Render { - type Output = (u32, f32); - - fn index<'a>(&'a self, index: usize) -> &'a Self::Output { - return &self.data[index]; - } -} - -impl IndexMut<usize> for Render { - fn index_mut<'a>(&'a mut self, index: usize) -> &'a mut Self::Output { - return &mut self.data[index]; - } -} diff --git a/source/benoit/benoit/render/allocate.rs b/source/benoit/benoit/render/allocate.rs deleted file mode 100644 index 8e9143d..0000000 --- a/source/benoit/benoit/render/allocate.rs +++ /dev/null @@ -1,45 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::configuration::Configuration; -use crate::benoit::render::Render; - -impl Render { - pub fn allocate(width: u32, height: u32) -> Result<Render, String> { - if width < Configuration::MIN_CANVAS_WIDTH || height < Configuration::MIN_CANVAS_WIDTH { return Err(format!("width and height may not be more than {}", Configuration::MIN_CANVAS_WIDTH)) }; - - let (canvas_size, overflow) = (height as usize).overflowing_mul(width as usize); - if overflow { return Err("overflow when calculating canvas size".to_string()) }; - - let data: Vec<(u32, f32)> = vec![(0x0, 0.0); canvas_size]; - - return Ok(Render { - width: width, - height: height, - - info: None, - - data: data, - }); - } -} diff --git a/source/benoit/benoit/render/render.rs b/source/benoit/benoit/render/render.rs deleted file mode 100644 index 4d11d14..0000000 --- a/source/benoit/benoit/render/render.rs +++ /dev/null @@ -1,136 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::{BAILOUT_DISTANCE, PRECISION}; -use crate::benoit::complex::Complex; -use crate::benoit::configuration::Configuration; -use crate::benoit::fractal::{Fractal, IteratorFunction}; -use crate::benoit::render::Render; -use crate::benoit::render_data::RenderData; - -extern crate rayon; -extern crate rug; - -use rayon::prelude::*; -use rug::{Assign, Float}; -use rug::float::Special; - -impl Render { - pub fn render( - &mut self, - fractal: Fractal, - centre: &Complex, - zoom: &Float, - extra: &Complex, - max_iter_count: u32, - ) -> Result<(), String> { - if *zoom <= 0x0 { return Err("zoom may not be negative nor zero".to_string()) }; - if max_iter_count < Configuration::MIN_MAX_ITER_COUNT { return Err(format!("maximum_iteration_count must be at least {}", Configuration::MIN_MAX_ITER_COUNT)) }; - - let data = RenderData::new( - &mut self.data[..], - self.width, - self.height, - centre.clone(), - extra.clone(), - zoom.clone(), - max_iter_count, - fractal.inverse, - fractal.julia, - ); - - let iterator = fractal.iterator(); - - self.data.par_iter_mut().for_each(|point| { - let (x, y) = data.coordinate(point); - *point = render_point(&data, x, y, iterator); - }); - - self.info = Some((fractal, max_iter_count)); - - return Ok(()); - } -} - -fn render_point(data: &RenderData, x: u32, y: u32, iterator: IteratorFunction) -> (u32, f32) { - let (centre, extra, zoom, max_iter_count, inverse, julia) = data.input(); - - let (x_offset, y_offset, x_factor, y_factor) = data.consts(); - - let x_temporary = (x as f32 + x_offset) * x_factor; - let y_temporary = (y as f32 + y_offset) * y_factor; - - let mut z = { - let mut a = Float::with_val(PRECISION, x_temporary / zoom); - a += ¢re.real; - - let mut b = Float::with_val(PRECISION, y_temporary / zoom); - b -= ¢re.imag; - - Complex {real: a, imag: b} - }; - - if inverse { - let mut factor_inverse = Float::with_val(PRECISION, &z.real * &z.real); - factor_inverse += &z.imag * &z.imag; - factor_inverse.recip_mut(); - - z.real *= &factor_inverse; - z.imag *= factor_inverse; - } - - // We can optimise pertubation by adding w (extra) - // to c. - let c = match julia { - false => Complex { real: Float::with_val(PRECISION, &z.real + &extra.real), imag: Float::with_val(PRECISION, &z.imag + &extra.imag) }, - true => extra.clone(), - }; - - let mut z_prev = Complex { - real: Float::with_val(PRECISION, Special::Nan), - imag: Float::with_val(PRECISION, Special::Nan), - }; - - let mut iter_count: u32 = 0x1; - let mut square_dist = Float::with_val(PRECISION, Special::Nan); - while { - square_dist.assign(&z.real * &z.real + &z.imag * &z.imag); - // Having a larger escape radius gives better - // results with regard to smoothing. - - // Check if the value is periodic, i.e. its - // sequence repeats. - let periodic = z.real == z_prev.real && z.imag == z_prev.imag; - if periodic { iter_count = max_iter_count } - - square_dist <= BAILOUT_DISTANCE && iter_count < max_iter_count - } { - z_prev.assign(&z); - - iterator(&mut z, &c); - - iter_count += 0x1; - } - - return (iter_count, square_dist.to_f32()); -} diff --git a/source/benoit/benoit/render_data.rs b/source/benoit/benoit/render_data.rs deleted file mode 100644 index 2f13d4b..0000000 --- a/source/benoit/benoit/render_data.rs +++ /dev/null @@ -1,112 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::width_height_ratio; -use crate::benoit::complex::Complex; - -extern crate rug; - -use rug::Float; -use std::mem::size_of; -use std::num::NonZeroUsize; -use std::ptr::addr_of; - -pub struct RenderData { - canvas_width: u32, - - centre: Complex, - extra: Complex, - zoom: Float, - - max_iter_count: u32, - - inverse: bool, - julia: bool, - - x_offset: f32, - y_offset: f32, - - x_factor: f32, - y_factor: f32, - - buffer: NonZeroUsize, -} - -impl RenderData { - #[must_use] - pub fn new( - buffer: &[(u32, f32)], - canvas_width: u32, - canvas_height: u32, - centre: Complex, - extra: Complex, - zoom: Float, - max_iter_count: u32, - inverse: bool, - julia: bool, - ) -> RenderData { - let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height); - - return RenderData { - canvas_width: canvas_width, - - centre: centre, - extra: extra, - zoom: zoom, - - max_iter_count: max_iter_count, - - inverse: inverse, - julia: julia, - - x_offset: canvas_width as f32 / -2.0, - y_offset: canvas_height as f32 / -2.0, - - x_factor: 4.0 / canvas_width as f32 * width_ratio, - y_factor: 4.0 / canvas_height as f32 * height_ratio, - - buffer: NonZeroUsize::new(buffer.as_ptr() as usize).unwrap(), - }; - } - - #[must_use] - pub fn input(&self) -> (&Complex, &Complex, &Float, u32, bool, bool) { - return (&self.centre, &self.extra, &self.zoom, self.max_iter_count, self.inverse, self.julia); - } - - #[must_use] - pub fn consts(&self) -> (f32, f32, f32, f32) { - return (self.x_offset, self.y_offset, self.x_factor, self.y_factor); - } - - #[must_use] - pub fn coordinate(&self, element: &(u32, f32)) -> (u32, u32) { - let element_addr = addr_of!(*element) as usize; - - let index = (element_addr - self.buffer.get()) / size_of::<(u32, f32)>(); - - let x = (index % self.canvas_width as usize) as u32; - let y = (index / self.canvas_width as usize) as u32; - return (x, y); - } -} diff --git a/source/benoit/benoit/script.rs b/source/benoit/benoit/script.rs deleted file mode 100644 index fd34ab2..0000000 --- a/source/benoit/benoit/script.rs +++ /dev/null @@ -1,62 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; -use crate::benoit::fractal::Fractal; -use crate::benoit::image::ImageFormat; -use crate::benoit::palette::Palette; - -extern crate rug; - -use rug::Float; - -pub mod animate; -pub mod configure; -pub mod dump_frame; -pub mod run; -pub mod still; - -pub struct Keyframe { - frame: u32, - centre: Complex, - extra: Complex, - zoom: Float, - max_iter_count: u32, - colour_range: f32, -} - -pub struct Script { - // Configuration: - fractal: Fractal, - - canvas_width: u32, - canvas_height: u32, - - palette: Palette, - - dump_path: String, - image_format: ImageFormat, - - start: Keyframe, - stop: Keyframe, -} diff --git a/source/benoit/benoit/script/animate.rs b/source/benoit/benoit/script/animate.rs deleted file mode 100644 index d30ac8c..0000000 --- a/source/benoit/benoit/script/animate.rs +++ /dev/null @@ -1,165 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::PRECISION; -use crate::benoit::image::Image; -use crate::benoit::render::Render; -use crate::benoit::script::Script; - -extern crate rug; - -use rug::Float; -use rug::ops::PowAssign; - -impl Script { - #[must_use] - pub(super) fn animate(&self) -> Result<(), String> { - // TO-DO: Proper animation for centre value when zooming. - - let frame_count = self.stop.frame - self.start.frame + 0x1; - assert!(frame_count >= 0x2); - - let mut render = Render::allocate(self.canvas_width, self.canvas_height)?; - let mut image = Image::allocate( self.canvas_width, self.canvas_height)?; - - // Variables: - let mut centre = self.start.centre.clone(); - let mut extra = self.start.extra.clone(); - let mut zoom = self.start.zoom.clone(); - let mut max_iter_count = self.start.max_iter_count as f32; - let mut colour_range = self.start.colour_range; - - // Steps & factors: - let centre_real_step = get_step_bigfloat(self.stop.frame, &self.start.centre.real, &self.stop.centre.real); - let centre_imag_step = get_step_bigfloat(self.stop.frame, &self.start.centre.imag, &self.stop.centre.imag); - let extra_real_step = get_step_bigfloat(self.stop.frame, &self.start.extra.real, &self.stop.extra.real); - let extra_imag_step = get_step_bigfloat(self.stop.frame, &self.start.extra.imag, &self.stop.extra.imag); - let zoom_factor = get_factor_bigfloat(self.stop.frame, &self.start.zoom, &self.stop.zoom); - let max_iter_count_step = get_step(self.stop.frame, self.start.max_iter_count as f32, self.stop.max_iter_count as f32); - let colour_range_step = get_step(self.stop.frame, self.start.colour_range, self.stop.colour_range); - - eprintln!(""); - eprintln!("animating {frame_count} frames: the {}", self.fractal.name()); - eprintln!(" re(c): {} \u{2192} {} (step: {centre_real_step})", self.start.centre.real, self.stop.centre.real); - eprintln!(" im(c): {} \u{2192} {} (step: {centre_imag_step})", self.start.centre.imag, self.stop.centre.imag); - eprintln!(" re(w): {} \u{2192} {} (step: {extra_real_step})", self.start.extra.real, self.stop.extra.real); - eprintln!(" im(w): {} \u{2192} {} (step: {extra_imag_step})", self.start.extra.imag, self.stop.extra.imag); - eprintln!(" zoom: {} \u{2192} {} (fac.: {zoom_factor})", self.start.zoom, self.stop.zoom); - eprintln!(" max. iter count: {} \u{2192} {} (step: {max_iter_count_step})", self.start.max_iter_count, self.stop.max_iter_count); - eprintln!(" col. range: {} \u{2192} {} (step: {colour_range_step})", self.start.colour_range, self.stop.colour_range); - eprintln!(""); - - for frame in 0x0..frame_count { - let frame_name = format!("frame{frame:010}"); - - Script::dump_frame( - self.dump_path.as_str(), - frame_name.as_str(), - &mut image, - &mut render, - self.fractal, - self.palette, - ¢re, - &extra, - &zoom, - max_iter_count.round() as u32, - colour_range, - self.image_format, - )?; - - centre.real += ¢re_real_step; - centre.imag += ¢re_imag_step; - extra.real += &extra_real_step; - extra.imag += &extra_imag_step; - zoom *= &zoom_factor; - max_iter_count += max_iter_count_step; - colour_range += colour_range_step; - } - - return Ok(()); - } -} - -#[must_use] -fn get_step(stop_x: u32, start_y: f32, stop_y: f32) -> f32 { - assert!(stop_x - START_X != 0x0); - - const START_X: u32 = 0x1; - - // a = (y1-y0)/(x1-x0) - return (stop_y - start_y) / (stop_x as f32 - START_X as f32); -} - -#[allow(dead_code)] -#[must_use] -fn get_factor(stop_x: u32, start_y: f32, stop_y: f32) -> f32 { - assert!(stop_x - START_X != 0x0); - - const START_X: u32 = 0x1; - - // a = (y1/y0)^(1/(x1-x0)) - return (stop_y / start_y).powf(1.0 / (stop_x as f32 - START_X as f32)); -} - -#[must_use] -fn get_step_bigfloat(stop_x: u32, start_y: &Float, stop_y: &Float) -> Float { - assert!(stop_x - START_X > 0x0); - - const START_X: u32 = 0x1; - - let numerator = Float::with_val(PRECISION, stop_y - start_y); - let denominator = stop_x - START_X; - - return numerator / denominator; -} - -#[must_use] -fn get_factor_bigfloat(stop_x: u32, start_y: &Float, stop_y: &Float) -> Float { - assert!(stop_x - START_X > 0x0); - if stop_y == start_y { return Float::with_val(PRECISION, 0x1) }; - - const START_X: u32 = 0x1; - - // To get the zoom factor, we first want the 'a' - // value of the growth function from (0) to - // (frame_count) on the x-dimension and from - // (zoom_start) to (stop_zoom) on the y-dimension: - // - // a = nroot(x1-x0,y1/y0), - // - // but this may be simplified for use with Rug - // because - // - // nroot(a,b) = b^(1/a), - // - // making the final equation - // - // (y1/y0)^(1/(x1-x0)) = (stop_zoom/zoom_start)^(1/(frame_count-1)). - - let exponent = 1.0 / (stop_x as f64 - START_X as f64); - - let mut factor = Float::with_val(PRECISION, stop_y / start_y); - factor.pow_assign(exponent); - - return factor; -} diff --git a/source/benoit/benoit/script/configure.rs b/source/benoit/benoit/script/configure.rs deleted file mode 100644 index be2e1cd..0000000 --- a/source/benoit/benoit/script/configure.rs +++ /dev/null @@ -1,60 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; -use crate::benoit::configuration::Configuration; -use crate::benoit::script::{Keyframe, Script}; - -impl Script { - #[must_use] - pub fn configure(configuration: Configuration) -> Script { - return Script { - fractal: configuration.fractal, - - canvas_width: configuration.canvas_width, - canvas_height: configuration.canvas_height, - palette: configuration.palette, - - dump_path: configuration.dump_path, - image_format: configuration.image_format, - - start: Keyframe { - frame: configuration.start_frame, - centre: Complex::new(configuration.start_centre_real, configuration.start_centre_imag), - extra: Complex::new(configuration.start_extra_real, configuration.start_extra_imag), - zoom: configuration.start_zoom, - max_iter_count: configuration.start_max_iter_count, - colour_range: configuration.start_colour_range, - }, - - stop: Keyframe { - frame: configuration.stop_frame, - centre: Complex::new(configuration.stop_centre_real, configuration.stop_centre_imag), - extra: Complex::new(configuration.stop_extra_real, configuration.stop_extra_imag), - zoom: configuration.stop_zoom, - max_iter_count: configuration.stop_max_iter_count, - colour_range: configuration.stop_colour_range, - }, - }; - } -} diff --git a/source/benoit/benoit/script/dump_frame.rs b/source/benoit/benoit/script/dump_frame.rs deleted file mode 100644 index 86b62c6..0000000 --- a/source/benoit/benoit/script/dump_frame.rs +++ /dev/null @@ -1,84 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; -use crate::benoit::fractal::Fractal; -use crate::benoit::image::{Image, ImageFormat}; -use crate::benoit::launcher::Launcher; -use crate::benoit::palette::Palette; -use crate::benoit::render::Render; -use crate::benoit::script::Script; - -extern crate rug; - -use rug::Float; -use std::time::Instant; - -impl Script { - pub(super) fn dump_frame( - dump_path: &str, - name: &str, - image: &mut Image, - render: &mut Render, - fractal: Fractal, - palette: Palette, - centre: &Complex, - extra: &Complex, - zoom: &Float, - max_iter_count: u32, - colour_range: f32, - image_format: ImageFormat, - ) -> Result<(), String> { - eprint!("\u{1B}[1m\u{1B}[94mX\u{1B}[0m \"{name}\" (2^{:.9}x): rendering...", zoom.to_f64().log2()); - Launcher::set_title("Rendering..."); - - let time_start = Instant::now(); - - render.render( - fractal, - centre, - zoom, - extra, - max_iter_count, - )?; - - let render_time = time_start.elapsed(); - eprint!(" {:.3}ms, colouring...", render_time.as_micros() as f32 / 1000.0); - Launcher::set_title("Colouring..."); - - image.colour(&render, palette, max_iter_count, colour_range)?; - - let colour_time = time_start.elapsed() - render_time; - eprint!(" {:.3}ms, dumping...", colour_time.as_micros() as f32 / 1000.0); - Launcher::set_title("Dumping..."); - - let path = format!("{dump_path}/{name}"); - image.dump(path.as_str(), image_format)?; - - let dump_time = time_start.elapsed() - colour_time - render_time; - eprintln!(" {:.3}ms\r\u{1B}[1m\u{1B}[92mO\u{1B}[0m", dump_time.as_micros() as f32 / 1000.0); - Launcher::set_title("Done, waiting..."); - - return Ok(()); - } -} diff --git a/source/benoit/benoit/script/run.rs b/source/benoit/benoit/script/run.rs deleted file mode 100644 index 5a0f4c7..0000000 --- a/source/benoit/benoit/script/run.rs +++ /dev/null @@ -1,34 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::script::Script; - -impl Script { - #[must_use] - pub fn run(self) -> Result<(), String> { - return match self.stop.frame == 0x0 && self.start.frame == 0x0 { - false => self.animate(), - true => self.still(), - }; - } -} diff --git a/source/benoit/benoit/script/still.rs b/source/benoit/benoit/script/still.rs deleted file mode 100644 index 65177d9..0000000 --- a/source/benoit/benoit/script/still.rs +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::image::Image; -use crate::benoit::render::Render; -use crate::benoit::script::Script; - -impl Script { - #[must_use] - pub(super) fn still(&self) -> Result<(), String> { - let frame_count = self.stop.frame - self.start.frame + 0x1; - assert!(frame_count == 0x1); - - let mut render = Render::allocate(self.canvas_width, self.canvas_height)?; - let mut image = Image::allocate( self.canvas_width, self.canvas_height)?; - - const FRAME_NAME: &str = "render"; - - eprintln!(""); - eprintln!("rendering still: the {}", self.fractal.name()); - eprintln!(" width: {}", self.canvas_width); - eprintln!(" height: {}", self.canvas_height); - eprintln!(" re(c): {}", self.stop.centre.real); - eprintln!(" im(c): {}", self.stop.centre.imag); - eprintln!(" re(w): {}", self.stop.extra.real); - eprintln!(" im(w): {}", self.stop.extra.imag); - eprintln!(" zoom: {}", self.stop.zoom); - eprintln!(" max. iter count: {}", self.stop.max_iter_count); - eprintln!(" col. range: {}", self.stop.colour_range); - eprintln!(""); - - Script::dump_frame( - self.dump_path.as_str(), - FRAME_NAME, - &mut image, - &mut render, - self.fractal, - self.palette, - &self.stop.centre, - &self.stop.extra, - &self.stop.zoom, - self.stop.max_iter_count, - self.stop.colour_range, - self.image_format, - )?; - - return Ok(()); - } -} diff --git a/source/benoit/benoit/video.rs b/source/benoit/benoit/video.rs deleted file mode 100644 index d0de33f..0000000 --- a/source/benoit/benoit/video.rs +++ /dev/null @@ -1,40 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -extern crate sdl2; - -use sdl2::{Sdl, VideoSubsystem}; -use sdl2::render::WindowCanvas; - -pub mod draw_image; -pub mod draw_textual_feedback; -pub mod draw_translation_feedback; -pub mod initialise; -pub mod sync; -pub mod update; - -pub struct Video { - pub sdl: Sdl, - pub sdl_video: VideoSubsystem, - pub canvas: WindowCanvas, -} diff --git a/source/benoit/benoit/video/draw_image.rs b/source/benoit/benoit/video/draw_image.rs deleted file mode 100644 index 9554b3b..0000000 --- a/source/benoit/benoit/video/draw_image.rs +++ /dev/null @@ -1,58 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::image::Image; -use crate::benoit::video::Video; - -extern crate sdl2; - -use sdl2::pixels::Color; -use sdl2::rect::Rect; - -impl Video { - pub fn draw_image(&mut self, image: &Image, scale: u32) { - let (canvas_width, canvas_height) = image.size(); - let canvas_size = canvas_height as usize * canvas_width as usize; - - for pixel in 0x0..canvas_size { - let x = pixel as u32 % canvas_width; - let y = pixel as u32 / canvas_width; - - let colour = { - let (red, green, blue) = image[pixel as usize]; - - Color::RGB(red, green, blue) - }; - - let square = Rect::new( - (x * scale) as i32, - (y * scale) as i32, - scale, - scale, - ); - - self.canvas.set_draw_color(colour); - self.canvas.fill_rect(square).unwrap(); - } - } -} diff --git a/source/benoit/benoit/video/draw_textual_feedback.rs b/source/benoit/benoit/video/draw_textual_feedback.rs deleted file mode 100644 index 72c9d2a..0000000 --- a/source/benoit/benoit/video/draw_textual_feedback.rs +++ /dev/null @@ -1,329 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::complex::Complex; -use crate::benoit::video::Video; - -extern crate rug; -extern crate sdl2; - -use rug::Float; -use sdl2::pixels::Color; -use sdl2::rect::Rect; -use sdl2::render::WindowCanvas; - -impl Video { - pub fn draw_textual_feedback(&mut self, centre: &Complex, zoom: &Float, max_iter_count: u32) { - let real_text = format!("REAL: {:.18}", centre.real.to_f64()); - let imag_text = format!("IMAG: {:.18}", centre.imag.to_f64()); - let zoom_text = format!("ZOOM: 2^{:.9}", zoom.to_f64().log2()); - let iter_text = format!("ITER: {}", max_iter_count); - - let string = format!("{real_text}\n{imag_text}\n{zoom_text}\n{iter_text}"); - - let mut offset_x = 0x1; - let mut offset_y = 0x1; - for character in string.chars() { - if character == '\n' { - offset_x = 0x1; - offset_y += TEXTURE_HEIGHT; - continue; - } - - let character = convert_character(character); - - draw_character(&mut self.canvas, offset_x, offset_y, character); - - offset_x += TEXTURE_WIDTH; - } - } -} - -fn draw_character(canvas: &mut WindowCanvas, x: u32, y: u32, character: u8) { - let texture = &FONT[character as usize]; - - for pixel in 0x0..TEXTURE_SIZE { - let alpha = texture[pixel] as u8 * 0xFF; - - let texture_y = pixel as u32 / TEXTURE_WIDTH; - let texture_x = pixel as u32 - texture_y * TEXTURE_WIDTH; - - let square = Rect::new( - (x * CHARACTER_SCALE + texture_x * CHARACTER_SCALE) as i32, - (y * CHARACTER_SCALE + texture_y * CHARACTER_SCALE) as i32, - CHARACTER_SCALE, - CHARACTER_SCALE, - ); - - canvas.set_draw_color(Color::RGBA(0xFF, 0xFF, 0xFF, alpha)); - canvas.fill_rect(square).unwrap(); - } -} - -fn convert_character(input: char) -> u8 { - return match input { - ' ' => 0x00, - 'A' => 0x01, - 'E' => 0x02, - 'G' => 0x03, - 'I' => 0x04, - 'L' => 0x05, - 'M' => 0x06, - 'O' => 0x07, - 'R' => 0x08, - 'T' => 0x09, - 'Z' => 0x0A, - '0' => 0x0B, - '1' => 0x0C, - '2' => 0x0D, - '3' => 0x0E, - '4' => 0x0F, - '5' => 0x10, - '6' => 0x11, - '7' => 0x12, - '8' => 0x13, - '9' => 0x14, - '-' => 0x15, - '^' => 0x16, - '.' => 0x17, - ':' => 0x18, - _ => 0x19, - }; -} - -const CHARACTER_SCALE: u32 = 0x2; - -const TEXTURE_WIDTH: u32 = 0x6; -const TEXTURE_HEIGHT: u32 = 0x6; -const TEXTURE_SIZE: usize = TEXTURE_HEIGHT as usize * TEXTURE_WIDTH as usize; - -const FONT: [[bool; TEXTURE_SIZE]; 0x1A] = [ - [ - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, false, false, - true, true, true, true, true, false, - true, true, false, true, true, false, - true, true, true, true, true, false, - true, true, false, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - true, true, false, false, false, false, - true, true, true, true, false, false, - true, true, false, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - true, true, false, false, false, false, - true, true, false, true, true, false, - true, true, false, false, true, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - false, false, true, false, false, false, - false, false, true, false, false, false, - false, false, true, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, - true, true, false, false, false, false, - true, true, false, false, false, false, - true, true, false, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, false, true, true, false, - true, true, true, true, true, false, - true, true, true, true, true, false, - true, true, false, true, true, false, - true, true, false, true, true, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, false, false, - true, true, false, true, true, false, - true, true, false, true, true, false, - true, true, false, true, true, false, - false, true, true, true, false, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, false, false, - true, true, false, true, true, false, - true, true, true, true, false, false, - true, true, false, true, true, false, - true, true, false, false, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - false, false, true, false, false, false, - false, false, true, false, false, false, - false, false, true, false, false, false, - false, false, true, false, false, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - false, false, true, true, true, false, - false, true, true, true, false, false, - true, true, true, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, false, false, - true, true, false, true, true, false, - true, true, false, true, true, false, - true, true, false, true, true, false, - false, true, true, true, false, false, - false, false, false, false, false, false, - ], - [ - false, true, true, false, false, false, - true, true, true, false, false, false, - false, false, true, false, false, false, - false, false, true, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, false, false, - true, false, false, true, true, false, - false, false, true, true, false, false, - false, true, true, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - true, false, false, true, true, false, - false, false, true, true, false, false, - false, false, false, true, true, false, - true, true, true, true, false, false, - false, false, false, false, false, false, - ], - [ - false, true, false, true, true, false, - true, true, false, true, true, false, - true, true, true, true, true, false, - false, false, false, true, true, false, - false, false, false, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - true, true, false, false, false, false, - true, true, true, true, true, false, - false, false, false, true, true, false, - true, true, true, true, false, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, true, false, - true, true, false, false, false, false, - true, true, true, true, true, false, - true, true, false, true, true, false, - false, true, true, true, true, false, - false, false, false, false, false, false, - ], - [ - true, true, true, true, true, false, - false, false, false, true, true, false, - false, false, true, true, false, false, - false, true, true, false, false, false, - true, true, false, false, false, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, false, false, - true, true, false, true, true, false, - false, true, true, true, false, false, - true, true, false, true, true, false, - false, true, true, true, false, false, - false, false, false, false, false, false, - ], - [ - false, true, true, true, true, false, - true, true, false, true, true, false, - true, true, true, true, true, false, - false, false, false, true, true, false, - true, true, true, true, false, false, - false, false, false, false, false, false, - ], - [ - false, false, false, false, false, false, - false, false, false, false, false, false, - true, true, true, true, true, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - ], - [ - false, false, true, false, false, false, - false, true, true, true, false, false, - true, true, false, true, true, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - ], - [ - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - true, false, false, false, false, false, - true, false, false, false, false, false, - false, false, false, false, false, false, - ], - [ - true, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - false, false, false, false, false, false, - true, false, false, false, false, false, - false, false, false, false, false, false, - ], - [ - true, true, false, false, false, false, - false, true, false, false, false, false, - true, true, false, false, false, false, - false, false, false, false, false, false, - true, false, false, false, false, false, - false, false, false, false, false, false, - ], -]; diff --git a/source/benoit/benoit/video/draw_translation_feedback.rs b/source/benoit/benoit/video/draw_translation_feedback.rs deleted file mode 100644 index 05ac70e..0000000 --- a/source/benoit/benoit/video/draw_translation_feedback.rs +++ /dev/null @@ -1,96 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::{PRECISION, width_height_ratio}; -use crate::benoit::complex::Complex; -use crate::benoit::video::Video; - -extern crate rug; -extern crate sdl2; - -use rug::Float; -use sdl2::pixels::Color; -use sdl2::rect::Rect; - -impl Video { - pub fn draw_translation_feedback(&mut self, canvas_width: u32, canvas_height: u32, scale: u32, prev_centre: &Complex, prev_zoom: &Float, next_centre: &Complex, next_zoom: &Float) { - let (width_ratio, height_ratio) = width_height_ratio(canvas_width, canvas_height); - - let canvas_width = Float::with_val(PRECISION, canvas_width * scale); - let canvas_height = Float::with_val(PRECISION, canvas_height * scale); - - let viewport = { - let (offset_x, offset_y, width, height) = { - let zoom_ratio = Float::with_val(PRECISION, next_zoom / prev_zoom); - - let mut width = Float::with_val(PRECISION, 1.0 / &zoom_ratio); - let mut height = Float::with_val(PRECISION, 1.0 / &zoom_ratio); - - // Remember that cartesian coordinates have an - // inverted vertical axis compared to those of - // SDL's coordinate system. - - let mut offset_x = next_centre.real.clone(); - let mut offset_y = Float::with_val(PRECISION, -&next_centre.imag); - - offset_x -= &prev_centre.real; - offset_y += &prev_centre.imag; - - offset_x /= 4.0; - offset_y /= 4.0; - - offset_x *= prev_zoom; - offset_y *= prev_zoom; - - offset_x /= width_ratio; - offset_y /= height_ratio; - - let mut zoom_offset = Float::with_val(PRECISION, 1.0 - &width); - zoom_offset /= 2.0; - - offset_x += &zoom_offset; - offset_y += &zoom_offset; - - offset_x *= &canvas_width; - offset_y *= &canvas_height; - width *= &canvas_width; - height *= &canvas_height; - - (offset_x.to_f32().round() as i32, offset_y.to_f32().round() as i32, width.to_f32().round() as u32, height.to_f32().round() as u32) - }; - - Rect::new( - offset_x, - offset_y, - width, - height, - ) - }; - - self.canvas.set_draw_color(Color::RGBA(0x0, 0x0, 0x0, 0x7F)); - self.canvas.fill_rect(viewport).unwrap(); - - self.canvas.set_draw_color(Color::RGB(0xFF, 0xFF, 0xFF)); - self.canvas.draw_rect(viewport).unwrap(); - } -} diff --git a/source/benoit/benoit/video/initialise.rs b/source/benoit/benoit/video/initialise.rs deleted file mode 100644 index f78ee7a..0000000 --- a/source/benoit/benoit/video/initialise.rs +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::VERSION; -use crate::benoit::video::Video; - -extern crate sdl2; - -use sdl2::pixels::Color; -use sdl2::render::BlendMode; - -impl Video { - #[must_use] - pub fn initialise(canvas_width: u32, canvas_height: u32, scale: u32) -> Result<Video, String> { - let sdl = match sdl2::init() { - Ok( sdl) => sdl, - Err(_) => return Err("unable to initialise sdl2".to_string()), - }; - - let sdl_video = match sdl.video() { - Ok( video) => video, - Err(_) => return Err("unable to initialise video".to_string()), - }; - - let window_title = format!("BENO\u{CE}T {:X}.{:X}.{:X}", VERSION.0, VERSION.1, VERSION.2); - - let mut window_builder = sdl_video.window(window_title.as_str(), canvas_width * scale, canvas_height * scale); - window_builder.position_centered(); - - let window = match window_builder.build() { - Ok( window) => window, - Err(_) => return Err("unable to open window".to_string()), - }; - - let mut canvas = match window.into_canvas().build() { - Ok( canvas) => canvas, - Err(_) => return Err("unable to build canvas".to_string()), - }; - - canvas.set_blend_mode(BlendMode::Blend); - - // We only want to scale the render, not the - // feedback, so we can't use SDL's scaling feature. - - let clear_colour = Color::RGB(0x00, 0x00, 0x00); - canvas.set_draw_color(clear_colour); - canvas.clear(); - canvas.present(); - - return Ok(Video { - sdl: sdl, - sdl_video: sdl_video, - canvas: canvas, - }); - } -} diff --git a/source/benoit/benoit/video/sync.rs b/source/benoit/benoit/video/sync.rs deleted file mode 100644 index 46112eb..0000000 --- a/source/benoit/benoit/video/sync.rs +++ /dev/null @@ -1,54 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::video::Video; - -use std::thread::sleep; -use std::time::{Duration, Instant}; - -impl Video { - pub fn sync(&self, start_frame: &Instant) -> Result<(), String> { - let frame_duration = { - let index = match self.canvas.window().display_index() { - Ok( index) => index, - Err(_) => return Err("unable to get display index".to_string()), - }; - - let mode = match self.sdl_video.current_display_mode(index) { - Ok( mode) => mode, - Err(_) => return Err("unable to get display mode".to_string()), - }; - - Duration::from_secs(0x1) / mode.refresh_rate as u32 - }; - - let remaining = match frame_duration.checked_sub(start_frame.elapsed()) { - Some(value) => value, - None => Duration::from_secs(0x0), - }; - - sleep(remaining); - - return Ok(()); - } -}
\ No newline at end of file diff --git a/source/benoit/benoit/video/update.rs b/source/benoit/benoit/video/update.rs deleted file mode 100644 index eab330a..0000000 --- a/source/benoit/benoit/video/update.rs +++ /dev/null @@ -1,30 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -use crate::benoit::video::Video; - -impl Video { - pub fn update(&mut self) { - self.canvas.present(); - } -} diff --git a/source/benoit/main.rs b/source/benoit/main.rs deleted file mode 100644 index 1264873..0000000 --- a/source/benoit/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -/* - Copyright 2021, 2023 Gabriel Bjørnager Jensen. - - This file is part of Benoit. - - Benoit is free software: you can redistribute it - and/or modify it under the terms of the GNU - Affero General Public License as published by - the Free Software Foundation, either version 3 - of the License, or (at your option) any later - version. - - Benoit is distributed in the hope that it will - be useful, but WITHOUT ANY WARRANTY; without - even the implied warranty of MERCHANTABILITY or - FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Affero General Public License for more details. - - You should have received a copy of the GNU - Affero General Public License along with Benoit. - If not, see <https://www.gnu.org/licenses/>. -*/ - -mod benoit; - -use crate::benoit::launcher::Launcher; - -use std::process::exit; - -fn main() { - let launcher = Launcher::new(); - let result = launcher.run(); - - if let Err(ref error) = result { eprintln!("error: {error}") }; - - let code: i32 = match result { - Ok( _) => 0x0, - Err(_) => 0x1, - }; - - exit(code); -} |