Nicolas Winkler 6 سال پیش
والد
کامیت
bec7179920
15فایلهای تغییر یافته به همراه730 افزوده شده و 111 حذف شده
  1. 40 39
      Almond.cpp
  2. 11 19
      Almond.h
  3. 19 16
      Almond.pro
  4. 53 3
      Almond.ui
  5. 143 25
      MandelWidget.cpp
  6. 21 3
      MandelWidget.h
  7. 4 3
      VideoStream.cpp
  8. 1 0
      VideoStream.h
  9. 3 0
      benchmarkdialog.cpp
  10. 169 0
      exportdialogs.cpp
  11. 57 0
      exportdialogs.h
  12. 3 3
      exportimagedialog.ui
  13. 178 0
      exportvideodialog.ui
  14. 10 0
      libmandel/include/MandelUtil.h
  15. 18 0
      libmandel/src/MandelUtil.cpp

+ 40 - 39
Almond.cpp

@@ -2,6 +2,7 @@
 #include <QIntValidator>
 #include <QFileDialog>
 #include <QMessageBox>
+#include <QGradient>
 #include "benchmarkdialog.h"
 
 #include <cmath>
@@ -16,6 +17,7 @@ Almond::Almond(QWidget *parent) :
     //qRegisterMetaType<MandelWidget>("MandelWidget");
     printf("created!\n");
     ui.verticalLayout_left->addWidget(mw.get());
+    ui.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
     //ui.verticalLayout_left->addWidget(new MyGLWidget(ui.centralWidget));
     //mw->show();
 }
