From a93410a3211c7ec6aea6cdc3983cb4048e36ba00 Mon Sep 17 00:00:00 2001 From: Andrea Bontempi Date: Tue, 20 Feb 2018 15:21:07 +0100 Subject: [PATCH] First commit --- .gitignore | 4 + CMakeLists.txt | 19 ++++ Fractal.cpp | 98 +++++++++++++++++++++ Fractal.hpp | 123 ++++++++++++++++++++++++++ README.md | 20 ++++- functions.hpp | 57 ++++++++++++ main.cpp | 234 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Fractal.cpp create mode 100644 Fractal.hpp create mode 100644 functions.hpp create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9b46f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.kdev4 +*/.kdev4/* +*/build/* +*/nbproject/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5ed25f8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.6) +project(mandelbrot) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O2 -ffast-math -fopenmp") +set(EXECUTABLE_NAME "mandelbrot") + +add_executable(${EXECUTABLE_NAME} main.cpp functions.hpp Fractal.hpp Fractal.cpp) + +link_directories(/usr/local/lib) + +set(BOOST_LIBS program_options) +find_package(Boost COMPONENTS ${BOOST_LIBS} REQUIRED) + +target_link_libraries (${EXECUTABLE_NAME} ${Boost_LIBRARIES}) +target_link_libraries (${EXECUTABLE_NAME} sfml-window) +target_link_libraries (${EXECUTABLE_NAME} sfml-system) +target_link_libraries (${EXECUTABLE_NAME} sfml-graphics) + +install(TARGETS ${EXECUTABLE_NAME} RUNTIME DESTINATION bin) diff --git a/Fractal.cpp b/Fractal.cpp new file mode 100644 index 0000000..5ec3d69 --- /dev/null +++ b/Fractal.cpp @@ -0,0 +1,98 @@ +#include "Fractal.hpp" +#include +#include +#include + +Fractal::Fractal(int image_width, int image_height, Domain domain, std::function (std::complex, std::complex)> fractal_function, std::function render_function) : domain(domain) { + this->fractal_function = fractal_function; + this->render_function = render_function; + this->image_height = image_height; + this->image_width = image_width; + this->frame.create(image_width, image_height, sf::Color(0, 0, 0)); + this->hasChanged = true; +} + +void Fractal::setFractalFunction(std::function (std::complex, std::complex)> fractal_function) { + this->fractal_function = fractal_function; + this->hasChanged = true; +} + +void Fractal::setRenderFunction(std::function render_function) { + this->render_function = render_function; + this->hasChanged = true; +} + +int Fractal::compute_point(std::complex point, int max_iterations) { + + std::complex z(0); + int iter = 0; + + while (abs(z) < 2.0 && iter < max_iterations) { + z = this->fractal_function(z, point); + iter++; + } + + return iter; + +} + +std::complex Fractal::scale_point(std::complex point) { + std::complex aux(point.real() / (double)this->image_width * this->domain.width() + this->domain.x_min, point.imag() / (double)this->image_height * this->domain.height() + domain.y_min); + return aux; +} + +sf::Image Fractal::getFrame(){ + + if (this->hasChanged) { + + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + + int max_iterations = compute_max_iterations(this->image_width, this->domain.width()); + + #pragma omp parallel for + for(int y = 0; y < this->image_height; y++) { + for(int x = 0; x < this->image_width; x++) { + std::complex point(x, y); + point = scale_point(point); + int iterations = compute_point(point, max_iterations); + sf::Color color = this->render_function(iterations, max_iterations); + this->frame.setPixel(x, y, color); + } + } + + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + + auto t = std::chrono::duration_cast(end - start).count(); + std::cerr << "Frame> Time: " << t << "ms, Max iterations: " << max_iterations << std::endl; + + this->hasChanged = false; + } + + return this->frame; + +} + +void Fractal::moveTo(int x, int y) { + std::complex point(x, y); + point = this->scale_point(point); + this->domain.centralize(point); + this->hasChanged = true; +} + +void Fractal::moveBy(double x, double y) { + this->domain.move(x, y); + this->hasChanged = true; +} + +void Fractal::zoom(double factor, bool invert) { + this->domain.zoom((invert)?1/factor:factor); + this->hasChanged = true; +} + +int Fractal::compute_max_iterations(int window_width, double domain_width) { + int max = 50 * std::pow(std::log10(window_width / domain_width), 1.25); + return (max > 0)? max : 0; +} + + + diff --git a/Fractal.hpp b/Fractal.hpp new file mode 100644 index 0000000..225031c --- /dev/null +++ b/Fractal.hpp @@ -0,0 +1,123 @@ +#ifndef LIBFRACTAL_H +#define LIBFRACTAL_H + +#include +#include +#include +#include "functions.hpp" + +struct Domain { + + double x_min, x_max, y_min, y_max; + + Domain(double x_min, double x_max, double y_min, double y_max) : x_min(x_min), x_max(x_max), y_min(y_min), y_max(y_max) {} + + double width() { + return x_max - x_min; + } + + double height() { + return y_max - y_min; + } + + double size() { + return width() * height(); + } + + double ratio() { + return width() / height(); + } + + void setRatio(double ratio) { + double factor = (width() / ratio) / 2; + double w = (y_max + y_min) / 2; + y_min = w - factor; + y_max = w + factor; + } + + void zoom(double factor) { + + double x_frac = width() / 2; + double y_frac = height() / 2; + + double x_factor = x_frac - (x_frac * factor); + double y_factor = y_frac - (y_frac * factor); + + x_min -= x_factor; + y_min -= y_factor; + + x_max += x_factor; + y_max += y_factor; + + } + + void move(double x, double y) { + + double deltax = x * (x_max - x_min); + x_min += deltax; + x_max += deltax; + + double deltay = y * (y_max - y_min); + y_min += deltay; + y_max += deltay; + + } + + void centralize(std::complex point) { + + double x_frac = width() / 2; + double y_frac = height() / 2; + + x_min = point.real() - x_frac; + y_min = point.imag() - y_frac; + + x_max = point.real() + x_frac; + y_max = point.imag() + y_frac; + + } + +}; + +class Fractal { + +private: + + int image_width, image_height; + std::function(std::complex, std::complex)> fractal_function; + std::function render_function; + sf::Image frame; + bool hasChanged; + Domain domain; + + std::complex scale_point(std::complex point); + int compute_point(std::complex point, int max_iterations); + int compute_max_iterations(int window_width, double domain_width); + +public: + + Fractal(int image_width, + int image_height, + Domain domain, + std::function(std::complex, std::complex)> fractal_function = fractal_mandelbrot, + std::function render_function = render_smooth + ); + + void setFractalFunction(std::function(std::complex, std::complex)> fractal_function); + + void setRenderFunction(std::function render_function); + + void moveTo(int x, int y); + + void moveBy(double x, double y); + + void zoom(double factor, bool invert = false); + + sf::Image getFrame(); + + Domain& getDomain() { + return this->domain; + } + +}; + +#endif //LIBFRACTAL_H diff --git a/README.md b/README.md index e38bb01..06f1170 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ # OpenMandelbrot -Open Mandelbrot + +Build and install +================= + +Installing the dependencies +--------------------------- +- cmake +- libboost +- SFML + +Compile! +-------- +```sh +git clone https://github.com/Andreabont/OpenMandelbrot.git +mkdir build +cd build +cmake .. +make +make install diff --git a/functions.hpp b/functions.hpp new file mode 100644 index 0000000..ac24e62 --- /dev/null +++ b/functions.hpp @@ -0,0 +1,57 @@ +#ifndef LIBFUNCTIONS_H +#define LIBFUNCTIONS_H + +#include +#include + +inline std::complex fractal_mandelbrot(std::complex z, std::complex c) { + return (z * z) + c; +} + +inline std::complex fractal_triple_mandelbrot(std::complex z, std::complex c) { + return (z * z * z) + c; +} + +inline std::complex fractal_quadruple_mandelbrot(std::complex z, std::complex c) { + return (z * z * z * z) + c; +} + +inline std::complex fractal_quintuple_mandelbrot(std::complex z, std::complex c) { + return (z * z * z * z * z) + c; +} + +inline sf::Color render_linear(int iteration_number, int max_iterations) { + int N = 256; // colors per element + int N3 = N * N * N; + double t = (double)iteration_number/(double)max_iterations; + // expand n on the 0 .. 256^3 interval (integers) + iteration_number = (int)(t * (double) N3); + + int b = iteration_number/(N * N); + int nn = iteration_number - b * N * N; + int r = nn/N; + int g = nn - r * N; + return sf::Color(r, g, b); +} + +inline sf::Color render_smooth(int iteration_number, int max_iterations) { + double t = (double)iteration_number/(double)max_iterations; + // Use smooth polynomials for r, g, b + int r = (int)(9*(1-t)*t*t*t*255); + int g = (int)(15*(1-t)*(1-t)*t*t*255); + int b = (int)(8.5*(1-t)*(1-t)*(1-t)*t*255); + return sf::Color(r, g, b); +} + +inline sf::Color render_black_and_white(int iteration_number, int max_iterations) { + double t = (double)iteration_number/(double)max_iterations; + return (t != 1)? sf::Color(0, 0, 0) : sf::Color(255, 255, 255); +} + +inline sf::Color render_green_gradient(int iteration_number, int max_iterations) { + double t = (double)iteration_number/(double)max_iterations; + if(t == 1) return sf::Color(0, 0, 0); + return (t > 0.5)? sf::Color(t*255, 255, t*255) : sf::Color(0, t*255, 0); +} + +#endif //LIBFUNCTIONS_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0a61694 --- /dev/null +++ b/main.cpp @@ -0,0 +1,234 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "functions.hpp" +#include "Fractal.hpp" + +const double DOMAIN_X_MIN = -2.0; +const double DOMAIN_X_MAX = 2.0; +const double DOMAIN_Y_MIN = -1.7; +const double DOMAIN_Y_MAX = 1.7; +const int IMAGE_WIDTH = 1920; +const int IMAGE_HEIGHT = 1080; +const int FRAMERATE = 20; + +int main(int argc, char **argv) { + + boost::program_options::options_description po ("Mandelbrot"); + + po.add_options() + ("help", "Help") + ("noratio", "Don't force the window ratio on domain") + ("framerate", boost::program_options::value()->default_value(FRAMERATE), "Framerate") + ("width", boost::program_options::value()->default_value(IMAGE_WIDTH), "Window width") + ("height", boost::program_options::value()->default_value(IMAGE_HEIGHT), "Window height") + ("domXmin", boost::program_options::value()->default_value(DOMAIN_X_MIN), "Domain X (Min)") + ("domXmax", boost::program_options::value()->default_value(DOMAIN_X_MAX), "Domain X (Max)") + ("domYmin", boost::program_options::value()->default_value(DOMAIN_Y_MIN), "Domain Y (Min)") + ("domYmax", boost::program_options::value()->default_value(DOMAIN_Y_MAX), "Domain Y (Max)") + ("filename", boost::program_options::value(), "Filename") + ; + + boost::program_options::variables_map vm; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, po), vm); + boost::program_options::notify(vm); + + if (vm.count("help")) { + std::cout << po << std::endl; + return 0; + } + + int screen_width = vm["width"].as(); + int screen_height = vm["height"].as(); + + double windowRatio = (double)screen_width / (double)screen_height; + + Fractal mandelbrot( + screen_width, + screen_height, + Domain( + vm["domXmin"].as(), + vm["domXmax"].as(), + vm["domYmin"].as(), + vm["domYmax"].as() + ) + ); + + if(!vm.count("noratio")){ + std::cerr << "Force ratio " << windowRatio << std::endl; + mandelbrot.getDomain().setRatio(windowRatio); + } + + if(vm.count("filename")) { + mandelbrot.getFrame().saveToFile(vm["filename"].as()); + std::cerr << "Screenshot stored in file " << vm["filename"].as() << std::endl; + return 0; + } + + sf::RenderWindow window(sf::VideoMode(screen_width, screen_height), "Mandelbrot"); + window.setFramerateLimit(vm["framerate"].as()); + + sf::Texture texture; + sf::Sprite sprite; + + while (window.isOpen()) { + + sf::Event event; + while (window.pollEvent(event)) { + + switch (event.type) { + + case sf::Event::Closed: + window.close(); + break; + + case sf::Event::MouseButtonReleased: { + + sf::Vector2i mousePosition = sf::Mouse::getPosition(window); + + switch (event.mouseButton.button) { + + case sf::Mouse::Left: + mandelbrot.moveTo(mousePosition.x, mousePosition.y); + break; + + case sf::Mouse::Right: + mandelbrot.moveTo(mousePosition.x, mousePosition.y); + break; + + default: + break; + + } + + } + break; + + case sf::Event::MouseWheelMoved: + + if(event.mouseWheel.delta > 0) { + mandelbrot.zoom(1.05); + } else { + mandelbrot.zoom(1.05, true); + } + + break; + + + case sf::Event::KeyPressed: + switch (event.key.code) { + + case sf::Keyboard::Escape: + window.close(); + break; + + case sf::Keyboard::Key::Add: + mandelbrot.zoom(1.5); + break; + + case sf::Keyboard::Key::Subtract: + mandelbrot.zoom(1.5, true); + break; + + case sf::Keyboard::Key::Right: + mandelbrot.moveBy(0.01, 0); + break; + + case sf::Keyboard::Key::Left: + mandelbrot.moveBy(-0.01, 0); + break; + + case sf::Keyboard::Key::Up: + mandelbrot.moveBy(0, -0.01); + break; + + case sf::Keyboard::Key::Down: + mandelbrot.moveBy(0, 0.01); + break; + + case sf::Keyboard::Key::S: { + std::time_t t = std::time(nullptr); + std::tm tm = *std::localtime(&t); + std::stringstream filename; + filename << "Screenshot_" << std::put_time(&tm, "%Y%m%d%H%M%S") << ".jpeg"; + mandelbrot.getFrame().saveToFile(filename.str()); + std::cerr << "Screenshot stored in file " << filename.str() << std::endl; + } + break; + + case sf::Keyboard::Key::D: { + std::cerr << std::scientific << std::setprecision(10) << "domXmin=" << mandelbrot.getDomain().x_min << ", domXmax=" << mandelbrot.getDomain().x_max << ", domYmin=" << mandelbrot.getDomain().y_min << ", domYmax=" << mandelbrot.getDomain().y_max << std::endl; + } + break; + + // Renderer selection + case sf::Keyboard::Key::Q: + std::cerr << "Selected smooth renderer" << std::endl; + mandelbrot.setRenderFunction(render_smooth); + break; + + case sf::Keyboard::Key::W: + std::cerr << "Selected linear renderer" << std::endl; + mandelbrot.setRenderFunction(render_linear); + break; + + case sf::Keyboard::Key::E: + std::cerr << "Selected black&white renderer" << std::endl; + mandelbrot.setRenderFunction(render_black_and_white); + break; + + case sf::Keyboard::Key::R: + std::cerr << "Selected green gradient renderer" << std::endl; + mandelbrot.setRenderFunction(render_green_gradient); + break; + + // Fractal selection + case sf::Keyboard::Key::Num1: + std::cerr << "Selected mandelbrot algorithm" << std::endl; + mandelbrot.setFractalFunction(fractal_mandelbrot); + break; + + case sf::Keyboard::Key::Num2: + std::cerr << "Selected triple mandelbrot algorithm" << std::endl; + mandelbrot.setFractalFunction(fractal_triple_mandelbrot); + break; + + case sf::Keyboard::Key::Num3: + std::cerr << "Selected quadruple mandelbrot algorithm" << std::endl; + mandelbrot.setFractalFunction(fractal_quadruple_mandelbrot); + break; + + case sf::Keyboard::Key::Num4: + std::cerr << "Selected quintuple mandelbrot algorithm" << std::endl; + mandelbrot.setFractalFunction(fractal_quintuple_mandelbrot); + break; + + default: + break; + + } + + default: + break; + + } + } + + window.clear(sf::Color::Black); + texture.loadFromImage(mandelbrot.getFrame()); + sprite.setTexture(texture); + window.draw(sprite); + window.display(); + + } + + return 0; + +}