diff --git a/CMakeLists.txt b/CMakeLists.txt index e694d23..46c093c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ add_library( fractal-generator_lib OBJECT source/lib.cpp source/graphics/basic_display.cpp + source/graphics/color_conversions.cpp ) target_include_directories( diff --git a/source/config.hpp b/source/config.hpp index 3ba00f7..bbc2908 100644 --- a/source/config.hpp +++ b/source/config.hpp @@ -1,7 +1,9 @@ #pragma once +#include + namespace fractal { -constexpr auto WINDOW_WIDTH = 800UZ; -constexpr auto WINDOW_HEIGHT = 600UZ; -constexpr auto FRAME_RATE{60UZ}; +constexpr std::size_t WINDOW_WIDTH = 800UZ; +constexpr std::size_t WINDOW_HEIGHT = 600UZ; +constexpr std::size_t FRAME_RATE = 60UZ; } // namespace fractal diff --git a/source/coordinates.hpp b/source/coordinates.hpp new file mode 100644 index 0000000..197be27 --- /dev/null +++ b/source/coordinates.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace fractal { +using display_coordinate = std::pair; +using complex_coordinate = std::complex; + +struct display_domain { + display_coordinate start_coordinate; + display_coordinate end_coordinate; + + class DisplayCoordinateIterator { + display_coordinate current_coordinate_; + display_coordinate end_coordinate_; + + public: + explicit DisplayCoordinateIterator(const display_domain& domain) : + current_coordinate_{domain.start_coordinate}, + end_coordinate_{domain.end_coordinate} + {} + + const display_coordinate& operator*() const { return current_coordinate_; } + + DisplayCoordinateIterator& operator++() + { + if (current_coordinate_.first == end_coordinate_.first) [[unlikely]] { + current_coordinate_.first = 0; + current_coordinate_.second++; + } + else { + current_coordinate_.first++; + } + return *this; + } + + bool operator==(const DisplayCoordinateIterator&) const = default; + }; + + DisplayCoordinateIterator begin() const { return DisplayCoordinateIterator{*this}; } + + DisplayCoordinateIterator end() const + { + return DisplayCoordinateIterator{ + display_domain{end_coordinate, end_coordinate} + }; + } +}; + +struct complex_domain { + complex_coordinate start_coordinate; + complex_coordinate end_coordinate; +}; + +inline float real_domain_size(const complex_domain& domain) +{ + return domain.end_coordinate.real() - domain.start_coordinate.real(); +} + +inline float imaginary_domain_size(const complex_domain& domain) +{ + return domain.end_coordinate.imag() - domain.start_coordinate.imag(); +} + +} // namespace fractal diff --git a/source/graphics/basic_display.cpp b/source/graphics/basic_display.cpp index 2287a02..c65c53d 100644 --- a/source/graphics/basic_display.cpp +++ b/source/graphics/basic_display.cpp @@ -1,13 +1,27 @@ #include "basic_display.hpp" +#include "graphics/color_conversions.hpp" + #include #include #include +#include + namespace fractal { -void BasicDisplay::set_pixel(std::size_t x_pos, std::size_t y_pos, uint8_t value) +void BasicDisplay::set_pixel(display_coordinate coordinate, uint16_t value) +{ + pixels_.at(coordinate.first).at(coordinate.second) = value; +} + +std::tuple interpolateColor( + float t, std::tuple color1, std::tuple color2 +) { - pixels_.at(x_pos).at(y_pos) = value; + int r = std::get<0>(color1) + t * (std::get<0>(color2) - std::get<0>(color1)); + int g = std::get<1>(color1) + t * (std::get<1>(color2) - std::get<1>(color1)); + int b = std::get<2>(color1) + t * (std::get<2>(color2) - std::get<2>(color1)); + return {r, g, b}; } void BasicDisplay::display_window() @@ -20,10 +34,12 @@ void BasicDisplay::display_window() image.create(WINDOW_WIDTH, WINDOW_HEIGHT); for (std::size_t x_pos = 0; x_pos < WINDOW_WIDTH; ++x_pos) { for (std::size_t y_pos = 0; y_pos < WINDOW_HEIGHT; ++y_pos) { - std::uint8_t pixel_value = pixels_.at(x_pos).at(y_pos); + std::uint16_t pixel_value = pixels_.at(x_pos).at(y_pos); + auto tuple = number_to_rgb(pixel_value); + image.setPixel( static_cast(x_pos), static_cast(y_pos), - sf::Color(pixel_value, pixel_value, pixel_value) + sf::Color(std::get<0>(tuple), std::get<1>(tuple), std::get<2>(tuple)) ); } } diff --git a/source/graphics/basic_display.hpp b/source/graphics/basic_display.hpp index 73c937f..0a99b71 100644 --- a/source/graphics/basic_display.hpp +++ b/source/graphics/basic_display.hpp @@ -1,6 +1,7 @@ #pragma once #include "config.hpp" +#include "coordinates.hpp" #include @@ -12,7 +13,7 @@ namespace fractal { class BasicDisplay { - std::array, WINDOW_WIDTH> pixels_{}; + std::array, WINDOW_WIDTH> pixels_{}; std::function on_resize_; public: @@ -22,7 +23,7 @@ class BasicDisplay { ) : on_resize_(std::move(on_resize)) {} - void set_pixel(std::size_t x_pos, std::size_t y_pos, std::uint8_t value); + void set_pixel(display_coordinate coordinate, uint16_t value); void display_window(); }; } // namespace fractal diff --git a/source/graphics/color_conversions.cpp b/source/graphics/color_conversions.cpp new file mode 100644 index 0000000..10c026d --- /dev/null +++ b/source/graphics/color_conversions.cpp @@ -0,0 +1,68 @@ +#include "color_conversions.hpp" + +#include + +namespace fractal { +std::tuple +hsv_to_rgb(float hue, float saturation, float value) +{ + float chroma = value * saturation; + float hue_prime = hue / 60.0f; + float uint16_termediate = + chroma * (1 - static_cast(std::fabs(std::fmod(hue_prime, 2) - 1))); + float red_temp = 0.0f; + float green_temp = 0.0f; + float blue_temp = 0.0f; + + if (0 <= hue_prime && hue_prime < 1) { + red_temp = chroma; + green_temp = uint16_termediate; + blue_temp = 0; + } + else if (1 <= hue_prime && hue_prime < 2) { + red_temp = uint16_termediate; + green_temp = chroma; + blue_temp = 0; + } + else if (2 <= hue_prime && hue_prime < 3) { + red_temp = 0; + green_temp = chroma; + blue_temp = uint16_termediate; + } + else if (3 <= hue_prime && hue_prime < 4) { + red_temp = 0; + green_temp = uint16_termediate; + blue_temp = chroma; + } + else if (4 <= hue_prime && hue_prime < 5) { + red_temp = uint16_termediate; + green_temp = 0; + blue_temp = chroma; + } + else if (5 <= hue_prime && hue_prime < 6) { + red_temp = chroma; + green_temp = 0; + blue_temp = uint16_termediate; + } + + float match_value = value - chroma; + float red = red_temp + match_value; + float green = green_temp + match_value; + float blue = blue_temp + match_value; + + auto red_int = static_cast(red * 255); + auto green_int = static_cast(green * 255); + auto blue_int = static_cast(blue * 255); + + return {red_int, green_int, blue_int}; +} + +std::tuple number_to_rgb(uint16_t number) +{ + float hue = (static_cast(number) / 65535.0f) * 360.0f; + float saturation = 1.0f; + float value = 1.0f; + + return hsv_to_rgb(hue, saturation, value); +} +} // namespace fractal diff --git a/source/graphics/color_conversions.hpp b/source/graphics/color_conversions.hpp new file mode 100644 index 0000000..d1da604 --- /dev/null +++ b/source/graphics/color_conversions.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +#include + +namespace fractal { +std::tuple +hsv_to_rgb(float hue, float saturation, float value); + +std::tuple number_to_rgb(uint16_t number); +} // namespace fractal diff --git a/source/graphics/display_to_complex.hpp b/source/graphics/display_to_complex.hpp new file mode 100644 index 0000000..9714e8a --- /dev/null +++ b/source/graphics/display_to_complex.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "coordinates.hpp" + +#include + +namespace fractal { +class DisplayToComplexCoordinates { + float real_scaling_factor_; + float imaginary_scaling_factor_; + complex_coordinate complex_domain_start_; + + static float real_scaling_factor( + const display_coordinate& display_top_right, + const complex_domain& complex_domain + ) + { + float real_d_size = real_domain_size(complex_domain); + return real_d_size / static_cast(display_top_right.first); + } + + static float imaginary_scaling_factor( + const display_coordinate& display_top_right, + const complex_domain& complex_domain + ) + { + float imaginary_d_size = imaginary_domain_size(complex_domain); + return imaginary_d_size / static_cast(display_top_right.second); + } + + static complex_coordinate to_complex(display_coordinate coordinate) + { + return { + static_cast(coordinate.first), static_cast(coordinate.second) + }; + } + +public: + DisplayToComplexCoordinates( + display_coordinate display_top_right, const complex_domain& complex_domain + ) : + real_scaling_factor_(real_scaling_factor(display_top_right, complex_domain)), + imaginary_scaling_factor_( + imaginary_scaling_factor(display_top_right, complex_domain) + ), + complex_domain_start_(complex_domain.start_coordinate) + {} + + complex_coordinate to_complex_projection(display_coordinate display_coord) + { + std::complex raw_complex_coord = to_complex(display_coord); + std::complex offset = { + real_scaling_factor_ * raw_complex_coord.real(), + imaginary_scaling_factor_ * raw_complex_coord.imag() + }; + return complex_domain_start_ + offset; + } +}; +} // namespace fractal diff --git a/source/main.cpp b/source/main.cpp index 14ce272..2a44372 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,31 +1,38 @@ +#include "config.hpp" +#include "coordinates.hpp" #include "graphics/basic_display.hpp" -#include "lib.hpp" +#include "graphics/display_to_complex.hpp" #include #include -#include -#include -#include +#include +#include -constexpr double DIVERGENCE_NORM = 4; -constexpr double X_DIM = 2; -constexpr double Y_DIM = 2; -constexpr int MAX_ITERATIONS = 50; +constexpr float DIVERGENCE_NORM = 4; +constexpr fractal::display_domain DISPLAY_DOMAIN{ + {0, 0 }, + {799, 599} +}; +constexpr fractal::complex_domain COMPLEX_DOMAIN{ + {-2, -1.5}, + {1, 1.5 } +}; +constexpr std::size_t MAX_ITERATIONS = 50; // https://en.wikipedia.org/wiki/Mandelbrot_set#Formal_definition -std::complex step(std::complex z_n, std::complex constant) +std::complex step(std::complex z_n, std::complex constant) { return z_n * z_n + constant; } -int compute_iterations( - std::complex z_0, std::complex constant, int max_iters +std::size_t compute_iterations( + std::complex z_0, std::complex constant, std::size_t max_iters ) { - int iterations = 0; - std::complex z_n = z_0; + std::size_t iterations = 0; + std::complex z_n = z_0; while (iterations < max_iters && std::norm(z_n) < DIVERGENCE_NORM) { z_n = step(z_n, constant); @@ -35,70 +42,38 @@ int compute_iterations( return iterations; } -void display_line() +void display_mandelbrot() { fractal::BasicDisplay display; - for (std::size_t i = 0; i < 100; i++) { - display.set_pixel(100, 100 + i, 255); - } - display.display_window(); -} - -void display_julia(std::size_t width, std::size_t height, std::complex constant) -{ - fractal::BasicDisplay display; - - auto x_step = X_DIM * 2 / static_cast(width); - auto y_step = Y_DIM * 2 / static_cast(height); - - for (std::size_t j = 0; j < height; ++j) { - for (std::size_t i = 0; i < width; ++i) { - double x = -X_DIM + i * x_step; - double y = -Y_DIM + j * y_step; - - auto iterations = compute_iterations({x, y}, constant, MAX_ITERATIONS); - display.set_pixel( - i, j, - static_cast(iterations / static_cast(MAX_ITERATIONS) * 255) - ); - } - } + fractal::DisplayToComplexCoordinates to_complex{ + DISPLAY_DOMAIN.end_coordinate, COMPLEX_DOMAIN + }; - display.display_window(); -} + auto process_coordinate = [&](const fractal::display_coordinate& coord) { + auto complex_coord = to_complex.to_complex_projection(coord); -void display_mandelbrot( - std::size_t width, std::size_t height, std::complex constant -) -{ - fractal::BasicDisplay display; - - auto x_step = X_DIM * 2 / static_cast(width); - auto y_step = Y_DIM * 2 / static_cast(height); + // Compute the number of iterations + auto iterations = compute_iterations({0, 0}, complex_coord, MAX_ITERATIONS); - for (std::size_t j = 0; j < height; ++j) { - for (std::size_t i = 0; i < width; ++i) { - // Compute complex coordinates from pixel index - double x = -X_DIM + i * x_step; - double y = -Y_DIM + j * y_step; + display.set_pixel( + coord, + static_cast( + (static_cast(iterations) / static_cast(MAX_ITERATIONS)) + * std::numeric_limits::max() + ) + ); + }; - // Compute the number of iterations - auto iterations = compute_iterations(constant, {x, y}, MAX_ITERATIONS); - - display.set_pixel( - i, j, - static_cast(iterations / static_cast(MAX_ITERATIONS) * 255) - ); - } - } + std::for_each(DISPLAY_DOMAIN.begin(), DISPLAY_DOMAIN.end(), process_coordinate); display.display_window(); } -int main(int argc, char** argv) +int main() { - auto const lib = library{}; + // TODO: actually use this + /* argparse::ArgumentParser program(lib.name); program.add_argument("width") @@ -121,8 +96,9 @@ int main(int argc, char** argv) auto width = program.get("width"); auto height = program.get("height"); + */ - display_mandelbrot(width, height, {0, 0}); + display_mandelbrot(); return 0; }