@@ -23,6 +25,7 @@ Almond::Almond(QWidget *parent) :
 void Almond::on_pushButton_clicked()
 {
     ExportImageDialog dialog(this);
+    //dialog.show();
     auto response = dialog.exec();
     if (response == 1) {
         mnd::MandelInfo mi;
@@ -41,60 +44,58 @@ void Almond::on_pushButton_clicked()
 }
 
 
-ExportImageDialog::ExportImageDialog(QWidget* parent) :
-    QDialog{ parent }
-{
-    eid.setupUi(this);
-    eid.maxIterations->setValidator(new QIntValidator(1, 10000000, this));
-    eid.imgWidth->setValidator(new QIntValidator(1, 10000000, this));
-    eid.imgHeight->setValidator(new QIntValidator(1, 10000000, this));
-}
-
-
-int ExportImageDialog::getMaxIterations(void) const
+void Almond::on_pushButton_2_clicked()
 {
-    return std::stoi(eid.maxIterations->text().toStdString());
+    BenchmarkDialog bd(mandelContext, this);
+    //bd.show();
+    bd.exec();
 }
 
-
-int ExportImageDialog::getWidth(void) const
+void Almond::on_zoom_out_clicked()
 {
-    return std::stoi(eid.imgWidth->text().toStdString());
+    mw->zoom(2);
 }
 
-
-int ExportImageDialog::getHeight(void) const
+void Almond::on_zoom_in_clicked()
 {
-    return std::stoi(eid.imgHeight->text().toStdString());
+    mw->zoom(0.5);
 }
 
-
-QString ExportImageDialog::getPath(void) const
+void Almond::on_maxIterations_editingFinished()
 {
-    return eid.savePath->text();
+    QString text = ui.maxIterations->text();
+    int maxIter = text.toInt();
+    mw->setMaxIterations(maxIter);
 }
 
-void ExportImageDialog::on_pushButton_clicked()
+void Almond::on_chooseGradient_clicked()
 {
-    QString saveAs = QFileDialog::getSaveFileName(this,
-            tr("Save exported image"), "",
-            tr("PNG image (*.png);;JPEG image (*.jpg);;All Files (*)"));
-    eid.savePath->setText(saveAs);
-    this->repaint();
+    QGradient qg;
 }
 
-void ExportImageDialog::on_buttonBox_accepted()
+void Almond::on_exportVideo_clicked()
 {
-    if (eid.savePath->text() == "") {
-        QMessageBox msgBox;
-        msgBox.setText("Please specify a path.");
-        msgBox.exec();
-        reject();
+    ExportVideoInfo evi;
+    evi.start = mnd::MandelViewport::standardView();
+    evi.end = mw->getViewport();
+    ExportVideoDialog dialog(this, evi);
+    //dialog.show();
+    auto response = dialog.exec();
+    printf("dialog executed\n"); fflush(stdout);
+    if (response == 1) {
+        mnd::MandelInfo mi;
+        evi = dialog.getExportVideoInfo();
+        //Video
+        /*mi.maxIter = dialog.getMaxIterations();
+        mi.view = mw->getViewport();
+        mi.bWidth = dialog.getWidth();
+        mi.bHeight = dialog.getHeight();
+        mi.view.adjustAspectRatio(mi.bWidth, mi.bHeight);
+        mnd::Generator& g = mandelContext.getDefaultGenerator();
+        auto fmap = Bitmap<float>(mi.bWidth, mi.bHeight);
+        g.generate(mi, fmap.pixels.get());
+        auto bitmap = fmap.map<RGBColor>([](float i) { return i < 0 ? RGBColor{ 0,0,0 } : RGBColor{ uint8_t(cos(i * 0.015f) * 127 + 127), uint8_t(sin(i * 0.01f) * 127 + 127), uint8_t(i) }; });//uint8_t(::sin(i * 0.01f) * 100 + 100), uint8_t(i) }; });
+        QImage img((unsigned char*)bitmap.pixels.get(), bitmap.width, bitmap.height, bitmap.width * 3, QImage::Format_RGB888);
+        img.save(dialog.getPath());*/
     }
 }
-
-void Almond::on_pushButton_2_clicked()
-{
-    BenchmarkDialog bd(mandelContext, this);
-    bd.exec();
-}

+ 11 - 19
Almond.h

@@ -1,12 +1,11 @@
 #pragma once
 
 #include <QtWidgets/QMainWindow>
-#include <QtWidgets/QDialog>
 #include "ui_Almond.h"
-#include "ui_exportimagedialog.h"
 
 #include <Mandel.h>
 #include "MandelWidget.h"
+#include "exportdialogs.h"
 
 #include <memory>
 
@@ -24,24 +23,17 @@ private slots:
 
     void on_pushButton_2_clicked();
 
-private:
-    Ui::AlmondClass ui;
-};
+    void on_zoom_out_clicked();
 
+    void on_zoom_in_clicked();
 
-class ExportImageDialog : public QDialog
-{
-    Q_OBJECT
-private:
-    Ui::ExportImageDialog eid;
-public:
-    ExportImageDialog(QWidget* parent);
+    void on_maxIterations_editingFinished();
 
-    int getMaxIterations(void) const;
-    int getWidth(void) const;
-    int getHeight(void) const;
-    QString getPath(void) const;
-private slots:
-    void on_pushButton_clicked();
-    void on_buttonBox_accepted();
+    void on_chooseGradient_clicked();
+
+    void on_exportVideo_clicked();
+
+private:
+    Ui::AlmondClass ui;
 };
+

+ 19 - 16
Almond.pro

@@ -32,6 +32,7 @@ SOURCES += \
         SectionManager.cpp \
         VideoStream.cpp \
         benchmarkdialog.cpp \
+        exportdialogs.cpp \
         main.cpp
 
 HEADERS += \
@@ -41,12 +42,14 @@ HEADERS += \
         MandelWidget.h \
         SectionManager.h \
         VideoStream.h \
-        benchmarkdialog.h
+        benchmarkdialog.h \
+        exportdialogs.h
 
 FORMS += \
         Almond.ui \
         benchmarks.ui \
-        exportimagedialog.ui
+        exportimagedialog.ui \
+        exportvideodialog.ui
 
 
 # Default rules for deployment.
@@ -64,44 +67,44 @@ else:unix:QMAKE_LFLAGS+= -fopenmp
 LIBS += -fopenmp
 unix:LIBS += -lm -latomic
 
-#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
-#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
-#else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
+win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
+else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavcodec
 
-#INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
-#DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
+INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
+DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
 
 #win32:CONFIG(release, debug|release): LIBS += -L$$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/lib/x86/' -lOpenCL
 #else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/lib/x86/' -lOpenCL
 #else:unix: LIBS += -lOpenCL
 
-#win32:INCLUDEPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
-#win32:DEPENDPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
+win32:INCLUDEPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
+win32:DEPENDPATH += $$PWD/'../../../../../Program Files (x86)/AMD APP SDK/3.0/include'
 
-#win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
-#else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
-#else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
+win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
+else:unix: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavformat
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavdevice
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavdevice
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavfilter
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavfilter
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavutil
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lavutil
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 
-#unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lswscale
+unix|win32: LIBS += -L$$PWD/../libs/ffmpeg-4.1.1-win32-dev/lib/ -lswscale
 
 #INCLUDEPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include
 #DEPENDPATH += $$PWD/../libs/ffmpeg-4.1.1-win32-dev/include

+ 53 - 3
Almond.ui

@@ -9,8 +9,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>712</width>
-    <height>268</height>
+    <width>752</width>
+    <height>609</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -21,7 +21,50 @@
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="2,0">
       <item>
-       <layout class="QVBoxLayout" name="verticalLayout_left"/>
+       <layout class="QVBoxLayout" name="verticalLayout_left">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_3">
+          <item>
+           <widget class="QPushButton" name="chooseGradient">
+            <property name="text">
+             <string>Choose Gradient</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLabel" name="label">
+            <property name="text">
+             <string>Max. Iterations</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QLineEdit" name="maxIterations">
+            <property name="text">
+             <string>2000</string>
+            </property>
+            <property name="maxLength">
+             <number>32</number>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="zoom_in">
+            <property name="text">
+             <string>Zoom In</string>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <widget class="QPushButton" name="zoom_out">
+            <property name="text">
+             <string>Zoom Out</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
       </item>
       <item>
        <layout class="QVBoxLayout" name="verticalLayout_right">
@@ -46,6 +89,13 @@
          </spacer>
         </item>
         <item>
+         <widget class="QPushButton" name="exportVideo">
+          <property name="text">
+           <string>Export Video</string>
+          </property>
+         </widget>
+        </item>
+        <item>
          <widget class="QPushButton" name="pushButton">
           <property name="text">
            <string>Export Image</string>

+ 143 - 25
MandelWidget.cpp

@@ -6,6 +6,31 @@ using namespace mnd;
 #include <QOpenGLVertexArrayObject>
 
 
+Texture::Texture(const Bitmap<RGBColor>& bitmap) :
+    context{ nullptr }
+{
+    glGenTextures(1, &id);
+    glBindTexture(GL_TEXTURE_2D, id);
+
+    long lineLength = (bitmap.width * 3 + 3) & ~3;
+
+    unsigned char* pixels = new unsigned char[lineLength * bitmap.height];
+    for (int i = 0; i < bitmap.width; i++) {
+        for (int j = 0; j < bitmap.height; j++) {
+            int index = i * 3 + j * lineLength;
+            RGBColor c = bitmap.get(i, j);
+            pixels[index] = c.r;
+            pixels[index + 1] = c.g;
+            pixels[index + 2] = c.b;
+        }
+    }
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, int(bitmap.width), int(bitmap.height), 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+}
+
 Texture::Texture(const Bitmap<RGBColor>& bitmap, QOpenGLContext* context) :
     context{ context }
 {
@@ -34,7 +59,7 @@ Texture::Texture(const Bitmap<RGBColor>& bitmap, QOpenGLContext* context) :
 
 Texture::~Texture(void)
 {
-    context->functions()->glDeleteTextures(1, &id);
+    glDeleteTextures(1, &id);
 }
 
 
@@ -65,10 +90,22 @@ void Texture::drawRect(float x, float y, float width, float height)
 
 MandelView::MandelView(mnd::Generator& generator, MandelWidget* mWidget) :
     generator{ &generator },
-    mWidget{ mWidget },
-    context{ new QOpenGLContext(this) }
+    mWidget{ mWidget }
+    //context{ new QOpenGLContext(this) }
+{
+    //context->setShareContext(mWidget->context()->contextHandle());
+    hasToCalc.store(false);
+    finish.store(false);
+}
+
+
+MandelView::~MandelView(void)
 {
-    context->setShareContext(mWidget->context()->contextHandle());
+    finish.store(true);
+    condVar.notify_one();
+    //calcThread.wait(100);
+    calcThread.wait(100);
+    calcThread.terminate();
 }
 
 
@@ -77,6 +114,54 @@ void MandelView::setGenerator(mnd::Generator& value)
     generator = &value;
 }
 
+void MandelView::start(void)
+{
+    this->moveToThread(&calcThread);
+    connect(&calcThread, SIGNAL(started()), this, SLOT(loop()));
+    calcThread.start();
+}
+
+
+void MandelView::loop(void)
+{
+    printf("thread!\n"); fflush(stdout);
+    //QGLWidget* hiddenWidget = new QGLWidget(nullptr, mWidget);
+    //hiddenWidget->setVisible(false);
+    //hiddenWidget->context()->contextHandle()->moveToThread(&calcThread);
+    //QOpenGLContext* context = hiddenWidget->context()->contextHandle();
+    //context->setShareContext(mWidget->context()->contextHandle());
+    //context->create();
+    //printf("sharing: %d\n", QOpenGLContext::areSharing(hiddenWidget->context()->contextHandle(), mWidget->context()->contextHandle()));
+    //fflush(stdout);
+    //std::this_thread::sleep_for(std::chrono::milliseconds(3000));
+    std::unique_lock<std::mutex> lock(mut);
+    while(true) {
+        printf("calcing!\n"); fflush(stdout);
+        if (finish.load()) {
+            break;
+        }
+        if (hasToCalc.exchange(false)) {
+            const MandelInfo& mi = toCalc.load();
+            auto fmap = Bitmap<float>(mi.bWidth, mi.bHeight);
+            generator->generate(mi, fmap.pixels.get());
+            auto* bitmap = new Bitmap(fmap.map<RGBColor>([&mi](float i) { return i > mi.maxIter ?
+                            RGBColor{ 0,0,0 } :
+                            RGBColor{ uint8_t(cos(i * 0.015f) * 127 + 127),
+                                      uint8_t(sin(i * 0.01f) * 127 + 127),
+                                      uint8_t(i) }; }));//uint8_t(::sin(i * 0.01f) * 100 + 100), uint8_t(i) }; });
+
+            //hiddenWidget->makeCurrent();
+            //Texture* tex = new Texture(bitmap);
+            //hiddenWidget->doneCurrent();
+            //Texture* tex = 0;
+            emit updated(bitmap);
+        }
+        printf("finished calcing!\n"); fflush(stdout);
+        condVar.wait(lock);
+        printf("waking!\n"); fflush(stdout);
+    }
+}
+
 void MandelView::adaptViewport(const MandelInfo mi)
 {
     //bmp->get(0, 0) = RGBColor{ 10, uint8_t(sin(1 / vp.width) * 127 + 127), 10 };
@@ -91,14 +176,19 @@ void MandelView::adaptViewport(const MandelInfo mi)
             printf("ready!\n");
         }
     }*/
