Demos: Add Mandelbrot demo

This adds a very rudimentary Mandelbrot viewer. It supports zooming
and pretty much nothing else. Not even color smoothing or super
sampling.
This commit is contained in:
Gunnar Beutner 2021-05-17 10:32:13 +02:00 committed by Andreas Kling
parent 7e799529b9
commit 51db8085f8
Notes: sideshowbarker 2024-07-18 17:57:39 +09:00
6 changed files with 230 additions and 0 deletions

View file

@ -0,0 +1,4 @@
[App]
Name=Mandelbrot
Executable=/bin/Mandelbrot
Category=Demos

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -5,6 +5,7 @@ add_subdirectory(Fire)
add_subdirectory(GLTeapot)
add_subdirectory(LibGfxDemo)
add_subdirectory(LibGfxScaleDemo)
add_subdirectory(Mandelbrot)
add_subdirectory(Mouse)
add_subdirectory(Screensaver)
add_subdirectory(Starfield)

View file

@ -0,0 +1,6 @@
set(SOURCES
Mandelbrot.cpp
)
serenity_app(Mandelbrot ICON app-mandelbrot)
target_link_libraries(Mandelbrot LibGUI LibCore LibGfx)

View file

@ -0,0 +1,219 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Gunnar Beutner <gbeutner@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/Icon.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Menubar.h>
#include <LibGUI/Painter.h>
#include <LibGUI/Widget.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <unistd.h>
class MandelbrotSet {
public:
MandelbrotSet()
{
set_view();
}
void reset()
{
set_view();
calculate();
}
void resize(Gfx::IntSize const& size)
{
m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRx8888, size);
calculate();
}
void zoom(Gfx::IntRect const& rect)
{
set_view(
rect.left() * (m_x_end - m_x_start) / m_bitmap->width() + m_x_start,
rect.right() * (m_x_end - m_x_start) / m_bitmap->width() + m_x_start,
rect.top() * (m_y_end - m_y_start) / m_bitmap->height() + m_y_start,
rect.bottom() * (m_y_end - m_y_start) / m_bitmap->height() + m_y_start);
calculate();
}
i32 mandelbrot(double px, double py, i32 max_iterations)
{
// Based on https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set
const double x0 = px * (m_x_end - m_x_start) / m_bitmap->width() + m_x_start;
const double y0 = py * (m_y_end - m_y_start) / m_bitmap->height() + m_y_start;
double x = 0;
double y = 0;
i32 iteration = 0;
double x2 = 0;
double y2 = 0;
while (x2 + y2 <= 4 && iteration < max_iterations) {
y = 2 * x * y + y0;
x = x2 - y2 + x0;
x2 = x * x;
y2 = y * y;
iteration++;
}
return iteration;
}
void calculate_pixel(int px, int py, int max_iterations)
{
auto iterations = mandelbrot(px, py, max_iterations);
double hue = (double)iterations * 360.0 / (double)max_iterations;
if (hue == 360.0)
hue = 0.0;
double saturation = 1.0;
double value = iterations < max_iterations ? 1.0 : 0;
m_bitmap->set_pixel(px, py, Color::from_hsv(hue, saturation, value));
}
void calculate(int max_iterations = 100)
{
if (!m_bitmap)
return;
for (int py = 0; py < m_bitmap->height(); py++)
for (int px = 0; px < m_bitmap->width(); px++)
calculate_pixel(px, py, max_iterations);
}
Gfx::Bitmap const& bitmap() const
{
return *m_bitmap;
}
private:
double m_x_start { 0 };
double m_x_end { 0 };
double m_y_start { 0 };
double m_y_end { 0 };
RefPtr<Gfx::Bitmap> m_bitmap;
void set_view(double x_start = -2.5, double x_end = 1.0, double y_start = -1.0, double y_end = 1.0)
{
m_x_start = x_start;
m_x_end = x_end;
m_y_start = y_start;
m_y_end = y_end;
}
};
class Mandelbrot : public GUI::Widget {
C_OBJECT(Mandelbrot)
private:
virtual void paint_event(GUI::PaintEvent&) override;
virtual void mousedown_event(GUI::MouseEvent& event) override;
virtual void mousemove_event(GUI::MouseEvent& event) override;
virtual void mouseup_event(GUI::MouseEvent& event) override;
virtual void resize_event(GUI::ResizeEvent& event) override;
bool m_dragging { false };
Gfx::IntPoint m_selection_start;
Gfx::IntPoint m_selection_end;
MandelbrotSet m_set;
};
void Mandelbrot::paint_event(GUI::PaintEvent& event)
{
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
painter.draw_scaled_bitmap(rect(), m_set.bitmap(), m_set.bitmap().rect());
if (m_dragging)
painter.draw_rect(Gfx::IntRect::from_two_points(m_selection_start, m_selection_end), Color::Blue);
}
void Mandelbrot::mousedown_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Left) {
if (!m_dragging) {
m_selection_start = event.position();
m_selection_end = event.position();
m_dragging = true;
update();
}
}
return GUI::Widget::mousedown_event(event);
}
void Mandelbrot::mousemove_event(GUI::MouseEvent& event)
{
if (m_dragging) {
m_selection_end = event.position();
update();
}
return GUI::Widget::mousemove_event(event);
}
void Mandelbrot::mouseup_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Left) {
auto selection = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end);
if (selection.width() > 0 && selection.height() > 0)
m_set.zoom(selection);
m_dragging = false;
update();
} else if (event.button() == GUI::MouseButton::Right) {
m_set.reset();
update();
}
return GUI::Widget::mouseup_event(event);
}
void Mandelbrot::resize_event(GUI::ResizeEvent& event)
{
m_set.resize(event.size());
}
int main(int argc, char** argv)
{
auto app = GUI::Application::construct(argc, argv);
if (pledge("stdio recvfd sendfd rpath", nullptr) < 0) {
perror("pledge");
return 1;
}
if (unveil("/res", "r") < 0) {
perror("unveil");
return 1;
}
if (unveil(nullptr, nullptr) < 0) {
perror("unveil");
return 1;
}
auto window = GUI::Window::construct();
window->set_double_buffering_enabled(false);
window->set_title("Mandelbrot");
window->set_minimum_size(320, 240);
window->resize(window->minimum_size() * 2);
auto menubar = GUI::Menubar::construct();
auto& file_menu = menubar->add_menu("&File");
file_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
window->set_menubar(move(menubar));
window->set_main_widget<Mandelbrot>();
window->show();
auto app_icon = GUI::Icon::default_icon("app-mandelbrot");
window->set_icon(app_icon.bitmap_for_size(16));
return app->exec();
}