As an exercise to learn how OpenGL and image creation worked, as well as to satisfy a curiosity I've developed for Chaos Theory, I decided to create a mandelbrot fractal drawer in C++, which can either draw to an OpenGL context or to a PNG image. I am a beginner at C++, and would much appreciate any constructive feedback. Also, please ridicule me on how I create my images, and how I can make them look better as well.
main.cpp
#include <complex>
#include <iostream>
#include <memory>
#include "Window.h"
#include "Draw_Buffer.h"
#include "Image_Buffer.h"
#include "Buffer_Base.h"
static constexpr float COMPLEX_INCREMENT = 0.005f;
template <typename T>
int iterations_till_escape(const std::complex<T> &c, int max_iterations) {
std::complex<T> z(0, 0);
for (int iter = 0; iter < max_iterations; ++iter) {
z = (z * z) + c;
if (std::abs(z) > 2) {
return iter;
}
}
return -1;
}
template <typename T>
RGB calculate_pixel(const std::complex<T> &c) {
int iterations = iterations_till_escape(c, 255);
if (iterations == -1) {
return RGB{0, 0, 0};
}
else {
GLubyte blue = iterations * 5;
return RGB{0, 0, blue};
}
}
int main() {
// Declare window object to represent the complex plane
Window<float> complex_plane(-2.2, 1.2, -1.7, 1.7);
// Declare window object to represent the OpenGL window
Window<int> window(0, ((std::abs(complex_plane.get_x_min()) + complex_plane.get_x_max()) / COMPLEX_INCREMENT),
0, ((std::abs(complex_plane.get_y_min()) + complex_plane.get_y_max()) / COMPLEX_INCREMENT));
std::unique_ptr<Buffer_Base<RGB>> pixel_buffer;
std::cout << "Running mandelbrot-fractal-drawer...\nWould you like to draw fractal to a window or an image?\n"
<< "Type W for window or I for image" << std::endl;
char response;
while (!(std::cin >> response))
;
if (response == 'W' || response == 'w') {
// Initialise pointer to a draw buffer
pixel_buffer.reset(new Draw_Buffer(&window, "vertex_shader.glsl", "fragment_shader.glsl"));
}
else if (response == 'I' || response == 'i') {
std::cout << "\nPlease enter the location to where you want the fractal to be drawn" << std::endl;
std::string src;
while (!(std::cin >> src))
;
// Initialise pointer to an image buffer
pixel_buffer.reset(new Image_Buffer(&window, src));
}
std::complex<float> pixel_iterator(complex_plane.get_x_min(), complex_plane.get_y_max());
while (pixel_iterator.imag() > complex_plane.get_y_min()) {
while (pixel_iterator.real() < complex_plane.get_x_max()) {
// Calculate the colour of the pixel using the mandelbrot function
*pixel_buffer << calculate_pixel(pixel_iterator);
// Increment
pixel_iterator.real(pixel_iterator.real() + COMPLEX_INCREMENT);
}
// Increment
pixel_iterator.imag(pixel_iterator.imag() - (COMPLEX_INCREMENT));
// Reset real iterator
pixel_iterator.real(complex_plane.get_x_min());
}
pixel_buffer->flush();
std::cout << "Closing down..." << std::endl;
}
Buffer_Base.h
#ifndef MANDELBROT_FRACTAL_DRAWER_BUFFER_BASE_H
#define MANDELBROT_FRACTAL_DRAWER_BUFFER_BASE_H
#include <vector>
#include <memory>
#include "Window.h"
template <typename T>
class Buffer_Base {
protected:
// The buffer itself
std::vector<T> buffer;
// Iterator to where in the buffer the appending is happening
typename std::vector<T>::iterator pos_iter;
// Represents the size of the window to which the buffer is writing
std::unique_ptr<Window<int>> window;
public:
Buffer_Base(Window<int> *win) :
buffer(win->size()), window(win) { pos_iter = buffer.begin(); }
virtual ~Buffer_Base() { };
virtual void flush() = 0;
Buffer_Base<T> &operator<<(T &&val) {
if (pos_iter != buffer.end()) {
*(pos_iter) = std::move(val);
++pos_iter;
}
return *this;
}
};
#endif //MANDELBROT_FRACTAL_DRAWER_BUFFER_BASE_H
RGB.h
#ifndef MANDELBROT_FRACTAL_DRAWER_RGB_H
#define MANDELBROT_FRACTAL_DRAWER_RGB_H
struct RGB {
unsigned char r;
unsigned char g;
unsigned char b;
};
#endif //MANDELBROT_FRACTAL_DRAWER_RGB_H
Get_GL.h
#ifndef MANDELBROT_FRACTAL_DRAWER_GET_GL_H
#define MANDELBROT_FRACTAL_DRAWER_GET_GL_H
#ifndef __APPLE__
#include <GL/gl.h>
#else
#include <OpenGL/gl.h>
#endif
#endif //MANDELBROT_FRACTAL_DRAWER_GET_GL_H
Window.h
#ifndef MANDELBROT_FRACTAL_DRAWER_WINDOW_H
#define MANDELBROT_FRACTAL_DRAWER_WINDOW_H
#include <complex>
template<typename T>
class Window {
T _x_min, _x_max, _y_min, _y_max;
public:
Window(T x_min, T x_max, T y_min, T y_max) : _x_min(x_min), _x_max(x_max), _y_min(y_min), _y_max(y_max) { }
// Util functions
T width() const {
return (_x_max - _x_min);
}
T height() const {
return (_y_max - _y_min);
}
T size() const {
return (height() * width());
}
// Setters and getters
T get_y_min() const {
return _y_min;
}
T get_y_max() const {
return _y_max;
}
T get_x_min() const {
return _x_min;
}
T get_x_max() const {
return _x_max;
}
void set_y_min(T _y_min) {
Window::_y_min = _y_min;
}
void set_y_max(T _y_max) {
Window::_y_max = _y_max;
}
void set_x_min(T _x_min) {
Window::_x_min = _x_min;
}
void set_x_max(T _x_max) {
Window::_x_max = _x_max;
}
// Reset values
void reset(T x_min, T x_max, T y_min, T y_max) {
_y_min(y_min);
_y_max(y_max);
_x_min(x_min);
_x_max(x_max);
}
};
Image_Buffer.h
#ifndef MANDELBROT_FRACTAL_DRAWER_IMAGE_BUFFER_H
#define MANDELBROT_FRACTAL_DRAWER_IMAGE_BUFFER_H
#include <string>
#include "Buffer_Base.h"
#include "RGB.h"
#include <png.h>
#define PNG_DEBUG 3
class Image_Buffer : public Buffer_Base<RGB> {
// Location to write image to
std::string file_src;
// PNG data
png_structp png_ptr;
png_infop info_ptr;
png_bytep row;
// File pointer
FILE *fp;
public:
Image_Buffer(Window<int> *, const std::string &);
~Image_Buffer();
virtual void flush() override;
};
#endif //MANDELBROT_FRACTAL_DRAWER_IMAGE_BUFFER_H
Image_Buffer.cpp
#include "Image_Buffer.h"
#include <png.h>
#include <fstream>
#include <stdexcept>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
Image_Buffer::Image_Buffer(Window<int> *win, const std::string &src) : Buffer_Base(win), file_src(src) { }
void Image_Buffer::flush() {
fp = fopen(file_src.c_str(), "wb");
if (!fp) {
std::ostringstream ss;
ss << "error: Unable to open file " << file_src << " for writing";
throw std::runtime_error(ss.str());
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
throw std::runtime_error("error: png_create_write_struct failed");
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
throw std::runtime_error("error: png_create_info_struct failed");
}
if (setjmp(png_jmpbuf(png_ptr))) {
throw std::runtime_error("Error during init_io");
}
png_init_io(png_ptr, fp);
// Write header (8 bit colour depth)
png_set_IHDR(png_ptr, info_ptr, window->width(), window->height(),
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_text title_text;
title_text.compression = PNG_TEXT_COMPRESSION_NONE;
title_text.key = "Title";
title_text.text = (char *)file_src.c_str();
png_set_text(png_ptr, info_ptr, &title_text, 1);
png_write_info(png_ptr, info_ptr);
std::vector<RGB> row(3 * window->width());
auto first = buffer.begin();
auto last = buffer.begin() + window->width();
while (first != buffer.end()) {
std::copy(first, last, row.begin());
png_write_row(png_ptr, (png_bytep)&row[0]);
first = last;
last += window->width();
}
png_write_end(png_ptr, NULL);
png_init_io(png_ptr, fp);
}
Image_Buffer::~Image_Buffer() {
if (fp) fclose(fp);
if (info_ptr) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
if (png_ptr) png_destroy_write_struct(&png_ptr, static_cast<png_infopp>(NULL));
}
Draw_Buffer.h
#ifndef MANDELBROT_FRACTAL_DRAWER_DRAW_BUFFER_H
#define MANDELBROT_FRACTAL_DRAWER_DRAW_BUFFER_H
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Get_GL.h"
#include "Buffer_Base.h"
#include "RGB.h"
class Draw_Buffer : public Buffer_Base<RGB> {
// Pointer to glfw screen
GLFWwindow *screen;
// Texture where pixels are written to
GLuint mandelbrot_tex;
// GLSL Shader program
GLuint shader_prog;
// Vertex shader
GLuint vertex_shader;
// Fragment shader
GLuint frag_shader;
// VAO
GLuint vao;
// Element buffer object
GLuint ebo;
// Vertex buffer object
GLuint vbo;
// Util function to compile shader
static void compile_shader(GLuint &shader, const std::string &src);
public:
Draw_Buffer(Window<int> *, const std::string &, const std::string &);
virtual ~Draw_Buffer() override;
void make_current() {
glfwMakeContextCurrent(screen);
}
virtual void flush() override;
};
#endif //MANDELBROT_FRACTAL_DRAWER_DRAW_BUFFER_H
Draw_Buffer.cpp
#include "Draw_Buffer.h"
#include "Buffer_Base.h"
#include <memory>
#include <algorithm>
#include <stdexcept>
#include <vector>
#include <sstream>
#include <fstream>
#include <string>
#include <iostream>
// Util function to compile a shader from source
void Draw_Buffer::compile_shader(GLuint &shader, const std::string &src) {
std::ifstream is(src);
std::string code;
std::string temp_str;
while (std::getline(is, temp_str)) {
code += temp_str + '\n';
}
const char *c_code = code.c_str();
glShaderSource(shader, 1, &c_code, NULL);
glCompileShader(shader);
}
Draw_Buffer::Draw_Buffer(Window<int> *win, const std::string &vertex_shader_src, const std::string &frag_shader_src) :
Buffer_Base(win) {
// Initialise GLFW
if (!glfwInit()) {
throw std::runtime_error("error: GLFW unable to initialise");
}
// Set up the window
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
screen = (glfwCreateWindow(win->width(), win->height(), "Mandelbrot Fractal", nullptr, nullptr));
make_current();
// Initialise glew
glewExperimental = GL_TRUE;
GLenum glewinit = glewInit();
if (glewinit != GLEW_OK) {
std::ostringstream ss;
ss << "error: Glew unable to initialise" << glewinit;
throw std::runtime_error(ss.str());
}
// Clear
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
// Generate shaders
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
GLint compile_status;
compile_shader(vertex_shader, vertex_shader_src);
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &compile_status);
if (compile_status != GL_TRUE) {
char buffer[512];
glGetShaderInfoLog(vertex_shader, 512, NULL, buffer);
throw std::runtime_error(buffer);
}
compile_shader(frag_shader, frag_shader_src);
glGetShaderiv(frag_shader, GL_COMPILE_STATUS, &compile_status);
if (compile_status != GL_TRUE) {
char buffer[512];
glGetShaderInfoLog(frag_shader, 512, NULL, buffer);
throw std::runtime_error(buffer);
}
// Put shaders into shader program
shader_prog = glCreateProgram();
glAttachShader(shader_prog, vertex_shader);
glAttachShader(shader_prog, frag_shader);
glBindFragDataLocation(shader_prog, 0, "outColor");
glLinkProgram(shader_prog);
glUseProgram(shader_prog);
// Create VAO
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// Create vertex and element buffers
const static GLfloat vertices[] = {
// Position Tex-coords
-1.0f, 1.0f, 0.0f, 0.0f, // Top-left
1.0f, 1.0f, 1.0f, 0.0f, // Top-right
1.0f, -1.0f, 1.0f, 1.0f, // Bottom-right
-1.0f, -1.0f, 0.0f, 1.0f // Bottom-left
};
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
const static GLuint elements[] = {
0, 1, 2,
2, 3, 0
};
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);
// Set shader attributes
GLint pos_attrib = glGetAttribLocation(shader_prog, "position");
glVertexAttribPointer(pos_attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(pos_attrib);
GLint tex_coord_attrib = glGetAttribLocation(shader_prog, "tex_coord");
glEnableVertexAttribArray(tex_coord_attrib);
glVertexAttribPointer(tex_coord_attrib, 2, GL_FLOAT, GL_FALSE,
4 * sizeof(GLfloat), (void*)(2 * sizeof(GLfloat)));
// Generate texture
glGenTextures(1, &mandelbrot_tex);
// Bind the texture information
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mandelbrot_tex);
glUniform1i(glGetUniformLocation(shader_prog, "tex"), 0);
}
Draw_Buffer::~Draw_Buffer() {
// Unbind buffer
glBindVertexArray(NULL);
// Delete shaders
glDeleteProgram(shader_prog);
glDeleteShader(vertex_shader);
glDeleteShader(frag_shader);
// Delete buffers
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ebo);
glDeleteVertexArrays(1, &vao);
// Terminate GLFW
glfwDestroyWindow(screen);
glfwTerminate();
}
void Draw_Buffer::flush() {
glClear(GL_COLOR_BUFFER_BIT);
// Reset texture
glBindTexture(GL_TEXTURE_2D, mandelbrot_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, window->width(), window->height(), 0, GL_RGB, GL_BYTE, &buffer[0]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Draw rectangle
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// Sends message if there is an OpenGL bug
GLenum err = glGetError();
if (err) {
std::stringstream ss;
ss << "GL Error: " << err;
throw std::runtime_error(ss.str());
}
// Swap buffers
glfwSwapBuffers(screen);
// Reset iterator
pos_iter = buffer.begin();
while(!glfwWindowShouldClose(screen)) {
glfwPollEvents();
}
}
fragment_shader.glsl
#version 150
in vec2 Tex_coord;
out vec4 outColor;
uniform sampler2D tex;
void main() {
outColor = texture(tex, Tex_coord);
}
vertex_shader.glsl
#version 150
in vec2 position;
in vec2 tex_coord;
out vec2 Tex_coord;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
Tex_coord = tex_coord;
}