-    if (!calc.valid() || calc.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) {
+    /*if (!calc.valid() || calc.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) {
         toCalc = mi;
         hasToCalc = true;
         calc = std::async([this, mi] () {
-            QOpenGLContext* context = new QOpenGLContext();
-            context->setShareContext(mWidget->context()->contextHandle());
-            context->create();
-             while(hasToCalc.exchange(false)) {
+            QGLWidget* hiddenWidget = new QGLWidget(nullptr, (QGLWidget*) mWidget);
+            QOpenGLContext* context = hiddenWidget->context()->contextHandle();
+            hiddenWidget->makeCurrent();
+            //context->setShareContext(mWidget->context()->contextHandle());
+            //context->create();
+            printf("sharing: %d\n", QOpenGLContext::areSharing(context, mWidget->context()->contextHandle()));
+            fflush(stdout);
+            //std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+            do {
                 auto fmap = Bitmap<float>(mi.bWidth, mi.bHeight);
                 generator->generate(mi, fmap.pixels.get());
                 auto bitmap = fmap.map<RGBColor>([&mi](float i) { return i > mi.maxIter ?
@@ -107,15 +197,19 @@ void MandelView::adaptViewport(const MandelInfo mi)
                                           uint8_t(sin(i * 0.01f) * 127 + 127),
                                           uint8_t(i) }; });//uint8_t(::sin(i * 0.01f) * 100 + 100), uint8_t(i) }; });
 
-                //Texture* tex = new Texture(bitmap, context);
-                //emit updated(tex);
-            }
+                Texture* tex = new Texture(bitmap, context);
+                //Texture* tex = 0;
+                emit updated(tex);
+            } while(hasToCalc.exchange(false));
         });
     }
-    else {
-        toCalc = mi;
-        hasToCalc = true;
-    }
+    else {*/
+
+    //std::unique_lock<std::mutex> lock(mut, std::try_to_lock);
+    toCalc = mi;
+    hasToCalc.exchange(true);
+    condVar.notify_one();
+    //}
 }
 
 
