diff --git a/headers/Mandelbrot.h b/headers/Mandelbrot.h index 18affb5..ea2519f 100644 --- a/headers/Mandelbrot.h +++ b/headers/Mandelbrot.h @@ -7,18 +7,23 @@ class Mandelbrot : public QObject, protected QOpenGLExtraFunctions { Q_OBJECT public: - void init(); - void draw(GLuint, QOpenGLShaderProgram&); + void init(GLuint); + void draw(); void setIterations(int); void translateRelative(QVector2D); void zoomRelative(int, QVector2D); void resizeRelative(QVector2D); + bool initShader(); private: int iterations = 0; QVector2D origin {-2, -1}; QVector2D size {2, 2}; float zoomModifier = 1.05; - void setShaderValues(QOpenGLShaderProgram&) const; + + GLuint vao; + + QOpenGLShaderProgram shader; + void setShaderValues(); }; diff --git a/headers/OutputWidget.h b/headers/OutputWidget.h index 43860b9..4d091cd 100644 --- a/headers/OutputWidget.h +++ b/headers/OutputWidget.h @@ -13,6 +13,7 @@ class OutputWidget : public QOpenGLWidget, protected QOpenGLExtraFunctions { public: explicit OutputWidget(QWidget* parent): QOpenGLWidget(parent) {} Mandelbrot& getMandelbrot() {return mandelbrot;} + void saveToImage(const std::function&, const std::function&); private: void initializeGL() override; void paintGL() override; @@ -27,11 +28,7 @@ private: QPoint mousePos; QSize oldSize {0, 0}; - void initShader(); - static std::vector genVertices(); GLuint createVAO(); - GLuint vao {}; - QOpenGLShaderProgram shader; Mandelbrot mandelbrot; }; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 8ebb63e..79da82f 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "../headers/MainWindow.h" MainWindow::MainWindow(): outputWidget(OutputWidget(this)) { @@ -28,10 +29,29 @@ void MainWindow::buildUI() { }); iterationsSlider->setValue(100); + auto saveInfo = new QLabel; + auto saveButton = new QPushButton("Save high resolution image"); + connect( + saveButton, + &QPushButton::clicked, + this, + [this, saveInfo](){ + saveInfo->setText("Receiving image..."); + update(); + this->outputWidget.saveToImage([saveInfo](){ + saveInfo->setText("Saving..."); + }, [saveInfo](bool success){ + QString result = success ? "Success" : "Failure"; + saveInfo->setText(result); + }); + }); + auto controls = new QGridLayout; controls->addWidget(iterationsCaption, 1, 1); controls->addWidget(iterationsLabel, 1, 2); controls->addWidget(iterationsSlider, 1, 3); + controls->addWidget(saveInfo, 2, 1, 1, 2); + controls->addWidget(saveButton, 2, 3, 1, 1); auto lyt = new QVBoxLayout(this); lyt->addWidget(&outputWidget); diff --git a/src/Mandelbrot.cpp b/src/Mandelbrot.cpp index bdbd665..994b51c 100644 --- a/src/Mandelbrot.cpp +++ b/src/Mandelbrot.cpp @@ -1,19 +1,31 @@ +#include +#include +#include #include "../headers/Mandelbrot.h" -void Mandelbrot::init() { +void Mandelbrot::init(GLuint vaoId) { initializeOpenGLFunctions(); + vao = vaoId; } -void Mandelbrot::draw(GLuint vao, QOpenGLShaderProgram& shader) { +void Mandelbrot::draw() { glClear(GL_COLOR_BUFFER_BIT); shader.bind(); - setShaderValues(shader); + setShaderValues(); glBindVertexArray(vao); - glDrawArrays(GL_QUADS, 0, 4); + glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); } -void Mandelbrot::setShaderValues(QOpenGLShaderProgram &shader) const { +bool Mandelbrot::initShader() { + if (!shader.addShaderFromSourceFile(QOpenGLShader::Vertex, "Vertex.glsl")) return false; + if (!shader.addShaderFromSourceFile(QOpenGLShader::Fragment, "Fragment.glsl")) return false; + if (!shader.link()) return false; + if (!shader.bind()) return false; + return true; +} + +void Mandelbrot::setShaderValues() { shader.setUniformValue("origin", origin); shader.setUniformValue("size", size); shader.setUniformValue("iterationCount", iterations); diff --git a/src/OutputWidget.cpp b/src/OutputWidget.cpp index f40a714..6ae4a42 100644 --- a/src/OutputWidget.cpp +++ b/src/OutputWidget.cpp @@ -1,5 +1,10 @@ #include #include +#include +#include +#include +#include +#include #include "../headers/OutputWidget.h" using std::cout, std::endl; @@ -7,19 +12,28 @@ using std::cout, std::endl; void OutputWidget::initializeGL() { cout << "Initialize OpenGL" << endl; initializeOpenGLFunctions(); - getMandelbrot().init(); - initShader(); + getMandelbrot().init(createVAO()); + if (!getMandelbrot().initShader()) close(); auto format = this->format(); cout << "OpenGL version: " << format.majorVersion() << "." << format.minorVersion() << endl; - vao = createVAO(); - glClearColor(0.5f, 0.5f, 0.5f, 1.0f); } void OutputWidget::paintGL() { - getMandelbrot().draw(vao, shader); + getMandelbrot().draw(); +} + +std::vector genVertices() { + return { + {-1, -1}, + {1, -1}, + {1, 1}, + {1, 1}, + {-1, 1}, + {-1, -1} + }; } GLuint OutputWidget::createVAO() { @@ -44,23 +58,6 @@ GLuint OutputWidget::createVAO() { return vaoId; } -std::vector OutputWidget::genVertices() { - return { - {-1, -1}, - {1, -1}, - {1, 1}, - {-1, 1} - }; -} - -void OutputWidget::initShader() { - if (!shader.addShaderFromSourceFile(QOpenGLShader::Vertex, "Vertex.glsl")) close(); - if (!shader.addShaderFromSourceFile(QOpenGLShader::Fragment, "Fragment.glsl")) close(); - if (!shader.link()) close(); - if (!shader.bind()) close(); -} - - QVector2D divide(QPoint p, QSize s){ return { float(p.x()) / float(s.width()), @@ -113,3 +110,38 @@ void OutputWidget::mouseReleaseEvent(QMouseEvent *e) { void OutputWidget::keyPressEvent(QKeyEvent *e) { } + +void OutputWidget::saveToImage(const std::function& receiveCb, const std::function& saveCb) { + double maxSize = pow(2, 14); + + double div = double(width()) / height(); + int w, h; + if (div > 1){ + w = int(maxSize); + h = int(maxSize / div); + } else { + h = int(maxSize); + w = int(maxSize * div); + } + + QOpenGLFramebufferObject fbo(w, h); + fbo.bind(); + + glViewport(0, 0, w, h); + getMandelbrot().draw(); + QOpenGLFramebufferObject::bindDefault(); + + QImage img = fbo.toImage(); + receiveCb(); + + QString fileName = QFileDialog::getSaveFileName(this, "", "", tr("Images (*.png *.xpm *.jpg)")); + if (fileName.isNull()){ + saveCb(false); + return; + } + + std::thread job([saveCb](const QImage& img, const QString& fileName) { + saveCb(img.save(fileName)); + }, img, fileName); + job.detach(); +}