@@ -128,7 +222,7 @@ MandelWidget::MandelWidget(mnd::MandelContext& ctxt, QWidget* parent) :
     this->setSizePolicy(QSizePolicy::Expanding,
         QSizePolicy::Expanding);
     QObject::connect(&mv, &MandelView::updated, this, &MandelWidget::viewUpdated, Qt::AutoConnection);
-    QObject::connect(this, &MandelWidget::needsUpdate, &mv, &MandelView::adaptViewport, Qt::AutoConnection);
+    QObject::connect(this, &MandelWidget::needsUpdate, &mv, &MandelView::adaptViewport, Qt::DirectConnection);
 
     if (!ctxt.getDevices().empty()) {
         if (auto* gen = ctxt.getDevices()[0].getGeneratorDouble(); gen) {
@@ -161,7 +255,8 @@ void MandelWidget::initializeGL(void)
     bitmap.get(0, 0) = RGBColor{50, 50, 50};
 
     tex = std::make_unique<Texture>(bitmap, context()->contextHandle());
-    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), 2000 });
+    mv.start();
+    requestRecalc();
 }
 
 
@@ -223,8 +318,29 @@ void MandelWidget::drawRubberband(void)
 }
 
 
+void MandelWidget::zoom(float scale)
+{
+    viewport.zoomCenter(scale);
+    requestRecalc();
+}
+
+
+void MandelWidget::setMaxIterations(int maxIter)
+{
+    this->maxIterations = maxIter;
+    requestRecalc();
+}
+
+
+void MandelWidget::requestRecalc()
+{
+    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), maxIterations });
+}
+
+
 void MandelWidget::resizeGL(int width, int height)
 {
+    glViewport(0, 0, (GLint) width, (GLint) height);
 }
 
 
@@ -252,7 +368,7 @@ void MandelWidget::resizeEvent(QResizeEvent* re)
     //else
     //    viewport.width = (viewport.height * aspect);
 
-    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), 2000 });
+    requestRecalc();
     //redraw();
 }
 
@@ -289,14 +405,16 @@ void MandelWidget::mouseReleaseEvent(QMouseEvent* me)
     viewport.height *= double(rect.height()) / full.height();
     viewport.normalize();
     rubberbandDragging = false;
-    emit needsUpdate(MandelInfo{ viewport, this->width(), this->height(), 2000 });
+    requestRecalc();
 }
 
 
-void MandelWidget::viewUpdated(Texture* bitmap)
+void MandelWidget::viewUpdated(Bitmap<RGBColor>* bitmap)
 {
-    tex = std::unique_ptr<Texture>(bitmap);//std::make_unique<Texture>(*bitmap);
-    //delete bitmap;
-    printf("viewUpdated\n");
-    emit repaint();
+    if (bitmap != nullptr) {
+        tex = std::make_unique<Texture>(*bitmap);
+        delete bitmap;
+        printf("viewUpdated\n");
+        emit repaint();
+    }
 }

+ 21 - 3
MandelWidget.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <QGLWidget>
+#include <QThread>
 #include <qopengl.h>
 #include <qopenglfunctions.h>
 #include <qopenglcontext.h>
@@ -13,6 +14,8 @@
 #include <Mandel.h>
 
 #include <future>
+#include <thread>
+#include <mutex>
 #include <atomic>
 
 class MandelWidget;
@@ -42,20 +45,29 @@ class MandelView : public QObject
     Q_OBJECT
 private:
     std::future<void> calc;
+    QThread calcThread;
+    std::mutex mut;
+    std::condition_variable condVar;
     std::atomic<mnd::MandelInfo> toCalc;
     std::atomic_bool hasToCalc;
+    std::atomic_bool finish;
     mnd::Generator* generator;
     MandelWidget* mWidget;
-    QOpenGLContext* context;
+    //QOpenGLContext* context;
 public:
     MandelView(mnd::Generator& generator, MandelWidget* mWidget);
+    ~MandelView(void);
 
     void setGenerator(mnd::Generator &value);
 
+    void start();
+private slots:
+    void loop();
+
 public slots:
     void adaptViewport(const mnd::MandelInfo vp);
 signals:
-    void updated(Texture* bitmap);
+    void updated(Bitmap<RGBColor>* bitmap);
 };
 
 class MandelWidget : public QGLWidget
@@ -67,6 +79,7 @@ private:
     mnd::MandelContext& mndContext;
 
     bool initialized = false;
+    int maxIterations = 2000;
 
     bool rubberbandDragging = false;
     QRectF rubberband;
@@ -93,8 +106,13 @@ public:
 
     void drawRubberband(void);
 
+    void zoom(float scale);
+    void setMaxIterations(int maxIter);
+
     //void redraw();
 
+    void requestRecalc(void);
+
     void resizeEvent(QResizeEvent* re) override;
     void mousePressEvent(QMouseEvent* me) override;
     void mouseMoveEvent(QMouseEvent* me) override;
@@ -104,6 +122,6 @@ public:
 signals:
     void needsUpdate(const mnd::MandelInfo vp);
 public slots:
-    void viewUpdated(Texture* bitmap);
+    void viewUpdated(Bitmap<RGBColor>* bitmap);
 };
 

+ 4 - 3
VideoStream.cpp

@@ -1,6 +1,7 @@
+#include "VideoStream.h"
+
 #ifdef FFMPEG_ENABLED
 
-#include "VideoStream.h"
 
 #include <iostream>
 
@@ -32,7 +33,7 @@ VideoStream::VideoStream(::size_t width, ::size_t height, const std::string& fil
     if (!pkt)
         exit(1);
 
-    codecContext->bit_rate = 500000 * 100;
+    codecContext->bit_rate = 100 * 1000 * 1000;
     codecContext->width = width;
     codecContext->height = height;
     codecContext->time_base = AVRational{ 1, 60 };
@@ -91,7 +92,7 @@ static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
             exit(1);
         }
 
-        printf("encoded frame %3\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
+        printf("encoded frame %3d\"PRId64\" (size=%5d)\n", pkt->pts, pkt->size);
         fwrite(pkt->data, 1, pkt->size, outfile);
         av_packet_unref(pkt);
     }

+ 1 - 0
VideoStream.h

@@ -1,4 +1,5 @@
 #pragma once
+#define FFMPEG_ENABLED
 #ifdef FFMPEG_ENABLED
 
 

+ 3 - 0
benchmarkdialog.cpp

@@ -168,6 +168,7 @@ BenchmarkDialog::BenchmarkDialog(mnd::MandelContext& mndContext, QWidget *parent
     benchmarker{ mndContext }
 {
     ui.setupUi(this);
+    printf("bench!\n"); fflush(stdout);
 
     auto& devices = mndContext.getDevices();
     int nDevices = devices.size() + 1;
@@ -196,6 +197,8 @@ BenchmarkDialog::BenchmarkDialog(mnd::MandelContext& mndContext, QWidget *parent
     connect(&benchThread, &QThread::started, &benchmarker, &Benchmarker::start);
     connect(&benchmarker, SIGNAL (finished()), &benchThread, SLOT (quit()));
     connect(&benchmarker, SIGNAL (update(BenchmarkResult)), this, SLOT (update(BenchmarkResult)));
+
+    ui.tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
 }
 
 

+ 169 - 0
exportdialogs.cpp

@@ -0,0 +1,169 @@
+#include "exportdialogs.h"
+
+#include <QIntValidator>
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include "Mandel.h"
+#include "VideoStream.h"
+
+static bool exportVideo(const ExportVideoInfo& evi);
+
+ExportImageDialog::ExportImageDialog(QWidget* parent) :
+    QDialog{ parent }
+{
+    eid.setupUi(this);
+    eid.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
+    eid.imgWidth->setValidator(new QIntValidator(1, 10000000, this));
+    eid.imgHeight->setValidator(new QIntValidator(1, 10000000, this));
+}
+
+
+int ExportImageDialog::getMaxIterations(void) const
+{
+    return std::stoi(eid.maxIterations->text().toStdString());
+}
+
+
+int ExportImageDialog::getWidth(void) const
+{
+    return std::stoi(eid.imgWidth->text().toStdString());
+}
+
+
+int ExportImageDialog::getHeight(void) const
+{
+    return std::stoi(eid.imgHeight->text().toStdString());
+}
+
+
+QString ExportImageDialog::getPath(void) const
+{
+    return eid.savePath->text();
+}
+
+void ExportImageDialog::on_pushButton_clicked()
+{
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr("PNG image (*.png);;JPEG image (*.jpg);;All Files (*)"));
+    eid.savePath->setText(saveAs);
+    this->repaint();
+}
+
+void ExportImageDialog::on_buttonBox_accepted()
+{
+    if (eid.savePath->text() == "") {
+        QMessageBox msgBox;
+        msgBox.setText("Please specify a path.");
+        msgBox.show();
+        msgBox.exec();
+        reject();
+    }
+}
+
+ExportVideoDialog::ExportVideoDialog(QWidget* parent, const ExportVideoInfo& evi) :
+    QDialog{ parent },
+    evi{ evi }
+{
+    evd.setupUi(this);
+    evd.maxIterations->setValidator(new QIntValidator(1, 1000000000, this));
+    evd.vidWidth->setValidator(new QIntValidator(1, 10000000, this));
+    evd.vidHeight->setValidator(new QIntValidator(1, 10000000, this));
+
+    evd.startX->setText(QString::number(evi.start.x));
+    evd.startY->setText(QString::number(evi.start.y));
+    evd.startW->setText(QString::number(evi.start.width));
+    evd.startH->setText(QString::number(evi.start.height));
+
+    evd.endX->setText(QString::number(evi.end.x));
+    evd.endY->setText(QString::number(evi.end.y));
+    evd.endW->setText(QString::number(evi.end.width));
+    evd.endH->setText(QString::number(evi.end.height));
+}
+
+const ExportVideoInfo& ExportVideoDialog::getExportVideoInfo(void) const
+{
+    return evi;
+}
+
+void ExportVideoDialog::on_buttonBox_accepted()
+{
+    if (evd.savePath->text() == "") {
+        QMessageBox* msgBox = new QMessageBox;
+        msgBox->setText("Please specify a path.");
+        msgBox->exec();
+        emit reject();
+    }
+
+    evi.path = evd.savePath->text();
+    evi.width = evd.vidWidth->text().toInt();
+    evi.height = evd.vidHeight->text().toInt();
+    evi.maxIterations = evd.maxIterations->text().toInt();
+    /*evi.start = mnd::MandelViewport {
+        evd.startX->text().toDouble(),
+        evd.startY->text().toDouble(),
+        evd.startW->text().toDouble(),
+        evd.startH->text().toDouble(),
+    };
+    evi.end = mnd::MandelViewport {
+        evd.endX->text().toDouble(),
+        evd.endY->text().toDouble(),
+        evd.endW->text().toDouble(),
+        evd.endH->text().toDouble(),
+    };*/
+
+    evi.start.adjustAspectRatio(evi.width, evi.height);
+    evi.end.adjustAspectRatio(evi.width, evi.height);
+
+    if (exportVideo(evi)) {
+        QMessageBox* msgBox = new QMessageBox;
+        msgBox->setText("Video successfully exported.");
+        msgBox->exec();
+    }
+}
+
+void ExportVideoDialog::on_pushButton_clicked()
+{
+    QString saveAs = QFileDialog::getSaveFileName(this,
+            tr("Save exported image"), "",
+            tr("MPEG video (*.mp4);;All Files (*)"));
+    evd.savePath->setText(saveAs);
+    this->repaint();
+}
+
+
+bool exportVideo(const ExportVideoInfo& evi)
+{
+    auto lerp = [] (double a, double b, double v) {
+        return a * (1 - v) + b * v;
+    };
+
+    mnd::MandelContext ctxt = mnd::initializeContext();
+    mnd::Generator& gen = *ctxt.getDevices()[0].getGeneratorDouble();
+    mnd::MandelInfo mi;
+    mi.bWidth = evi.width;
+    mi.bHeight = evi.height;
+    mi.maxIter = evi.maxIterations;
+
+    VideoStream vs(evi.width, evi.height, evi.path.toStdString());
+
+    double x = evi.end.x + evi.end.width / 2;
+    double y = evi.end.y + evi.end.height / 2;
+    double w = evi.start.width;
+    double h = evi.start.height;
+
+    while(w > evi.end.width || h > evi.end.height) {
+        mi.view = mnd::MandelViewport{ x - w/2, y - h/2, w, h };
+
+        Bitmap<float> raw{ evi.width, evi.height };
+        gen.generate(mi, raw.pixels.get());
+        vs.addFrame(raw.map<RGBColor>([] (float x) { return
+            RGBColor{ uint8_t(::sin(x / 100) * 127 + 127), uint8_t(::sin(x / 213) * 127 + 127), uint8_t(::cos(x / 173) * 127 + 127) };
+        }));
+
+        w *= 0.975;
+        h *= 0.975;
+    }
+    return true;
+}

+ 57 - 0
exportdialogs.h

@@ -0,0 +1,57 @@
+#ifndef EXPORTDIALOGS_H
+#define EXPORTDIALOGS_H
+
+#include "Mandel.h"
+
+#include <QtWidgets/QDialog>
+
+#include "ui_exportimagedialog.h"
+#include "ui_exportvideodialog.h"
+
+class ExportImageDialog : public QDialog
+{
+    Q_OBJECT
+private:
+    Ui::ExportImageDialog eid;
+public:
+    ExportImageDialog(QWidget* parent);
+
+    int getMaxIterations(void) const;
+    int getWidth(void) const;
+    int getHeight(void) const;
+    QString getPath(void) const;
+private slots:
+    void on_pushButton_clicked();
+    void on_buttonBox_accepted();
+};
+
+
+struct ExportVideoInfo {
+    mnd::MandelViewport start;
+    mnd::MandelViewport end;
+
+    int width;
+    int height;
+    int maxIterations;
+
+    QString path;
+};
+
+
+class ExportVideoDialog : public QDialog
+{
+    Q_OBJECT
+private:
+    Ui::ExportVideoDialog evd;
+
+    ExportVideoInfo evi;
+public:
+    ExportVideoDialog(QWidget* parent, const ExportVideoInfo& evi);
+
+    const ExportVideoInfo& getExportVideoInfo(void) const;
+private slots:
+    void on_buttonBox_accepted();
+    void on_pushButton_clicked();
+};
+
+#endif // EXPORTDIALOGS_H

+ 3 - 3
exportimagedialog.ui

@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>400</width>
-    <height>184</height>
+    <height>170</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -17,7 +17,7 @@
    <property name="geometry">
     <rect>
      <x>40</x>
-     <y>140</y>
+     <y>130</y>
      <width>341</width>
      <height>32</height>
     </rect>
@@ -35,7 +35,7 @@
      <x>10</x>
      <y>10</y>
      <width>371</width>
-     <height>121</height>
+     <height>111</height>
     </rect>
    </property>
    <layout class="QFormLayout" name="formLayout">

+ 178 - 0
exportvideodialog.ui

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ExportVideoDialog</class>
+ <widget class="QDialog" name="ExportVideoDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>393</width>
+    <height>219</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <widget class="QDialogButtonBox" name="buttonBox">
+   <property name="geometry">
+    <rect>
+     <x>40</x>
+     <y>180</y>
+     <width>341</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <property name="orientation">
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <property name="standardButtons">
+    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+   </property>
+  </widget>
+  <widget class="QWidget" name="formLayoutWidget">
+   <property name="geometry">
+    <rect>
+     <x>10</x>
+     <y>10</y>
+     <width>371</width>
+     <height>161</height>
+    </rect>
+   </property>
+   <layout class="QFormLayout" name="formLayout">
+    <item row="0" column="0">
+     <widget class="QLabel" name="label">
+      <property name="text">
+       <string>Maximum iterations</string>
+      </property>
+     </widget>
+    </item>
+    <item row="0" column="1">
+     <widget class="QLineEdit" name="maxIterations">
+      <property name="text">
+       <string>5000</string>
+      </property>
+     </widget>
+    </item>
+    <item row="1" column="0">
+     <widget class="QLabel" name="label_2">
+      <property name="text">
+       <string>Video Width</string>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="0">
+     <widget class="QLabel" name="label_3">
+      <property name="text">
+       <string>Video Height</string>
+      </property>
+     </widget>
+    </item>
+    <item row="1" column="1">
+     <widget class="QLineEdit" name="vidWidth">
+      <property name="text">
+       <string>1920</string>
+      </property>
+     </widget>
+    </item>
+    <item row="2" column="1">
+     <widget class="QLineEdit" name="vidHeight">
+      <property name="text">
+       <string>1080</string>
+      </property>
+     </widget>
+    </item>
+    <item row="5" column="0">
+     <widget class="QPushButton" name="pushButton">
+      <property name="text">
+       <string>Save As</string>
+      </property>
+     </widget>
+    </item>
+    <item row="5" column="1">
+     <widget class="QLineEdit" name="savePath"/>
+    </item>
+    <item row="3" column="0">
+     <widget class="QLabel" name="label_4">
+      <property name="text">
+       <string>Start View</string>
+      </property>
+     </widget>
+    </item>
+    <item row="3" column="1">
+     <layout class="QHBoxLayout" name="horizontalLayout">
+      <item>
+       <widget class="QLineEdit" name="startX"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="startY"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="startW"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="startH"/>
+      </item>
+     </layout>
+    </item>
+    <item row="4" column="0">
+     <widget class="QLabel" name="label_5">
+      <property name="text">
+       <string>End View</string>
+      </property>
+     </widget>
+    </item>
+    <item row="4" column="1">
+     <layout class="QHBoxLayout" name="horizontalLayout_2">
+      <item>
+       <widget class="QLineEdit" name="endX"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="endY"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="endW"/>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="endH"/>
+      </item>
+     </layout>
+    </item>
+   </layout>
+  </widget>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ExportVideoDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ExportVideoDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>

+ 10 - 0
libmandel/include/MandelUtil.h

@@ -32,6 +32,16 @@ struct mnd::MandelViewport
      * \brief make sure width and height are positive
      */
     void normalize(void);
+
+    /*!
+     * \brief zoom in around the center by a factor specified
+     */
+    void zoomCenter(float scale);
+
+    /*!
+     * \brief returns a viewport where the whole mandelbrot set can be observed
+     */
+    static MandelViewport standardView(void);
 };
 
 struct mnd::MandelInfo

+ 18 - 0
libmandel/src/MandelUtil.cpp

@@ -24,3 +24,21 @@ void MandelViewport::normalize(void)
     }
 }
 
+
+void MandelViewport::zoomCenter(float scale)
+{
+    x += width * (1 - scale) / 2;
+    y += height * (1 - scale) / 2;
+    width *= scale;
+    height *= scale;
+}
+
+MandelViewport MandelViewport::standardView(void)
+{
+    return MandelViewport{
+        -2.25,
+        -1.5,
+        3,
+        3
+    };
+}